summaryrefslogtreecommitdiff
path: root/bubbob
diff options
context:
space:
mode:
authorDiego Roversi <diegor@tiscali.it>2019-09-08 18:12:27 +0200
committerDiego Roversi <diegor@tiscali.it>2019-09-08 18:12:27 +0200
commit1d9925c287b318ec21343e2682b51ab6a36ae8db (patch)
tree17d1c0ac21eea6f291146520afa8381db4586fb4 /bubbob
initial commit from cvs 1.6.2
Diffstat (limited to 'bubbob')
-rw-r--r--bubbob/.cvsignore4
-rw-r--r--bubbob/Makefile2
-rwxr-xr-xbubbob/bb.py345
-rw-r--r--bubbob/binboards.py122
-rw-r--r--bubbob/boarddef.py69
-rw-r--r--bubbob/boards.py1470
-rw-r--r--bubbob/bonuses.py2504
-rw-r--r--bubbob/bubbles.py1284
-rw-r--r--bubbob/command.py10
-rw-r--r--bubbob/doc/.cvsignore1
-rwxr-xr-xbubbob/doc/bonus-doc.py170
-rw-r--r--bubbob/doc/images/.cvsignore1
-rw-r--r--bubbob/ext1/.cvsignore2
-rw-r--r--bubbob/ext1/__init__.py403
-rw-r--r--bubbob/ext1/brick.wavbin0 -> 29150 bytes
-rw-r--r--bubbob/ext1/image1-0.ppmbin0 -> 7732 bytes
-rw-r--r--bubbob/ext1/music.wavbin0 -> 748826 bytes
-rw-r--r--bubbob/ext1/wall.wavbin0 -> 13274 bytes
-rw-r--r--bubbob/ext2/.cvsignore1
-rw-r--r--bubbob/ext2/__init__.py578
-rw-r--r--bubbob/ext2/image1.ppmbin0 -> 24635 bytes
-rw-r--r--bubbob/ext2/image2.ppmbin0 -> 3898 bytes
-rw-r--r--bubbob/ext2/music.wavbin0 -> 353000 bytes
-rw-r--r--bubbob/ext3/.cvsignore2
-rw-r--r--bubbob/ext3/__init__.py367
-rw-r--r--bubbob/ext3/image1-0.ppmbin0 -> 3124 bytes
-rw-r--r--bubbob/ext3/music.wavbin0 -> 284134 bytes
-rw-r--r--bubbob/ext3/shoot.wavbin0 -> 13274 bytes
-rw-r--r--bubbob/ext4/.cvsignore2
-rw-r--r--bubbob/ext4/__init__.py440
-rw-r--r--bubbob/ext4/image1-0.ppmbin0 -> 1588 bytes
-rw-r--r--bubbob/ext4/music.wavbin0 -> 566532 bytes
-rw-r--r--bubbob/ext5/.cvsignore1
-rw-r--r--bubbob/ext5/__init__.py289
-rw-r--r--bubbob/ext5/image1.ppm5
-rw-r--r--bubbob/ext5/image2.ppm5
-rw-r--r--bubbob/ext5/image3.ppm5
-rw-r--r--bubbob/ext5/image4.ppmbin0 -> 9274 bytes
-rw-r--r--bubbob/ext5/music.wavbin0 -> 346458 bytes
-rw-r--r--bubbob/ext5/ouch.wavbin0 -> 2631 bytes
-rw-r--r--bubbob/ext6/.cvsignore2
-rw-r--r--bubbob/ext6/__init__.py268
-rw-r--r--bubbob/ext6/crash.wavbin0 -> 13123 bytes
-rw-r--r--bubbob/ext6/image1-0.ppmbin0 -> 1971 bytes
-rw-r--r--bubbob/ext6/music.wavbin0 -> 431045 bytes
-rw-r--r--bubbob/ext7/.cvsignore2
-rw-r--r--bubbob/ext7/__init__.py399
-rw-r--r--bubbob/ext7/fire.wavbin0 -> 3352 bytes
-rw-r--r--bubbob/ext7/hit.wavbin0 -> 3352 bytes
-rw-r--r--bubbob/ext7/image1-0.ppmbin0 -> 93365 bytes
-rw-r--r--bubbob/ext7/music.wavbin0 -> 379900 bytes
-rw-r--r--bubbob/images.py542
-rw-r--r--bubbob/images/.cvsignore7
-rw-r--r--bubbob/images/10000_0.ppmbin0 -> 10852 bytes
-rw-r--r--bubbob/images/20000_0.ppmbin0 -> 13282 bytes
-rw-r--r--bubbob/images/30000_0.ppmbin0 -> 13282 bytes
-rw-r--r--bubbob/images/40000_0.ppmbin0 -> 13282 bytes
-rw-r--r--bubbob/images/50000_0.ppmbin0 -> 12877 bytes
-rw-r--r--bubbob/images/60000_0.ppmbin0 -> 13012 bytes
-rw-r--r--bubbob/images/70000_0.ppmbin0 -> 13012 bytes
-rw-r--r--bubbob/images/big_bubble.ppm4
-rw-r--r--bubbob/images/big_bubble_2.ppm4
-rw-r--r--bubbob/images/black.ppm5
-rw-r--r--bubbob/images/blitzy.ppmbin0 -> 33845 bytes
-rw-r--r--bubbob/images/blitzy_angry.ppmbin0 -> 12341 bytes
-rw-r--r--bubbob/images/blitzy_shot.ppmbin0 -> 1588 bytes
-rw-r--r--bubbob/images/bonus_0.ppmbin0 -> 17339 bytes
-rw-r--r--bubbob/images/bonus_1.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_10.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_11.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_12.ppmbin0 -> 12341 bytes
-rw-r--r--bubbob/images/bonus_2.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_3.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_4.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_5.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_6.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_7.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_8.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bonus_9.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/bubble.ppm5
-rw-r--r--bubbob/images/buildcolors.py324
-rw-r--r--bubbob/images/butterfly.ppmbin0 -> 27701 bytes
-rw-r--r--bubbob/images/cream_pie_big.ppmbin0 -> 24352 bytes
-rw-r--r--bubbob/images/diamond_big_blue.ppmbin0 -> 24352 bytes
-rw-r--r--bubbob/images/diamond_big_purple.ppm5
-rw-r--r--bubbob/images/diamond_big_red.ppmbin0 -> 24352 bytes
-rw-r--r--bubbob/images/diamond_big_yellow.ppmbin0 -> 24352 bytes
-rw-r--r--bubbob/images/digits_0.ppmbin0 -> 7193 bytes
-rw-r--r--bubbob/images/door.ppmbin0 -> 3130 bytes
-rw-r--r--bubbob/images/dragon_0.ppmbin0 -> 61493 bytes
-rw-r--r--bubbob/images/dragon_bubble_0.ppmbin0 -> 49205 bytes
-rw-r--r--bubbob/images/extend.ppmbin0 -> 55355 bytes
-rw-r--r--bubbob/images/extra1.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/extra2.ppmbin0 -> 6203 bytes
-rw-r--r--bubbob/images/extra3.ppmbin0 -> 21518 bytes
-rw-r--r--bubbob/images/extra4.ppmbin0 -> 9229 bytes
-rw-r--r--bubbob/images/extra5.ppm5
-rw-r--r--bubbob/images/extra6.ppmbin0 -> 11374 bytes
-rw-r--r--bubbob/images/extra7.ppm13
-rw-r--r--bubbob/images/extra8.ppmbin0 -> 34259 bytes
-rw-r--r--bubbob/images/fire_drop.ppmbin0 -> 444 bytes
-rw-r--r--bubbob/images/fire_surface.ppmbin0 -> 3085 bytes
-rw-r--r--bubbob/images/fish_0.ppmbin0 -> 21557 bytes
-rw-r--r--bubbob/images/flappy.ppmbin0 -> 46133 bytes
-rw-r--r--bubbob/images/flapy_angry.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/game_over_0.ppmbin0 -> 6196 bytes
-rw-r--r--bubbob/images/ghost.ppm6
-rw-r--r--bubbob/images/ghosty.ppmbin0 -> 46133 bytes
-rw-r--r--bubbob/images/ghosty_angry.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/glue.ppm4
-rw-r--r--bubbob/images/gramy.ppmbin0 -> 58421 bytes
-rw-r--r--bubbob/images/gramy_angry.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/hat1.ppm5
-rw-r--r--bubbob/images/hat2.ppm5
-rw-r--r--bubbob/images/hat5.ppm5
-rw-r--r--bubbob/images/ice_cyan_big.ppmbin0 -> 24358 bytes
-rw-r--r--bubbob/images/ice_violet_big.ppmbin0 -> 24358 bytes
-rw-r--r--bubbob/images/keys.ppmbin0 -> 49167 bytes
-rw-r--r--bubbob/images/level_digits.ppmbin0 -> 25253 bytes
-rw-r--r--bubbob/images/lightning_large.ppmbin0 -> 17833 bytes
-rw-r--r--bubbob/images/lightning_small.ppmbin0 -> 1741 bytes
-rw-r--r--bubbob/images/monky.ppmbin0 -> 61493 bytes
-rw-r--r--bubbob/images/monky_angry.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/nasty.ppmbin0 -> 46133 bytes
-rw-r--r--bubbob/images/nasty_angry.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/orcy.ppmbin0 -> 70709 bytes
-rw-r--r--bubbob/images/orcy_angry.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/palettes.datbin0 -> 28800 bytes
-rw-r--r--bubbob/images/pastec_big.ppm44
-rw-r--r--bubbob/images/peach_big.ppmbin0 -> 24352 bytes
-rw-r--r--bubbob/images/point_0.ppmbin0 -> 86453 bytes
-rw-r--r--bubbob/images/red_Hurry_up.ppmbin0 -> 9851 bytes
-rw-r--r--bubbob/images/sheep.ppmbin0 -> 27707 bytes
-rw-r--r--bubbob/images/shot.ppmbin0 -> 24629 bytes
-rw-r--r--bubbob/images/spinning_drop.ppmbin0 -> 4660 bytes
-rw-r--r--bubbob/images/springy.ppmbin0 -> 58421 bytes
-rw-r--r--bubbob/images/springy_angry.ppmbin0 -> 36917 bytes
-rw-r--r--bubbob/images/star_large.ppmbin0 -> 36923 bytes
-rw-r--r--bubbob/images/sugar_pie_big.ppmbin0 -> 24358 bytes
-rw-r--r--bubbob/images/water_flow.ppm4
-rw-r--r--bubbob/images/water_still.ppm4
-rw-r--r--bubbob/images/water_surface.ppm4
-rw-r--r--bubbob/images/yellow_Hurry_up.ppmbin0 -> 9851 bytes
-rw-r--r--bubbob/levels/Arena.binbin0 -> 164736 bytes
-rw-r--r--bubbob/levels/CompactLevels.py1902
-rw-r--r--bubbob/levels/HouseOfFun.binbin0 -> 196096 bytes
-rw-r--r--bubbob/levels/Levels.binbin0 -> 178432 bytes
-rw-r--r--bubbob/levels/LostLevels.binbin0 -> 178432 bytes
-rw-r--r--bubbob/levels/README.txt23
-rw-r--r--bubbob/levels/RandomLevels.py362
-rw-r--r--bubbob/levels/rnglevel1276
-rw-r--r--bubbob/levels/scratch.py2301
-rw-r--r--bubbob/macbinary.py260
-rw-r--r--bubbob/mnstrmap.py397
-rw-r--r--bubbob/monsters.py937
-rw-r--r--bubbob/music/Snd1-8.wavbin0 -> 532268 bytes
-rw-r--r--bubbob/music/Snd2-8.wavbin0 -> 10778156 bytes
-rw-r--r--bubbob/music/Snd3-8.wavbin0 -> 1890476 bytes
-rw-r--r--bubbob/music/Snd4-8.wavbin0 -> 339884 bytes
-rw-r--r--bubbob/music/Snd5-8.wavbin0 -> 503468 bytes
-rw-r--r--bubbob/music/Snd6-8.wavbin0 -> 9895724 bytes
-rw-r--r--bubbob/patmap.py177
-rw-r--r--bubbob/player.py1213
-rw-r--r--bubbob/ranking.py391
-rw-r--r--bubbob/save_rnglevel.py125
-rwxr-xr-xbubbob/setup.py22
-rw-r--r--bubbob/sounds/die.wavbin0 -> 29758 bytes
-rw-r--r--bubbob/sounds/extra.wavbin0 -> 3084 bytes
-rw-r--r--bubbob/sounds/extralife.wavbin0 -> 20624 bytes
-rw-r--r--bubbob/sounds/fruit.wavbin0 -> 3300 bytes
-rw-r--r--bubbob/sounds/hell.wavbin0 -> 41036 bytes
-rw-r--r--bubbob/sounds/hurry.wavbin0 -> 19536 bytes
-rw-r--r--bubbob/sounds/jump.wavbin0 -> 5516 bytes
-rw-r--r--bubbob/sounds/letsgo.wavbin0 -> 19007 bytes
-rw-r--r--bubbob/sounds/pop.wavbin0 -> 2837 bytes
-rw-r--r--bubbob/sounds/shh.wavbin0 -> 18272 bytes
-rw-r--r--bubbob/sounds/yippee.wavbin0 -> 6305 bytes
-rw-r--r--bubbob/sprmap.py533
-rw-r--r--bubbob/statesaver.c610
-rw-r--r--bubbob/statesaver.py135
-rwxr-xr-xbubbob/statesaver.sobin0 -> 62672 bytes
-rw-r--r--bubbob/test_rnglevel.py64
-rw-r--r--bubbob/test_statesaver.py127
-rw-r--r--bubbob/tmp/pat00.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat01.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat02.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat03.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat04.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat05.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat06.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat07.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat08.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat09.ppmbin0 -> 17294 bytes
-rw-r--r--bubbob/tmp/pat10.ppmbin0 -> 6925 bytes
-rw-r--r--bubbob/tmp/pat11.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat12.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat13.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat14.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat15.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat16.ppm4
-rw-r--r--bubbob/tmp/pat17.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat18.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat19.ppmbin0 -> 30734 bytes
-rw-r--r--bubbob/tmp/pat20.ppmbin0 -> 7693 bytes
204 files changed, 20607 insertions, 0 deletions
diff --git a/bubbob/.cvsignore b/bubbob/.cvsignore
new file mode 100644
index 0000000..687980a
--- /dev/null
+++ b/bubbob/.cvsignore
@@ -0,0 +1,4 @@
+*.py[co]
+build
+*.so
+*.pyd
diff --git a/bubbob/Makefile b/bubbob/Makefile
new file mode 100644
index 0000000..ab56103
--- /dev/null
+++ b/bubbob/Makefile
@@ -0,0 +1,2 @@
+statesaver.so: statesaver.c
+ python setup.py build_ext -i
diff --git a/bubbob/bb.py b/bubbob/bb.py
new file mode 100755
index 0000000..c0ab351
--- /dev/null
+++ b/bubbob/bb.py
@@ -0,0 +1,345 @@
+#! /usr/bin/env python
+
+from __future__ import generators
+
+# __________
+import os, sys
+if __name__ == '__main__':
+ LOCALDIR = sys.argv[0]
+else:
+ LOCALDIR = __file__
+try:
+ LOCALDIR = os.readlink(LOCALDIR)
+except:
+ pass
+LOCALDIR = os.path.dirname(os.path.abspath(LOCALDIR))
+# ----------
+
+import random, time
+
+sys.path.insert(0, os.path.join(os.path.dirname(LOCALDIR), 'common'))
+sys.path.insert(0, LOCALDIR)
+import gamesrv
+
+PROFILE = 0
+
+
+class BubBobGame(gamesrv.Game):
+
+ FnDesc = "Bub & Bob"
+ FnBasePath = "bubbob"
+ Quiet = 0
+ End = 0
+
+ def __init__(self, levelfile,
+ beginboard = 1,
+ stepboard = 1,
+ finalboard = 100,
+ limitlives = None,
+ extralife = 50000,
+ lifegainlimit = None,
+ autoreset = 0,
+ metaserver = 0,
+ monsters = 0):
+ gamesrv.Game.__init__(self)
+ self.game_reset_gen = None
+ self.levelfile = levelfile
+ self.beginboard = beginboard
+ self.finalboard = finalboard
+ self.stepboard = stepboard
+ self.limitlives = limitlives
+ self.lifegainlimit = lifegainlimit
+ self.extralife = extralife
+ self.autoreset = autoreset
+ self.metaserver = metaserver
+ self.f_monsters = monsters
+ self.updatemetaserver()
+ levelsname, ext = os.path.splitext(os.path.basename(levelfile))
+ self.FnDesc = BubBobGame.FnDesc + ' ' + levelsname
+ self.reset()
+ self.openserver()
+
+ def openboard(self, num=None):
+ self.End = 0
+ if num is None:
+ num = self.beginboard-1
+ import boards
+ boards.loadmodules(force=1)
+ import boards # possibly re-import
+ self.width = boards.bwidth + 9*boards.CELL
+ self.height = boards.bheight
+ boards.curboard = None
+ boards.BoardGen = [boards.next_board(num)]
+ self.updatemetaserver()
+
+ def reset(self):
+ import player
+ self.openboard()
+ for p in player.BubPlayer.PlayerList:
+ p.reset()
+
+ def FnPlayers(self):
+ from player import BubPlayer
+ result = {}
+ for p in BubPlayer.PlayerList:
+ result[p.pn] = p
+ return result
+
+ def FnFrame(self):
+ if self.metaregister:
+ self.do_updatemetaserver()
+ frametime = 0.0
+ for i in xrange(500):
+ import boards
+ for gen in boards.BoardGen[:]:
+ try:
+ frametime += gen.next()
+ except StopIteration:
+ try:
+ boards.BoardGen.remove(gen)
+ except ValueError:
+ pass
+ if frametime >= 1.1:
+ break
+ else:
+ # should normally never occur
+ boards.BoardGen[:] = [boards.next_board()]
+ frametime = 1.0
+ if self.game_reset_gen is None:
+ if self.End and self.autoreset:
+ self.game_reset_gen = boards.game_reset()
+ else:
+ try:
+ self.game_reset_gen.next()
+ except StopIteration:
+ self.game_reset_gen = None
+ return frametime * boards.FRAME_TIME
+
+ def FnServerInfo(self, msg):
+ try:
+ from images import writestr
+ writestr(50, 50, msg)
+ self.sendudpdata()
+ except:
+ pass
+
+ def FnExcHandler(self, kbd):
+ if kbd:
+ self.FnServerInfo("Server was Ctrl-C'ed!")
+ else:
+ self.FnServerInfo('Ooops -- server crash!')
+ from player import BubPlayer
+ if kbd and not [p for p in BubPlayer.PlayerList if p.isplaying()]:
+ return 0
+ import traceback
+ print "-"*60
+ traceback.print_exc()
+ print "-"*60
+ if not kbd:
+ try:
+ if self.metaserver:
+ try:
+ import metaclient
+ except ImportError:
+ pass
+ else:
+ if metaclient.metaclisrv:
+ metaclient.metaclisrv.send_traceback()
+ except Exception, e:
+ print '! %s: %s' % (e.__class__.__name__, e)
+ import boards
+ num = getattr(boards.curboard, 'num', None)
+ if self.Quiet:
+ print "Crash recovery! Automatically restarting board %s" % num
+ import time; time.sleep(2)
+ else:
+ print "Correct the problem and leave pdb to restart board %s..."%num
+ import pdb; pdb.post_mortem(sys.exc_info()[2])
+ self.openboard(num)
+ return 1
+
+ def FnListBoards():
+ import boards
+ result = []
+ for fn in os.listdir('levels'):
+ base, ext = os.path.splitext(fn)
+ if ext in ('.py', '.bin'):
+ result.append((base, os.path.join('levels', fn)))
+ return result
+ FnListBoards = staticmethod(FnListBoards)
+
+ def FnExtraDesc(self):
+ import boards
+ s = gamesrv.Game.FnExtraDesc(self)
+ if boards.curboard and self.End != 'gameover':
+ s = '%s, board %d' % (s, boards.curboard.num+1)
+ return s
+
+ def do_updatemetaserver(self):
+ self.metaregister -= 1
+ if self.metaregister > 0:
+ return
+ if self.metaserver and (self.autoreset or self.End != 'gameover'):
+ setuppath('metaserver')
+ import metaclient
+ metaclient.meta_register(self)
+ print '.'
+ else:
+ try:
+ import metaclient
+ except ImportError:
+ pass
+ else:
+ metaclient.meta_unregister(self)
+
+ def updatemetaserver(self):
+ self.metaregister = 2
+
+ updateboard = updateplayers = updatemetaserver
+
+
+def setuppath(dirname):
+ dir = os.path.abspath(os.path.join(LOCALDIR, os.pardir, dirname))
+ if not os.path.isdir(dir):
+ print >> sys.stderr, (
+ '../%s: directory not found ("cvs update -d" ?)' % dirname)
+ sys.exit(1)
+ if dir not in sys.path:
+ sys.path.insert(0, dir)
+
+def parse_cmdline(argv):
+ # parse command-line
+ def usage():
+ print >> sys.stderr, 'usage:'
+ print >> sys.stderr, ' python bb.py'
+## print >> sys.stderr, ' python bb.py [-w/--webbrowser=no]'
+## print >> sys.stderr, 'where:'
+## print >> sys.stderr, ' -w --webbrowser=no don''t automatically start web browser'
+ print >> sys.stderr, 'or:'
+ print >> sys.stderr, ' python bb.py [level-file.bin] [-m] [-b#] [-s#] [-l#] [-M#]'
+ print >> sys.stderr, 'with options:'
+ print >> sys.stderr, ' -m --metaserver register the server on the Metaserver so anyone can join'
+ print >> sys.stderr, ' -b# --begin # start at board number # (default 1)'
+ print >> sys.stderr, ' --start # synonym for --begin'
+ print >> sys.stderr, ' --final # end at board number # (default 100)'
+ print >> sys.stderr, ' -s# --step # advance board number by steps of # (default 1)'
+ print >> sys.stderr, ' -l# --lives # limit the number of lives to #'
+ print >> sys.stderr, ' --extralife # gain extra life every # points'
+ print >> sys.stderr, ' --limitlives # max # of lives player can gain in one board'
+ print >> sys.stderr, ' -M# --monsters # multiply the number of monsters by #'
+ print >> sys.stderr, ' (default between 1.0 and 2.0 depending on # of players)'
+ print >> sys.stderr, ' -i --infinite restart the server at the end of the game'
+ print >> sys.stderr, ' --port LISTEN=# set fixed tcp port for game server'
+ print >> sys.stderr, ' --port HTTP=# set fixed tcp port for http server'
+ print >> sys.stderr, ' -h --help display this text'
+ #print >> sys.stderr, ' -rxxx record the game in file xxx'
+ sys.exit(1)
+
+ try:
+ from getopt import gnu_getopt as getopt
+ except ImportError:
+ from getopt import getopt
+ from getopt import error
+ try:
+ opts, args = getopt(argv, 'mb:s:l:M:ih',
+ ['metaserver', 'start=', 'step=',
+ 'lives=', 'monsters=', 'infinite', 'help',
+ 'extralife=', 'limitlives=', 'final=',
+ 'saveurlto=', 'quiet', 'port=', 'makeimages'])
+ except error, e:
+ print >> sys.stderr, 'bb.py: %s' % str(e)
+ print >> sys.stderr
+ usage()
+
+ options = {}
+ #webbrowser = 1
+ save_url_to = None
+ quiet = 0
+ for key, value in opts:
+ if key in ('-m', '--metaserver'):
+ options['metaserver'] = 1
+ elif key in ('-b', '--start', '--begin'):
+ options['beginboard'] = int(value)
+ elif key in ('-s', '--step'):
+ options['stepboard'] = int(value)
+ elif key in ('-l', '--lives'):
+ options['limitlives'] = int(value)
+ elif key in ('--limitlives'):
+ options['lifegainlimit'] = int(value)
+ elif key in ('--final'):
+ options['finalboard'] = int(value)
+ if options['finalboard'] < options['beginboard']:
+ print >> sys.stderr, 'bb.py: final board value must be larger than begin board.'
+ sys.exit(1)
+ elif key in ('--extralife'):
+ options['extralife'] = int(value)
+ elif key in ('-M', '--monsters'):
+ options['monsters'] = float(value)
+ elif key in ('-i', '--infinite'):
+ options['autoreset'] = 1
+ elif key in ('-h', '--help'):
+ usage()
+ elif key == '--saveurlto':
+ save_url_to = value
+ elif key == '--quiet':
+ quiet = 1
+ elif key == '--port':
+ portname, portvalue = value.split('=')
+ portvalue = int(portvalue)
+ import msgstruct
+ msgstruct.PORTS[portname] = portvalue
+ elif key == '--makeimages':
+ import images
+ sys.exit(0)
+ #elif key in ('-w', '--webbrowser'):
+ # webbrowser = value.startswith('y')
+ if args:
+ if len(args) > 1:
+ print >> sys.stderr, 'bb.py: multiple level files specified'
+ sys.exit(1)
+ levelfile = os.path.abspath(args[0])
+ os.chdir(LOCALDIR)
+ BubBobGame(levelfile, **options)
+ else:
+ if options:
+ print >> sys.stderr, 'bb.py: command-line options ignored'
+ start_metaserver(save_url_to, quiet)
+
+def start_metaserver(save_url_to, quiet):
+ os.chdir(LOCALDIR)
+ setuppath('http2')
+ import httppages
+ httppages.main(BubBobGame, save_url_to, quiet)
+
+
+def setup():
+ keybmp = gamesrv.getbitmap(os.path.join('images', 'keys.ppm'))
+ def keybmplist(x):
+ return [keybmp.geticon(x, y, 32, 32) for y in range(0, 128, 32)]
+ BubBobGame.FnKeys = [
+ ("right", keybmplist(0), "kRight"),
+ ("left", keybmplist(32), "kLeft"),
+ ("jump", keybmplist(64), "kJump"),
+ ("fire", keybmplist(96), "kFire"),
+ ("-right", [], "kmRight"),
+ ("-left", [], "kmLeft"),
+ ("-jump", [], "kmJump"),
+ ("-fire", [], "kmFire"),
+ ]
+
+setup()
+
+def main():
+ parse_cmdline(sys.argv[1:])
+ if not PROFILE:
+ gamesrv.mainloop()
+ else:
+ import profile
+ prof = profile.Profile()
+ try:
+ prof = prof.run('gamesrv.mainloop()')
+ finally:
+ prof.dump_stats('profbb')
+
+if __name__ == '__main__':
+ main()
diff --git a/bubbob/binboards.py b/bubbob/binboards.py
new file mode 100644
index 0000000..af878ff
--- /dev/null
+++ b/bubbob/binboards.py
@@ -0,0 +1,122 @@
+import macbinary
+import boards
+import gamesrv
+from mnstrmap import Nasty, Monky, Ghosty, Flappy
+from mnstrmap import Springy, Orcy, Gramy, Blitzy
+from images import KEYCOL
+
+keycol = (KEYCOL & 0xFF,
+ (KEYCOL>>8) & 0xFF,
+ (KEYCOL>>16) & 0xFF)
+
+
+def meancolor(img):
+ r1 = g1 = b1 = 0
+ count = float(len(img) * len(img[0]))
+ for line in img:
+ for r, g, b in line:
+ r1 += r
+ g1 += g
+ b1 += b
+ return r1/count, g1/count, b1/count
+
+def addshadow(img, (r1, g1, b1), depth=8):
+ w = len(img[0])
+ h = len(img)
+ pad = depth * [keycol]
+ result = [line + pad for line in img] + [
+ (w+depth) * [keycol] for d in range(depth)]
+ for d in range(depth):
+ f = 1.0 - float(d)/depth
+ color = (r1 * f, g1 * f, b1 * f)
+ for i in range(w):
+ result[h+d][1+d+i] = color
+ for i in range(h):
+ result[1+d+i][w+d] = color
+ return result
+
+def addrshadow(img, (r1, g1, b1), depth=8):
+ w = len(img[0])
+ h = len(img)
+ pad = depth * [keycol]
+ result = [line + pad for line in img]
+ for d in range(depth):
+ f = 1.0 - float(d)/depth
+ color = (r1 * f, g1 * f, b1 * f)
+ for i in range(h):
+ result[i][w+d] = color
+ return result
+
+
+def load(filename):
+ print "Loading %s..." % filename
+ Bin = macbinary.MacBinary(filename)
+ levels = {}
+ mnstrlist = [Nasty, Monky, Ghosty, Flappy,
+ Springy, Orcy, Gramy, Blitzy]
+
+ for key, lvl in Bin['LEVL'].items():
+ d = lvl.getlevel(mnstrlist)
+ class BinBoard(boards.Board):
+ pass
+ for key1, value1 in d.items():
+ setattr(BinBoard, key1, value1)
+ levels[key] = BinBoard
+
+ def loader(code, rsrc=Bin['ppat'], cache={}):
+ try:
+ return cache[code]
+ except KeyError:
+ pass
+ keycol1 = None
+ bid = code[0]
+ result = None
+ if code[1] == 'l':
+ # left border wall
+ img = rsrc[bid + 228].getimage()
+ color = meancolor(img)
+ img = [line[:32] for line in img]
+ result = addrshadow(img, color)
+ elif code[1] == 'r':
+ # right border wall
+ img = rsrc[bid + 228].getimage()
+ w = len(img[0])
+ assert w in (32, 64), bid
+ if w == 64:
+ color = meancolor(img)
+ img = [line[32:64] for line in img]
+ result = addrshadow(img, color)
+ else:
+ # normal wall
+ dx, dy = code[1:]
+ img = rsrc[bid + 128].getimage()
+ w = len(img[0])
+ h = len(img)
+ assert w & 15 == h & 15 == 0, bid
+ dx *= 16
+ dy *= 16
+ if dx < w and dy < h:
+ color = meancolor(img)
+ img = [line[dx:dx+16] for line in img[dy:dy+16]]
+ result = addshadow(img, color)
+ keycol1 = KEYCOL
+ if result is not None:
+ w, h, data = macbinary.image2rgb(result)
+ ppmdata = "P6\n%d %d\n255\n%s" % (w, h, data)
+ result = gamesrv.newbitmap(ppmdata, keycol1), (0, 0, w, h)
+ cache[code] = result
+ return result
+
+ def bin_haspat(code, loader=loader):
+ try:
+ return loader(code) is not None
+ except KeyError:
+ return 0
+ def bin_loadpattern(code, keycol=None, loader=loader):
+ result = loader(code)
+ assert result is not None, code
+ return result
+
+ boards.haspat = bin_haspat
+ boards.loadpattern = bin_loadpattern
+ return levels
diff --git a/bubbob/boarddef.py b/bubbob/boarddef.py
new file mode 100644
index 0000000..168a3e1
--- /dev/null
+++ b/bubbob/boarddef.py
@@ -0,0 +1,69 @@
+import boards, mnstrmap
+
+class left:
+ dir = 1
+ def __init__(self, cls):
+ self.cls = cls
+ def build(self, x, y):
+ return self.cls(x=x, y=y-1, dir=self.dir)
+
+class right(left):
+ dir = 0
+
+LNasty = left(mnstrmap.Nasty)
+RNasty = right(mnstrmap.Nasty)
+
+LMonky = left(mnstrmap.Monky)
+RMonky = right(mnstrmap.Monky)
+
+LGhosty = left(mnstrmap.Ghosty)
+RGhosty = right(mnstrmap.Ghosty)
+
+LFlappy = left(mnstrmap.Flappy)
+RFlappy = right(mnstrmap.Flappy)
+
+LSpringy = left(mnstrmap.Springy)
+RSpringy = right(mnstrmap.Springy)
+
+LOrcy = left(mnstrmap.Orcy)
+ROrcy = right(mnstrmap.Orcy)
+
+LGramy = left(mnstrmap.Gramy)
+RGramy = right(mnstrmap.Gramy)
+
+LBlitzy = left(mnstrmap.Blitzy)
+RBlitzy = right(mnstrmap.Blitzy)
+
+
+# Sugar around the Board class
+class Level(boards.Board):
+
+ WIND_DELTA = boards.CELL
+ winds = None
+ monsters = []
+
+ def __init__(self, num):
+ walls = [line for line in self.walls.split('\n') if line]
+ self.monsters = list(self.monsters)
+ for y in range(len(walls)):
+ line = walls[y]
+ for x in range(len(line)):
+ c = line[x]
+ if c != ' ' and c != '#':
+ deflist = getattr(self, c)
+ if isinstance(deflist, left):
+ deflist = (deflist,)
+ for builder in deflist:
+ self.monsters.append(builder.build(x,y))
+ self.walls = self.walls.replace(c, ' ')
+ if self.winds is None:
+ width = len(walls[0])
+ height = len(walls)
+ spaces = " " * (width-6)
+ lbar = '>'*(width/2-2)
+ rbar = '<'*(width/2-2)
+ winds = ['>> ' + spaces + ' <<',
+ lbar + 'x'*(width-len(lbar)-len(rbar)) + rbar]
+ winds += ['>>^' + spaces + '^<<'] * (height-2)
+ self.winds = '\n'.join(winds)
+ boards.Board.__init__(self, num)
diff --git a/bubbob/boards.py b/bubbob/boards.py
new file mode 100644
index 0000000..dced6f5
--- /dev/null
+++ b/bubbob/boards.py
@@ -0,0 +1,1470 @@
+from __future__ import generators
+import random, os, sys, math
+import gamesrv
+import images
+
+CELL = 16 # this constant is inlined at some places, don't change
+HALFCELL = CELL//2
+FRAME_TIME = 0.025
+#DEFAULT_LEVEL_FILE = 'levels/scratch.py'
+
+BOARD_BKGND = 1 # 0 = black, 1 = darker larger wall tiles
+
+
+class Copyable:
+ pass # see bonuses.py, class Clock
+
+
+class Board(Copyable):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ WIND_DELTA = HALFCELL
+
+ def __init__(self, num):
+ # the subclasses should define 'walls', 'winds', 'monsters'
+ self.walls = walls = [line for line in self.walls.split('\n') if line]
+ self.winds = winds = [line for line in self.winds.split('\n') if line]
+ self.num = num
+ self.width = len(walls[0])
+ self.height = len(walls)
+ for line in walls:
+ assert len(line) == self.width, "some wall lines are longer than others"
+ for line in winds:
+ assert len(line) == self.width, "some wind lines are longer than others"
+ #assert walls[0] == walls[-1], "first and last lines must be identical"
+ assert len(winds) == self.height, "wall and wind heights differ"
+ self.walls_by_pos = {}
+ self.sprites = {}
+ if self.top:
+ testline = self.walls[0]
+ else:
+ testline = self.walls[-1]
+ self.holes = testline.find(' ') >= 0
+ self.playingboard = 0
+ self.bonuslevel = not self.monsters or (gamesrv.game.finalboard is not None and self.num >= gamesrv.game.finalboard)
+ self.cleaning_gen_state = 0
+
+ def set_musics(self, prefix=[]):
+ if (self.num+1) % 20 < 10:
+ gamesrv.set_musics(prefix + [images.music_intro], [images.music_game],
+ reset=0)
+ else:
+ gamesrv.set_musics(prefix + [], [images.music_game2], reset=0)
+
+ def writesprites(self, name, xyicolist):
+ sprlist = self.sprites.setdefault(name, [])
+ xyicolist = xyicolist[:]
+ xyicolist.reverse()
+ for s in sprlist[:]:
+ if xyicolist:
+ s.move(*xyicolist.pop())
+ else:
+ s.kill()
+ sprlist.remove(s)
+ while xyicolist:
+ x, y, ico = xyicolist.pop()
+ sprlist.append(gamesrv.Sprite(ico, x, y))
+
+ def enter(self, complete=1, inplace=0, fastreenter=False):
+ global curboard
+ if inplace:
+ print "Re -",
+ print "Entering board", self.num+1
+ self.set_musics()
+ # add board walls
+ l = self.sprites.setdefault('walls', [])
+ bl = self.sprites.setdefault('borderwalls', [])
+ if inplace:
+ deltay = 0
+ else:
+ deltay = bheight
+ wnx = wny = 1
+ while haspat((self.num, wnx, 0)):
+ wnx += 1
+ while haspat((self.num, 0, wny)):
+ wny += 1
+ self.wnx = wnx
+ self.wny = wny
+
+ if haspat((self.num, 'l')):
+ lefticon = patget((self.num, 'l'))
+ if haspat((self.num, 'r')):
+ righticon = patget((self.num, 'r'))
+ else:
+ righticon = lefticon
+ xrange = range(2, self.width-2)
+ else:
+ xrange = range(self.width)
+ lefticon = righticon = None
+
+ if BOARD_BKGND == 1:
+ gl = self.sprites.setdefault('background', [])
+ xmax = (self.width-2)*CELL
+ ymax = self.height*CELL
+ y = -HALFCELL
+ ystep = 0
+ firstextra = 1
+ while y < ymax:
+ x = 2*CELL+HALFCELL
+ xstep = 0
+ while x < xmax:
+ bitmap, rect = loadpattern((self.num, xstep, ystep),
+ images.KEYCOL)
+ bitmap, rect = images.makebkgndpattern(bitmap, rect)
+ if firstextra:
+ # special position where a bit of black might show up
+ x -= rect[2]
+ xstep = (xstep-1) % wnx
+ firstextra = 0
+ continue
+ bkgndicon = bitmap.geticon(*rect)
+ w = gamesrv.Sprite(bkgndicon, x, y + deltay)
+ gl.append(w)
+ x += rect[2]
+ xstep = (xstep+1) % wnx
+ y += rect[3]
+ ystep = (ystep+1) % wny
+ else:
+ gl = []
+
+ if lefticon is not None:
+ for y in range(0, self.height, lefticon.h // CELL):
+ bl.append(gamesrv.Sprite(lefticon, 0, y*CELL + deltay))
+
+ for y in range(self.height):
+ for x in xrange:
+ c = self.walls[y][x]
+ if c == '#':
+ wallicon = patget((self.num, x%wnx, y%wny), images.KEYCOL)
+ w = gamesrv.Sprite(wallicon, x*CELL, y*CELL + deltay)
+ l.append(w)
+ self.walls_by_pos[y,x] = w
+
+ if not l:
+ # self.sprites['walls'] must not be empty, for putwall
+ wallicon = patget((self.num, 0, 0), images.KEYCOL)
+ w = gamesrv.Sprite(wallicon, 0, -wallicon.h)
+ l.append(w)
+
+ if righticon is not None:
+ for y in range(0, self.height, lefticon.h // CELL):
+ bl.append(gamesrv.Sprite(righticon, (self.width-2)*CELL, y*CELL + deltay))
+
+ while deltay:
+ dy = -min(deltay, 8)
+ for w in gl:
+ w.step(0, dy)
+ for w in l:
+ w.step(0, dy)
+ for w in bl:
+ w.step(0, dy)
+ deltay += dy
+ yield 1
+
+ if inplace:
+ for w in images.ActiveSprites:
+ w.to_front()
+
+ curboard = self
+ if gamesrv.game:
+ gamesrv.game.updateboard()
+ if not complete:
+ return
+ # add players
+ from player import BubPlayer, scoreboard
+ if not inplace:
+ random.shuffle(BubPlayer.PlayerList)
+ scoreboard(1, inplace=inplace)
+ if not fastreenter:
+ random.shuffle(BubPlayer.PlayerList)
+ playing = []
+ for p in BubPlayer.PlayerList:
+ if p.isplaying():
+ p.enterboard(playing)
+ p.zarkon()
+ playing.append(p)
+ for d in BubPlayer.DragonList:
+ d.enter_new_board()
+ else:
+ # kill stuff left over from leave(inplace=1) (Big Clock bonus only)
+ import bonuses
+ keepme = bonuses.Points
+ dragons = {}
+ playing = []
+ for p in BubPlayer.PlayerList:
+ if p.isplaying():
+ for d in p.dragons:
+ if hasattr(d, 'dcap'):
+ d.dcap['shield'] = 90
+ dragons[d] = True
+ playing.append(p)
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, keepme) or s in dragons:
+ pass
+ else:
+ s.kill()
+ # add monsters
+ if not self.bonuslevel:
+ import monsters
+ f_monsters = gamesrv.game.f_monsters
+ if f_monsters < 0.1:
+ f_monsters = max(1.0, min(2.0, (len(playing)-2)/2.2+1.0))
+ for mdef in self.monsters:
+ if not fastreenter:
+ yield 2
+ cls = getattr(monsters, mdef.__class__.__name__)
+ dir = mdef.dir
+ i = random.random()
+ while i < f_monsters:
+ cls(mdef, dir=dir)
+ dir = -dir
+ i += 1.0
+ self.playingboard = 1
+
+ def putwall(self, x, y, w=None):
+ wallicon = patget((self.num, x%self.wnx, y%self.wny), images.KEYCOL)
+ if w is None:
+ w = gamesrv.Sprite(wallicon, 0, bheight)
+ l = self.sprites['walls']
+ w.to_back(l[-1])
+ l.append(w)
+ self.walls_by_pos[y,x] = w
+ if y >= 0:
+ line = self.walls[y]
+ self.walls[y] = line[:x] + '#' + line[x+1:]
+
+ def killwall(self, x, y, kill=1):
+ w = self.walls_by_pos[y,x]
+ if kill:
+ l = self.sprites['walls']
+ if len(l) > 1:
+ l.remove(w)
+ w.kill()
+ else:
+ # self.sprites['walls'] must never be empty
+ # or putwall will crash!
+ w.move(0, -bheight)
+ del self.walls_by_pos[y,x]
+ line = self.walls[y]
+ self.walls[y] = line[:x] + ' ' + line[x+1:]
+ return w
+
+ def reorder_walls(self):
+ walls_by_pos = self.walls_by_pos
+ items = [(yx, w1.ico) for yx, w1 in walls_by_pos.items()]
+ if not items:
+ return # otherwise self.sprites['walls'] would be emptied
+ items.sort()
+ l = self.sprites['walls']
+ while len(l) > len(items):
+ l.pop().kill()
+ assert len(items) == len(l)
+ for ((y,x), ico), w2 in zip(items, l):
+ w2.move(x*CELL, y*CELL, ico)
+ walls_by_pos[y,x] = w2
+
+ def leave(self, inplace=0):
+ global curboard
+ if not gamesrv.has_loop_music():
+ gamesrv.fadeout(1.5)
+ from player import BubPlayer
+ for p in BubPlayer.PlayerList:
+ if p.isplaying():
+ p.savecaps()
+ if BubPlayer.LeaveBonus:
+ for t in BubPlayer.LeaveBonus:
+ yield t
+ BubPlayer.LeaveBonus = None
+ curboard = None
+ if inplace:
+ i = -1
+ else:
+ while images.ActiveSprites:
+ s = random.choice(images.ActiveSprites)
+ s.kill()
+ yield 0.9
+ i = 0
+ sprites = []
+ for l in self.sprites.values():
+ sprites += l
+ self.sprites.clear()
+ self.walls_by_pos.clear()
+ random.shuffle(sprites)
+ for s in sprites:
+ s.kill()
+ if i:
+ i -= 1
+ else:
+ yield 0.32
+ i = 3
+ if not inplace:
+ for p in BubPlayer.PlayerList:
+ if p.isplaying():
+ p.zarkoff()
+ yield 4
+
+ def clean_gen_state(self):
+ self.cleaning_gen_state = 1
+ while len(BoardGen) > 1:
+ #yield force_singlegen()
+ #if 'flood' in self.sprites:
+ # for s in self.sprites['flood']:
+ # s.kill()
+ # del self.sprites['flood']
+ yield normal_frame()
+ self.cleaning_gen_state = 0
+
+def bget(x, y):
+ if 0 <= x < curboard.width:
+ if y < 0 or y >= curboard.height:
+ y = 0
+ return curboard.walls[y][x]
+ else:
+ return '#'
+
+def wget(x, y):
+ delta = curboard.WIND_DELTA
+ x = (x + delta) // 16
+ y = (y + delta) // 16
+ if 0 <= x < curboard.width:
+ if y < 0:
+ y = 0
+ elif y >= curboard.height:
+ y = -1
+ return curboard.winds[y][x]
+ elif x < 0:
+ return '>'
+ else:
+ return '<'
+
+def onground(x, y):
+ if y & 15:
+ return 0
+ x0 = (x+5) // 16
+ x1 = (x+16) // 16
+ x2 = (x+27) // 16
+ y0 = y // 16 + 2
+
+ if x0 < 0 or x2 >= curboard.width:
+ return 0
+ y1 = y0 - 1
+ if not (0 < y0 < curboard.height):
+ if y0 != curboard.height:
+ y1 = 0
+ y0 = 0
+ y0 = curboard.walls[y0]
+ y1 = curboard.walls[y1]
+ return (' ' == y1[x0] == y1[x1] == y1[x2] and
+ not (' ' == y0[x0] == y0[x1] == y0[x2]))
+ #return (' ' == bget(x0,y0-1) == bget(x1,y0-1) == bget(x2,y0-1) and
+ # not (' ' == bget(x0,y0) == bget(x1,y0) == bget(x2,y0)))
+ #return (bget(x1,y0-1)==' ' and
+ # ((bget(x1,y0)=='#') or
+ # (bget(x0,y0)=='#' and bget(x0,y0-1)==' ') or
+ # (bget(x2,y0)=='#' and bget(x2,y0-1)==' ')))
+
+def onground_nobottom(x, y):
+ return onground(x, y) and y+32 < bheight
+
+def underground(x, y):
+ if y % CELL:
+ return 0
+ x0 = (x+5) // CELL
+ x1 = (x+CELL) // CELL
+ x2 = (x+2*CELL-5) // CELL
+ y0 = y // CELL
+
+ if x0 < 0 or x2 >= curboard.width:
+ return 0
+ y1 = y0 - 1
+ if not (0 < y0 < curboard.height):
+ if y0 != curboard.height:
+ y1 = 0
+ y0 = 0
+ y0 = curboard.walls[y0]
+ y1 = curboard.walls[y1]
+ return (' ' == y0[x0] == y0[x1] == y0[x2] and
+ not (' ' == y1[x0] == y1[x1] == y1[x2]))
+
+def x2bounds(x):
+ if x < 32:
+ return 32
+ elif x > bwidth - 64:
+ return bwidth - 64
+ else:
+ return x
+
+def vertical_warp(nx, ny):
+ if ny >= bheight:
+ ny -= bheightmod
+ elif ny < -32:
+ ny += bheightmod
+ return nx, ny
+
+def vertical_warp_sprite(spr):
+ if spr.y >= bheight:
+ spr.step(0, -bheightmod)
+ elif spr.y < -32:
+ spr.step(0, bheightmod)
+
+##def vertical_warp(nx, ny):
+## if ny >= bheight:
+## ny -= bheightmod
+## elif ny < -32:
+## ny += bheightmod
+## else:
+## return (nx, ny), 0
+## from player import BubPlayer
+## if BubPlayer.Moebius:
+## nx = bwidth - 2*CELL - nx
+## return (nx, ny), 1
+## else:
+## return (nx, ny), 0
+
+
+MODULES = ['boards', 'bonuses', 'bubbles', 'images',
+ 'mnstrmap', 'monsters', 'player', 'ranking',
+ 'binboards', 'macbinary', 'boarddef',
+ 'ext1', 'ext2', 'ext3', 'ext4', 'ext5', 'ext6', 'ext7']
+
+def loadmodules(force=0):
+ levelfilename = gamesrv.game.levelfile
+ modulefiles = {None: levelfilename}
+ for m in MODULES:
+ if os.path.isfile(m+'.py'):
+ modulefiles[m] = m+'.py'
+ elif os.path.isfile(os.path.join(m, '__init__.py')):
+ modulefiles[m] = os.path.join(m, '__init__.py')
+ mtimes = {}
+ for m, mfile in modulefiles.items():
+ mtimes[m] = os.stat(mfile).st_mtime
+ reload = force or (mtimes != getattr(sys, 'ST_MTIMES', None))
+ import player
+ playerlist = player.BubPlayer.PlayerList
+ if reload:
+ delete = hasattr(sys, 'ST_MTIMES')
+ sys.ST_MTIMES = mtimes
+ if delete:
+ print "Reloading modules."
+ for m, mfile in modulefiles.items():
+ if m is not None and m in sys.modules:
+ del sys.modules[m]
+
+ # Clear
+ clearallsprites()
+
+ import player
+ for p in playerlist:
+ player.upgrade(p)
+ for n in range(len(playerlist), images.MAX):
+ playerlist.append(player.BubPlayer(n))
+ player.BubPlayer.PlayerList = playerlist
+ if reload:
+ import boards
+ from images import haspat, loadpattern
+ boards.haspat = haspat
+ boards.loadpattern = loadpattern
+ del boards.BoardList[:]
+ if levelfilename.lower().endswith('.py'):
+ levels = {}
+ print 'Source level file:', levelfilename
+ execfile(levelfilename, levels)
+ if 'GenerateLevels' in levels:
+ levels = levels['GenerateLevels']()
+ if isinstance(levels, list):
+ levels = dict(zip(range(len(levels)), levels))
+ else:
+ import binboards
+ levels = binboards.load(levelfilename)
+ boards.register(levels)
+ return reload
+
+def clearallsprites():
+ gamesrv.clearsprites()
+ import images
+ del images.ActiveSprites[:]
+ images.SpritesByLoc.clear()
+
+def wait_for_one_player():
+ from player import BubPlayer
+ clearallsprites()
+ nimages = None
+ while not [p for p in BubPlayer.PlayerList if p.isplaying()]:
+ yield 3
+ if not nimages:
+ desc = getattr(gamesrv.game, 'FnDesc', '?')
+ host, port = getattr(gamesrv.game, 'address', ('?', '?'))
+ images.writestrlines([
+ "Welcome to",
+ desc.upper(),
+ "at %s:%s" % (host.lower(), port),
+ None,
+ "Click on your Favorite Color's Dragon",
+ "Choose four keys: Right, Left, Jump, Shoot",
+ "and Let's Go!",
+ None,
+ "Click again for more than one player",
+ "on the same machine.",
+ ])
+
+ from mnstrmap import PlayerBubbles
+ nimages = [PlayerBubbles.bubble[0],
+ PlayerBubbles.bubble[1],
+ PlayerBubbles.bubble[1],
+ PlayerBubbles.bubble[0],
+ PlayerBubbles.bubble[2],
+ PlayerBubbles.bubble[2]]
+ screenwidth = bwidth + 9*CELL
+ screenheight = bheight
+
+ def welcomebubbling(self):
+ fx = self.x
+ dx = (random.random() - 0.5) * 1.9
+ for y in range(self.y-3, -self.ico.h, -3):
+ fx += dx
+ self.move(int(fx), y)
+ yield None
+ if y == self.ypop:
+ from mnstrmap import PlayerBubbles
+ self.setimages(None)
+ self.gen.append(self.die(PlayerBubbles.explosion))
+ self.kill()
+
+ yield 10
+ gamesrv.set_musics([], [images.music_game2], reset=0)
+
+ if ((not images.ActiveSprites or random.random() < 0.05678) and
+ gamesrv.clients):
+ # make sure the extension's images are loaded too
+ # NB. this is also needed for import auto-detection
+ import ext1; import ext2; import ext3
+ import ext4; import ext5; import ext6
+ import ext7
+
+ ico = images.sprget(nimages[0])
+ s = images.ActiveSprite(ico,
+ random.randrange(0, screenwidth-ico.w),
+ screenheight)
+ s.ypop = random.randrange(-ico.h, screenheight)
+ s.gen = [welcomebubbling(s)]
+ s.setimages(s.cyclic(nimages, speed=1))
+ if random.random() > 0.4321:
+ try:
+ key, (filename, (x, y, w, h)) = random.choice(
+ images.sprmap.items())
+ except:
+ w = h = 0
+ if w == h == 32:
+ s2 = images.ActiveSprite(images.sprget(key), -32, 0)
+ s2.gen = [s2.following(s, (s.ico.w-32)//2, (s.ico.h-32)//2)]
+ s.ypop = None
+ images.action(images.ActiveSprites[:])
+
+def patget(n, keycol=None):
+ bitmap, rect = loadpattern(n, keycol)
+ return bitmap.geticon(*rect)
+
+def get_lives():
+ return gamesrv.game.limitlives
+
+def do_nothing():
+ while True:
+ yield 5
+
+BoardList = []
+curboard = None
+BoardGen = [do_nothing()]
+
+def next_board(num=0, complete=1, fastreenter=False):
+ yield force_singlegen()
+ set_frametime(1.0)
+ brd = curboard
+ inplace = 0
+ if brd:
+ inplace = brd.bonuslevel or fastreenter
+ num = brd.num
+ if not inplace:
+ num += gamesrv.game.stepboard
+ if num >= len(BoardList):
+ num = len(BoardList)-1
+ if (gamesrv.game.finalboard is not None and num > gamesrv.game.finalboard):
+ num = gamesrv.game.finalboard
+ for t in brd.leave(inplace=inplace):
+ yield t
+
+ # reset global board state
+ from player import BubPlayer, reset_global_board_state
+ reset_global_board_state()
+ if not inplace:
+ del BubPlayer.MonsterList[:]
+ # wait for at least one player
+ for t in wait_for_one_player():
+ yield t
+ # reload modules if changed
+ if loadmodules():
+ import boards
+ boards.BoardGen = [boards.next_board(num)]
+ return
+
+ if num < 0:
+ num = 0
+ elif num >= len(BoardList):
+ num = len(BoardList)-1
+ brd = BoardList[num](num)
+ for t in brd.enter(complete, inplace=inplace, fastreenter=fastreenter):
+ yield t
+
+ if brd.bonuslevel:
+ gen = bonus_play
+ else:
+ gen = normal_play
+ BoardGen[0] = gen()
+
+def set_frametime(ft, privtime=100):
+ from player import BubPlayer
+ BubPlayer.BaseFrametime = ft
+ BubPlayer.PlayersPrivateTime = privtime
+ images.loadsounds(1.0 / ft)
+
+def extra_boardgen(gen, at_end=0):
+ if curboard.playingboard:
+ if at_end or not BoardGen:
+ BoardGen.append(gen)
+ else:
+ BoardGen.insert(1, gen)
+
+def replace_boardgen(gen, force=0):
+ if curboard.playingboard or force:
+ curboard.playingboard = 0
+ BoardGen[0] = gen
+
+def force_singlegen():
+ del BoardGen[1:]
+ return 0
+
+def has_singlegen():
+ return len(BoardGen) <= 1
+
+def display_hat(p, d):
+ if p.team == -1 or getattr(d,'isdying',0) or hasattr(d,'no_hat'):
+ return
+ try:
+ bottom_up = d.bottom_up()
+ except AttributeError:
+ bottom_up = 0
+ try:
+ image = ('hat', p.team, d.dir, d.hatangle)
+ except AttributeError:
+ image = ('hat', p.team)
+ if bottom_up:
+ image = 'vflip', image
+ y = d.y
+ else:
+ y = d.y - 16
+ ico = images.sprget(image)
+ if (getattr(d,'hatsprite',None) is None or
+ not d.hatsprite.alive):
+ d.hatsprite = images.ActiveSprite(ico, d.x, y)
+ else:
+ d.hatsprite.to_front()
+ d.hatsprite.move(d.x, y, ico)
+ d.hatsprite.gen = [d.hatsprite.die([None])]
+
+def normal_frame():
+ from player import BubPlayer
+ BubPlayer.FrameCounter += 1
+
+ # main generator dispatch loop
+ images.action(images.ActiveSprites[:])
+
+ frametime = 10
+ for p in BubPlayer.PlayerList:
+ if p.isplaying():
+ frametime = BubPlayer.BaseFrametime
+ p.zarkon()
+ for d in p.dragons:
+ d.to_front()
+ display_hat(p, d)
+ d.prefix(p.pn)
+ if not (BubPlayer.FrameCounter & 31):
+ gamesrv.compactsprites()
+ reset = getattr(BubPlayer, 'MultiplyerReset', 0)
+ if reset and BubPlayer.FrameCounter >= reset:
+ BubPlayer.MultiplyerReset = 0
+ set_frametime(1.0)
+ return frametime
+
+def normal_play():
+ from player import BubPlayer
+ import bonuses
+ framecounter = 0
+ bonus_callback = bonuses.start_normal_play()
+ while BubPlayer.MonsterList:
+ bonus_callback()
+ yield normal_frame()
+ if not BubPlayer.DragonList:
+ continue
+ framecounter += 1
+ BASE = 500
+ if not (framecounter % BASE):
+ if framecounter == 4*BASE:
+ from monsters import Monster
+ from mnstrmap import BigImages
+ ico = images.sprget(BigImages.hurryup[1])
+ s = images.ActiveSprite(ico, (bwidth-ico.w)//2, (bheight-ico.h)//2)
+ s.setimages(s.die(BigImages.hurryup * 12, 2))
+ images.Snd.Hurry.play()
+ mlist = [s for s in images.ActiveSprites
+ if (isinstance(s, Monster) and s.regular() and
+ not s.angry)]
+ if mlist:
+ s = random.choice(mlist)
+ s.angry = [s.genangry()]
+ s.resetimages()
+ if framecounter >= 6*BASE:
+ mlist = [s for s in images.ActiveSprites
+ if isinstance(s, Monster) and s.regular() and s.angry]
+ if mlist:
+ images.Snd.Hell.play()
+ gamesrv.set_musics([], [])
+ s = random.choice(mlist)
+ s.become_ghost()
+ framecounter = -200
+ else:
+ framecounter = 2*BASE
+ if framecounter == 0:
+ curboard.set_musics()
+ replace_boardgen(last_monster_killed(), 1)
+
+##def normal_play():
+## # TESTING!!
+## from player import BubPlayer
+## for p in BubPlayer.PlayerList:
+## if not p.icons:
+## p.loadicons(p.icons, images.sprget)
+## results = {BubPlayer.PlayerList[0]: 100,
+## BubPlayer.PlayerList[1]: 200,
+## BubPlayer.PlayerList[2]: 300,
+## BubPlayer.PlayerList[3]: 400,
+## BubPlayer.PlayerList[4]: 100,
+## BubPlayer.PlayerList[5]: 200,
+## BubPlayer.PlayerList[6]: 300,
+## BubPlayer.PlayerList[7]: 400,
+## BubPlayer.PlayerList[8]:1000,
+## BubPlayer.PlayerList[9]:1000,
+## }
+## maximum = None
+## for t in result_ranking(results, maximum):
+## yield t
+
+def last_monster_killed(end_delay=390, music=None):
+ from player import BubPlayer
+ for t in exit_board(music=music):
+ yield t
+ if curboard.bonuslevel:
+ curboard.playingboard = 1
+ for t in bonus_play():
+ yield t
+ end_delay -= 1
+ if end_delay <= 0:
+ replace_boardgen(next_board(), 1)
+ break
+ else:
+ for i in range(end_delay):
+ yield normal_frame()
+ replace_boardgen(next_board(), 1)
+
+##def bonus_play():
+## from player import BubPlayer
+## import bubbles
+## while BubPlayer.LimitScoreColor is None:
+## yield normal_frame()
+## players = [(p.points, p.pn) for p in BubPlayer.PlayerList
+## if p.isplaying()]
+## if players:
+## players.sort()
+## points, BubPlayer.LimitScoreColor = players[-1]
+## BubPlayer.LimitScore = ((points + limit) // 100000) * 100000
+## for p in BubPlayer.PlayerList:
+## if p.isplaying():
+## p.givepoints(0) # check LimitScore and update scoreboard()
+## while not (BubPlayer.BubblesBecome or BubPlayer.MegaBonus):
+## if random.random() < 0.06:
+## bubbles.newbonusbubble()
+## yield normal_frame()
+## # special board end
+## import monsters
+## monsters.argh_em_all()
+## replace_boardgen(last_monster_killed(), 1)
+
+class TimeCounter(Copyable):
+ def __init__(self, limittime, blink=0):
+ from player import BubPlayer
+ self.saved_time = BubPlayer.LimitTime
+ self.time = limittime / FRAME_TIME
+ self.prev = None
+ self.blink = blink
+ def update(self, t):
+ from player import BubPlayer, scoreboard
+ self.time -= t
+ if self.time < 0.0:
+ self.time = 0.0
+ BubPlayer.LimitTime = self.time * FRAME_TIME
+ next = int(BubPlayer.LimitTime)
+ if self.blink and BubPlayer.LimitTime - next >= 0.5:
+ BubPlayer.LimitTime = next = None
+ if self.prev != next:
+ scoreboard(compresslimittime=1)
+ self.prev = next
+ def restore(self):
+ from player import BubPlayer
+ BubPlayer.LimitTime = self.saved_time
+
+def bonus_play():
+ from player import BubPlayer
+ import bubbles
+ BubPlayer.MegaBonus = None
+ BubPlayer.BubblesBecome = None
+ Time0 = 5.0 / FRAME_TIME # when to slow down time
+ tc = TimeCounter(BubPlayer.LimitTime or 180.9) # 3:00
+ prev = None
+ while not (BubPlayer.BubblesBecome or BubPlayer.MegaBonus):
+ if random.random() < 0.099:
+ bubbles.newbonusbubble()
+ t = normal_frame()
+ tc.update(t)
+ if tc.time < Time0:
+ if tc.time <= 0.5:
+ tc.time = 0.5
+ BubPlayer.LimitTime = 0.0
+ t *= math.sqrt(Time0 / tc.time)
+ yield t
+ if tc.time == 0.5:
+ gamesrv.game.End = 'gameover'
+ gamesrv.game.updateboard()
+ replace_boardgen(game_over(), 1)
+ return
+ # special board end
+ import monsters
+ monsters.argh_em_all()
+ replace_boardgen(last_monster_killed(), 1)
+
+def game_over():
+ yield force_singlegen()
+ from player import scoreboard
+ import ranking
+ images.Snd.Extralife.play()
+ gamesrv.set_musics([], [images.music_potion])
+ scoreboard()
+ for t in ranking.game_over():
+ yield t
+
+def game_reset():
+ import time
+ from player import BubPlayer
+ t1 = time.time()
+ while 1:
+ yield 0
+ if BubPlayer.LimitTime and BubPlayer.LimitTime >= 1.0:
+ # someone else ticking the clock, try again later
+ return
+ if abs(time.time() - t1) > 2.0:
+ break
+ # anyone playing ?
+ if not gamesrv.game.End:
+ return # yes -> cancel game_reset()
+ # let's tick the clock !
+ tc = TimeCounter(60.9, blink=1) # 1:00
+ t1 = time.time()
+ while tc.time:
+ yield 0
+ # anyone playing now ?
+ if not gamesrv.game.End:
+ tc.restore()
+ return # yes -> cancel game_reset()
+ t = time.time() # use real time
+ deltat = (t-t1)/FRAME_TIME
+ if deltat < 1.0:
+ deltat = 1.0
+ elif deltat > 100.0:
+ deltat = 100.0
+ tc.update(deltat)
+ t1 = t
+ gamesrv.game.reset()
+
+##def wasting_play():
+## from player import BubPlayer, scoreboard
+## import bubbles
+## curboard.wastingplay = {}
+## for p in BubPlayer.PlayerList:
+## if p.isplaying():
+## p.letters = {}
+## p.bonbons = p.points // 50000
+## scoreboard()
+
+## while len(BubPlayer.DragonList) > 1:
+## if random.random() < 0.03:
+## bubbles.newbubble(1)
+## yield normal_frame()
+## for d in BubPlayer.DragonList:
+## curboard.wastingplay[d.bubber] = len(curboard.wastingplay)
+## for i in range(50):
+## yield normal_frame()
+
+## total = len(curboard.wastingplay)
+## results = [(total-n, p) for p, n in curboard.wastingplay.items()]
+## results.sort()
+## results = [(p, str(n)) for n, p in results]
+## for t in display_ranking(results):
+## yield t
+## # never ending
+
+def skiplevels(blink, skip):
+ # (not used any more)
+ saved = BoardGen[:]
+ while skip:
+ skip -= 1
+ BoardGen[:] = saved
+ for i in range(10): # frozen pause
+ yield 3
+ if blink:
+ blink.step(-bwidth, 0)
+ yield 3.33
+ blink.step(bwidth, 0)
+ blink = None
+ for t in next_board(complete=(skip==0)):
+ yield t
+
+def exit_board(delay=8, music=None, repeatmusic=[]):
+ from bubbles import Bubble
+ from bonuses import RandomBonus, end_normal_play
+ from player import BubPlayer
+ from monsters import Monster
+ end_normal_play()
+ curboard.playingboard = 0
+ actives = images.ActiveSprites[:]
+ for s in actives:
+ if ((isinstance(s, Monster) and s.still_playing())
+ or isinstance(s, RandomBonus)):
+ s.kill()
+ music = music or []
+ if BubPlayer.MegaBonus:
+ music[:1] = [images.music_modern]
+ if music or repeatmusic:
+ gamesrv.set_musics(music, repeatmusic)
+ for i in range(delay):
+ yield normal_frame()
+ bubble_outcome = BubPlayer.BubblesBecome or Bubble.pop
+ for s in actives:
+ if isinstance(s, Bubble):
+ bubble_outcome(s)
+ yield normal_frame()
+ if BubPlayer.MegaBonus:
+ BubPlayer.MegaBonus()
+
+def potion_fill(blist, big=0):
+ from player import BubPlayer
+ from bonuses import Bonus
+ #timeleft = 1680.0
+ for t in exit_board(0, music=[images.music_potion]):
+ #timeleft -= t
+ yield t
+ notes = all_notes = []
+ y = 1
+ while y < 11 or (y < height-2 and (len(all_notes) < 10 or big)):
+ for x in range(2, width-3, 2):
+ if ' ' == bget(x,y) == bget(x+1,y) == bget(x,y+1) == bget(x+1,y+1):
+ b = Bonus(x*CELL, y*CELL, falling=0, *blist[((x+y)//2)%len(blist)])
+ b.timeout = (444,666)[big]
+ all_notes.append(b)
+ for i in range(2):
+ t = normal_frame()
+ #timeleft -= t
+ yield t
+ y += 2
+ while notes: #and timeleft > 0.0:
+ notes = [b for b in notes if b.alive]
+ t = normal_frame()
+ #timeleft -= t
+ yield t
+ for i in range(10):
+ t = normal_frame()
+ #timeleft -= t
+ yield t
+ results = {}
+ for b in all_notes:
+ for d in b.taken_by:
+ bubber = d.bubber
+ results[bubber] = results.get(bubber, 0) + 1
+ for t in result_ranking(results, len(all_notes)):
+ yield t
+ #fadeouttime = 3.33
+ #fullsoundframes = bonusframes - 10 - int(fadeouttime / FRAME_TIME)
+ #for i in range(fullsoundframes):
+ # yield normal_frame()
+ #gamesrv.fadeout(fadeouttime)
+ #for i in range(fullsoundframes, 490):
+ # yield normal_frame()
+
+def result_ranking(results, maximum=None, timeleft=200):
+ import ranking
+ results = ranking.ranking_picture(results, maximum, timeleft is not None)
+ if curboard.bonuslevel and timeleft is not None:
+ play_again = bonus_play()
+ else:
+ play_again = None
+ for t in ranking.display(results, timeleft, play_again):
+ yield t
+ if gamesrv.game.End != 'gameover':
+ gamesrv.set_musics([], [])
+ replace_boardgen(next_board(), 1)
+
+def extra_water_flood():
+ from mnstrmap import Flood
+ from monsters import Monster
+ waves_icons = [images.sprget(n) for n in Flood.waves]
+ fill_icon = images.sprget(Flood.fill)
+ bspr = []
+ if 'flood' in curboard.sprites:
+ return # only one flooding at a time
+ curboard.sprites['flood'] = bspr
+ waves_sprites = [gamesrv.Sprite(waves_icons[0], x, bheight-CELL)
+ for x in range(0, bwidth, CELL)]
+ bspr += waves_sprites
+ fill_by_line = []
+ poplist = [None]
+ while waves_sprites[0].y > 0:
+ yield 0
+ waves_icons.insert(0, waves_icons.pop())
+ for s in waves_sprites:
+ s.seticon(waves_icons[0])
+ yield 0
+ sprites = [gamesrv.Sprite(fill_icon, s.x, s.y) for s in waves_sprites]
+ bspr += sprites
+ fill_by_line.append(sprites)
+ for s in waves_sprites:
+ s.step(0, -16)
+ for s in images.touching(0, waves_sprites[0].y, bwidth, bheight):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ while 1:
+ for i in range(2):
+ yield 0
+ waves_icons.insert(0, waves_icons.pop())
+ for s in waves_sprites:
+ s.seticon(waves_icons[0])
+ if not fill_by_line:
+ break
+ for s in fill_by_line.pop():
+ s.kill()
+ for s in waves_sprites:
+ s.step(0, 16)
+ for s in waves_sprites:
+ s.kill()
+ del curboard.sprites['flood']
+
+def extra_aquarium():
+ from mnstrmap import Flood
+ from player import BubPlayer
+ for i in range(200):
+ if 'flood' not in curboard.sprites: # only one flooding at a time
+ break
+ yield 0
+ if curboard.cleaning_gen_state:
+ return
+ else:
+ return
+ curboard.sprites['flood'] = []
+ gl = curboard.sprites.setdefault('background', [])
+ curboard.holes = True # so that random PlainBubbles show up anyway
+ walls = curboard.sprites['walls']
+ seen = {}
+
+ def newsprite(ico, x, y):
+ s = gamesrv.Sprite(ico, x, y)
+ s.to_back(walls[0])
+ gl.append(s)
+ return s
+
+ def fishplayers(ymin):
+ for d in BubPlayer.DragonList:
+ if d not in seen and d.y >= ymin:
+ seen[d] = True
+ d.become_fish()
+ d.bubber.emotic(d, 4)
+
+ waves_icons = [images.sprget(n) for n in Flood.waves]
+ fill_icon = images.sprget(Flood.fill)
+ waves_sprites = [newsprite(waves_icons[0], x, bheight-CELL)
+ for x in range(2*CELL + HALFCELL, bwidth - 2*CELL, CELL)]
+ while waves_sprites[0].y > -fill_icon.h:
+ fishplayers(waves_sprites[0].y)
+ yield 0
+ waves_icons.append(waves_icons.pop(0))
+ for s in waves_sprites:
+ s.seticon(waves_icons[0])
+ fishplayers(waves_sprites[0].y)
+ yield 0
+ for s in waves_sprites:
+ newsprite(fill_icon, s.x, s.y)
+ for s in waves_sprites:
+ s.step(0, -16)
+ for s in waves_sprites:
+ s.kill()
+ BubPlayer.SuperFish = True
+ fishplayers(-sys.maxint)
+
+def extra_walls_falling():
+ walls_by_pos = curboard.walls_by_pos
+ moves = 1
+ while moves and not curboard.cleaning_gen_state:
+ moves = 0
+ for y in range(height-3, -1, -1):
+ for x in range(2, width-2):
+ if ((y,x) in walls_by_pos and
+ (y+1,x) not in walls_by_pos and
+ (y+2,x) not in walls_by_pos):
+ y0 = y
+ while (y0-1,x) in walls_by_pos:
+ y0 -= 1
+ w = curboard.killwall(x, y0, 0)
+ curboard.putwall(x, y+1, w)
+ moves = 1
+ curboard.reorder_walls()
+ for y in range(6):
+ yield 0
+
+def single_blocks_falling(xylist):
+ walls_by_pos = curboard.walls_by_pos
+ while xylist:
+ newlist = []
+ for x, y in xylist:
+ if ((y,x) in walls_by_pos and (y+1,x) not in walls_by_pos and
+ y < curboard.height-1):
+ newlist.append((x, y+1))
+ for x, y in newlist:
+ w = curboard.killwall(x, y-1, 0)
+ curboard.putwall(x, y, w)
+ xylist = newlist
+ curboard.reorder_walls()
+ for i in range(7):
+ yield 0
+
+def extra_display_repulse(cx, cy, dlimit=5000, dfactor=1000):
+ offsets = {}
+ for s in gamesrv.sprites_by_n.values():
+ x, y = s.getdisplaypos()
+ if x is not None:
+ dx = x - cx
+ dy = y - cy
+ d = dx*dx + dy*dy + 100
+ if d <= dlimit:
+ dx = (dx*dfactor)//d
+ dy = (dy*dfactor)//d
+ offsets[s] = dx, dy
+ s.setdisplaypos(int(x+dx), int(y+dy))
+ yield 0
+ yield 0
+ while offsets:
+ prevoffsets = offsets
+ offsets = {}
+ for s, (dx, dy) in prevoffsets.items():
+ if s.alive:
+ if dx < 0:
+ dx += max(1, (-dx)//5)
+ elif dx:
+ dx -= max(1, dx//5)
+ if dy < 0:
+ dy += max(1, (-dy)//5)
+ elif dy:
+ dy -= max(1, dy//5)
+ if dx or dy:
+ offsets[s] = dx, dy
+ s.setdisplaypos(int(s.x+dx), int(s.y+dy))
+ yield 0
+
+def extra_bkgnd_black(cx, cy):
+ gl = curboard.sprites.get('background')
+ dist = 0
+ while gl:
+ dist += 17
+ dist2 = dist * dist
+ gl2 = []
+ for s in gl:
+ if (s.x-cx)*(s.x-cx) + (s.y-cy)*(s.y-cy) < dist2:
+ s.kill()
+ else:
+ gl2.append(s)
+ gl[:] = gl2
+ yield 0
+
+def extra_light_off(timeout, icocache={}):
+ for i in range(timeout):
+ if curboard.cleaning_gen_state:
+ break
+ dragons = {}
+ import player
+ playerlist = player.BubPlayer.PlayerList
+ for bubber in playerlist:
+ for dragon in bubber.dragons:
+ dragons[dragon] = True
+ for s in gamesrv.sprites_by_n.values():
+ try:
+ ico = icocache[s.ico, s in dragons]
+ except KeyError:
+ ico = images.make_darker(s.ico, s in dragons)
+ icocache[s.ico, s in dragons] = ico
+ s.setdisplayicon(ico)
+ yield 0
+ for s in gamesrv.sprites_by_n.values():
+ s.setdisplayicon(s.ico)
+
+def extra_swap_up_down(N=27):
+ # unregister all walls
+ walls = curboard.walls_by_pos.items()
+ walls.sort()
+ if not walls:
+ return
+ curboard.walls_by_pos.clear()
+ emptyline = '##' + ' '*(width-4) + '##'
+ curboard.walls = [emptyline] * height
+ l = curboard.sprites['walls']
+ wallicon = l[0].ico
+ wallpool = l[:]
+ l[:] = [gamesrv.Sprite(wallicon, 0, -wallicon.h)]
+
+ # force the top half of the walls on front
+ #for (y,x), w in walls:
+ # if y*2 < height:
+
+ # show the walls swapping up/down
+ ycenter = ((height-1)*CELL) // 2
+ for i in range(N):
+ alpha = math.cos((math.pi*(i+1))/N)
+ ymap = {}
+ for y in range(height):
+ ymap[y] = int(alpha*(y*CELL-ycenter)) + ycenter
+ for (y,x), w in walls:
+ if y in ymap:
+ w.move(x*CELL, ymap[y])
+ yield 0
+ if i == (N+1)//2:
+ # reorder the wall sprites in the middle of the swap
+ walls = [((-y,x), w) for (y,x), w in walls]
+ walls.sort()
+ for i in range(len(walls)):
+ (y,x), w = walls[i]
+ walls[i] = (y,x), wallpool[i]
+ walls = [((-y,x), w) for (y,x), w in walls]
+ walls.sort()
+ # reverse all dragons!
+ from player import BubPlayer
+ for dragon in BubPlayer.DragonList:
+ dragon.dcap['gravity'] *= -1.0
+
+ # freeze the walls in their new position
+ i = 0
+ for (y,x), w in walls:
+ y = height-1 - y
+ if 0 <= y < height and (y,x) not in curboard.walls_by_pos:
+ w = wallpool[i]
+ i += 1
+ curboard.putwall(x, y, w)
+ l[:0] = wallpool[:i]
+ for w in wallpool[i:]:
+ w.kill()
+ curboard.reorder_walls()
+
+def extra_catch_all_monsters(dragons=[], everything=False):
+ from monsters import Monster
+ from bubbles import BigBubbleCatcher
+ from bonuses import Bonus, BonusMaker
+ if not dragons:
+ from player import BubPlayer
+ dragons = BubPlayer.DragonList
+ i = 0
+ give_up = 33
+ for s in images.ActiveSprites[:]:
+ if curboard.cleaning_gen_state:
+ break
+ while not dragons:
+ give_up -= 1
+ if give_up == 0:
+ return
+ yield 0
+ if not s.alive or not s.touchable:
+ continue
+ if isinstance(s, Bonus):
+ ok = s.bubblable
+ elif isinstance(s, BonusMaker):
+ ok = everything
+ else:
+ ok = isinstance(s, Monster)
+ if ok:
+ dragon = dragons[i%len(dragons)]
+ BigBubbleCatcher(dragon, s, 542 + 17*i)
+ i += 1
+ yield 0
+ yield 0
+
+def extra_make_random_level(cx=None, cy=None, repeat_delay=200):
+ from bonuses import DustStar
+ # generate any random level
+ localdir = os.path.dirname(__file__)
+ filename = os.path.join(localdir, 'levels', 'RandomLevels.py')
+ d = {}
+ execfile(filename, d)
+ Level = d['GenerateSingleLevel'](curboard.width, curboard.height)
+ lvl = Level(curboard.num)
+ walllist = []
+ if cx is None: cx = bwidth // 2
+ if cy is None: cy = bheight // 2
+ for y in range(curboard.height):
+ dy = cy - (y*16+8)
+ dy2 = dy*dy*0.75
+ for x in range(2, curboard.width-2):
+ dx = cx - (x*16+8)
+ d2 = dx*dx + dy2
+ walllist.append((d2, x, y))
+ walllist.sort()
+
+ # dynamically replace the current level's walls with the new level's
+ dist = 0
+ speedf = 15.0
+ added = 0
+ for d2, x, y in walllist:
+ while d2 > dist*dist:
+ if added:
+ curboard.reorder_walls()
+ added = 0
+ yield 0
+ dist += 4.1
+ speedf *= 0.99
+ if curboard.walls[y][x] == ' ':
+ if lvl.walls[y][x] == ' ':
+ continue
+ else:
+ curboard.putwall(x, y)
+ added = 1
+ big = 1
+ else:
+ if lvl.walls[y][x] == ' ':
+ curboard.killwall(x, y)
+ big = 1
+ else:
+ big = 0
+ sx = x*16
+ sy = y*16
+ DustStar(sx - 8*big,
+ sy - 8*big,
+ speedf * (sx-cx) / dist,
+ speedf * (sy-cy) / dist,
+ big=big)
+ # patch the winds too
+ curboard.winds = lvl.winds
+ curboard.reorder_walls()
+ yield 0
+ # wait a bit and restart
+ if repeat_delay < 1000:
+ for i in range(repeat_delay):
+ yield 0
+ if curboard.cleaning_gen_state:
+ return
+ extra_boardgen(extra_make_random_level(
+ repeat_delay = repeat_delay * 3 // 2))
+
+def extra_bubbles(timeout):
+ from bubbles import newforcedbubble
+ falloff = 0.25
+ L = math.log(0.965) # same speed falloff rate as in throwing_bubble()
+ cx = (bwidth - CELL) // 2
+ cy = (bheight - CELL) // 2
+ for i in range(timeout):
+ if curboard.cleaning_gen_state:
+ return
+ if random.random() < falloff:
+ bubble = newforcedbubble()
+ if bubble:
+ tx = random.randrange(CELL, bwidth - 2*CELL) - cx
+ ty = random.randrange(CELL, bheight - 2*CELL) - cy
+ if ty == 0:
+ ty = 1
+ dist = math.sqrt(tx * tx + ty * ty)
+ acos = tx / dist
+ asin = ty / dist
+ hspeed = 4 - dist * L
+ bubble.thrown_bubble(cx, cy, hspeed, (acos, asin))
+ falloff *= 0.998
+ yield 0
+
+
+def initsubgame(music, displaypoints):
+ from player import BubPlayer, scoreboard
+ for t in exit_board(0, repeatmusic=[music]):
+ yield t
+ BubPlayer.DisplayPoints = displaypoints
+ scoreboard()
+ for t in curboard.clean_gen_state():
+ yield t
+
+def register(dict):
+ global width, height, bwidth, bheight, bheightmod
+ items = dict.items()
+ items.sort()
+ for name, board in items:
+ try:
+ if not issubclass(board, Board) or board is Board:
+ continue
+ except TypeError:
+ continue
+ BoardList.append(board)
+ # check sizes
+ assert BoardList, "board file does not define any board"
+ B = BoardList[0]
+ try:
+ test = B(-1)
+ width = test.width
+ height = test.height
+ for B in BoardList[1:]:
+ test = B(-1)
+ assert test.width == width, "some boards have a different width"
+ assert test.height == height, "some boards have a different height"
+ except Exception, e:
+ print 'Caught "%s" in level "%s":' % (e, B.__name__)
+ raise e
+ bwidth = width*CELL
+ bheight = height*CELL
+ bheightmod = (height+2)*CELL
+
+##def define_boards(filename):
+## global curboard, boards, width, height, bwidth, bheight, bheightmod
+## curboard = None
+## boards = []
+## def board((wallfile, wallrect), shape):
+## lines = shape.strip().split('\n')
+## bmp = gamesrv.getbitmap(wallfile)
+## wallicon = bmp.geticon(*wallrect)
+## boards.append(Board(lines, wallicon))
+## d = {'board': board}
+## execfile(filename, d)
+## assert boards, "board file does not define any board"
+## width = boards[0].width
+## height = boards[0].height
+## for b in boards[1:]:
+## assert b.width == width, "some boards have a different width"
+## assert b.height == height, "some boards have a different height"
+## bwidth = width*CELL
+## bheight = height*CELL
+## bheightmod = len(boards[0].lines)*CELL
+
+
+#try:
+# import psyco
+#except ImportError:
+# pass
+#else:
+# psyco.bind(normal_frame)
diff --git a/bubbob/bonuses.py b/bubbob/bonuses.py
new file mode 100644
index 0000000..6873188
--- /dev/null
+++ b/bubbob/bonuses.py
@@ -0,0 +1,2504 @@
+from __future__ import generators
+import random, os, math
+import random as random_module
+import gamesrv
+import images
+import boards
+from boards import *
+from images import ActiveSprite
+from mnstrmap import GreenAndBlue, Bonuses, Diamonds, Stars, BigImages
+from mnstrmap import PotionBonuses, Fire
+from player import BubPlayer
+
+
+questionmarklist = ['questionmark3',
+ 'questionmark4',
+ 'questionmark5',
+ 'questionmark4',
+ 'questionmark3',
+ 'questionmark2',
+ 'questionmark1',
+ 'questionmark2']
+
+class Bonus(ActiveSprite):
+ bubblable = 1
+ touchable = 1
+ points = 750
+ timeout = 250
+ sound = 'Fruit'
+ endaction = None
+ multiply = 1
+ killgens = 1
+
+ def __init__(self, x, y, nimage=None, points=None, falling=1):
+ if nimage is not None:
+ self.nimage = nimage
+ if points is not None:
+ self.points = points
+ ActiveSprite.__init__(self, images.sprget(self.nimage), x, y)
+ self.taken_by = []
+ self.gen.append(self.timeouter())
+ if falling:
+ self.gen.append(self.faller())
+
+ def buildoutcome(self):
+ return (self.__class__,)
+
+ def faller(self):
+ while self.y < boards.bheight:
+ if onground_nobottom(self.x, self.y):
+ yield None
+ yield None
+ else:
+ self.move(self.x, (self.y+4) & ~3)
+ yield None
+ self.kill()
+
+ def timeouter(self):
+ for i in range(self.timeout):
+ yield None
+ if self.timeout:
+ self.kill()
+
+ def touched(self, dragon):
+ dx, dy, dw, dh = dragon.x, dragon.y, dragon.ico.w, dragon.ico.h
+ if (dx + dw > self.x + 10 and
+ dy + dh > self.y + 8 and
+ self.x + self.ico.w > dx + 10 and
+ self.y + self.ico.h > dy + 10):
+ self.reallytouched(dragon)
+
+ def reallytouched(self, dragon):
+ if not self.taken_by:
+ if self.killgens:
+ self.gen = []
+ self.gen.append(self.taking())
+ sound = self.sound
+ if sound:
+ if isinstance(sound, str):
+ sound = getattr(images.Snd, sound)
+ self.play(sound)
+ if dragon not in self.taken_by:
+ self.taken_by.append(dragon)
+ if isinstance(self, (RandomBonus, MonsterBonus)):
+ s_bonus = dragon.bubber.stats.setdefault('bonus', {})
+ s_bonus[self.nimage] = s_bonus.get(self.nimage, 0) + 1
+
+ def taking(self, follow_dragons=0, delay=1):
+ from player import Dragon
+ for t in range(delay):
+ yield None # time to be taken by several dragons
+ if self.points:
+ for p in self.taken_by:
+ if follow_dragons and p.alive:
+ s = p
+ else:
+ s = self
+ points(s.x + s.ico.w//2, s.y + s.ico.h//2 - CELL, p, self.points)
+ dragons = [d for d in self.taken_by if isinstance(d, Dragon)]
+ if self.taken1(dragons) != -1:
+ self.kill()
+
+ def taken1(self, dragons):
+ for d in dragons * self.multiply:
+ if d.alive:
+ self.taken(d)
+
+ def taken(self, dragon):
+ pass
+
+ def in_bubble(self, bubble):
+ self.untouchable()
+ bubble.move(self.x, self.y)
+ bubble.to_front()
+ self.to_front()
+ self.gen = [self.bubbling(bubble, self.ico)]
+ self.move(bubble.x+8, bubble.y+8, images.sprget('questionmark3'))
+ self.setimages(self.cyclic(questionmarklist, 2))
+
+ def bubbling(self, bubble, ico):
+ while not hasattr(bubble, 'poplist'):
+ self.move(bubble.x+8, bubble.y+8)
+ yield None
+ if bubble.poplist is not None:
+ dragon = bubble.poplist[0]
+ if dragon is not None:
+ self.play(images.Snd.Yippee)
+ if dragon not in self.taken_by:
+ self.taken_by.append(dragon)
+ if self.points > 10:
+ dragon.bubber.givepoints(self.points - 10)
+ pn = dragon.bubber.pn
+ if self.points in GreenAndBlue.points[pn]:
+ Points(bubble.x + bubble.ico.w//2, bubble.y, pn, self.points)
+ self.taken1(BubPlayer.DragonList)
+ p = Parabolic(ico, bubble.x, bubble.y)
+ p.gen.append(p.moving(-1.0))
+ self.kill()
+
+ def is_on_ground(self):
+ return onground(self.x, self.y)
+
+
+def points(x, y, dragon, points):
+ dragon.bubber.givepoints(abs(points))
+ pn = dragon.bubber.pn
+ if points in GreenAndBlue.points[pn]:
+ Points(x, y, pn, points)
+
+class Points(ActiveSprite):
+
+ def __init__(self, x, y, pn, points):
+ ico = images.sprget(GreenAndBlue.points[pn][points])
+ ActiveSprite.__init__(self, ico, x - ico.w//2, max(8, y))
+ self.nooverlap = 1
+ self.gen.append(self.raiser())
+
+ def raiser(self):
+ wait = 0
+ for s in images.ActiveSprites:
+ if s is self:
+ break
+ if (isinstance(s, Points) and s.nooverlap and
+ abs(self.x-s.x)<self.ico.w*2//3 and
+ abs(self.y-s.y)<self.ico.h):
+ wait += 5
+ for t in range(wait):
+ yield None
+ for i in range(25):
+ if i == 7:
+ self.nooverlap = 0
+ self.step(0, -2)
+ yield None
+ if self.y <= 0:
+ break
+ for i in range(20):
+ yield None
+ self.kill()
+
+
+class Parabolic(ActiveSprite):
+ fallstraight = 0
+ fallspeed = 4
+
+ def moving(self, y_amplitude = -8.0):
+ bottom_up = self.fallspeed < 0
+ dxy = [(random.random()-0.5) * 15.0,
+ (random.random()+0.5) * y_amplitude * (1,-1)[bottom_up]]
+ if bottom_up:
+ kw = {'gravity': -0.3}
+ else:
+ kw = {}
+ for n in self.parabolic(dxy, self.fallstraight, **kw):
+ progress = self.parabole_progress = dxy[1] * (1,-1)[bottom_up]
+ yield n
+ if progress >= 4.0 and self.fallstraight:
+ del self.parabole_progress
+ self.gen.append(self.falling())
+ return
+ self.kill()
+
+ def falling(self):
+ nx, ny = vertical_warp(self.x, self.y & ~3)
+ if self.fallspeed < 0:
+ groundtest = underground
+ else:
+ groundtest = onground
+ while not groundtest(nx, ny):
+ ny += self.fallspeed
+ nx, ny1 = vertical_warp(nx, ny)
+ if ny1 != ny:
+ ny = ny1
+ self.wrapped_around()
+ self.move(nx, ny)
+ yield None
+ self.move(nx, ny)
+ self.build()
+ self.kill()
+
+ def killmonsters(self, poplist):
+ from monsters import Monster
+ while 1:
+ for s in self.touching(0):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ yield None
+
+ def build(self):
+ pass
+
+ def wrapped_around(self):
+ pass
+
+
+class Parabolic2(Parabolic):
+ points = 0
+
+ def __init__(self, x, y, imglist, imgspeed=3, onplace=0, y_amplitude=-8.0):
+ Parabolic.__init__(self, images.sprget(imglist[0]), x, y)
+ if onplace:
+ self.gen.append(self.falling())
+ else:
+ self.gen.append(self.moving(y_amplitude))
+ if len(imglist) > 1:
+ self.setimages(self.cyclic(imglist, imgspeed))
+
+ def touched(self, dragon, rect=None):
+ if self.points:
+ points(self.x + self.ico.w/2, self.y + self.ico.h/2 - CELL,
+ dragon, self.points)
+ self.kill()
+
+
+class BonusMaker(Parabolic2):
+ fallstraight = 1
+ touchable = 1
+
+ def __init__(self, x, y, imglist, imgspeed=3, onplace=0, outcome=None):
+ assert outcome
+ self.outcome = outcome
+ if outcome == (Flower2,):
+ self.fallspeed = -self.fallspeed
+ Parabolic2.__init__(self, x, y, imglist, imgspeed, onplace)
+
+ def falling(self):
+ cls = self.outcome[0]
+ if issubclass(cls, Megabonus):
+ self.build()
+ return self.die([])
+ else:
+ return Parabolic2.falling(self)
+
+ def wrapped_around(self):
+ cls = self.outcome[0]
+ if issubclass(cls, RandomBonus) and not boards.curboard.playingboard:
+ self.kill()
+
+ def build(self):
+ cls = self.outcome[0]
+ args = self.outcome[1:]
+ if issubclass(cls, RandomBonus) and not boards.curboard.playingboard:
+ return None
+ else:
+ return cls(self.x, self.y, *args)
+
+ def touched(self, dragon, rect=None):
+ pass
+
+ def in_bubble(self, bubble):
+ bonus = self.build()
+ self.kill()
+ if bonus:
+ bonus.in_bubble(bubble)
+ return bonus
+
+class BonusMakerExtraStar(ActiveSprite):
+
+ def __init__(self, x, y, sx, sy, colorname):
+ self.sx = sx
+ self.sy = sy
+ imglist = [('smstar', colorname, k) for k in range(2)]
+ ActiveSprite.__init__(self, images.sprget(imglist[-1]),
+ x + HALFCELL, y + HALFCELL)
+ self.setimages(self.cyclic(imglist, speed=2))
+
+ def follow_bonusmaker(self, bm):
+ for t in range(4):
+ yield None
+ if hasattr(bm, 'parabole_progress'):
+ break
+ else:
+ self.kill()
+ return
+ start = bm.parabole_progress
+ if start < 3.9:
+ while bm.alive and hasattr(bm, 'parabole_progress'):
+ f = (bm.parabole_progress-start) / (4.0-start)
+ self.move(bm.x + HALFCELL + int(f*self.sx),
+ bm.y + HALFCELL + int(f*self.sy))
+ yield None
+ self.kill()
+
+
+class MonsterBonus(Bonus):
+
+ def __init__(self, x, y, multiple, forceimg=0):
+ self.level = multiple
+ if multiple >= len(Bonuses.monster_bonuses):
+ multiple = len(Bonuses.monster_bonuses) - 1
+ img, pts = Bonuses.monster_bonuses[multiple]
+ Bonus.__init__(self, x, y, forceimg or img, pts)
+
+ def buildoutcome(self):
+ return (self.__class__, self.level)
+
+ def taken(self, dragon):
+ dragon.carrybonus(self, 543)
+
+class IceMonsterBonus(MonsterBonus):
+
+ def __init__(self, x, y, multiple):
+ self.level = multiple
+ if multiple >= 1:
+ img, pts = Bonuses.violet_ice, 750
+ else:
+ img, pts = Bonuses.cyan_ice, 700
+ Bonus.__init__(self, x, y, img, pts)
+
+
+
+class DustStar(ActiveSprite):
+ localrandom = random.Random()
+
+ def __init__(self, x, y, basedx, basedy, big=1, clock=0):
+ self.colorname = self.localrandom.choice(Stars.COLORS)
+ self.imgspeed = self.localrandom.randrange(3, 6)
+ self.rotation_reversed = self.localrandom.random() < 0.5
+ ico, imggen = self.select_ico(getattr(Stars, self.colorname))
+ ActiveSprite.__init__(self, ico, x, y)
+ self.setimages(imggen)
+ self.gen.append(self.fly(basedx, basedy, big))
+ if not big:
+ self.make_small()
+ elif clock:
+ self.setimages(None)
+ self.seticon(images.sprget(Bonuses.clock))
+
+ def select_ico(self, imglist):
+ if self.rotation_reversed:
+ imglist = list(imglist)
+ imglist.reverse()
+ return (images.sprget(imglist[-1]),
+ self.cyclic(imglist, self.imgspeed))
+
+ def make_small(self):
+ images = [('smstar', self.colorname, k) for k in range(2)]
+ ico, imggen = self.select_ico(images)
+ self.seticon(ico)
+ self.setimages(imggen)
+
+ def fly(self, dx, dy, big):
+ random = self.localrandom
+ dx += (random.random() - 0.5) * 2.8
+ dy += (random.random() - 0.5) * 2.8
+ fx = self.x
+ fy = self.y
+ if big:
+ j = 0
+ else:
+ j = 2
+ while j < 3:
+ ttl = random.expovariate(1.0 / 12)
+ if ttl > 35:
+ ttl = 35
+ for i in range(int(ttl)+4):
+ fx += dx
+ fy += dy
+ self.move(int(fx), int(fy))
+ yield None
+ if j == 0:
+ self.make_small()
+ fx += 8
+ fy += 8
+ j += 1
+ self.kill()
+
+
+class RandomBonus(Bonus):
+ timeout = 500
+
+class TemporaryBonus(RandomBonus):
+ captime = 0
+ bonusleveldivider = 2
+ def taken(self, dragon):
+ dragon.dcap[self.capname] += 1
+ self.carried(dragon)
+ def carried(self, dragon):
+ captime = self.captime
+ if boards.curboard.bonuslevel:
+ captime = (captime or 999) // self.bonusleveldivider
+ if captime:
+ dragon.carrybonus(self, captime)
+ else:
+ dragon.carrybonus(self)
+ self.endaction = None
+ def endaction(self, dragon):
+ if dragon.dcap[self.capname] >= 1:
+ dragon.dcap[self.capname] -= 1
+
+
+class ShoeSpeed(RandomBonus):
+ "Fast Runner. Cumulative increase of horizontal speed."
+ nimage = Bonuses.shoe
+ bigbonus = {'multiply': 3}
+ bigdoc = "Run Really Fast."
+ def taken(self, dragon):
+ dragon.dcap['hspeed'] += 1
+ dragon.carrybonus(self)
+
+class CoffeeSpeed(RandomBonus):
+ "Caffeine. Cumulative increase of the horizontal speed and fire rate."
+ nimage = Bonuses.coffee
+ big = 0
+ bigbonus = {'big': 1, 'multiply': 3}
+ bigdoc = "Super-Excited! Break through walls!"
+ def taken(self, dragon):
+ dragon.dcap['hspeed'] += 0.5
+ dragon.dcap['firerate'] += 1
+ if self.big:
+ dragon.dcap['breakwalls'] = 1
+ dragon.carrybonus(self)
+
+class Butterfly(TemporaryBonus):
+ "Lunar Gravity. Allows you to jump twice as high as before."
+ nimage = Bonuses.butterfly
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Butterflies all around."
+ def taken1(self, dragons):
+ if self.big:
+ import mnstrmap, monsters
+ for i in range(17):
+ monsters.Butterfly(mnstrmap.Butterfly,
+ self.x + random.randrange(-40, 41),
+ self.y + random.randrange(-30, 31),
+ random.choice([-1, 1]))
+ else:
+ TemporaryBonus.taken1(self, dragons)
+ def taken(self, dragon):
+ dragon.dcap['gravity'] *= 0.5
+ self.carried(dragon)
+ def endaction(self, dragon):
+ dragon.dcap['gravity'] *= 2.0
+
+class Cocktail(TemporaryBonus):
+ "Short Lived Bubbles. Makes your bubbles explode more quickly."
+ nimage = Bonuses.cocktail
+ points = 2000
+ capname = 'bubbledelay'
+ bigbonus = {'multiply': 3}
+ bigdoc = "Makes your bubbles explode at once. Dangerous!"
+
+class Extend(RandomBonus):
+ "E X T E N D. Gives you your missing letters and clear the level. "
+ nimage = Bonuses.extend
+ points = 0
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "A lot of letter bubbles! Run! Run!"
+
+ def taken1(self, dragons):
+ if self.big:
+ self.letterexplosion()
+ else:
+ RandomBonus.taken1(self, dragons)
+
+ def taken(self, dragon):
+ from bubbles import extend_name
+ names = [extend_name(l) for l in range(6)]
+ missing = [name for name in names if name not in dragon.bubber.letters]
+ x = dragon.x + dragon.ico.w//2
+ y = dragon.y
+ points(x, y, dragon, 10000*len(missing))
+ for l in range(6):
+ if extend_name(l) in missing:
+ dragon.bubber.giveletter(l, promize=0)
+
+ def letterexplosion(self):
+ from bubbles import LetterBubble
+ playercount = len([p for p in BubPlayer.PlayerList if p.isplaying()])
+ N = 3 + (playercount > 3)
+ angles = [i*(2.0*math.pi/N) for i in range(N)]
+ for l, dx, dy in [(0, 5, 9), (1, 16, 10), (2, 26, 8),
+ (3, 7, 23), (4, 15, 24), (5, 25, 24)]:
+ delta = 2.0*math.pi * random.random()
+ angles = [angle-delta for angle in angles]
+ x = self.x + self.ico.w//2 + 3*(dx-16)
+ y = self.y + self.ico.h//2 + 3*(dy-16)
+ for angle in angles:
+ bubble = LetterBubble(None, l)
+ bubble.thrown_bubble(x, y, 7.0 + 4.0 * random.random(),
+ (math.cos(angle), math.sin(angle)))
+
+class HeartPoison(RandomBonus):
+ "Heart Poison. Freeze all free monsters."
+ nimage = Bonuses.heart_poison
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Freeze all other players too!"
+ def taken1(self, dragons):
+ import monsters
+ monsters.freeze_em_all()
+ if self.big:
+ def heart_pause(dragon, gen):
+ for i in range(222):
+ yield None
+ dragon.gen = gen
+ for d in BubPlayer.DragonList:
+ if d not in dragons:
+ d.gen = [heart_pause(d, d.gen)]
+
+class VioletNecklace(RandomBonus):
+ "Monster Duplicator. Double the number of free monsters."
+ points = 650
+ nimage = Bonuses.violet_necklace
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "This level's boring, let's bring even more monsters..."
+ def taken1(self, dragons):
+ if self.big:
+ import monsters, mnstrmap
+ mlist = 2*['Nasty', 'Monky', 'Springy', 'Orcy', 'Gramy', 'Blitzy']
+ wrange = (boards.bwidth - 8*CELL) // 2
+ for dir in [1, -1]:
+ for i in range(len(mlist)):
+ name = mlist[i]
+ mdef = getattr(mnstrmap, name)
+ cls = getattr(monsters, name)
+ x = wrange * i // len(mlist)
+ if dir == 1:
+ x = 2*CELL + HALFCELL + x
+ else:
+ x = boards.bwidth - 4*CELL - HALFCELL - x
+ y = -2*CELL - i * 2*CELL
+ cls(mdef, x, y, dir)
+ else:
+ for s in BubPlayer.MonsterList[:]:
+ if s.regular():
+ for i in range(self.multiply):
+ s.__class__(s.mdef, s.x, s.y, -s.dir * (-1)**i)
+
+class WandBonus(RandomBonus):
+ "Wand/Chest. Turn the bubble into bonuses at the end of the level."
+ nimages = [Bonuses.brown_wand, Bonuses.yellow_wand, Bonuses.green_wand,
+ Bonuses.violet_wand, Bonuses.blue_wand, Bonuses.red_wand,
+ Bonuses.violet_chest, Bonuses.blue_chest, Bonuses.red_chest,
+ Bonuses.yellow_chest,
+ ]
+ Modes = [
+ (Bonuses.brown_wand, 750, Bonuses.cyan_ice, 700, BigImages.cyan_ice, 20000),
+ (Bonuses.yellow_wand, 750, Bonuses.violet_ice, 750, BigImages.violet_ice, 20000),
+ (Bonuses.green_wand, 750, Bonuses.peach2, 800, BigImages.peach2, 30000),
+ (Bonuses.violet_wand, 750, Bonuses.pastec2, 850, BigImages.pastec2, 30000),
+ (Bonuses.blue_wand, 750, Bonuses.cream_pie, 900, BigImages.cream_pie, 40000),
+ (Bonuses.red_wand, 750, Bonuses.sugar_pie, 950, BigImages.sugar_pie, 40000),
+ (Bonuses.violet_chest, 2000, Diamonds.violet, 6000, BigImages.violet, 60000),
+ (Bonuses.blue_chest, 2000, Diamonds.blue, 7000, BigImages.blue, 60000),
+ (Bonuses.red_chest, 2000, Diamonds.red, 8000, BigImages.red, 70000),
+ (Bonuses.yellow_chest, 2000, Diamonds.yellow, 9000, BigImages.yellow, 70000),
+ ]
+ def __init__(self, x, y):
+ self.mode = random.choice(WandBonus.Modes)
+ RandomBonus.__init__(self, x, y, *self.mode[:2])
+ def taken1(self, dragons):
+ BubPlayer.BubblesBecome = self.bubble_outcome
+ BubPlayer.MegaBonus = self.mega_bonus
+ def bubble_outcome(self, bubble):
+ if bubble.pop():
+ x = bubble.x
+ if x < 2*CELL:
+ x = 2*CELL
+ elif x > boards.bwidth - 4*CELL:
+ x = boards.bwidth - 4*CELL
+ Bonus(x, bubble.y, *self.mode[2:4])
+ def mega_bonus(self):
+ nico, npoints = self.mode[4:6]
+ ico = images.sprget(nico)
+ x = random.randrange(0, boards.bwidth-ico.w)
+ mb = Megabonus(x, -ico.h, nico, npoints)
+ mb.outcome = (Bonus,) + self.mode[2:4]
+ mb.outcome_image = self.mode[2]
+WandBonus1 = WandBonus # increase probability
+
+class Megabonus(Bonus):
+ touchable = 0
+ vspeed = 6
+ sound = 'Extra'
+ coverwithbonus = 99
+ fallerdelay = 71
+
+ def faller(self):
+ self.fullpoints = self.points
+ self.bubbles = {}
+ for t in range(self.fallerdelay):
+ yield None
+ self.ready_to_go()
+ self.bubbles_pos = list(self.bubbles_position())
+ self.gen.append(self.animate_bubbles())
+ y0 = self.y - HALFCELL
+ ymax = boards.bheight - CELL - self.ico.h
+ self.touchable = 1
+ ny = self.y
+ while self.y >= y0:
+ if self.vspeed:
+ ny += self.vspeed
+ if ny > ymax:
+ ny = ymax
+ self.vspeed = 0
+ self.move(self.x, int(ny))
+ yield None
+ self.kill()
+
+ def ready_to_go(self):
+ pass
+
+ def is_on_ground(self):
+ return self.y == boards.bheight - CELL - self.ico.h
+
+ def kill(self):
+ for bubble in self.bubbles.values():
+ bubble.pop()
+ Bonus.kill(self)
+
+ def taken(self, dragon):
+ poplist = [dragon]
+ for bubble in self.bubbles.values():
+ bubble.pop(poplist)
+
+ def bubbles_position(self):
+ import time; start=time.time()
+ cx = self.ico.w//2 - CELL
+ cy = self.ico.h//2 - CELL
+ positions = []
+ pi2 = math.pi * 2
+ dist = 10.0
+ for i in range(31):
+ while 1:
+ angle = random.random() * pi2
+ nx = cx + int(dist*math.sin(angle))
+ ny = cy + int(dist*math.cos(angle))
+ for ox, oy in positions:
+ if (nx-ox)*(nx-ox) + (ny-oy)*(ny-oy) < 220:
+ dist += 0.3
+ break
+ else:
+ break
+ positions.append((nx, ny))
+ #print time.time()-start
+ return positions
+## nx = 5
+## ny = 6
+## xmargin = 2
+## ymargin = 7
+## xstep = (self.ico.w+2*xmargin-2*CELL) / float(nx-1)
+## ystep = (self.ico.h+2*ymargin-2*CELL) / float(ny-1)
+## for dx in range(nx):
+## corner = dx in [0, nx-1]
+## for dy in range(corner, ny-corner):
+## dx1 = int(dx*xstep)-xmargin
+## dy1 = int(dy*ystep)-ymargin
+## yield (dx1 + random.randrange(-2,3),
+## dy1 + random.randrange(-2,3))
+
+ def nearest_free_point(self, x0, y0):
+ distlst = [((x0-x)*(x0-x)+(y0-y)*(y0-y)+random.random(), x, y)
+ for x, y in self.bubbles_pos if (x, y) not in self.bubbles]
+ if distlst:
+ ignored, dx, dy = min(distlst)
+ return dx, dy
+ else:
+ return None, None
+
+ def in_bubble(self, bubble):
+ if not self.touchable:
+ return # bubbling a BonusMaker about to make a big bonus
+ dx, dy = self.nearest_free_point(bubble.x-self.x, bubble.y-self.y)
+ if dx is not None:
+ self.cover_bubble(dx, dy, bubble.d.bubber)
+ self.gen.append(self.cover_bubbles(bubble.d.bubber))
+ bubble.kill()
+
+ def cover_bubbles(self, bubber):
+ while 1:
+ for t in range(2):
+ yield None
+ bubbles = [dxy for dxy, b in self.bubbles.items()
+ if b.bubber is bubber]
+ if not bubbles:
+ break
+ dx, dy = self.nearest_free_point(*random.choice(bubbles))
+ if dx is None:
+ break
+ self.cover_bubble(dx, dy, bubber)
+ self.untouchable()
+
+ def cover_bubble(self, dx, dy, bubber):
+ if (dx, dy) in self.bubbles:
+ return
+ from bubbles import Bubble
+ if len(self.bubbles) & 1:
+ MegabonusBubble = Bubble
+ elif self.coverwithbonus:
+ self.coverwithbonus -= 1
+ outcome = self.outcome
+ outcome_image = self.outcome_image
+ class MegabonusBubble(Bubble):
+ def popped(self, dragon):
+ BonusMaker(self.x, self.y, [outcome_image], outcome=outcome)
+ return 10
+ else:
+ MegabonusBubble = Bubble
+
+ nimages = GreenAndBlue.normal_bubbles[bubber.pn]
+ b = MegabonusBubble(images.sprget(nimages[1]), self.x+dx, self.y+dy)
+ b.dx = dx
+ b.dy = dy
+ b.bubber = bubber
+ b.nimages = nimages
+ self.bubbles[dx, dy] = b
+ self.timeout = 0
+ f = float(len(self.bubbles)) / len(self.bubbles_pos)
+ self.vspeed = -0.73*f + self.vspeed*(1.0-f)
+ self.points = int(self.fullpoints*(1.0-f) / 10000.0 + 0.9999) * 10000
+
+ def animate_bubbles(self):
+ if 0: # disabled clipping
+ d = {}
+ for dx, dy in self.bubbles_pos:
+ d[dx] = d[dy] = None
+ north = d.copy()
+ south = d.copy()
+ west = d.copy()
+ east = d.copy()
+ del d
+ for dx, dy in self.bubbles_pos:
+ lst = [y for x, y in self.bubbles_pos if x==dx and y<dy]
+ if lst: north[dy] = max(lst)
+ lst = [y for x, y in self.bubbles_pos if x==dx and y>dy]
+ if lst: south[dy] = min(lst)
+ lst = [x for x, y in self.bubbles_pos if x<dx and y==dy]
+ if lst: west[dx] = max(lst)
+ lst = [x for x, y in self.bubbles_pos if x>dx and y==dy]
+ if lst: east[dx] = min(lst)
+ W = 2*CELL
+ H = 2*CELL
+ bubbles = self.bubbles
+ while 1:
+ for cycle in [1]*8 + [2]*10 + [1]*8 + [0]*10:
+ yield None
+ for (dx, dy), bubble in bubbles.items():
+ if not hasattr(bubble, 'poplist'):
+ if 0: # disabled clipping
+ if (dx, north[dy]) in bubbles:
+ margin_n = (north[dy]+H-dy)//2
+ else:
+ margin_n = 0
+ if (dx, south[dy]) in bubbles:
+ margin_s = (dy+H-south[dy])//2
+ else:
+ margin_s = 0
+ if (west[dx], dy) in bubbles:
+ margin_w = (west[dx]+W-dx)//2
+ else:
+ margin_w = 0
+ if (east[dx], dy) in bubbles:
+ margin_e = (dx+W-east[dx])//2
+ else:
+ margin_e = 0
+ r = (margin_w,
+ margin_n,
+ W-margin_w-margin_e,
+ H-margin_n-margin_s)
+ bubble.move(self.x + bubble.dx + margin_w,
+ self.y + bubble.dy + margin_n,
+ images.sprget_subrect(
+ bubble.nimages[cycle], r))
+ else:
+ bubble.move(self.x + bubble.dx,
+ self.y + bubble.dy,
+ images.sprget(bubble.nimages[cycle]))
+ elif len(bubbles) == len(self.bubbles_pos):
+ self.pop_bubbles(bubble.poplist)
+ return
+
+ def pop_bubbles(self, poplist):
+ def bubble_timeout(bubble, vspeed):
+ ny = bubble.y
+ for t in range(random.randrange(15,25)):
+ if hasattr(bubble, 'poplist'):
+ return
+ ny += vspeed
+ bubble.move(bubble.x, int(ny))
+ yield None
+ bubble.pop(poplist)
+
+ for bubble in self.bubbles.values():
+ bubble.gen.append(bubble_timeout(bubble, self.vspeed))
+ self.bubbles.clear()
+ self.kill()
+
+class Cactus(RandomBonus):
+ "Cactus. Drop a big version of a random bonus."
+ points = 600
+ nimage = 'cactus'
+ extra_cheat_arg = None
+ bigbonus = {'multiply': 3}
+ bigdoc = "Let's get more big bonuses!"
+
+ def taken1(self, dragons):
+ count = 0
+ while count < self.multiply:
+ args = ()
+ if self.extra_cheat_arg:
+ cls = globals()[self.extra_cheat_arg]
+ self.extra_cheat_arg = None
+ elif bigclockticker and bigclockticker.state == 'pre':
+ cls = Clock
+ args = (1,)
+ else:
+ cls = random.choice(Classes)
+ if makecactusbonus(cls, *args):
+ count += 1
+ cactusbonussound()
+
+#Cactus1 = Cactus # increase probability
+
+OFFSCREEN = -3*CELL
+def makecactusbonus(cls, *args):
+ bonus = cls(OFFSCREEN, 0, *args)
+ if not bonus.alive or getattr(bonus, 'bigbonus', None) is None:
+ if bonus.alive:
+ bonus.kill()
+ return None
+ bonus.__dict__.update(bonus.bigbonus)
+ bonus.untouchable()
+ bonus.gen = []
+ megacls = bonus.bigbonus.get('megacls', Cactusbonus)
+ mb = megacls(0, -3*CELL, 'cactus', 10000) # temp image
+ mb.outcome = (cls,) + (args or bonus.bigbonus.get('outcome_args', ()))
+ mb.outcome_image = bonus.nimage
+ mb.bonus = bonus
+ mb.gen.append(mb.prepare_image())
+ mb.gen.append(mb.remove_if_no_bonus())
+ return mb
+
+def cactusbonussound():
+ gamesrv.set_musics([], [])
+ boards.curboard.set_musics(prefix=[images.music_modern])
+ boards.curboard.set_musics()
+
+class Cactusbonus(Megabonus):
+ coverwithbonus = 5
+
+ def prepare_image(self):
+ while images.computebiggericon(self.bonus.ico) is None:
+ yield None
+
+ def remove_if_no_bonus(self):
+ while self.bonus.alive:
+ yield None
+ self.kill()
+
+ def ready_to_go(self):
+ ico = images.biggericon(self.bonus.ico)
+ x = random.randrange(0, boards.bwidth-ico.w)
+ self.move(x, -ico.h, ico)
+
+ def taken1(self, dragons):
+ d1 = list(dragons)
+ Megabonus.taken1(self, dragons)
+ if self.bonus.alive:
+ x = self.x + self.ico.w//2 - CELL
+ y = self.y + self.ico.h//2 - CELL
+ self.bonus.move(x, y)
+ res = self.bonus.taken1(d1)
+ self.untouchable()
+ if res == -1:
+ self.taken_by = []
+ self.gen.append(self.touchdelay(10))
+ self.bonus.move(OFFSCREEN, 0)
+ else:
+ self.bonus.kill()
+ return res
+
+ def kill(self):
+ Megabonus.kill(self)
+ if self.bonus.alive:
+ self.bonus.kill()
+
+class LongDurationCactusbonus(Cactusbonus):
+ timeout = 500
+ killgens = 0
+
+def starexplosion(x, y, multiplyer, killmonsters=0, outcomes=[]):
+ outcomes = list(outcomes)
+ poplist = [None]
+ for i in range(multiplyer):
+ colors = list(Stars.COLORS)
+ random.shuffle(colors)
+ for colorname in colors:
+ images = getattr(Stars, colorname)
+ if outcomes:
+ outcome = outcomes.pop()
+ extra_stars = []
+ if hasattr(outcome[0], 'extra_stars_location'):
+ for sx, sy in outcome[0].extra_stars_location:
+ extra_stars.append(BonusMakerExtraStar(x, y, sx, sy,
+ colorname))
+ bm = BonusMaker(x, y, images, outcome=outcome)
+ for star in extra_stars:
+ star.gen.append(star.follow_bonusmaker(bm))
+ else:
+ b = Parabolic2(x, y, images)
+ if killmonsters:
+ b.gen.append(b.killmonsters(poplist))
+
+class HomingStar(ActiveSprite):
+ def __init__(self, x, y, colorname, poplist):
+ imglist = getattr(Stars, colorname)
+ ActiveSprite.__init__(self, images.sprget(imglist[0]), x, y)
+ self.colorname = colorname
+ self.setimages(self.cyclic(imglist, 2))
+ self.gen.append(self.homing(poplist))
+
+ def homing(self, poplist):
+ from monsters import Monster
+ target = None
+ vx = (random.random() - 0.5) * 6.6
+ vy = (random.random() - 0.5) * 4.4
+ nx = self.x
+ ny = self.y
+ counter = 10
+ while 1:
+ if random.random() < 0.02:
+ target = None
+ if target is None or not target.alive:
+ bestdist = 1E10
+ for s in BubPlayer.MonsterList:
+ if isinstance(s, Monster):
+ dx = s.x - nx
+ dy = s.y - ny
+ dist = dx*dx + dy*dy + (random.random() * 25432.1)
+ if dist < bestdist:
+ bestdist = dist
+ target = s
+ if target is None:
+ break
+ dx = target.x - nx
+ dy = target.y - ny
+ dist = dx*dx + dy*dy
+ if dist <= 3*CELL*CELL:
+ target.argh(poplist)
+ break
+ yield None
+ vx = (vx + dx * 0.005) * 0.96
+ vy = (vy + dy * 0.005) * 0.96
+ nx += vx
+ ny += vy
+ self.move(int(nx), int(ny))
+ if counter:
+ counter -= 1
+ else:
+ img = ('smstar', self.colorname, random.randrange(2))
+ s = ActiveSprite(images.sprget(img), self.x + 8, self.y + 8)
+ s.gen.append(s.die([None], speed=10))
+ counter = 3
+ self.kill()
+
+class Book(RandomBonus):
+ "Magic Bomb. Makes a magical explosion killing touched monsters."
+ points = 2000
+ nimage = Bonuses.book
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Homing Magical Stars."
+ def taken1(self, dragons):
+ if self.big:
+ poplist = [None]
+ x = self.x + (self.ico.w - 2*CELL) // 2
+ y = self.y + (self.ico.h - 2*CELL) // 2
+ colors = list(Stars.COLORS)
+ random.shuffle(colors)
+ for colorname in colors + colors[len(colors)//2:]:
+ HomingStar(x, y, colorname, poplist)
+ else:
+ starexplosion(self.x, self.y, self.multiply, killmonsters=1)
+
+class Potion(RandomBonus):
+ "Potions. Clear the level and fill its top with bonuses."
+ nimages = [Bonuses.red_potion, Bonuses.green_potion, Bonuses.yellow_potion,
+ 'potion4']
+ Potions = [(Bonuses.red_potion, 150, [(PotionBonuses.coin, 350),
+ (PotionBonuses.rainbow, 600)]),
+ (Bonuses.green_potion, 350, [(PotionBonuses.flower, 1000),
+ (PotionBonuses.trefle, 2000)]),
+ (Bonuses.yellow_potion, 550, [(PotionBonuses.green_note, 2000),
+ (PotionBonuses.blue_note, 3000)]),
+ ('potion4', 750, None),
+ ]
+ LocalDir = os.path.dirname(__file__) or os.curdir
+ Extensions = [s for s in os.listdir(LocalDir)
+ if s.startswith('ext') and
+ os.path.isdir(os.path.join(LocalDir, s))]
+ random.shuffle(Extensions)
+ extra_cheat_arg = None
+ big = 0
+ bigdoc = "Fill the whole level with bonuses."
+
+ def __init__(self, x, y):
+ p_normal = 3
+ if boards.curboard.bonuslevel:
+ p_extension = 2 # make extensions rare in the bonus level
+ else:
+ p_extension = 5
+ if self.extra_cheat_arg:
+ Potion.Extensions.append(self.extra_cheat_arg)
+ p_normal = 0
+ if not Potion.Extensions:
+ p_extension = 0
+ choices = []
+ for mode in Potion.Potions:
+ if mode[2] is None:
+ p = p_extension
+ else:
+ p = p_normal
+ choices += [mode] * p
+ self.mode = random.choice(choices)
+ if self.mode[2] is not None:
+ self.bigbonus = {'big': 1}
+ RandomBonus.__init__(self, x, y, *self.mode[:2])
+ def taken1(self, dragons):
+ blist = self.mode[2]
+ if blist is not None:
+ if random.random() < 0.6:
+ blist = [random.choice(blist)]
+ boards.replace_boardgen(boards.potion_fill(blist, self.big))
+ else:
+ n_players = len([p for p in BubPlayer.PlayerList if p.isplaying()])
+ while Potion.Extensions:
+ ext = Potion.Extensions.pop()
+ #print "Trying potion:", ext
+ ext = __import__(ext, globals(),locals(), ['run','min_players'])
+ if n_players >= ext.min_players:
+ ext.run()
+ boards.BoardGen.append(boards.extra_bkgnd_black(self.x, self.y))
+ #print "Accepted because:", n_players, ">=", ext.min_players
+ break
+ else:
+ #print "Rejected because:", n_players, "<", ext.min_players
+ pass
+
+class FireBubble(RandomBonus):
+ "Fire Bubbles. Makes you fire napalm bubbles."
+ nimage = Bonuses.hamburger
+ bubkind = 'FireBubble'
+ bubcount = 10
+ bigbonus = {'bubkind': 'BigFireBubble'}
+ bigdoc = "Makes you shoot fire - you're a dragon after all."
+ def taken(self, dragon):
+ dragon.dcap['shootbubbles'] = [self.bubkind] * self.bubcount
+ dragon.carrybonus(self)
+
+class WaterBubble(FireBubble):
+ "Water Bubbles. Your bubbles will now be filled with water."
+ nimage = Bonuses.beer
+ bubkind = 'WaterBubble'
+ bigbonus = {'bubkind': 'SnookerBubble'}
+ bigdoc = "Snooker balls."
+
+class LightningBubble(FireBubble):
+ "Lightning Bubbles."
+ nimage = Bonuses.french_fries
+ bubkind = 'LightningBubble'
+ bigbonus = {'bubkind': 'BigLightBubble'}
+ bigdoc = "Even-more-lightning Bubbles."
+
+class Megadiamond(Megabonus):
+ nimage = BigImages.red
+ points = 20000
+ fallerdelay = 0
+ outcome = (MonsterBonus, -1)
+ outcome_image = Bonuses.monster_bonuses[-1][0]
+ extra_stars_location = [ (-24,-28),(0,-28),(24,-28),
+ (-40,-11), (40,-11),
+ (-32, 8), (32, 8),
+ (-16,23), (16,23),
+ (0,38), ]
+ def __init__(self, x, y):
+ ico = images.sprget(self.nimage)
+ x -= (ico.w - 2*CELL) // 2
+ y -= (ico.h - 2*CELL) // 2
+ Megabonus.__init__(self, x, y)
+
+class Door(RandomBonus):
+ "Magic Door. Let bonuses come in!"
+ points = 1000
+ nimage = Bonuses.door
+ diamond_outcome = (MonsterBonus, -1)
+ bigbonus = {'diamond_outcome': (Megadiamond,)}
+ bigdoc = "Let bigger bonuses come in!"
+ def taken1(self, dragons):
+ starexplosion(self.x, self.y, 2,
+ outcomes = [self.diamond_outcome] * 10)
+
+class LongFire(RandomBonus):
+ "Long Fire. Increase the range of your bubble throw out."
+ nimage = Bonuses.softice1
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Throw bubbles that split into more bubbles."
+ def taken(self, dragon):
+ if self.big:
+ dragon.dcap['shootbubbles'] = ['MoreBubblesBubble'] * 10
+ else:
+ dragon.dcap['shootthrust'] *= 1.5
+ dragon.carrybonus(self)
+
+class Glue(RandomBonus):
+ "Glue. Triple fire."
+ nimage = 'glue'
+ points = 850
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Heptuple fire. (That's 7.)"
+ def taken(self, dragon):
+ if self.big:
+ dragon.dcap['flower'] = -16 # heptuple fire
+ elif dragon.dcap['flower'] >= 0:
+ dragon.dcap['flower'] = -1 # triple fire
+ else:
+ dragon.dcap['flower'] -= 1 # cumulative effect
+ dragon.carrybonus(self)
+
+class ShortFire(RandomBonus):
+ "Short Fire. Shorten the range of your bubble throw out."
+ nimage = Bonuses.softice2
+ points = 300
+ factor = 1 / 1.5
+ bigbonus = {'factor': 0}
+ bigdoc = "What occurs if you throw bubbles at range zero?"
+ def taken(self, dragon):
+ dragon.dcap['shootthrust'] *= self.factor
+ dragon.carrybonus(self)
+
+class HighSpeedFire(RandomBonus):
+ "High Speed Fire. Increase your fire rate."
+ nimage = Bonuses.custard_pie
+ points = 700
+ bigbonus = {'multiply': 4}
+ bigdoc = "Machine-gun speed!"
+ def taken(self, dragon):
+ dragon.dcap['firerate'] += 1.5
+ dragon.carrybonus(self)
+
+class Mushroom(TemporaryBonus):
+ "Bouncy Bouncy. Makes you jump continuously."
+ nimage = Bonuses.mushroom
+ points = 900
+ capname = 'pinball'
+ captime = 625
+ bigbonus = {'captime': captime*2, 'multiply': 2}
+ bigdoc = "The same, but even more annoying."
+
+class AutoFire(TemporaryBonus):
+ "Auto Fire. Makes you fire continuously."
+ nimage = Bonuses.rape
+ points = 800
+ capname = 'autofire'
+ captime = 675
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Adds many bubbles to the level."
+ def taken1(self, dragons):
+ if self.big:
+ boards.extra_boardgen(boards.extra_bubbles(900))
+ else:
+ TemporaryBonus.taken1(self, dragons)
+
+class Insect(RandomBonus):
+ "Crush World."
+ nimage = Bonuses.insect
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "What if the level looked like that instead... Or like that... Or..."
+ def taken1(self, dragons):
+ if self.big:
+ if dragons:
+ d = random.choice(dragons)
+ cx, cy = d.x, d.y
+ else:
+ cx, cy = None, None
+ boards.extra_boardgen(boards.extra_make_random_level(cx, cy))
+ else:
+ boards.extra_boardgen(boards.extra_walls_falling())
+
+class Ring(TemporaryBonus):
+ "The One Ring."
+ nimage = Bonuses.ring
+ points = 4000
+ capname = 'ring'
+ captime = 700
+ bonusleveldivider = 5
+ bigbonus = {'multiply': 3}
+ bigdoc = "Where am I?"
+
+class GreenPepper(TemporaryBonus):
+ "Hot Pepper. Run! Run! That burns."
+ nimage = Bonuses.green_pepper
+ capname = 'hotstuff'
+ captime = 100
+ bigbonus = {'captime': 250, 'multiply': 2}
+ bigdoc = "That burns a lot!"
+
+class Lollipop(TemporaryBonus):
+ "Yo Man! Makes you walk backward."
+ nimage = Bonuses.lollipop
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Just swapping 'left' and 'right' is not confusing enough."
+ def taken(self, dragon):
+ dragon.dcap['left2right'] = -dragon.dcap['left2right']
+ if self.big:
+ perm = range(4)
+ while perm[0] == 0 or perm[1] == 1 or perm[2] == 2 or perm[3] == 3:
+ random.shuffle(perm)
+ names = ('key_left', 'key_right', 'key_jump', 'key_fire')
+ dragon.dcap['key_right'] = names[perm[0]]
+ dragon.dcap['key_left'] = names[perm[1]]
+ dragon.dcap['key_jump'] = names[perm[2]]
+ dragon.dcap['key_fire'] = names[perm[3]]
+ self.carried(dragon)
+ def endaction(self, dragon):
+ dragon.dcap['left2right'] = -dragon.dcap['left2right']
+ for name in ('key_left', 'key_right', 'key_jump', 'key_fire'):
+ dragon.dcap[name] = name
+
+class Chickpea(TemporaryBonus):
+ "Basilik. Allows you to touch the monsters."
+ nimage = Bonuses.chickpea
+ points = 800
+ capname = 'overlayglasses'
+ captime = 400
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Turn off the light."
+
+ def taken1(self, dragons):
+ if self.big:
+ boards.extra_boardgen(boards.extra_light_off(597), 1)
+ else:
+ TemporaryBonus.taken1(self, dragons)
+
+ def taken(self, dragon):
+ TemporaryBonus.taken(self, dragon)
+ dragon.dcap['shield'] += 420
+
+class IceCream(RandomBonus):
+ "Icecream. An icecream which is so good you'll always want more."
+ nimages = [Bonuses.icecream6, Bonuses.icecream5,
+ Bonuses.icecream4, Bonuses.icecream3]
+ IceCreams = [(Bonuses.icecream6, 250),
+ (Bonuses.icecream5, 500),
+ (Bonuses.icecream4, 1000),
+ (Bonuses.icecream3, 2000)]
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "BIG ice creams!"
+ def __init__(self, x, y, generation=0):
+ self.generation = generation
+ RandomBonus.__init__(self, x, y, *self.IceCreams[generation])
+ def taken1(self, dragons):
+ nextgen = self.generation + 1
+ if nextgen < len(self.IceCreams):
+ for i in range(2):
+ if self.big:
+ makecactusbonus(IceCream, nextgen)
+ else:
+ x, y = chooseground(200)
+ if x is None:
+ return
+ IceCream(x, y, nextgen)
+ if self.big:
+ cactusbonussound()
+
+class Grenade(RandomBonus):
+ "Barbecue."
+ nimage = Bonuses.grenade
+ points = 550
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "360-degree flames."
+ def taken1(self, dragons):
+ from bubbles import FireFlame
+ poplist = [None]
+ for y in range(1, boards.height-1):
+ for x in range(2, boards.width-2):
+ if bget(x,y) != ' ':
+ continue
+ if bget(x,y+1) == '#':
+ FireFlame(x, y, poplist)
+ elif self.big:
+ if bget(x,y-1) == '#':
+ FireFlame(x, y, poplist, flip='vflip')
+ elif bget(x-1,y) == '#':
+ FireFlame(x, y, poplist, flip='cw')
+ elif bget(x+1,y) == '#':
+ FireFlame(x, y, poplist, flip='ccw')
+
+class Conch(RandomBonus):
+ "Sea Shell. Let's bring the sea here!"
+ nimage = Bonuses.conch
+ points = 650
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Aquarium."
+ def taken1(self, dragons):
+ if self.big:
+ gen = boards.extra_aquarium
+ else:
+ gen = boards.extra_water_flood
+ boards.extra_boardgen(gen())
+
+def fire_rain(x, poplist):
+ from bubbles import FireDrop
+ FireDrop(x, -CELL, poplist)
+
+def water_rain(x, poplist):
+ from bubbles import watercell
+ watercell(x, 0, poplist)
+
+def ball_rain(x, poplist):
+ from bubbles import SpinningBall
+ SpinningBall(x, -CELL, poplist)
+
+class Umbrella(RandomBonus):
+ "Umbrellas. Beware of what's going to fall on everyone's head!"
+ nimages = [Bonuses.brown_umbrella, Bonuses.grey_umbrella,
+ Bonuses.violet_umbrella]
+ Umbrellas = [(Bonuses.brown_umbrella, 900, fire_rain, 10, 60),
+ (Bonuses.grey_umbrella, 950, water_rain, 5, 60),
+ (Bonuses.violet_umbrella, 1000, ball_rain, 9, 120)]
+ bigbonus = {'multiply': 3.1416}
+ bigdoc = "It's raining hard."
+ def __init__(self, x, y):
+ self.mode = random.choice(Umbrella.Umbrellas)
+ RandomBonus.__init__(self, x, y, *self.mode[:2])
+ def taken1(self, dragons):
+ for i in range(self.multiply):
+ boards.extra_boardgen(self.raining())
+ def raining(self):
+ builder, drops, timemax = self.mode[2:]
+ timemax = int(timemax * math.sqrt(self.multiply))
+ drops = int(drops * self.multiply)
+ times = [random.randrange(0, timemax) for i in range(drops)]
+ poplist = [None]
+ for t in range(timemax):
+ for i in range(times.count(t)):
+ x = random.randrange(2*CELL, bwidth-3*CELL+1)
+ builder(x, poplist)
+ yield 0
+
+class Fruits(RandomBonus):
+ "Fruits. A small little bonus. But the size doesn't matter, does it? If you're lucky enough you might get a great shower!"
+ nimages = [Bonuses.kirsh, Bonuses.erdbeer, Bonuses.tomato,
+ Bonuses.apple, Bonuses.corn, Bonuses.radish]
+ bubblable = 0
+ sound = 'Extra'
+ Fruits = [(Bonuses.kirsh, 100),
+ #(Bonuses.icecream1, 150),
+ (Bonuses.erdbeer, 150),
+ #(Bonuses.fish1, 250),
+ (Bonuses.tomato, 200),
+ #(Bonuses.donut, 250),
+ (Bonuses.apple, 250),
+ (Bonuses.corn, 300),
+ #(Bonuses.icecream2, 600),
+ (Bonuses.radish, 350),
+ ]
+ def __init__(self, x, y): # x and y ignored !
+ fine = 0
+ for i in range(20):
+ x0 = random.randint(3, boards.width-5)
+ y0 = random.randint(1, boards.height-3)
+ for xt in range(x0-1, x0+3):
+ if xt == x0-1 or xt == x0+2:
+ yplus = 1
+ else:
+ yplus = 0
+ for yt in range(y0+yplus, y0+4-yplus):
+ if bget(xt,yt) != ' ':
+ break
+ else:
+ continue
+ break
+ else:
+ x, y = x0*CELL, y0*CELL
+ fine = 1
+ break
+ mode = random.choice(Fruits.Fruits)
+ RandomBonus.__init__(self, x, y, falling=0, *mode)
+ self.repeatcount = 0
+ if not fine:
+ self.kill()
+ elif random.random() < 0.04:
+ self.superfruit = mode
+ self.sound = 'Shh'
+ self.points = 0
+ self.repeatcount = random.randrange(50,100)
+ def taken1(self, dragons):
+ if self.repeatcount:
+ image, points = self.superfruit
+ f = Parabolic2(self.x, self.y, [image], y_amplitude = -1.5)
+ f.points = points
+ f.touchable = 1
+ self.repeatcount -= 1
+ self.gen.append(self.taking(1, 2))
+ return -1
+Fruits1 = Fruits # increase probability
+Fruits2 = Fruits
+Fruits3 = Fruits
+Fruits4 = Fruits
+Fruits5 = Fruits
+Fruits6 = Fruits
+
+class BlueNecklace(RandomBonus):
+ "Self Duplicator. Mirror yourself."
+ points = 1000
+ nimage = Bonuses.blue_necklace
+ copies = 1
+ bigbonus = {'copies': 3}
+ bigdoc = "Mirrors vertically too."
+ def taken(self, dragon):
+ dragons = [dragon]
+ modes = [(-1, 1), (1, -1), (-1, -1)][:self.copies]
+ modes.reverse()
+ dcap = dragon.dcap.copy()
+ for sign, gravity in modes:
+ if len(dragon.bubber.dragons) >= 7:
+ break # avoid burning the server with two much dragons
+ d1 = self.makecopy(dragon, sign, gravity, dcap)
+ dragons.append(d1)
+ d1 = random.choice(dragons)
+ d1.carrybonus(self, 250)
+
+ def makecopy(self, dragon, sign=-1, gravity=1, dcap=None):
+ from player import Dragon
+ dcap = dcap or dragon.dcap
+ d = Dragon(dragon.bubber, dragon.x, dragon.y, -dragon.dir, dcap)
+ d.dcap['left2right'] = sign * dcap['left2right']
+ d.dcap['gravity'] = gravity * dcap['gravity']
+ d.up = dragon.up
+ s = (dcap['shield'] + 12) & ~3
+ dragon.dcap['shield'] = s+2
+ if sign*gravity > 0:
+ s += 2
+ d.dcap['shield'] = s
+ dragon.bubber.dragons.append(d)
+ return d
+
+class Monsterer(RandomBonus):
+ "Monsterificator. Let's play on the other side!"
+ nimages = [Bonuses.red_crux, Bonuses.blue_crux]
+ Sizes = [(Bonuses.red_crux, 800), (Bonuses.blue_crux, 850)]
+ mlist = [['Nasty', 'Monky', 'Springy', 'Orcy'],
+ ['Ghosty', 'Flappy', 'Gramy', 'Blitzy']
+ ]
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Ta, ta ta, ta, taaaaaa..."
+ def __init__(self, x, y):
+ self.mode = random.choice([0,1])
+ RandomBonus.__init__(self, x, y, *self.Sizes[self.mode])
+ def taken(self, dragon):
+ mcls = random.choice(self.mlist[self.mode])
+ dragon.become_monster(mcls, self.big)
+
+Monsterer1 = Monsterer # increase probability
+
+class Bubblizer(RandomBonus):
+ "Bubblizer."
+ points = 750
+ nimage = Bonuses.gold_crux
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Special powers for your bubble."
+ def taken(self, dragon):
+ args = (dragon.bubber.pn,)
+ if self.big:
+ from bubbles import SnookerBubble, BigLightBubble
+ bcls = random.choice([SnookerBubble, BigLightBubble])
+ if bcls is SnookerBubble:
+ args = (dragon, dragon.x, dragon.y, 1000000)
+ else:
+ from bubbles import FireBubble, WaterBubble, LightningBubble
+ bcls = random.choice([FireBubble, WaterBubble, LightningBubble])
+ b = bcls(*args)
+ b.move(dragon.x, dragon.y)
+ if not dragon.become_bubblingeyes(b):
+ b.kill()
+
+class Carrot(RandomBonus):
+ "Angry Monster. Turns all free monsters angry."
+ nimage = Bonuses.carrot
+ points = 950
+ ghost = 0
+ bigbonus = {'ghost': 1}
+ bigdoc = "What do angry monsters turn into if you don't hurry up?"
+ def taken1(self, dragons):
+ from monsters import Monster
+ lst = [s for s in images.ActiveSprites
+ if isinstance(s, Monster) and s.regular()]
+ if lst:
+ if self.ghost:
+ images.Snd.Hell.play()
+ for s in lst:
+ s.become_ghost()
+ else:
+ for s in lst:
+ s.angry = [s.genangry()]
+ s.resetimages()
+
+class Egg(RandomBonus):
+ "Teleporter. Exchange yourself with somebody else."
+ nimage = Bonuses.egg
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Exchange colors too."
+ def taken1(self, dragons):
+ if self.big:
+ self.exchange_bubbers()
+ else:
+ self.exchange_dragons(dragons)
+
+ def exchange_dragons(self, dragons):
+ dragons = [d for d in dragons if d in d.bubber.dragons]
+ alldragons = [d for d in BubPlayer.DragonList if d in d.bubber.dragons]
+ others = [d for d in alldragons if d not in dragons]
+ xchg = {}
+ random.shuffle(dragons)
+ random.shuffle(others)
+ while dragons and others:
+ d1 = dragons.pop()
+ d2 = others.pop()
+ xchg[d1] = d2.bubber
+ xchg[d2] = d1.bubber
+ if len(dragons) > 1:
+ copy = dragons[:]
+ for i in range(10):
+ random.shuffle(copy)
+ for j in range(len(dragons)):
+ if dragons[j] == copy[j]:
+ break
+ else:
+ break
+ for d1, d2 in zip(dragons, copy):
+ xchg[d1] = d2.bubber
+ elif len(dragons) == 1:
+ x, y = chooseground(200)
+ if x is not None:
+ d1 = dragons[0]
+ d1.move(x, y)
+ d1.dcap['shield'] = 50
+ for d1, bubber2 in xchg.items():
+ d1.bubber.dragons.remove(d1)
+ d1.bubber = bubber2
+ bubber2.dragons.append(d1)
+ d1.dcap['shield'] = 50
+
+ def exchange_bubbers(self):
+ self.exchange_dragons(list(BubPlayer.DragonList))
+ players = [p for p in BubPlayer.PlayerList
+ if p.isplaying()]
+ if len(players) > 1:
+ while 1:
+ copy = players[:]
+ random.shuffle(copy)
+ for j in range(len(players)):
+ if players[j] is copy[j]:
+ break
+ else:
+ break
+ for b1, b2 in zip(players, copy):
+ for d in b1.dragons:
+ d.dcap['bubbericons'] = b2
+
+class Bomb(RandomBonus):
+ "Baaoouuuummmm! Explode that wall!"
+ nimage = Bonuses.bomb
+ bigbonus = {'multiply': 3.8}
+ bigdoc = "Makes a BIG hole."
+ def taken1(self, dragons):
+ bomb_explosion(self.x, self.y, self.multiply)
+
+def bomb_explosion(x0, y0, multiply=1, starmul=2):
+ RADIUS = 3.9 * CELL * math.sqrt(multiply)
+ Radius2 = RADIUS * RADIUS
+ brd = boards.curboard
+ cx = x0 + HALFCELL
+ cy = y0 + HALFCELL - RADIUS/2
+ for y in range(0, brd.height):
+ dy1 = abs(y*CELL - cy)
+ dy2 = abs((y-(brd.height-1))*CELL - cy)
+ dy3 = abs((y+(brd.height-1))*CELL - cy)
+ dy = min(dy1, dy2, dy3)
+ for x in range(2, brd.width-2):
+ dx = x*CELL - cx
+ if dx*dx + dy*dy < Radius2:
+ try:
+ brd.killwall(x,y)
+ except KeyError:
+ pass
+ brd.reorder_walls()
+ starexplosion(x0, y0, starmul)
+ gen = boards.extra_display_repulse(x0+CELL, y0+CELL,
+ 15000 * multiply,
+ 1000 * multiply)
+ boards.extra_boardgen(gen)
+
+class Ham(RandomBonus):
+ "Protein. Let's build something!"
+ nimage = Bonuses.ham
+ bigbonus = {'multiply': 3.4}
+ bigdoc = "Builds something BIG."
+ def taken1(self, dragons):
+ RADIUS = 3.9 * CELL * math.sqrt(self.multiply)
+ Radius2 = RADIUS * RADIUS
+ brd = boards.curboard
+ cx = self.x + HALFCELL
+ cy = self.y + HALFCELL - RADIUS/2
+ xylist = []
+ for y in range(0, brd.height):
+ dy1 = abs(y*CELL - cy)
+ dy2 = abs((y-(brd.height-1))*CELL - cy)
+ dy3 = abs((y+(brd.height-1))*CELL - cy)
+ dy = min(dy1, dy2, dy3)
+ for x in range(2, brd.width-2):
+ dx = x*CELL - cx
+ if dx*dx + dy*dy < Radius2:
+ if (y,x) not in brd.walls_by_pos and random.random() < 0.5:
+ brd.putwall(x,y)
+ xylist.append((x, y))
+ brd.reorder_walls()
+ boards.extra_boardgen(boards.single_blocks_falling(xylist))
+ gen = boards.extra_display_repulse(self.x+CELL, self.y+CELL,
+ 5000 * self.multiply,
+ 1000 * self.multiply)
+ boards.extra_boardgen(gen)
+
+class Chestnut(RandomBonus):
+ "Relativity. Speed up or slow down the game."
+ nimage = Bonuses.chestnut
+ sound = None
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Relative relativity - not the same one for players and monsters."
+ def taken1(self, dragons):
+ timeout = 500
+ if not self.big:
+ ft = random.choice([0.5, 2.0])
+ boards.set_frametime(ft)
+ if ft == 2.0:
+ timeout = 430
+ else:
+ if random.randrange(0, 2) == 1:
+ # super-fast game
+ boards.set_frametime(0.25)
+ timeout = 1800
+ else:
+ # board unchanged, players slower
+ boards.set_frametime(1.0, privtime=250)
+ timeout = 800
+ BubPlayer.MultiplyerReset = BubPlayer.FrameCounter + timeout
+ self.play(images.Snd.Fruit)
+
+
+try:
+ import statesaver
+except ImportError:
+ print "'statesaver' module not compiled, no clock bonus"
+ Clock = None
+else:
+ import new
+ try:
+ from statesaver import standard_build # PyPy
+ except ImportError:
+ def standard_build(self):
+ return new.instance(self.__class__)
+ boards.Copyable.inst_build = standard_build
+ gamesrv.Sprite.inst_build = standard_build
+
+ def copygamestate():
+ # makes a copy of the game state.
+ ps = []
+ for p1 in BubPlayer.PlayerList:
+ #if p1.isplaying():
+ d = p1.__dict__.copy()
+ for key in BubPlayer.TRANSIENT_DATA:
+ if key in d:
+ del d[key]
+ ps.append(d)
+ #else:
+ # ps.append(None)
+ topstate = (
+ [g for g in boards.BoardGen if not g.gi_running],
+ boards.curboard,
+ images.ActiveSprites,
+ images.SpritesByLoc,
+ BubPlayer.__dict__.items(),
+ gamesrv.sprites,
+ gamesrv.sprites_by_n,
+ ps,
+ images.Snd.__dict__.items(),
+ )
+ #import pdb; pdb.set_trace()
+ return statesaver.copy(topstate)
+
+ def restoregamestate(savedstate):
+ (boards.BoardGen,
+ boards.curboard,
+ images.ActiveSprites,
+ images.SpritesByLoc,
+ BubPlayerdictitems,
+ gamesrv.sprites,
+ gamesrv.sprites_by_n,
+ ps,
+ imagesSnddictitems,
+ ) = savedstate
+ for key, value in BubPlayerdictitems:
+ try:
+ setattr(BubPlayer, key, value)
+ except (AttributeError, TypeError):
+ pass
+ for key, value in imagesSnddictitems:
+ try:
+ setattr(images.Snd, key, value)
+ except (AttributeError, TypeError):
+ pass
+
+ for p, d in zip(BubPlayer.PlayerList, ps):
+ #if d is None:
+ # p.reset()
+ #else:
+ p.__dict__.update(d)
+ if not p.isplaying():
+ p.zarkoff()
+
+ class Clock(RandomBonus):
+ "Time Machine. Let's do it again!"
+ touchable = 0
+ points = 0
+ nimage = Bonuses.clock
+ ticker = None
+ bigdoc = "Let's do the whole level again - with the help of ghosts from your own past."
+
+ def __init__(self, x, y, big=0):
+ RandomBonus.__init__(self, -boards.bwidth, 0)
+ #print "starting clock"
+ self.savedstate = None
+ self.savedscreens = []
+ if bigclockticker:
+ if not big:
+ self.kill() # confusion between the two levels of saving
+ return
+ if x == OFFSCREEN:
+ if bigclockticker.state == 'pre':
+ self.bigbonus = {'ticker': bigclockticker}
+ bigclockticker.state = 'seen'
+ else:
+ # when taken, this has the same effect as the big clock
+ self.ticker = bigclockticker
+ self.move(x, y)
+ self.touchable = 1
+ return
+ self.gen = [self.delayed_show()]
+
+ def delayed_show(self):
+ boards.extra_boardgen(self.state_saver())
+ for i in range(10):
+ yield None
+ if self.savedstate is not None:
+ for i in range(55):
+ yield None
+ x, y = chooseground(200)
+ if x is not None:
+ self.move(x, y)
+ self.touchable = 1
+ self.gen.append(self.timeouter())
+ self.gen.append(self.faller())
+ return
+ self.kill()
+
+ def taken1(self, dragons):
+ if self.ticker:
+ return self.ticker.taken(dragons)
+ savedstate = self.savedstate
+ self.savedstate = None
+ if savedstate is not None:
+ boards.replace_boardgen(self.state_restorer(savedstate,
+ self.savedscreens,
+ self))
+ self.untouchable()
+ return -1
+
+ def state_saver(self):
+ # called from BoardGen
+ self.savedstate = copygamestate()
+ while self.alive:
+ gamesrv.sprites[0] = ''
+ data = ''.join(gamesrv.sprites)
+ self.savedscreens.append(data)
+ yield 0
+ yield 0
+ self.savedscreens.append(data)
+ yield 0
+ yield 0
+ self.savedscreens = []
+ def state_restorer(self, savedstate, savedscreens, blinkme):
+ # called from BoardGen
+ from player import scoreboard
+ status = 0
+ for t in range(10):
+ if not (t & 1):
+ gamesrv.sprites[0] = ''
+ savedscreens.append(''.join(gamesrv.sprites))
+ time = boards.normal_frame()
+ for i in range(t):
+ status += 1
+ if status % 3 == 0 and blinkme.alive:
+ if status % 6 == 0:
+ blinkme.step(boards.bwidth, 0)
+ else:
+ blinkme.step(-boards.bwidth, 0)
+ yield time
+ yield boards.force_singlegen()
+ yield 15.0
+ for p1 in BubPlayer.PlayerList:
+ del p1.dragons[:]
+ delay = 8.5
+ gamesrv.clearsprites()
+ while savedscreens:
+ gamesrv.sprites[:] = ['', savedscreens.pop()]
+ if delay > 0.6:
+ delay *= 0.9
+ yield delay
+ yield 15.0
+ restoregamestate(savedstate)
+ scoreboard()
+ yield 2.5
+
+ class DragonGhost(ActiveSprite):
+ def __init__(self, entry):
+ ActiveSprite.__init__(self, entry.ico, entry.x, entry.y)
+
+ def setentry(self, entry):
+ #ico = images.make_darker(entry.ico, True)
+ self.lastx = self.x
+ self.lasty = self.y
+ self.move(entry.x, entry.y, entry.ico)
+ self.entry = entry
+ self.bubber = entry.d.bubber
+ self.dir = entry.dir
+ self.poplist = [self]
+
+ def integrate(self):
+ self.play(images.Snd.Shh)
+ for j in range(15):
+ DustStar(self.x, self.y, 0, -3, clock=j==14)
+
+ def disintegrate(self):
+ self.play(images.Snd.Shh)
+ dx = self.x - self.lastx
+ dy = self.y - self.lasty
+ if dx < -4: dx = -4
+ if dy < -4: dy = -4
+ if dx > 4: dx = 4
+ if dy > 4: dy = 4
+ for j in range(15):
+ DustStar(self.x, self.y, dx, dy, clock=j==14)
+ self.kill()
+
+ def bottom_up(self):
+ return self.entry.dcap['gravity'] < 0.0
+
+ class SavedDragonEntry(object):
+ __slots__ = ['d', 'x', 'y', 'ico', 'flag', 'dir', 'dcap']
+ def __init__(self, d, flag, dir, dcap):
+ self.d = d
+ self.x = d.x
+ self.y = d.y
+ self.ico = d.ico
+ self.flag = flag
+ self.dir = dir
+ self.dcap = dcap
+
+ class SavedFrameEntry(object):
+ __slots__ = ['saved_next', 'tick', 'dragonlist', 'shoots1']
+ def __init__(self, tick, dragonlist):
+ self.saved_next = None
+ self.tick = tick
+ self.dragonlist = dragonlist
+ self.shoots1 = []
+
+ class BigClockTicker:
+ dragonlist = None
+ tick = 1000
+
+ def __init__(self):
+ global random
+ random = random_module.Random()
+ localrandom = DustStar.localrandom
+ self.state = 'pre'
+ self.randombase1 = hash(localrandom.random()) * 914971L
+ self.randombase2 = hash(localrandom.random()) * 914971L
+ self.saved_next = None
+ self.saved_last = self
+ random.seed(self.randombase1)
+ random_module.seed(self.randombase2)
+ self.latest_entries = {}
+
+ def common_tick(self, entry):
+ self.dragonlist = entry.dragonlist
+ random.seed(self.randombase1 - entry.tick)
+ random_module.seed(self.randombase2 - entry.tick)
+ bonus_frame_tick()
+ random.seed(self.randombase1 + entry.tick)
+ random_module.seed(self.randombase2 + entry.tick)
+
+ def save_frame_tick(self):
+ entry = self.save_frame()
+ self.common_tick(entry)
+
+ def save_frame(self):
+ from player import Dragon
+ from bubbles import DragonBubble
+ tick = self.saved_last.tick + 1
+ dragonlist = []
+ new_entries = {}
+ for bubber in BubPlayer.PlayerList:
+ if bubber.isplaying():
+ for d in bubber.dragons:
+ try:
+ dcap = self.latest_entries[d].dcap
+ except KeyError:
+ dcap = None
+ dir = getattr(d, 'dir', 1)
+ cur_dcap = getattr(d, 'dcap', Dragon.DCAP)
+ if dcap != cur_dcap:
+ dcap = cur_dcap.copy()
+ if isinstance(d, Dragon):
+ if d.monstervisible():
+ flag = 'visible'
+ else:
+ flag = 'hidden'
+ else:
+ flag = 'other'
+ entry = SavedDragonEntry(d, flag, dir, dcap)
+ new_entries[d] = entry
+ dragonlist.append(entry)
+ self.latest_entries = new_entries
+ entry = SavedFrameEntry(tick, dragonlist)
+ self.saved_last.saved_next = entry
+ self.saved_last = entry
+ return entry
+
+ def taken(self, dragons):
+ boards.replace_boardgen(self.jump_to_past())
+
+ def jump_to_past(self):
+ self.state = 'restoring'
+ boards.replace_boardgen(boards.next_board(fastreenter=True), 1)
+
+ def restore(self):
+ self.ghosts = {}
+ random.seed(self.randombase1)
+ random_module.seed(self.randombase2)
+
+ def show_ghosts(self, dragonlist, interact):
+ new_ghosts = {}
+ for entry in dragonlist:
+ try:
+ ghost = self.ghosts[entry.d]
+ except KeyError:
+ ghost = DragonGhost(entry)
+ ghost.setentry(entry)
+ new_ghosts[entry.d] = ghost
+ if (interact and entry.flag != 'other' and
+ not entry.dcap.get('infinite_shield')):
+ touching = images.touching(entry.x+1, entry.y+1, 30, 30)
+ touching.reverse()
+ for s in touching:
+ if isinstance(s, interact):
+ s.touched(ghost)
+ for d, ghost in self.ghosts.items():
+ if d not in new_ghosts:
+ ghost.kill()
+ self.ghosts = new_ghosts
+
+ def restore_frame_tick(self):
+ from bubbles import Bubble, DragonBubble
+ interact = (Bonus, Parabolic2, Bubble)
+ self.save_frame()
+ entry = self.saved_next
+ self.saved_next = entry.saved_next
+ self.common_tick(entry)
+ self.show_ghosts(entry.dragonlist, interact)
+ for args in entry.shoots1:
+ DragonBubble(*args)
+ if self.state == 'restoring' and self.ghosts:
+ self.state = 'post'
+ for ghost in self.ghosts.values():
+ ghost.integrate()
+
+ def flush_ghosts(self):
+ if self.latest_entries:
+ for ghost in self.ghosts.values():
+ ghost.disintegrate()
+ self.latest_entries.clear()
+ self.dragonlist = None
+
+bigclockticker = None
+
+class MultiStones(RandomBonus):
+ "Gems. Very demanded stones. It will take time to pick it up."
+ nimages = [Bonuses.emerald, Bonuses.sapphire, Bonuses.ruby]
+ Stones = [(Bonuses.emerald, 1000),
+ (Bonuses.sapphire, 2000),
+ (Bonuses.ruby, 3000),
+ ]
+ killgens = 0
+ big = 0
+ bigdoc = "Stones so big you will jump of joy picking them up."
+ def __init__(self, x, y, mode=None):
+ mode = mode or random.choice(MultiStones.Stones)
+ RandomBonus.__init__(self, x, y, *mode)
+ self.bigbonus = {'big': 1, 'outcome_args': (mode,),
+ 'megacls': LongDurationCactusbonus}
+ self.multi = 10
+ def taken1(self, dragons):
+ if self.big:
+ self.repulse(dragons)
+ self.multi -= (len(dragons) or 1)
+ if self.multi > 0:
+ self.taken_by = []
+ self.untouchable()
+ self.gen.append(self.touchdelay(5))
+ return -1 # don't go away
+ def repulse(self, dragons):
+ for d in dragons:
+ repulse_dragon(d)
+
+def repulse_dragon(d):
+ if hasattr(d, 'become_bubblingeyes'):
+ from bubbles import Bubble
+ ico = images.sprget(GreenAndBlue.normal_bubbles[d.bubber.pn][0])
+ b = Bubble(ico, d.x, d.y)
+ d.become_bubblingeyes(b)
+ b.pop()
+
+class Slippy(TemporaryBonus):
+ "Greased Feet. Do you want some ice skating?"
+ nimage = Bonuses.orange_thing
+ points = 900
+ capname = 'slippy'
+ captime = 606
+ bigbonus = {'multiply': 3}
+ bigdoc = "Zip zip zip bouncing off walls!"
+
+class Aubergine(TemporaryBonus):
+ "Mirror. The left hand is the one with the thumb on the right, right?"
+ nimage = Bonuses.aubergine
+ big = 0
+ bigbonus = {'big': 1, 'multiply': 2}
+ bigdoc = "Super Bonus-catching teleport ability."
+ def taken(self, dragon):
+ if self.big:
+ dragon.dcap['teleport'] = dragon.bubber.pcap['teleport'] = 1
+ else:
+ dragon.dcap['lookforward'] = -dragon.dcap['lookforward']
+ self.carried(dragon)
+ def endaction(self, dragon):
+ if self.big:
+ pass
+ else:
+ dragon.dcap['lookforward'] = -dragon.dcap['lookforward']
+
+class WhiteCarrot(TemporaryBonus):
+ "Fly. Become a great flying dragon!"
+ nimage = Bonuses.white_carrot
+ points = 650
+ capname = 'fly'
+ captime = 650
+ bigbonus = {'capname': 'jumpdown',
+ 'captime': 999999}
+ bigdoc = "Jump down off the ground!"
+ def taken(self, dragon):
+ TemporaryBonus.taken(self, dragon)
+ dragon.bubber.pcap['jumpdown'] = dragon.dcap['jumpdown']
+
+class AmphetamineSpeed(TemporaryBonus):
+ "Amphetamine Dose. Increase of your general speed!"
+ nimage = Bonuses.tin
+ points = 700
+ bigbonus = {'multiply': 3}
+ bigdoc = "Let's move!"
+ def taken(self, dragon):
+ dragon.angry = dragon.angry + [dragon.genangry()]
+ dragon.carrybonus(self, 633)
+ def endaction(self, dragon):
+ dragon.angry = dragon.angry[1:]
+
+class Sugar1(Bonus):
+ nimage = Bonuses.yellow_sugar
+ timeout = 2600
+ points = 250
+ def taken(self, dragon):
+ #if boards.curboard.wastingplay is None:
+ dragon.carrybonus(self, 99999)
+ #else:
+ # from player import scoreboard
+ # dragon.bubber.bonbons += 1
+ # scoreboard()
+
+class Sugar2(Sugar1):
+ timeout = 2500
+ points = 500
+ nimage = Bonuses.blue_sugar
+
+class Pear(RandomBonus):
+ "Pear. Will explode into sugars for your pockets but watch out or you'll lose them!"
+ points = 1000
+ nimage = Bonuses.green_thing
+ bigbonus = {'multiply': 4}
+ bigdoc = "The more the better."
+ def taken1(self, dragons):
+ starexplosion(self.x, self.y, 3 * self.multiply,
+ outcomes = [random.choice([(Sugar1,), (Sugar2,)])
+ for i in range(18 * self.multiply)])
+
+class Megalightning(ActiveSprite):
+ def __init__(self, dragon):
+ ActiveSprite.__init__(self, images.sprget(BigImages.blitz),
+ gamesrv.game.width, gamesrv.game.height)
+ self.gen.append(self.killing(dragon))
+
+ def killing(self, dragon):
+ from monsters import Monster
+ from bubbles import Bubble
+ poplist = [dragon]
+ while 1:
+ for s in self.touching(10):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ elif isinstance(s, Bubble):
+ s.pop(poplist)
+ yield None
+ yield None
+
+ def moving_to(self, x1, y1):
+ x0 = self.x
+ y0 = self.y
+ x1 += CELL - self.ico.w//2
+ y1 += CELL - self.ico.h//2
+ deltax = x1 - x0
+ if deltax > -100:
+ deltax = -100
+ deltay = y1 - y0
+ a = - deltay / float(deltax*deltax)
+ b = 2 * deltay / float(deltax)
+ for x in range(self.x, -self.ico.w, -13):
+ x1 = x - x0
+ self.move(x, y0 + int((a*x1+b)*x1))
+ yield None
+ self.kill()
+
+class Fish2(RandomBonus):
+ "Rotten Fish. Will blast monsters up to here, so move it around!"
+ points = 3000
+ nimage = Bonuses.fish2
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Gives seven blasts."
+ def taken1(self, dragons):
+ dragon = random.choice(dragons or [None])
+ if not self.big:
+ m = Megalightning(dragon)
+ m.gen.append(m.moving_to(self.x, self.y))
+ else:
+ N = 7
+ base = random.random() * 2*math.pi
+ angles = [base + (math.pi*2 * n)/N for n in range(N)]
+ random.shuffle(angles)
+ for angle in angles:
+ m = Megalightning(dragon)
+ dx = 13 * math.cos(angle)
+ dy = 12 * math.sin(angle)
+ maxlive = max((gamesrv.game.width + m.ico.w) // 13,
+ (gamesrv.game.height + m.ico.h) // 12)
+ m.move(self.x + (self.ico.w - m.ico.w) // 2 - int(dx*maxlive),
+ self.y + (self.ico.h - m.ico.h) // 2 - int(dy*maxlive))
+ m.gen.append(m.straightline(dx, dy))
+ m.gen.append(m.die([None], maxlive*2))
+
+
+class Sheep(RandomBonus):
+ "Sheep. What a stupid beast!"
+ nimage = 'sheep-sm'
+ points = 800
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "You're a sheep. Let's bounce around."
+ def __init__(self, x, y):
+ RandomBonus.__init__(self, x, y)
+ if boards.curboard.bonuslevel:
+ self.kill()
+ def taken1(self, dragons):
+ if not self.big:
+ self.points0 = {}
+ for p in BubPlayer.PlayerList:
+ self.points0[p] = p.points
+ BubPlayer.LeaveBonus = self.boardleave()
+ else:
+ from player import Dragon
+ BubPlayer.SuperSheep = True
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons:
+ if isinstance(d, Dragon):
+ d.become_monster('Sheep')
+
+ def boardleave(self):
+ from player import BubPlayer
+ BubPlayer.OverridePlayerIcon = images.sprget(self.nimage)
+ gamesrv.set_musics([], [])
+ images.Snd.Yippee.play()
+ slist = []
+ ico = images.sprget('sheep-big')
+ for p in BubPlayer.PlayerList:
+ if p.isplaying() and p.dragons:
+ d = random.choice(p.dragons)
+ dx = (d.ico.w - ico.w) // 2
+ dy = (d.ico.h - ico.h) // 2
+ s = ActiveSprite(ico, d.x + dx, d.y + dy)
+ dir = getattr(d, 'dir', None)
+ if dir not in [-1, 1]:
+ dir = random.choice([-1, 1])
+ s.gen.append(s.parabolic([dir, -2.0]))
+ slist.append(s)
+ for d in p.dragons[:]:
+ d.kill()
+ delta = {}
+ for p in BubPlayer.PlayerList:
+ if p.points or p.isplaying():
+ delta[p] = 2 * (self.points0[p] - p.points)
+ vy = 0
+ while delta or slist:
+ ndelta = {}
+ for p, dp in delta.items():
+ if dp:
+ d1 = max(-250, min(250, dp))
+ p.givepoints(d1)
+ if p.points > 0:
+ ndelta[p] = dp - d1
+ delta = ndelta
+ images.action(slist)
+ slist = [s for s in slist if s.y < boards.bheight]
+ yield 1
+
+class Flower(RandomBonus):
+ "Flower. Fire in all directions."
+ nimage = 'flower'
+ points = 800
+ big = 0
+ bigbonus = {'big': 1, 'multiply': 5}
+ bigdoc = "Rotational Bubble Thrower (tm)."
+ def taken(self, dragon):
+ if self.big:
+ dragon.dcap['bigflower'] = -99
+ dragon.dcap['autofire'] = 22
+ else:
+ dragon.dcap['flower'] += 12
+ dragon.carrybonus(self)
+
+class Flower2(TemporaryBonus):
+ "Bottom-up Flower. Turn you upside-down."
+ nimage = 'flower2'
+ points = 1000
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Turn the level upside-down."
+ def __init__(self, *args):
+ RandomBonus.__init__(self, *args)
+ if self.x != OFFSCREEN:
+ while not underground(self.x, self.y):
+ self.step(0, -CELL)
+ if self.y < 0:
+ self.kill()
+ return
+ def taken1(self, dragons):
+ if self.big:
+ boards.extra_boardgen(boards.extra_swap_up_down())
+ else:
+ RandomBonus.taken1(self, dragons)
+ def faller(self):
+ while self.y >= 0:
+ if underground(self.x, self.y):
+ yield None
+ yield None
+ else:
+ self.move(self.x, (self.y-1) & ~3)
+ yield None
+ self.kill()
+ def taken(self, dragon):
+ dragon.dcap['gravity'] *= -1.0
+ self.carried(dragon)
+ def endaction(self, dragon):
+ dragon.dcap['gravity'] *= -1.0
+ def is_on_ground(self):
+ return underground(self.x, self.y)
+
+##class Moebius(RandomBonus):
+## "Moebius Band. Bottom left is top right and bottom right is top left... or vice-versa."
+## nimage = 'moebius'
+## points = 900
+## def taken1(self, dragons):
+## BubPlayer.Moebius = not BubPlayer.Moebius
+
+class StarBubble(FireBubble):
+ "Star Bubbles. Makes you fire bonus bubbles."
+ nimage = 'moebius'
+ bubkind = 'StarBubble'
+ bubcount = 3
+ bigbonus = {'bubcount': 10}
+ bigdoc = "More bonus bubbles => more confusion."
+
+class Donut(RandomBonus):
+ "Donut. Catch every free monster in a bubble."
+ nimage = Bonuses.donut
+ points = 950
+ big = 0
+ bigbonus = {'big': 1}
+ bigdoc = "Catch dragons too."
+
+ def taken1(self, dragons):
+ extra_boardgen(boards.extra_catch_all_monsters(dragons, self.big))
+ if self.big:
+ # catch all dragons as well
+ from bubbles import NormalBubble
+ for dragon in BubPlayer.DragonList[:]:
+ b = NormalBubble(dragon, dragon.x, dragon.y, 542)
+ if not dragon.become_bubblingeyes(b):
+ b.kill()
+
+
+Classes = [c for c in globals().values()
+ if type(c)==type(RandomBonus) and issubclass(c, RandomBonus)]
+Classes.remove(RandomBonus)
+Classes.remove(TemporaryBonus)
+Cheat = []
+#Classes = [Cactus, Insect] # CHEAT
+
+AllOutcomes = ([(c,) for c in Classes if c is not Fruits] +
+ 2 * [(MonsterBonus, lvl)
+ for lvl in range(len(Bonuses.monster_bonuses))])
+
+for c in Classes:
+ assert (getattr(c, 'points', 0) or 100) in GreenAndBlue.points[0], c
+
+def getdragonlist():
+ if bigclockticker and bigclockticker.dragonlist is not None:
+ return [entry for entry in bigclockticker.dragonlist
+ if entry.flag != 'other']
+ else:
+ return BubPlayer.DragonList
+
+def getvisibledragonlist():
+ if bigclockticker and bigclockticker.dragonlist is not None:
+ return [entry for entry in bigclockticker.dragonlist
+ if entry.flag == 'visible']
+ else:
+ return [d for d in BubPlayer.DragonList if d.monstervisible()]
+
+def record_shot(args):
+ if bigclockticker:
+ entry = bigclockticker.saved_last
+ if hasattr(entry, 'shoots1'):
+ entry.shoots1.append(args)
+
+
+def chooseground(tries=15):
+ avoidlist = getdragonlist()
+ for i in range(tries):
+ x0 = random.randint(2, boards.width-4)
+ y0 = random.randint(1, boards.height-3)
+ if (' ' == bget(x0,y0+1) == bget(x0+1,y0+1) and
+ '#' == bget(x0,y0+2) == bget(x0+1,y0+2)):
+ x0 *= CELL
+ y0 *= CELL
+ for dragon in avoidlist:
+ if abs(dragon.x-x0) < 3*CELL and abs(dragon.y-y0) < 3*CELL:
+ break
+ else:
+ return x0, y0
+ else:
+ return None, None
+
+def newbonus():
+ others = [s for s in images.ActiveSprites if isinstance(s, RandomBonus)]
+ if others:
+ return
+ if BubPlayer.SuperSheep:
+ return
+ x, y = chooseground()
+ if x is None:
+ return
+ cls = random.choice(Classes)
+ cls(x, y)
+
+##def newbonus():
+## others = [s for s in images.ActiveSprites if isinstance(s, RandomBonus)]
+## if others:
+## return
+## for cls in Classes:
+## x, y = chooseground(200)
+## if x is not None:
+## cls(x, y)
+
+def cheatnew():
+ if Cheat:
+ x, y = chooseground()
+ if x is None:
+ return
+ cls = random.choice(Cheat)
+ if not isinstance(cls, tuple):
+ cls = cls,
+ else:
+ Cheat.remove(cls)
+ if len(cls) > 1:
+ class C(cls[0]):
+ extra_cheat_arg = cls[1]
+ cls = (C,)
+ cls[0](x, y)
+
+def bonus_frame_tick():
+ if random.random() < 0.04:
+ cheatnew()
+ if random.random() < 0.15:
+ newbonus()
+ else:
+ import bubbles
+ bubbles.newbubble()
+
+def start_normal_play():
+ global bigclockticker
+ if bigclockticker and bigclockticker.state == 'restoring':
+ bigclockticker.restore()
+ return bigclockticker.restore_frame_tick
+ if (Clock and not boards.curboard.bonuslevel and
+ random.choice(Classes) is Clock):
+ bigclockticker = BigClockTicker()
+ return bigclockticker.save_frame_tick
+ else:
+ bigclockticker = None
+ return bonus_frame_tick
+
+def end_normal_play():
+ if bigclockticker and bigclockticker.state == 'post':
+ bigclockticker.flush_ghosts()
+
+# hack hack hack!
+def __cheat(c):
+ c = c.split(',')
+ c[0] = globals()[c[0]]
+ assert issubclass(c[0], Bonus)
+ Cheat.append(tuple(c))
+import __builtin__
+__builtin__.__cheat = __cheat
diff --git a/bubbob/bubbles.py b/bubbob/bubbles.py
new file mode 100644
index 0000000..9734826
--- /dev/null
+++ b/bubbob/bubbles.py
@@ -0,0 +1,1284 @@
+from __future__ import generators
+import random, math
+import gamesrv
+import images
+import boards
+from boards import *
+from images import ActiveSprite
+from mnstrmap import GreenAndBlue, LetterBubbles, Stars
+from mnstrmap import Lightning, Water, Fire, SpinningBalls, PlayerBubbles
+
+
+bubble_wind = {
+ '<': (-1, 0),
+ '>': (+1, 0),
+ '^': ( 0,-1),
+ 'v': ( 0,+1),
+ 'x': ( 0, 0),
+ }
+
+
+class Bubble(ActiveSprite):
+ exploding_bubbles = range(131,136)
+ red_bubbles = [156, 157, 156, 155]
+ white_bubbles = [164, 165, 164, 163]
+ pink_bubbles = [172, 173, 172, 171]
+ check_onbubble = ([(0,-1)], [(0,1)])
+
+ touchable = 1
+ warp = 0
+ default_windless = None
+ catch_dragons = None
+ nimages = GreenAndBlue.normal_bubbles
+
+ def touched(self, dragon):
+ dx, dy = dragon.x, dragon.y
+ o = []
+ if abs(self.x - dx) >= 25:
+ if self.x < dx:
+ o.append((1,0))
+ else:
+ o.append((-1,0))
+ if abs(self.y - dy) >= 25:
+ if self.y < dy:
+ o.append((0,1))
+ else:
+ o.append((0,-1))
+ if o:
+ self.obstacle = o
+ elif (self.catch_dragons and
+ abs(self.x - dx) < 15 and abs(self.y - dy) < 15):
+ if dragon not in self.catch_dragons:
+ self.catch_dragons.append(dragon)
+## elif not self.pop(getattr(dragon, 'poplist', None)):
+## if self.x < dx:
+## o.append((1,0))
+## else:
+## o.append((-1,0))
+## if self.y < dy:
+## o.append((0,1))
+## else:
+## o.append((0,-1))
+## self.obstacle = o
+ else:
+ self.pop(getattr(dragon, 'poplist', None))
+ return o == self.check_onbubble[dragon.bottom_up()]
+
+ def can_catch_dragons(self, author, catch_myself=0):
+ self.catch_dragons = [author]
+ if catch_myself:
+ self.catch_dragons.append(author)
+ self.move(author.x, author.y)
+ self.gen.append(self.catching_dragons())
+
+ def catching_dragons(self):
+ from player import Dragon
+ yield None # time to catch several dragons
+ dragons = [d for d in self.catch_dragons if isinstance(d, Dragon)]
+ self.catch_dragons = None
+ if len(dragons) >= 2:
+ import bonuses
+ author = dragons.pop(0)
+ imglist = [self.nimages[d.dcap.get('bubbericons',
+ d.bubber).pn][i]
+ for d in dragons for i in [1,2,1,0]]
+ self.setimages(self.cyclic(imglist))
+ self.warp = 1
+ caught = [(bonus.points, bonus) for d in dragons
+ if d.bubber is not author.bubber
+ for bonus in d.listcarrybonuses()
+ if isinstance(bonus, CatchNote)]
+ caught.sort()
+ # count caught dragons, excluding team mates, but including self
+ count = 0
+ for d in dragons:
+ if (d.bubber is author.bubber or
+ not d.bubber.sameteam(author.bubber)):
+ count += 1
+ if count:
+ if count == 1:
+ points = 250
+ else:
+ self.play(images.Snd.Extra)
+ if count == 2:
+ points = 10000
+ elif count == 3:
+ points = 30000
+ else:
+ points = 70000
+ caught.append((points, CatchNote(points)))
+ caught = caught[-3:]
+ for points, bonus in caught:
+ author.carrybonus(bonus, 111)
+ bonuses.points(self.x, self.y-HALFCELL, author, points)
+ for d in dragons:
+ d.become_bubblingeyes(self)
+ s_catch = author.bubber.stats.setdefault('catch', {})
+ s_catch[d.bubber] = s_catch.get(d.bubber, 0) + 1
+
+ def pop(self, poplist=None):
+ if self.touchable:
+ self.play(images.Snd.Pop)
+ self.poplist = poplist
+ self.untouchable()
+ self.gen = [self.die(Bubble.exploding_bubbles)]
+ if poplist:
+ dragon = poplist[0]
+ points = self.popped(dragon)
+ if dragon:
+ dragon.bubber.givepoints(points)
+ dragon.bubber.stats['bubble'] += 1
+ self.gen.append(self.poprec())
+ return 1
+ else:
+ return 0
+
+ def popped(self, dragon):
+ return 10
+
+ def poprec(self):
+ yield None
+ for s in self.touching(0):
+ if isinstance(s, Bubble):
+ s.pop(self.poplist)
+
+ def normal_movements(self, dx=0, dy=-1, timeout=800):
+ self.obstacle = []
+ time = 0
+ touchbubble = timeout = (timeout or 0) * 2
+ while 1:
+ del self.obstacle[:]
+ yield None
+ timeout -= 2
+ if not timeout:
+ self.setimages(self.bubble_red())
+ if timeout > touchbubble:
+ continue
+ if timeout&2:
+ for s in self.touching(13+(timeout&6)):
+ if isinstance(s, Bubble) and s is not self:
+ if (s.x-self.x)*dx > 0 or (s.y-self.y)*dy > 0:
+ touchbubble = timeout - (timeout&12) - 3
+ break
+ if timeout > touchbubble:
+ continue
+ if (dx,dy) not in self.obstacle:
+ if dx==dy==0:
+ if len(self.obstacle)==1:
+ dx1, dy1 = self.obstacle[0]
+ self.step(-dx1, -dy1)
+ else:
+ self.step(dx, dy)
+ if self.y < -32 or self.y >= boards.bheight:
+ if not self.warp:
+ self.poplist = None
+ self.kill()
+ return
+ self.vertical_warp()
+## dx = -dx
+ w = wget(self.x, self.y)
+ if w != ' ':
+ dx, dy = bubble_wind[w]
+ elif self.default_windless:
+ dx, dy = self.default_windless
+
+ if dx == dy == 0:
+ # this is the same as the whole loop but runs faster
+ while len(self.obstacle) != 1:
+ del self.obstacle[:]
+ yield None
+ timeout -= 2
+ if not timeout:
+ self.setimages(self.bubble_red())
+
+ def bubble_red(self, speed=5):
+ for n in self.imgseq(Bubble.white_bubbles, repeat=3):
+ yield n
+ for n in self.imgseq(Bubble.pink_bubbles, repeat=4):
+ yield n
+ for n in self.imgseq(Bubble.red_bubbles, repeat=4):
+ yield n
+ for n in self.imgseq([Bubble.pink_bubbles[0], Bubble.red_bubbles[0]],
+ speed=2, repeat=10):
+ yield n
+ self.pop()
+
+ def startnormalbubble(self, dx=0, dy=-1, timeout=800):
+ self.touchable = 1
+ self.gen.append(self.normal_movements(dx=dx, dy=dy, timeout=timeout))
+ imglist = GreenAndBlue.normal_bubbles[self.d.bubber.pn]
+ self.setimages(self.cyclic([imglist[1],
+ imglist[2],
+ imglist[1],
+ imglist[0]]))
+
+ def startsnookerbubble(self, timeout, hspeed, monsterpoplist=None):
+ self.gen.append(self.snooker_movements(dir=hspeed, timeout=timeout))
+ self.gen.append(self.kill_touching_monsters(monsterpoplist))
+ colorname = random.choice(Stars.COLORS)
+ imglist = [('smstar', colorname, k) for k in range(2)]
+ self.to_front()
+ s = images.ActiveSprite(images.sprget(imglist[-1]), self.x, self.y)
+ s.setimages(s.cyclic(imglist, speed=2))
+ s.gen.append(s.following(self))
+ def to_front():
+ Bubble.to_front(self)
+ s.to_front()
+ self.to_front = to_front
+
+ def snooker_movements(self, dir, dy=0.3, timeout=500):
+ icons = [images.sprget(n)
+ for n in GreenAndBlue.normal_bubbles[self.d.bubber.pn]]
+ icotimeout = 0
+ ico = icons[1]
+ yfrac = 0.0
+ self.dragon_jumped = False
+ for i in xrange(timeout):
+ hspeed = random.randrange(2, 4)
+ if ico is not icons[1]:
+ icotimeout += 1
+ if icotimeout >= 16:
+ ico = icons[1]
+ icotimeout = 0
+ elif abs(dy) > 16:
+ if dy > 0:
+ dy = 16.0
+ else:
+ dy = -16.0
+ self.touchable = 1
+ if self.dragon_jumped:
+ self.untouchable()
+ if self.dragon_jumped[1]: # bottom-up dragon
+ dy = -abs(dy)
+ else:
+ dy = abs(dy)
+ self.dragon_jumped = False
+ stepx = 0
+ y1 = (self.y + 27) // CELL
+ if dir < 0:
+ x1 = (self.x + 5) // CELL
+ if bget(x1, y1) == ' ' == bget(x1, y1-1):
+ stepx = -hspeed
+ else:
+ ico = icons[0]
+ dir = 1
+ else:
+ x1 = (self.x + 26) // CELL
+ if bget(x1, y1) == ' ' == bget(x1, y1-1):
+ stepx = hspeed
+ else:
+ ico = icons[0]
+ dir = -1
+ x = self.x
+ deltay = yfrac
+ if x < 32:
+ x += hspeed
+ dir = 1
+ elif x > boards.bwidth - 64:
+ x -= hspeed
+ dir = -1
+ else:
+ x += stepx
+ dy += 0.21
+ deltay = yfrac + dy
+ y = self.y
+ while deltay >= 1.0:
+ deltay -= 1.0
+ if onground(x, y-4):
+ ico = icons[2]
+ deltay = -deltay
+ dy = -abs(dy)
+ else:
+ y += 1
+ while deltay < 0.0:
+ deltay += 1.0
+ if underground(x, y+4):
+ ico = icons[2]
+ dy = abs(dy) * 0.95
+ break
+ y -= 1
+ self.move(x, y, ico)
+ self.vertical_warp()
+ yfrac = deltay
+ yield None
+ self.pop()
+
+ def kill_touching_monsters(self, poplist=None):
+ from monsters import Monster
+ poplist = poplist or [self.d]
+ while 1:
+ yield None
+ for s in self.touching(10):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ yield None
+
+
+class NormalBubble(Bubble):
+ warp = 1
+
+ def __init__(self, dragon, x, y, timeout=800):
+ imglist1 = GreenAndBlue.new_bubbles[dragon.bubber.pn]
+ Bubble.__init__(self, images.sprget(imglist1[0]), x, y)
+ self.d = dragon
+ self.startnormalbubble(timeout=timeout)
+
+class SnookerBubble(Bubble):
+ warp = 1
+ touchable = 0
+
+ def __init__(self, dragon, x, y, timeout=500):
+ imglist1 = GreenAndBlue.new_bubbles[dragon.bubber.pn]
+ Bubble.__init__(self, images.sprget(imglist1[0]), x, y)
+ self.d = dragon
+ self.startsnookerbubble(timeout=timeout, hspeed=dragon.dir)
+
+
+class BigBubbleCatcher(ActiveSprite):
+ numlist = [(PlayerBubbles.explosion[2], 1),
+ (PlayerBubbles.explosion[1], 2),
+ (PlayerBubbles.explosion[0], 2),
+ (PlayerBubbles.bubble[1], 5),
+ (PlayerBubbles.appearing[4], 3),
+ (PlayerBubbles.appearing[3], 3),
+ (PlayerBubbles.appearing[2], 2),
+ (PlayerBubbles.appearing[1], 2),
+ (PlayerBubbles.appearing[0], 2)]
+
+ def __init__(self, dragon, target, timeout):
+ img = images.sprget(PlayerBubbles.explosion[2])
+ ActiveSprite.__init__(self, img, -img.w, 0)
+ self.dragon = dragon
+ self.target = target
+ self.timeout = timeout
+ self.gen.append(self.follow())
+ self.recenter(PlayerBubbles.explosion[2])
+ for imgnum, delay in self.numlist:
+ images.sprget(imgnum) # preload
+
+ def recenter(self, imgnum):
+ s = self.target
+ if not s.alive or not s.touchable:
+ self.kill()
+ else:
+ img = images.sprget(imgnum)
+ self.move(s.x + (s.ico.w - img.w) // 2,
+ s.y + (s.ico.h - img.h) // 2,
+ img)
+
+ def follow(self):
+ for imgnum, delay in self.numlist:
+ for t in range(delay):
+ self.recenter(imgnum)
+ yield None
+ self.recenter(imgnum)
+ if self.alive:
+ s = self.target
+ s.in_bubble(NormalBubble(self.dragon, s.x, s.y, self.timeout))
+ self.kill()
+
+
+class CatchNote:
+ def __init__(self, points):
+ self.points = points
+ def endaction(self, dragon):
+ pass
+
+
+class DragonBubble(Bubble):
+ touchable = 0
+
+ def __init__(self, d, x, y, dir, special_bubble=None, angle=0,
+ thrustfactor=None, shootthrust=None):
+ self.d = d
+ pn = d.bubber.pn
+ imglist1 = GreenAndBlue.new_bubbles[pn]
+ imglist2 = GreenAndBlue.normal_bubbles[pn]
+ if angle:
+ asin, acos = math.sin(angle), math.cos(angle)
+ else:
+ asin, acos = 0, 1
+ Bubble.__init__(self, images.sprget(imglist1[0]), x + 12*dir, y)
+ self.setimages(self.imgseq(imglist1[1:] + imglist2[2:3], 4))
+ if shootthrust is None:
+ shootthrust = d.dcap['shootthrust']
+ hspeed = dir * shootthrust
+ if thrustfactor is not None:
+ negative = hspeed < 0
+ hspeed = (abs(hspeed) - 4.0) * thrustfactor + 4.0
+ if negative:
+ hspeed = -hspeed
+ self.gen.append(self.throw_bubble(hspeed, special_bubble, (acos,asin)))
+
+ def throw_bubble(self, hspeed, special_bubble=None, (acos,asin)=(1,0)):
+ from monsters import Monster
+ nx = self.x
+ ny = self.y
+ stop = 0
+ withmonster = 0
+ specialangle = (acos,asin) != (1,0)
+ if special_bubble == 'BigFireBubble':
+ if not specialangle:
+ BigFireBubble(self.x, self.y, hspeed, self.d)
+ self.kill()
+ return
+
+ self.warp = 0
+ monsterpoplist = [self.d]
+ while abs(hspeed) >= 4.0:
+ touched_monsters = [s for s in self.touching(9)
+ if isinstance(s, Monster)]
+ if touched_monsters:
+ if special_bubble == 'SnookerBubble':
+ for monster in touched_monsters:
+ monster.argh(monsterpoplist)
+ else:
+ monster = random.choice(touched_monsters)
+ in_bubble = monster.in_bubble(self)
+ withmonster = self.withmonster = 1
+ if in_bubble is None:
+ self.warp = 1
+ try:
+ key = monster.mdef.jailed[0]
+ except AttributeError:
+ pass
+ else:
+ s_monster = self.d.bubber.stats.setdefault(
+ 'monster', {})
+ s_monster[key] = s_monster.get(key, 0) + 1
+ break
+ if specialangle:
+ nx, ny = vertical_warp(nx + hspeed*acos, ny + hspeed*asin)
+## if moebius:
+## acos = -acos
+ else:
+ nx += hspeed
+ hspeed *= 0.965
+ xc = int(nx-3.8)//CELL+1
+ yc = (self.y+HALFCELL)//CELL
+ if bget(xc,yc) == '#' == bget(xc, yc+1):
+ stop += 1
+ if stop <= 1:
+ self.move(int(nx+0.5), int(ny+0.5))
+ yield None
+
+ if special_bubble == 'SnookerBubble':
+ if stop > 1:
+ hspeed = -hspeed
+ self.startsnookerbubble(self.d.dcap['bubbledelay'] or 800,
+ hspeed, monsterpoplist)
+ return
+ if not withmonster:
+ from bonuses import Bonus, BonusMaker
+ touched_bonuses = [s for s in self.touching(15)
+ if isinstance(s, Bonus) and s.bubblable]
+ if touched_bonuses:
+ random.choice(touched_bonuses).in_bubble(self)
+ withmonster = 1
+ else:
+ touched_bonuses = [s for s in self.touching(7)
+ if isinstance(s, BonusMaker)]
+ if touched_bonuses:
+ bonusmaker = random.choice(touched_bonuses)
+ if bonusmaker.in_bubble(self):
+ withmonster = 1
+ if not self.alive:
+ return
+ if special_bubble:
+ cls = globals()[special_bubble]
+ if not withmonster:
+ b = cls(self.d.bubber.pn)
+ b.move(self.x, self.y)
+ b.can_catch_dragons(self.d, hspeed == 0)
+ self.kill()
+ return
+ bubbledelay = self.d.dcap['bubbledelay']
+ if bubbledelay:
+ timeout = 1
+ if bubbledelay > 1:
+ self.gen.append(self.delayed_pop(7))
+ else:
+ timeout = 800
+ self.startnormalbubble(timeout=timeout)
+ if not withmonster:
+ self.can_catch_dragons(self.d, hspeed == 0)
+
+ def delayed_pop(self, delay):
+ for i in range(delay):
+ yield None
+ self.pop()
+
+
+class FishBubble(Bubble):
+ touchable = 0
+
+ def __init__(self, dragon):
+ ico = images.sprget(GreenAndBlue.new_bubbles[dragon.bubber.pn][0])
+ Bubble.__init__(self, ico, dragon.x + dragon.dir*12, dragon.y)
+ timeout = random.randrange(50, 150)
+ self.gen.append(self.normal_movements(timeout=timeout))
+ self.gen.append(self.fuzz())
+
+ def fuzz(self):
+ while 1:
+ prevx = self.x
+ yield None
+ yield None
+ yield None
+ if prevx == self.x:
+ self.step(random.choice([-1, 1]), 0)
+
+ def bubble_red(self, *args, **kwds):
+ self.gen.append(self.die([]))
+
+
+class BubblingEyes(ActiveSprite):
+
+ def __init__(self, bubber, saved_caps, bubble):
+ ico = images.sprget(('eyes', 0, 0))
+ ActiveSprite.__init__(self, ico, bubble.x, bubble.y)
+ self.bubber = bubber
+ self.dcap = saved_caps
+ self.gen = [self.playing_bubble(bubble)]
+
+ def bottom_up(self):
+ return self.dcap['gravity'] < 0.0
+
+ def playing_bubble(self, bubble):
+ from player import Dragon
+ bottom_up = self.bottom_up()
+ flip = 'vflip'*bottom_up
+ timer = 0
+ red = 0
+ normalbub = bubble.imgsetter
+ redblinker = bubble.cyclic([Bubble.pink_bubbles[0], Bubble.red_bubbles[0]], 2)
+ bubber = self.bubber
+ ndir = random.choice([-1, 1])
+ prev_dx_dy = None
+ while not hasattr(bubble, 'poplist'):
+ dx = bubber.wannago(self.dcap)
+ if dx:
+ ndir = dx
+ if bubber.key_jump:
+ dy = -1
+ else:
+ dy = 0
+ if bubber.key_fire:
+ red += 1
+ if red > 20:
+ d = Dragon(bubber, self.x, self.y, ndir, self.dcap)
+ Bubble.pop(bubble, [d]) # hack to pop SolidBubbles too
+ d.kill()
+ break
+ if bubble.imgsetter is not redblinker:
+ normalbub = bubble.imgsetter
+ bubble.setimages(redblinker)
+ else:
+ #red = 0
+ if bubble.imgsetter is redblinker:
+ bubble.setimages(normalbub)
+ key = ('eyes', dx, dy)
+ if timer < 50:
+ if (timer % 9) < 3:
+ key = 'eyes-blink'
+ elif random.random() < 0.1:
+ key = 'eyes-blink'
+ timer += 1
+ if bubble.x <= 3*HALFCELL and dx < 0:
+ dx = 0
+ if bubble.x >= boards.bwidth - 7*HALFCELL and dx > 0:
+ dx = 0
+ if bottom_up:
+ dy = -dy
+ nx = bubble.x + dx
+ ny = bubble.y + dy
+ if timer&1:
+ nx += dx
+ else:
+ ny += dy
+ nx, ny = vertical_warp(nx, ny)
+ bubble.move(nx, ny)
+ self.move(nx+dx, ny+dy, images.sprget((flip, key)))
+## if moebius:
+## self.dcap['left2right'] *= -1
+ if dx == dy == 0:
+ bubble.default_windless = prev_dx_dy
+ else:
+ prev_dx_dy = dx, dy
+ bubble.default_windless = 0, 0
+ yield None
+ # jumping out of the bubble
+ if bottom_up:
+ kw = {'gravity': -0.3}
+ else:
+ kw = {}
+ from player import BubPlayer
+ bi = self.dcap.get('bubbericons', bubber)
+ if BubPlayer.SuperFish and 'fish' in bi.transformedicons:
+ self.setimages(None)
+ self.seticon(bi.transformedicons['fish'][0, +1])
+ else:
+ self.setimages(self.cyclic(
+ [(flip, n) for n in GreenAndBlue.comming[bi.pn]], 2))
+ dxy = [(random.random()-0.5) * 9.0,
+ (random.random()+0.5) * (-5.0,5.0)[bottom_up]]
+ for n in self.parabolic(dxy, 1, **kw):
+ yield n
+ if dxy[1] * (1,-1)[bottom_up] >= 4.0:
+ break
+ if dxy[0] < 0:
+ ndir = -1
+ else:
+ ndir = 1
+ d = Dragon(bubber, self.x, self.y, ndir, self.dcap)
+ d.dcap['shield'] = 50
+ bubber.dragons.append(d)
+ self.kill()
+
+ def kill(self):
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+
+class BonusBubble(Bubble):
+ max = None
+ timeout = None
+ flip = ''
+
+ def __init__(self, pn, nimages=None, top=None):
+ if nimages is None:
+ nimages = self.nimages[pn]
+ b = boards.curboard
+ if top is None:
+ top = b.top
+ if top == 0:
+ testline = b.walls[-1]
+ x, y = self.findhole(testline), boards.bheight
+ dx, dy = 0, -1
+ elif top == 1:
+ testline = b.walls[0]
+ x, y = self.findhole(testline), -2*CELL
+ dx, dy = 0, 1
+ elif top == 2:
+ x, y = -2*CELL, random.randint(2*CELL, boards.bheight-4*CELL)
+ dx, dy = 1, 0
+ else: # top == 3:
+ x, y = (boards.bwidth - CELL,
+ random.randint(2*CELL, boards.bheight-4*CELL))
+ dx, dy = -1, 0
+ Bubble.__init__(self, images.sprget((self.flip, nimages[0])), x, y)
+ self.gen.append(self.normal_movements(dx=dx, dy=dy,
+ timeout=self.timeout))
+ if len(nimages) == 3:
+ nimages = [nimages[1], nimages[2], nimages[1], nimages[0]]
+ if len(nimages) > 1:
+ self.setimages(self.cyclic([(self.flip, n) for n in nimages]))
+
+ def findhole(self, testline):
+ holes = [x for x in range(len(testline)-1) if testline[x:x+2]==' ']
+ if not holes:
+ holes = range(2, len(testline)-3)
+ return random.choice(holes) * CELL
+
+ def thrown_bubble(self, x, y, hspeed, acossin):
+ self.untouchable()
+ self.move(x, y)
+ self.gen = [self.throwing_bubble(hspeed, acossin, self.imgsetter)]
+
+ def throwing_bubble(self, hspeed, (acos,asin), restore_img):
+ nx = self.x
+ ny = self.y
+ while abs(hspeed) >= 4.0:
+ nx, ny = vertical_warp(nx + hspeed*acos, ny + hspeed*asin)
+## if moebius:
+## acos = -acos
+ if nx <= CELL:
+ acos = abs(acos)
+ if nx >= boards.bwidth-3*CELL:
+ acos = -abs(acos)
+ hspeed *= 0.965
+ self.move(int(nx+0.5), int(ny+0.5))
+ yield None
+ self.touchable = 1
+ self.gen.append(self.normal_movements(timeout=self.timeout))
+ self.setimages(restore_img)
+
+
+class PlainBubble(BonusBubble):
+ timeout = 500
+ def condition():
+ return boards.curboard.holes
+
+def extend_name(l):
+ text = 'extend'
+ return text[:l] + text[l].upper() + text[l+1:]
+
+class LetterBubble(BonusBubble):
+ max = 2
+ def condition():
+ return boards.curboard.letter
+ def __init__(self, pn, l=None):
+ if l is None:
+ l = random.randint(0,5)
+ self.l = l
+ lettername = extend_name(self.l)
+ BonusBubble.__init__(self, pn, nimages=getattr(LetterBubbles, lettername))
+ def popped(self, dragon):
+ if dragon:
+ dragon.bubber.giveletter(self.l)
+ return 50
+
+class FireFlame(ActiveSprite):
+ timeout = 17
+ def __init__(self, x0, y0, poplist, dirs=None, countdown=0, flip=''):
+ ico = images.sprget((flip, Fire.ground[0]))
+ ActiveSprite.__init__(self, ico, x0*CELL, y0*CELL)
+ if not countdown:
+ dirs = []
+ self.poplist = poplist
+ self.gen.append(self.burning(dirs, countdown))
+ self.setimages(self.cyclic([(flip, n) for n in Fire.ground], 1))
+ def burning(self, dirs, countdown):
+ from monsters import Monster
+ x0 = self.x//CELL
+ y0 = self.y//CELL
+ for dir in dirs:
+ if bget(x0+dir, y0+1) == '#' and bget(x0+dir, y0) == ' ':
+ FireFlame(x0+dir, y0, self.poplist, [dir], countdown-1)
+ for i in range(self.timeout):
+ yield None
+ if self.poplist:
+ for s in self.touching(0):
+ if isinstance(s, Monster):
+ s.argh(self.poplist)
+ yield None
+ self.kill()
+
+class FireDrop(ActiveSprite):
+ def __init__(self, x, y, poplist=None):
+ ActiveSprite.__init__(self, images.sprget(Fire.drop), x, y)
+ self.poplist = poplist or [None]
+ self.gen.append(self.dropping())
+ def dropping(self):
+ x0 = self.x//CELL
+ while bget(x0, self.y//CELL) == '#' or bget(x0, self.y//CELL+1) != '#':
+ if self.y >= boards.bheight:
+ self.kill()
+ return
+ self.move(self.x, (self.y + 8) & ~7)
+ yield None
+ y0 = self.y//CELL
+ #if bget(x0-1, y0) == ' ':
+ FireFlame(x0, y0, self.poplist, [-1, 1], 5)
+ self.kill()
+
+class FireBubble(BonusBubble):
+ max = 4
+ nimages = GreenAndBlue.fire_bubbles
+ def condition():
+ return boards.curboard.fire
+ def popped(self, dragon):
+ if dragon:
+ x0 = self.x // CELL + 1
+ y0 = self.y // CELL + 1
+ if bget(x0, y0) == '#':
+ x1 = (self.x + HALFCELL) // CELL
+ if x1 == x0:
+ tries = [x1+1, x1-1]
+ else:
+ tries = [x1, x1+2]
+ for x1 in tries:
+ if bget(x1, y0) == ' ':
+ x0 = x1
+ break
+ FireDrop(x0*CELL, self.y)
+ return 10
+
+##class BombBubble(FireBubble):
+## flip = 'vflip'
+## def popped(self, dragon):
+## if dragon:
+## import bonuses
+## bonuses.bomb_explosion(self.x, self.y + CELL, starmul=1)
+## return 100
+
+##class WaterCell(ActiveSprite):
+## ICONS = {
+## ( 0,1, None) : Water.bottom,
+## ( 1,0, None) : Water.start_left,
+## (-1,0, None) : Water.start_right,
+## ( 0,0, None) : Water.bottom,
+
+## (0,1, 0,1) : Water.v_flow,
+## (0,1, 1,0) : Water.bl_corner,
+## (0,1, -1,0) : Water.br_corner,
+
+## (-1,0, 0,1) : Water.tl_corner,
+## (-1,0, 1,0) : Water.start_right,
+## #(-1,0, -1,0) : Water.h_flow,
+
+## (1,0, 0,1) : Water.tr_corner,
+## #(1,0, 1,0) : Water.h_flow,
+## (1,0, -1,0) : Water.start_left,
+
+## (0,0, 0,1) : Water.top,
+## (0,0, 1,0) : Water.top,
+## (0,0, -1,0) : Water.top,
+
+## (None, 0,1) : Water.top,
+## (None, -1,0) : Water.start_left,
+## (None, 1,0) : Water.start_right,
+## (None, 0,0) : Water.top,
+## }
+
+## def __init__(self, x, y):
+## ActiveSprite.__init__(self, images.sprget(Water.top), x, y)
+## self.touchable = 1
+
+## def ready(self, celllist):
+## self.gen.append(self.flooding(celllist))
+
+## def flooding(self, celllist):
+## from monsters import Monster
+## x0 = self.x // 16
+## y0 = self.y // 16
+## ping = 0
+## dir = random.choice([-1, 1])
+## take_with_us = [[] for cell in celllist]
+## poplist = [None]
+## icons = {}
+## for key, value in self.ICONS.items():
+## icons[key] = images.sprget(value)
+## icodef = images.sprget(Water.h_flow)
+## stop = 0
+## while not stop:
+## dx = dy = 0
+## if bget(x0, y0+1) == ' ':
+## dy = y0*16 < boards.bheight
+## ping = 0
+## elif bget(x0+dir, y0) == ' ':
+## dx = dir
+## elif bget(x0-dir, y0) == ' ':
+## ping += 1
+## if ping < 3:
+## dir = -dir
+## dx = dir
+## # change the head icon
+## head = celllist[0]
+## second = celllist[1]
+## head.seticon(icons.get((x0-second.x//16, y0-second.y//16,
+## dx, dy), icodef))
+## # move the tail to the new head position
+## x0 += dx
+## y0 += dy
+## newhead = celllist.pop()
+## celllist.insert(0, newhead)
+## newhead.move(x0*16, y0*16, icons.get((dx,dy, None), icodef))
+## # change the new tail icon
+## tail = celllist[-1]
+## second = celllist[-2]
+## tail.seticon(icons.get((None, (second.x-tail.x)//16,
+## (second.y-tail.y)//16), icodef))
+## # take monsters with us
+## for i in range(0, len(celllist), 3):
+## for s in celllist[i].touching(0):
+## if isinstance(s, Monster):
+## s.untouchable()
+## s.gen = []
+## take_with_us[i].append(s)
+## elif isinstance(s, Bubble):
+## s.pop(poplist)
+## yield 0
+## stop = dx == dy == 0
+## for cell, takelist in zip(celllist, take_with_us):
+## stop &= cell.x == newhead.x and cell.y == newhead.y
+## for s in takelist:
+## if s.alive:
+## s.move(x2bounds(cell.x-8), cell.y-16)
+## if stop:
+## s.argh(poplist, onplace=1)
+## for c in celllist:
+## c.kill()
+## def touched(self, dragon):
+## dragon.watermove(x2bounds(self.x-HALFCELL), self.y-CELL+1)
+## return 1
+
+class WaterCell(ActiveSprite):
+ TESTLIST = [(-CELL,0), (CELL,0), (0,CELL), (0,-CELL)]
+ ICONS = [Water.v_flow,
+ Water.start_left,
+ Water.start_right,
+ Water.h_flow,
+ Water.top,
+ Water.tr_corner,
+ Water.tl_corner,
+ Water.h_flow,
+
+ Water.bottom,
+ Water.br_corner,
+ Water.bl_corner,
+ Water.h_flow,
+ Water.v_flow,
+ Water.v_flow,
+ Water.v_flow,
+ Water.v_flow]
+
+ def __init__(self, x, y, dir, watercells, poplist, repeat):
+ ActiveSprite.__init__(self, images.sprget(Water.top), x, y)
+ self.poplist = poplist
+ self.take_with_me = []
+ self.ping = 0
+ self.repeat = repeat
+ self.watercells = watercells
+ self.touchable = repeat % 3 == 1
+ if (x, y, dir) not in watercells:
+ watercells[x,y,dir] = self
+ if None not in watercells or not watercells[None].alive:
+ self.in_charge()
+ else:
+ watercells[x,y,dir].join(self)
+
+ def join(self, other):
+ self.take_with_me += other.take_with_me
+ self.ping = min(self.ping, other.ping)
+ self.repeat += other.repeat
+ self.touchable = self.touchable or other.touchable
+ del other.take_with_me[:]
+ other.kill()
+
+ def in_charge(self):
+ self.gen = [self.flooding()]
+ self.watercells[None] = self
+
+ def kill(self):
+ from monsters import Monster
+ for s in self.take_with_me[:]:
+ if isinstance(s, Monster) and s.alive:
+ s.argh(self.poplist, onplace=1)
+ del self.take_with_me[:]
+ ActiveSprite.kill(self)
+ if not self.watercells[None].alive:
+ del self.watercells[None]
+ for s in self.watercells.values():
+ if s.alive:
+ s.in_charge()
+ break
+
+ def flooding(self):
+ from monsters import Monster
+ watercells = self.watercells
+ while watercells[None] is self:
+
+ new = []
+ nwatercells = {None: self}
+ for key, s in watercells.items():
+ if key:
+ x, y, dir = key
+ if s.repeat:
+ new.append((x, y, dir, watercells,
+ s.poplist, s.repeat-1))
+ s.repeat = 0
+ x0 = x // CELL
+ y0 = y // CELL
+ if bget(x0, y0+1) == ' ':
+ if y >= boards.bheight:
+ s.kill()
+ continue
+ s.ping = 0
+ y += CELL
+ elif bget(x0+dir, y0) == ' ':
+ x += dir*CELL
+ elif bget(x0-dir, y0) == ' ':
+ s.ping += 1
+ if s.ping == 3:
+ s.kill()
+ continue
+ dir = -dir
+ x += dir*CELL
+ else:
+ s.kill()
+ continue
+ key = x, y, dir
+ if key in nwatercells:
+ nwatercells[key].join(s)
+ else:
+ nwatercells[key] = s
+
+ watercells.clear()
+ watercells.update(nwatercells)
+ for args in new:
+ WaterCell(*args)
+
+ for key, s in watercells.items():
+ if key:
+ x, y, dir = key
+ flag = 0
+ for k in range(4):
+ dx, dy = s.TESTLIST[k]
+ if ((x+dx, y+dy, -1) in watercells or
+ (x+dx, y+dy, 1) in watercells):
+ flag += 1<<k
+ ico = images.sprget(s.ICONS[flag])
+ s.move(x, y, ico)
+ if s.touchable:
+ for s1 in s.touching(0):
+ if isinstance(s1, Monster):
+ s1.untouchable()
+ s1.gen = []
+ s.take_with_me.append(s1)
+ elif isinstance(s1, Bubble):
+ s1.pop(s.poplist)
+ for s1 in s.take_with_me:
+ if s1.alive:
+ s1.move(x2bounds(x-HALFCELL), y-CELL)
+ yield None
+ if not watercells[None].alive:
+ self.in_charge()
+
+ def touched(self, dragon):
+ dragon.watermove(x2bounds(self.x-HALFCELL), self.y-CELL+1)
+ return 1
+
+def watercell(x, y, poplist, dir=None, repeat=4):
+ b = boards.curboard
+ if not hasattr(b, 'watercells'):
+ b.watercells = {}
+ dir = dir or random.choice([-1, 1])
+ WaterCell(x, y, dir, b.watercells, poplist, repeat)
+
+class WaterBubble(BonusBubble):
+ max = 4
+ nimages = GreenAndBlue.water_bubbles
+ def condition():
+ return boards.curboard.water
+ def popped(self, dragon):
+ if dragon:
+ x0 = self.x // CELL + 1
+ y0 = self.y // CELL + 1
+ for x1 in [x0, x0+1, x0-1]:
+ if bget(x1,y0) == ' ' or bget(x1,y0+1) == ' ':
+ x0 = x1
+ break
+ watercell(x0*CELL, y0*CELL, [None], repeat=19)
+ return 10
+
+##class SolidBubble(WaterBubble):
+## timeout = 450
+## solidbubble = 1
+## flip = 'vflip'
+
+## def bubble_red(self, *args):
+## self.solidbubble = 0
+## return WaterBubble.bubble_red(self, *args)
+
+## def pop(self, poplist=None):
+## return (not (self.solidbubble and poplist is not None)
+## and WaterBubble.pop(self, poplist))
+
+## def popped(self, dragon):
+## return 100
+
+class FiredLightning(ActiveSprite):
+ def __init__(self, x, y, dir, poplist, diry=0):
+ ActiveSprite.__init__(self, images.sprget(Lightning.fired), x, y)
+ self.dir = int(13*dir)
+ self.diry = int(13*diry)
+ self.gen.append(self.moving(poplist))
+ def moving(self, poplist):
+ from monsters import Monster
+ while (-2*CELL < self.x < boards.bwidth and
+ -2*CELL < self.y < boards.bheight):
+ for s in self.touching(2):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ elif isinstance(s, Bubble):
+ s.pop(poplist)
+ self.step(self.dir, self.diry)
+ yield None
+ self.kill()
+
+class LightningBubble(BonusBubble):
+ max = 4
+ nimages = GreenAndBlue.light_bubbles
+ def condition():
+ return boards.curboard.lightning
+ def popped(self, dragon):
+ if dragon:
+ FiredLightning(self.x, self.y, -dragon.dir, self.poplist)
+ return 10
+
+class BigLightBubble(LightningBubble):
+ flip = 'vflip'
+ def popped(self, dragon):
+ base = random.random() * 2.0*math.pi
+ for a in range(7):
+ FiredLightning(self.x, self.y, math.cos(base+a), self.poplist,
+ diry = -math.sin(base+a))
+ return 100
+
+
+class BigFireBubble(ActiveSprite):
+ touchable = 1
+
+ def __init__(self, x, y, hspeed, author):
+ if hspeed > 0:
+ imgs = PlayerBubbles.right_weapon
+ else:
+ imgs = PlayerBubbles.left_weapon
+ ActiveSprite.__init__(self, images.sprget(imgs[-1]), x, y)
+ self.setimages(self.cyclic(imgs, 2))
+ self.author = author
+ self.gen.append(self.moving(hspeed))
+ self.play(images.Snd.Shh)
+
+ def moving(self, hspeed):
+ from monsters import Monster
+ if abs(hspeed) < 3:
+ if hspeed > 0:
+ hspeed = 3
+ else:
+ hspeed = -3
+ fx = self.x
+ poplist = [self.author]
+ while 1:
+ fx += hspeed
+ self.move(int(fx), self.y)
+ xc = int(fx)//CELL + 1
+ yc = (self.y+HALFCELL)//CELL
+ if bget(xc,yc) == '#' == bget(xc, yc+1):
+ break
+ yield None
+ for s in self.touching(4):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ self.kill()
+
+ def touched(self, dragon):
+ if dragon is not self.author:
+ import bonuses
+ bonuses.repulse_dragon(dragon)
+
+
+class SpinningBall(ActiveSprite):
+ def __init__(self, x, y, poplist):
+ ActiveSprite.__init__(self, images.sprget(SpinningBalls.free[-1]), x,y)
+ self.poplist = poplist
+ self.gen.append(self.dropping())
+ imgs = SpinningBalls.free
+ if random.random() < 0.5:
+ imgs = list(imgs)
+ imgs.reverse()
+ self.setimages(self.cyclic(imgs, random.randrange(2,5)))
+ self.touchable = 1
+ def dropping(self):
+ from monsters import Monster
+ for ny in range(self.y, boards.bheight, 5):
+ self.move(self.x, ny)
+ yield None
+ for s in self.touching(0):
+ if isinstance(s, Monster):
+ s.argh(self.poplist)
+ elif isinstance(s, Bubble):
+ s.pop(self.poplist)
+ self.kill()
+ def touched(self, dragon):
+ dragon.die()
+
+class StarBubble(BonusBubble):
+ timeout = 250
+ def __init__(self, pn):
+ self.colorname = random.choice(Stars.COLORS)
+ BonusBubble.__init__(self, pn, [('starbub', self.colorname, i)
+ for i in range(3)])
+## def __init__(self, pn):
+## BonusBubble.__init__(self, pn)
+## self.colorname = random.choice(Stars.COLORS)
+## starimg = [('smstar', self.colorname, 0),
+## ('smstar', self.colorname, 1)]
+## smallstar = ActiveSprite(images.sprget(starimg[-1]),
+## self.x+8, self.y+8)
+## smallstar.setimages(smallstar.cyclic(starimg))
+## smallstar.gen.append(smallstar.following(self, 8, 8))
+ def popped(self, dragon):
+ if dragon:
+ from bonuses import BonusMaker, AllOutcomes, Parabolic2
+ BonusMaker(self.x, self.y, getattr(Stars, self.colorname),
+ outcome=random.choice(AllOutcomes))
+ for i in range(2):
+ Parabolic2(self.x, self.y, [('smstar', self.colorname, i)
+ for i in range(2)])
+ return 100
+
+class MonsterBubble(BonusBubble):
+ timeout = 100
+ def __init__(self, pn, mcls):
+ import monsters, mnstrmap
+ BonusBubble.__init__(self, pn)
+ mdef = getattr(mnstrmap, mcls.__name__)
+ m = mcls(mdef, self.x, self.y, 1)
+ m.in_bubble(self)
+
+class MoreBubblesBubble(BonusBubble):
+
+ def can_catch_dragons(self, author, catch_myself=0):
+ # at this point, explode the bubble into more bubbles
+ d = author
+ for angle in [math.pi, math.pi/2, -math.pi/2, 0]:
+ for factor in [0.2, 0.55, 0.9, 1.25, 1.6]:
+ DragonBubble(d, self.x, self.y, d.dir,
+ angle = angle, thrustfactor = factor)
+ self.pop()
+
+
+Classes = ([PlainBubble] * 7 +
+ [FireBubble, WaterBubble, LightningBubble] * 4 +
+ [LetterBubble])
+
+def newbubble(): #force=0):
+ #if force:
+ #cls = PlainBubble
+ #else:
+ cls = random.choice(Classes)
+ if not cls.__dict__['condition']():
+ return
+ if cls.max is not None:
+ others = [s for s in images.ActiveSprites if isinstance(s, cls)]
+ if len(others) >= cls.max:
+ return
+ sendbubble(cls)
+
+def newforcedbubble():
+ choices = [PlainBubble] * 5
+ for cls in [FireBubble, WaterBubble, LightningBubble]:
+ if cls.__dict__['condition']():
+ n = 4
+ else:
+ n = 1
+ choices.extend([cls] * n)
+ cls = random.choice(choices)
+ return sendbubble(cls)
+
+def sendbubble(cls, *args, **kw):
+ from player import BubPlayer
+ players = [p for p in BubPlayer.PlayerList if p.isplaying()]
+ if not players:
+ return None
+ pn = random.choice(players).pn
+ return cls(pn, *args, **kw)
+
+def newbonusbubble():
+ boards.curboard.top = random.choice([0,0,0, 1,1,1, 2,2, 3,3])
+ r = random.random()
+ if r < 0.14:
+ sendbubble(random.choice(Classes))
+ elif r < 0.16:
+ from player import BubPlayer
+ import monsters
+ mcls = random.choice(monsters.MonsterClasses)
+ for d in BubPlayer.DragonList:
+ sendbubble(MonsterBubble, mcls)
+ else:
+ sendbubble(StarBubble)
diff --git a/bubbob/command.py b/bubbob/command.py
new file mode 100644
index 0000000..71239ed
--- /dev/null
+++ b/bubbob/command.py
@@ -0,0 +1,10 @@
+import os, sys
+
+levels, ext = os.path.splitext(os.path.basename(sys.argv[1]))
+for ext in ['.py', '.bin']:
+ levelfile = 'levels/%s%s' % (levels, ext)
+ if os.path.exists(levelfile):
+ break
+sys.argv[1] = levelfile
+
+execfile('bb.py')
diff --git a/bubbob/doc/.cvsignore b/bubbob/doc/.cvsignore
new file mode 100644
index 0000000..2d19fc7
--- /dev/null
+++ b/bubbob/doc/.cvsignore
@@ -0,0 +1 @@
+*.html
diff --git a/bubbob/doc/bonus-doc.py b/bubbob/doc/bonus-doc.py
new file mode 100755
index 0000000..f03b013
--- /dev/null
+++ b/bubbob/doc/bonus-doc.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+
+import os, sys, string, struct
+os.chdir(os.pardir)
+sys.path.insert(0, os.getcwd())
+sys.path.insert(1, os.path.abspath(os.path.join(os.pardir, 'common')))
+
+from images import sprmap
+import bonuses, images
+
+try:
+ import psyco; psyco.full()
+except ImportError:
+ pass
+
+def create_image(name,source,extralines=0,alt=''):
+ if len(sys.argv) == 2 and sys.argv[1] == '-i':
+ return
+ print name, source
+ src = open(source[0],'r')
+ assert src.readline().strip() == 'P6'
+ line = src.readline()
+ while line[0] == '#':
+ line = src.readline()
+ size = string.split(line)
+ w = string.atoi(size[0])
+ h = string.atoi(size[1])
+ c = src.readline().strip()
+ data = src.read()
+ src.close()
+ img = os.popen("convert PPM:- doc/images/"+name+'.png','w')
+ print >> img, 'P6'
+ print >> img, source[1][2], source[1][3]+extralines
+ print >> img, c
+ cx = source[1][0]+source[1][2]//2
+ cy = source[1][1]+source[1][3]*6//7
+ for y in range(source[1][1],source[1][1]+source[1][3]):
+ for x in range(source[1][0],source[1][0]+source[1][2]):
+ rgb = data[y*3*w+3*x:y*3*w+3*x+3]
+ if rgb == '\x01\x01\x01':
+ d = (x-cx)*(x-cx)+(y-cy)*(y-cy)*6
+ if d > 255: d = 255
+ rgb = chr(d)*3
+ img.write(rgb)
+ for y in range(y+1, y+1+extralines):
+ for x in range(source[1][0],source[1][0]+source[1][2]):
+ d = (x-cx)*(x-cx)+(y-cy)*(y-cy)*6
+ if d > 255: d = 255
+ rgb = chr(d)*3
+ img.write(rgb)
+ img.close()
+ return html_tag(name, alt)
+
+def html_tag(name, alt):
+ url = 'images/%s.png' % (name,)
+ f = open('doc/'+url, 'rb')
+ alldata = f.read()
+ f.close()
+ url = 'data:image/png;base64,' + alldata.encode('base64').replace('\n','')
+ return '<IMG SRC="%s" ALT="%s">' % (url, alt)
+
+
+def split_name(name):
+ "Split a name into its words based on capitalisation."
+ words = []
+ word = ''
+ for c in name:
+ if c.isupper() and word != '':
+ words.append(word)
+ word = c
+ else:
+ word += c
+ words.append(word)
+ return words
+
+dfile = open('doc/bonuses.html','w')
+print >> dfile, """<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<HTML>
+ <HEAD>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
+ <META NAME="Author" CONTENT="The Bub's Brothers">
+ <TITLE>The Bub's Brothers Bonuses</TITLE>
+ </HEAD>
+ <BODY bgcolor=white text=black>
+ <TABLE cellspacing=0 border=0>
+ <TR>
+ <TD width=132 align=right>
+ </TD>
+ <TD width=20>
+ </TD>
+ <TD bgcolor="#80FF80" align=center>
+ <I>regular bonus</I>
+ </TD>
+ <TD bgcolor="#80FF80" width=20>
+ </TD>
+ <TD bgcolor="#80FF80" align=center>
+ <I>big bonus</I>
+ </TD>
+ </TR>
+"""
+#" A stupid comment to stop emacs from mis-fontifying.
+
+class Potion4:
+ "Subgame! For a while, let's play one of the seven mini-games."
+ nimage = 'potion4'
+
+# Some classes exists in more than one example just to increase their
+# probability. Removes the duplicate with the help of this dict.
+processed = {}
+
+for bonus in bonuses.Classes:
+ if processed.has_key(bonus):
+ continue
+ name = split_name(bonus.__name__)
+ name.reverse()
+ processed[bonus] = string.join(name)
+ if bonus is bonuses.Potion:
+ processed[Potion4] = string.join(name) + '.'
+
+def sorter(a,b):
+ if a[1] == b[1]:
+ return 0
+ elif a[1] < b[1]:
+ return -1
+ else:
+ return 1
+
+sorted_classes = processed.items()
+sorted_classes.sort(sorter)
+
+for clasindex, clas in enumerate(sorted_classes):
+ bonus = clas[0]
+ images = ''
+ name = bonus.__name__
+ if bonus.__dict__.has_key('nimages'):
+ # A multi image entry.
+ i = 0
+ l = len(bonus.nimages)
+ for image in bonus.nimages:
+ if image == 'potion4':
+ continue
+ images += create_image(name+`i`, sprmap[image], alt=name)
+ i += 1
+ if (l-3*(i/3) >= 3) and (i % 3) == 0:
+ images += '<br>'
+ elif bonus.__dict__.has_key('nimage'):
+ images = create_image(name, sprmap[bonus.nimage], alt=name)
+ doc = bonus.__doc__
+ if doc == None:
+ doc = ''
+ bigdoc = getattr(bonus, 'bigdoc', None) or ''
+ if hasattr(bonus, 'bigbonus'):
+ assert bigdoc, "missing 'bigdoc' on %r" % (bonus,)
+ if clasindex % 2 == 1:
+ bgcolor = '"#E0FFE0"'
+ else:
+ bgcolor = 'white'
+ print >> dfile, '<TR><TD width=132 align=right>',
+ print >> dfile, images,
+ print >> dfile, '</TD><TD width=20></TD>',
+ print >> dfile, '<TD bgcolor=%s>%s</TD>' % (bgcolor, doc),
+ print >> dfile, '</TD><TD width=20 bgcolor=%s></TD>' % bgcolor,
+ print >> dfile, '<TD bgcolor=%s>%s</TD></TR>' % (bgcolor, bigdoc)
+
+print >> dfile, """ </TABLE>
+ </BODY>
+</HTML>
+"""
+
+dfile.close()
diff --git a/bubbob/doc/images/.cvsignore b/bubbob/doc/images/.cvsignore
new file mode 100644
index 0000000..e33609d
--- /dev/null
+++ b/bubbob/doc/images/.cvsignore
@@ -0,0 +1 @@
+*.png
diff --git a/bubbob/ext1/.cvsignore b/bubbob/ext1/.cvsignore
new file mode 100644
index 0000000..553d0ec
--- /dev/null
+++ b/bubbob/ext1/.cvsignore
@@ -0,0 +1,2 @@
+*.py[co]
+image1-[2-9].ppm
diff --git a/bubbob/ext1/__init__.py b/bubbob/ext1/__init__.py
new file mode 100644
index 0000000..9f90b0c
--- /dev/null
+++ b/bubbob/ext1/__init__.py
@@ -0,0 +1,403 @@
+from __future__ import generators
+import os, math, random
+import images, gamesrv
+from images import ActiveSprite
+from boards import CELL, HALFCELL
+from mnstrmap import GreenAndBlue
+from bubbles import BubblingEyes, Bubble
+from bonuses import Bonus
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+
+localmap = {
+ 'ark-paddle': ('image1-%d.ppm', (0, 0, 80, 32)),
+ }
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+snd_wall = gamesrv.getsample(os.path.join(LocalDir, 'wall.wav'))
+snd_brick = gamesrv.getsample(os.path.join(LocalDir, 'brick.wav'))
+
+
+def aget(x, y):
+ if 0 <= x < curboard.width and y >= 0:
+ if y >= curboard.height:
+ return ' '
+ return curboard.walls[y][x]
+ else:
+ return '#'
+
+def sign(x):
+ if x >= 0.0:
+ return 1
+ else:
+ return -1
+
+
+class PaddleEyes(BubblingEyes):
+
+ def __init__(self, bubber, saved_caps, paddle):
+ BubblingEyes.__init__(self, bubber, saved_caps, paddle)
+ self.deltax = (paddle.ico.w - self.ico.w) // 2
+ self.deltay = (paddle.ico.h - self.ico.h) // 2
+ self.step(self.deltax, self.deltay)
+
+ def playing_bubble(self, paddle, accel=0.75, vmax=4.5):
+ import boards
+ dx = self.deltax
+ dy = self.deltay
+ bubber = paddle.bubber
+ vx = 0.0
+ fx = paddle.x
+ while paddle.alive:
+ wannago = bubber.wannago(self.dcap)
+ if paddle.timeleft is None:
+ keydy = 0
+ else:
+ keydy = -1
+ key = ('eyes', wannago, keydy)
+ if fx < 2*CELL:
+ if vx < 0.0:
+ vx = -vx * 0.45
+ wannago = 1
+ elif fx + paddle.ico.w > boards.bwidth - 2*CELL:
+ if vx > 0.0:
+ vx = -vx * 0.45
+ wannago = -1
+ if not wannago:
+ if -accel <= vx <= accel:
+ vx = 0
+ elif vx < 0.0:
+ wannago = 0.7
+ else:
+ wannago = -0.7
+ vx += accel * wannago
+ if vx < -vmax:
+ vx = -vmax
+ elif vx > vmax:
+ vx = vmax
+ fx += vx
+ paddle.move(int(fx), paddle.y)
+ self.move(paddle.x+dx, paddle.y+dy, images.sprget(key))
+ yield None
+ self.kill()
+
+ def bottom_up(self):
+ return 0
+
+
+class Paddle(ActiveSprite):
+
+ def __init__(self, arkanoid, bubber, px, py):
+ ico = images.sprget(('ark-paddle', bubber.pn))
+ ActiveSprite.__init__(self, ico, px - (ico.w-2*CELL)//2,
+ py - (ico.h-2*CELL)//2)
+ self.arkanoid = arkanoid
+ self.bubber = bubber
+ self.timeleft = None
+ self.gen.append(self.bounce_down())
+ self.gen.append(self.bkgndstuff())
+ self.arkanoid.paddles.append(self)
+
+ def bounce_down(self):
+ import boards
+ target_y = boards.bheight - self.ico.h
+ fy = self.y
+ vy = 0.0
+ while fy < target_y or abs(vy) > 0.3:
+ if fy < target_y:
+ vy += 0.3
+ elif vy > 0.0:
+ vy = -vy / 3.0
+ fy += vy
+ self.move(self.x, int(fy))
+ yield None
+ while self.y > target_y:
+ self.step(0, -2)
+ yield None
+ self.move(self.x, target_y)
+ self.gen.append(self.wait_and_shoot())
+
+ def wait_and_shoot(self):
+ timeout = 30
+ while timeout > 0:
+ timeout -= self.arkanoid.ready
+ yield None
+ self.gen.append(self.catch(Ball(self)))
+
+ def catch(self, ball):
+ import boards
+ while ball.alive:
+ if ball.y > boards.bheight//2+1 and ball.vy > 0.0:
+ deltay = self.y - Ball.Y_MARGIN - ball.y
+ self.timeleft = deltay / ball.vy
+ #if -1.25 <= self.timeleft <= 0.5:
+ if -12 <= deltay <= 1:
+ ball.bouncepad(self.arkanoid.paddles)
+ yield None
+ self.timeleft = None
+ if ball.missed:
+ self.kill()
+
+ def kill(self):
+ images.Snd.Pop.play(1.0, pad=0.0)
+ images.Snd.Pop.play(1.0, pad=1.0)
+ ico = images.sprget(Bubble.exploding_bubbles[0])
+ for i in range(11):
+ s = ActiveSprite(ico,
+ self.x + random.randrange(self.ico.w) - CELL,
+ self.y + random.randrange(self.ico.h) - CELL)
+ s.gen.append(s.die(Bubble.exploding_bubbles))
+ try:
+ self.arkanoid.paddles.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+ def bkgndstuff(self):
+ while 1:
+ if self.timeleft is not None:
+ self.arkanoid.order.append((self.timeleft, self))
+ yield None
+ touching = images.touching(self.x+1, self.y+1,
+ self.ico.w-2, self.ico.h-2)
+ touching.reverse()
+ for s in touching:
+ if isinstance(s, Bonus):
+ s.touched(self)
+
+ def score(self, hits):
+ bricks = self.arkanoid.bricks
+ bricks[self.bubber] = bricks.get(self.bubber, 0) + hits
+ self.bubber.givepoints(125*(2**hits))
+
+
+class Ball(ActiveSprite):
+
+ Y_MARGIN = 20
+ SPEED = 5.8
+
+ def __init__(self, paddle):
+ self.paddle = paddle
+ imglist1 = GreenAndBlue.new_bubbles[paddle.bubber.pn]
+ ActiveSprite.__init__(self, images.sprget(imglist1[0]),
+ paddle.x + paddle.ico.w//2,
+ paddle.y - Ball.Y_MARGIN)
+ self.missed = 0
+ self.setimages(self.imgseq(imglist1[1:], 6))
+ self.bounceangle(0.2)
+ self.gen.append(self.flying())
+
+ def bouncepad(self, paddles):
+ for paddle in paddles:
+ dx = (self.x + self.ico.w//2) - (paddle.x + paddle.ico.w//2)
+ dxmax = paddle.ico.w//2
+ angle = float(dx) / dxmax
+ if 0.0 <= angle <= 1.0:
+ angle = angle * 1.111 + 0.07
+ elif -1.0 <= angle <= 0.0:
+ angle = angle * 1.111 - 0.07
+ else:
+ continue
+ self.bounceangle(angle)
+ self.play(snd_wall)
+ break
+
+ def bounceangle(self, angle):
+ self.vx = math.sin(angle) * self.SPEED
+ self.vy = - math.cos(angle) * self.SPEED
+
+ def flying(self):
+ import boards
+ fx = self.x
+ fy = self.y
+ while self.y < boards.bheight:
+ fx += self.vx
+ fy += self.vy
+ self.move(int(fx), int(fy))
+ yield None
+ cx = self.x // CELL + 1
+ cy = self.y // CELL + 1
+ dx = sign(self.vx)
+ dy = sign(self.vy)
+ hits = 0.0
+ if aget(cx, cy) == '#':
+ hits += self.ahit(cx, cy, 0, 0)
+ if aget(cx+dx, cy) == '#':
+ hits += self.ahit(cx+dx, cy, 0, dy)
+ self.vx = -self.vx
+ if aget(cx, cy+dy) == '#':
+ hits += self.ahit(cx, cy+dy, dx, 0)
+ self.vy = -self.vy
+ if hits:
+ hits = int(hits)
+ if hits:
+ self.paddle.score(hits)
+ self.play(snd_brick)
+ else:
+ self.play(snd_wall)
+ self.missed = 1
+ self.kill()
+
+ def ahit(self, cx, cy, dx, dy):
+ total = 0.01
+ for i in (-1, 0, 1):
+ x = cx + i*dx
+ y = cy + i*dy
+ if (2 <= x < curboard.width - 2 and 0 <= y < curboard.height and
+ aget(x, y) == '#'):
+ curboard.killwall(x, y)
+ self.paddle.arkanoid.killedbricks += 1
+ total += 1.0
+ return total
+
+ def pop(self):
+ self.play(images.Snd.Pop)
+ self.gen = [self.die(Bubble.exploding_bubbles)]
+
+
+class Arkanoid:
+
+ def bgen(self, limittime = 60.1): # 0:60
+ import boards
+ from player import BubPlayer
+
+ self.bricks = {}
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+
+ tc = boards.TimeCounter(limittime)
+ self.ready = 0
+ self.builddelay = {}
+ self.nbbricks = 0
+ self.order = []
+ self.paddles = []
+ #finish = 0
+ for t in self.frame():
+ self.order_paddles()
+ t = boards.normal_frame()
+ self.build_paddles()
+ yield t
+ #if len(self.paddles) == 0:
+ # finish += 1
+ # if finish == 20:
+ # break
+ #else:
+ # finish = 0
+ tc.update(t)
+ if tc.time == 0.0:
+ break
+ if (BubPlayer.FrameCounter & 15) == 7:
+ for s in images.ActiveSprites:
+ if isinstance(s, Bonus):
+ s.timeout = 0 # bonuses stay
+ elif isinstance(s, Bubble):
+ s.pop()
+
+ tc.restore()
+ self.ready = 0
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, Ball):
+ s.pop()
+ for t in boards.result_ranking(self.bricks, self.nbbricks):
+ self.build_paddles()
+ yield t
+ self.remove_paddles()
+ self.unframe()
+
+ def displaypoints(self, bubber):
+ return self.bricks.get(bubber, 0)
+
+ def frame(self):
+ for y in range(curboard.height-1, curboard.height//2, -1):
+ yield None
+ yield None
+ for x in range(2, curboard.width-2):
+ if aget(x, y) == '#':
+ curboard.killwall(x, y)
+ brickline = curboard.width-4
+ expected = (brickline * curboard.height) // 5
+ y = curboard.height//2
+ nbbricks = 0
+ while y>=0 and nbbricks + (y+1)*brickline >= expected:
+ yield None
+ for x in range(2, curboard.width-2):
+ if aget(x, y) == '#':
+ nbbricks += 1
+ y -= 1
+ while y >= -1:
+ yield None
+ yield None
+ for x in range(2, curboard.width-2):
+ if y < 0 or aget(x, y) == ' ':
+ curboard.putwall(x, y)
+ nbbricks += brickline
+ curboard.reorder_walls()
+ y -= 1
+
+ nbbricks -= brickline
+ self.ready = 1
+ self.nbbricks = nbbricks
+ self.killedbricks = 0
+ while self.killedbricks < self.nbbricks:
+ yield None
+
+ def unframe(self):
+ for x in range(2, curboard.width-2):
+ curboard.killwall(x, -1)
+
+ def build_paddles(self):
+ from player import BubPlayer
+ for p in BubPlayer.PlayerList:
+ dragons = [d for d in p.dragons if not isinstance(d, PaddleEyes)]
+ if dragons and len(p.dragons) == len(dragons):
+ if self.builddelay.get(p):
+ self.builddelay[p] -= 1
+ else:
+ self.builddelay[p] = 53
+ dragon = random.choice(dragons)
+ paddle = Paddle(self, p, dragon.x, dragon.y)
+ eyes = PaddleEyes(p, dragon.dcap, paddle)
+ p.dragons.append(eyes)
+ p.emotic(dragon, 4)
+ for d in dragons:
+ d.kill()
+
+ def order_paddles(self):
+ self.order.sort()
+ self.order.reverse()
+ for timeleft, paddle in self.order:
+ try:
+ self.paddles.remove(paddle)
+ except ValueError:
+ pass
+ else:
+ self.paddles.insert(0, paddle)
+ paddle.to_front()
+ del self.order[:]
+
+ def remove_paddles(self):
+ killclasses = (Paddle, PaddleEyes, Ball, Bonus)
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, killclasses):
+ s.kill()
+
+# This game is suitable for at least min_players players
+min_players = 1
+
+def run():
+ global curboard
+ import boards
+ from boards import curboard
+ boards.replace_boardgen(Arkanoid().bgen())
+
+def setup():
+ from player import BubPlayer
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ if filename.find('%d') >= 0:
+ for p in BubPlayer.PlayerList:
+ images.sprmap[key, p.pn] = (filename % p.pn, rect)
+ else:
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext1/brick.wav b/bubbob/ext1/brick.wav
new file mode 100644
index 0000000..5f62625
--- /dev/null
+++ b/bubbob/ext1/brick.wav
Binary files differ
diff --git a/bubbob/ext1/image1-0.ppm b/bubbob/ext1/image1-0.ppm
new file mode 100644
index 0000000..03d6b3a
--- /dev/null
+++ b/bubbob/ext1/image1-0.ppm
Binary files differ
diff --git a/bubbob/ext1/music.wav b/bubbob/ext1/music.wav
new file mode 100644
index 0000000..261b9cf
--- /dev/null
+++ b/bubbob/ext1/music.wav
Binary files differ
diff --git a/bubbob/ext1/wall.wav b/bubbob/ext1/wall.wav
new file mode 100644
index 0000000..f93df15
--- /dev/null
+++ b/bubbob/ext1/wall.wav
Binary files differ
diff --git a/bubbob/ext2/.cvsignore b/bubbob/ext2/.cvsignore
new file mode 100644
index 0000000..539da74
--- /dev/null
+++ b/bubbob/ext2/.cvsignore
@@ -0,0 +1 @@
+*.py[co]
diff --git a/bubbob/ext2/__init__.py b/bubbob/ext2/__init__.py
new file mode 100644
index 0000000..81cd833
--- /dev/null
+++ b/bubbob/ext2/__init__.py
@@ -0,0 +1,578 @@
+from __future__ import generators
+import os, math, random
+import images, gamesrv
+from images import ActiveSprite
+from boards import CELL, HALFCELL, bget
+from mnstrmap import GreenAndBlue, Ghost
+from bonuses import Bonus
+from bubbles import Bubble
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+
+localmap = {
+ ('pac-lg', -1,0) : ('image1.ppm', ( 0, 0, 32, 32)),
+ ('pac-sm', -1,0) : ('image1.ppm', (32, 0, 32, 32)),
+ ('pac-lg', 0,-1) : ('image1.ppm', ( 0, 32, 32, 32)),
+ ('pac-sm', 0,-1) : ('image1.ppm', (32, 32, 32, 32)),
+ ('pac-lg', 1,0) : ('image1.ppm', ( 0, 64, 32, 32)),
+ ('pac-sm', 1,0) : ('image1.ppm', (32, 64, 32, 32)),
+ ('pac-lg', 0,1) : ('image1.ppm', ( 0, 96, 32, 32)),
+ ('pac-sm', 0,1) : ('image1.ppm', (32, 96, 32, 32)),
+ 'pac-black' : ('image2.ppm', ( 0, 0, 32, 32)),
+ 'pac-dot' : ('image2.ppm', ( 0, 32, 8, 8)),
+ }
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+
+
+class PacSprite(ActiveSprite):
+
+ def __init__(self, ico, x, y):
+ import boards
+ if y < -2*CELL:
+ y = -2*CELL
+ elif y > boards.bheight:
+ y = boards.bheight
+ x = (x+HALFCELL) & -CELL
+ y = (y+HALFCELL) & -CELL
+ ActiveSprite.__init__(self, ico, x, y)
+ self.wannadx = self.wannady = 0
+
+ def moving(self):
+ import boards
+ dx = dy = 0
+ turned = None
+ was_clear = 0
+ while 1:
+ if dx or dy:
+ frontx = self.x+CELL+dx*(CELL+1)
+ fronty = self.y+CELL+dy*(CELL+1)
+ clear = (bget((frontx+dy)//CELL, (fronty-dx)//CELL) == ' ' and
+ bget((frontx-dy)//CELL, (fronty+dx)//CELL) == ' ')
+ if clear:
+ blocked = 0
+ else:
+ blocked = (was_clear or (self.x<=2*CELL and dx<0) or
+ (self.x>=boards.bwidth-4*CELL and dx>0))
+ else:
+ blocked = 1
+ if blocked:
+ if turned:
+ dx, dy = turned
+ turned = None
+ continue
+ self.lastmove = None
+ else:
+ if turned:
+ self.resetimages(dx, dy)
+ turned = None
+ self.lastmove = dx, dy
+ self.step(2*dx, 2*dy)
+ self.vertical_warp()
+ was_clear = clear
+ yield None
+ if self.wannadx != dx or self.wannady != dy:
+ if ((self.wannadx and not (self.y % CELL)) or
+ (self.wannady and not (self.x % CELL))):
+ turned = dx, dy
+ dx = self.wannadx
+ dy = self.wannady
+
+
+class Pac(PacSprite):
+ no_hat = 1
+
+ def __init__(self, pacman, bubber, x, y, dcap):
+ ico = GreenAndBlue.normal_bubbles[bubber.pn][1]
+ PacSprite.__init__(self, images.sprget(('eyes', 0, 0)), x, y)
+ self.bubble = ActiveSprite(images.sprget(ico), x, y)
+ self.bubber = bubber
+ self.pacman = pacman
+ self.ready = 0
+ self.gen.append(self.playing())
+ self.pacman.pacs.append(self)
+ self.dcap = dcap
+
+ def resetimages(self, dx, dy):
+ self.ready = 1
+ self.setimages(self.cyclic([('pac-lg', dx, dy),
+ 'pac-black',
+ ('pac-sm', dx, dy)], 5))
+
+ def to_front(self):
+ self.bubble.to_front()
+ ActiveSprite.to_front(self)
+
+ def kill(self):
+ self.play(images.Snd.Pop)
+ self.bubble.gen = [self.bubble.die(Bubble.exploding_bubbles)]
+ self.pacman.latestposition[self.bubber] = self.x, self.y
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ try:
+ self.pacman.pacs.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+ def playing(self):
+ bubber = self.bubber
+ for t in self.moving():
+ if self.pacman.ready:
+ d = [(bubber.key_left, -1, 0),
+ (bubber.key_right, 1, 0),
+ (bubber.key_jump, 0,-1),
+ (bubber.key_fire, 0, 1)]
+ d.sort()
+ if d[-1][0] > d[-2][0]:
+ self.wannadx, self.wannady = d[-1][1:]
+
+ self.bubble.move(self.x, self.y)
+ yield None
+
+ if self.ready:
+ touching = images.touching(self.x+CELL-3, self.y+CELL-3, 6, 6)
+ touching.reverse()
+ for s in touching:
+ if isinstance(s, Bonus):
+ s.touched(self)
+ elif isinstance(s, PacGhost):
+ self.kill()
+ return
+
+
+class PacGhost(PacSprite):
+
+ def __init__(self, pacman, x, y):
+ left = random.random() < 0.5
+ if left:
+ ico = Ghost.left[0]
+ else:
+ ico = Ghost.right[0]
+ PacSprite.__init__(self, images.sprget(ico), x, y)
+ self.pacman = pacman
+ self.gen.append(self.waiting())
+ if left:
+ self.wannadx = -1
+ else:
+ self.wannadx = 1
+ self.resetimages(self.wannadx, self.wannady)
+
+ def resetimages(self, dx, dy):
+ if dx > 0:
+ self.setimages(self.cyclic(Ghost.right, 3))
+ elif dx < 0:
+ self.setimages(self.cyclic(Ghost.left, 3))
+ #else: don't change image
+
+ def waiting(self, delay=45):
+ while not self.pacman.ready:
+ yield None
+ for i in range(delay):
+ yield None
+ self.gen.append(self.walking())
+
+ def walking(self):
+ round = 0
+ self.touchable = 1
+ lastmove_x = 0
+ for t in self.moving():
+ if random.random() < 0.1:
+ lastmove_x = self.wannadx
+ if self.lastmove is None and random.random() < 0.75:
+ round = 0 # try to move again immediately
+ if self.lastmove is None or random.random() < 0.01:
+ dragons = self.pacman.pacs or images.ActiveSprites
+ distances = [(abs(dragon.x-self.x)+abs(dragon.y-self.y),
+ dragon) for dragon in dragons]
+ distance, dragon = min(distances)
+ if lastmove_x:
+ self.wannadx = 0
+ if (dragon.y < self.y) ^ (random.random() < 0.3):
+ self.wannady = -1
+ else:
+ self.wannady = 1
+ else:
+ self.wannady = 0
+ if (dragon.x < self.x) ^ (random.random() < 0.3):
+ self.wannadx = -1
+ else:
+ self.wannadx = 1
+ else:
+ lastmove_x = self.lastmove[0]
+## for i in range(10):
+## dragon = random.choice(dragons)
+## dx = dragon.x - self.x
+## dy = dragon.y - self.y
+## if dx or dy:
+## dist = math.sqrt(dx*dx+dy*dy)
+## dx /= dist
+## dy /= dist
+## wx, wy = random.choice([(-1,0), (1,0), (0,-1), (0,1)])
+## ex = wx-dx
+## ey = wy-dy
+## if ex*ex + ey*ey < random.random()*3.14:
+## break
+## self.wannadx = wx
+## self.wannady = wy
+ if round == 0: # go just a bit faster than the players
+ round = 6
+ else:
+ round -= 1
+ yield None
+
+
+class FruitBonus(Bonus):
+ pass
+
+
+class Pacman:
+
+ def bgen(self, limittime = 45.1): # 0:45
+ import boards
+ from player import BubPlayer
+
+ self.ready = 0
+ self.dots = []
+ monsters = BubPlayer.MonsterList[:]
+ random.shuffle(monsters)
+ keep = len([p for p in BubPlayer.PlayerList if p.isplaying()])
+ monsters = monsters[:2 + keep//2]
+ for d in monsters:
+ PacGhost(self, d.x, d.y)
+
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+
+ tc = boards.TimeCounter(limittime)
+ self.builddelay = {}
+ self.latestposition = {}
+ self.pacs = []
+ #finish = 0
+ for t in self.frame():
+ t = boards.normal_frame()
+ self.build_pacs()
+ yield t
+ #if len(self.pacs) == 0:
+ # finish += 1
+ # if finish == 20:
+ # break
+ #else:
+ # finish = 0
+ tc.update(t)
+ if tc.time == 0.0:
+ break
+ if (BubPlayer.FrameCounter & 15) == 7:
+ for s in images.ActiveSprites:
+ if isinstance(s, Bubble):
+ s.pop()
+
+ tc.restore()
+ self.ready = 0
+ results = {}
+ for b in self.dots:
+ for d in b.taken_by:
+ bubber = d.bubber
+ results[bubber] = results.get(bubber, 0) + 1
+ for t in boards.result_ranking(results, len(self.dots)):
+ self.remove_pacs()
+ yield t
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, Bonus):
+ s.kill()
+
+ def displaypoints(self, bubber):
+ result = 0
+ for b in self.dots:
+ for d in b.taken_by:
+ if d.bubber is bubber:
+ result += 1
+ return result
+
+ def frame(self):
+ import boards
+ from bonuses import Fruits
+ for t in self.digwalls():
+ yield t
+
+ def anywall(x1,y1,x2,y2):
+ for tx in range(x1, x2):
+ for ty in range(y1, y2):
+ if bget(tx, ty) == '#':
+ return 1
+ return 0
+
+ def give_one_point(dragon):
+ dragon.bubber.displaypoints += 1
+ scoreboard()
+
+ dots = self.dots
+ ico = images.sprget('pac-dot')
+ for x in range(boards.width):
+ for y in range(boards.height):
+ if not anywall(x, y, x+2, y+2):
+ if anywall(x-1, y-1, x+3, y+3):
+ b = Bonus((x+1)*CELL - ico.w//2,
+ (y+1)*CELL - ico.h//2,
+ 'pac-dot', points=-100, falling=0)
+ b.sound = 'Extra'
+ b.timeout = 0
+ b.taken = give_one_point
+ dots.append(b)
+
+ for s in images.ActiveSprites:
+ if isinstance(s, PacGhost):
+ s.to_front()
+ yield None
+
+ self.ready = 1
+
+ for i in range(len([s for s in images.ActiveSprites
+ if isinstance(s, PacGhost)])):
+ for j in range(32):
+ yield None
+ for j in range(100):
+ x = random.randrange(4, boards.width-6)
+ y = random.randrange(3, boards.height-5)
+ if not anywall(x-2, y-2, x+4, y+4):
+ nimage, points = random.choice(Fruits.Fruits)
+ points += 650 # boost to the range 750-1000
+ b = FruitBonus(x*CELL, y*CELL,
+ nimage, points, falling=0)
+ b.timeout = 0
+ break
+
+ while dots:
+ dots = [b for b in dots if b.alive]
+ yield None
+
+ def build_pacs(self):
+ from player import BubPlayer
+ for p in BubPlayer.PlayerList:
+ dragons = [d for d in p.dragons if not isinstance(d, Pac)]
+ if dragons and len(p.dragons) == len(dragons):
+ if self.builddelay.get(p):
+ self.builddelay[p] -= 1
+ else:
+ self.builddelay[p] = 109
+ dragon = random.choice(dragons)
+ if p in self.latestposition:
+ dragon.move(*self.latestposition[p])
+ pac = Pac(self, p, dragon.x, dragon.y, dragon.dcap)
+ p.dragons.append(pac)
+ p.emotic(dragon, 4)
+ for d in dragons:
+ d.kill()
+
+ def remove_pacs(self):
+ from player import Dragon
+ killclasses = (PacSprite, FruitBonus, Dragon)
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, killclasses):
+ s.kill()
+
+ def digwalls(self):
+ import boards
+ holes = {}
+ for x in range(2, boards.width-2):
+ y = boards.height-1
+ if bget(x, 0) == '#' or bget(x, y) == '#':
+ if bget(x, 0) == ' ': curboard.putwall(x, 0)
+ if bget(x, y) == ' ': curboard.putwall(x, y)
+ curboard.reorder_walls()
+ for y in range(1, boards.height-1):
+ if bget(x, y) == ' ':
+ holes[x, y] = 0
+ if x % 7 == 0:
+ yield None
+
+ # 'holes' maps coordinates (x,y) to 0 (not processed)
+ # or 1 (processed).
+ # All processed holes are pacman-connected.
+
+ def rdig(x1,y1,x2,y2, reversed, holes=holes):
+ # digs the rectangle (x1,y1,x2,y2) and marks it as
+ # processed. Also recursively mark as processed all existing
+ # holes that are pacman-connected to the rectangle.
+ xrange = range(x1,x2)
+ yrange = range(y1,y2)
+ if not reversed:
+ xrange.reverse()
+ yrange.reverse()
+ if len(xrange) > len(yrange):
+ xylist = [(x, y) for x in xrange for y in yrange]
+ else:
+ xylist = [(x, y) for y in yrange for x in xrange]
+ t = 0
+ for x, y in xylist:
+ if bget(x, y) == '#':
+ curboard.killwall(x, y)
+ if t == 0:
+ yield None
+ t = 2
+ else:
+ t -= 1
+ holes.setdefault((x, y), 0)
+ fill = []
+ for x in range(x1,x2-1):
+ for y in range(y1,y2-1):
+ fill.append((x, y))
+ for x, y in fill:
+ if ((x, y) in holes and
+ (x, y+1) in holes and
+ (x+1, y) in holes and
+ (x+1, y+1) in holes):
+ if (holes[x, y] == 0 or
+ holes[x, y+1] == 0 or
+ holes[x+1, y] == 0 or
+ holes[x+1, y+1] == 0):
+
+ holes[x, y] = 1
+ holes[x, y+1] = 1
+ holes[x+1, y] = 1
+ holes[x+1, y+1] = 1
+ fill.append((x+1,y))
+ fill.append((x-1,y))
+ fill.append((x,y+1))
+ fill.append((x,y-1))
+
+ def joined(x1,y1,x2,y2, holes=holes, boards=boards):
+ # returns
+ # 1 if the rectangle (x1,y1,x2,y2) is pac-connected to
+ # some already-processed holes
+ # 0 if it is not
+ # -1 if (x1,y1,x2,y2) is out of the screen
+ if x1<2 or y1<1 or x2>boards.width-2 or y2>boards.height-1:
+ return -1
+ accum1 = accum2 = 0
+ for x in range(x1,x2):
+ if holes.get((x,y1-1)):
+ accum1 += 1
+ if accum1 == 2:
+ return 1
+ else:
+ accum1 = 0
+ if holes.get((x,y2)):
+ accum2 += 1
+ if accum2 == 2:
+ return 1
+ else:
+ accum2 = 0
+ accum1 = accum2 = 0
+ for y in range(y1,y2):
+ if holes.get((x1-1,y)):
+ accum1 += 1
+ if accum1 == 2:
+ return 1
+ else:
+ accum1 = 0
+ if holes.get((x2,y)):
+ accum2 += 1
+ if accum2 == 2:
+ return 1
+ else:
+ accum2 = 0
+ return 0
+
+ if not holes:
+ holes[boards.width//2, boards.height//2] = 0
+ holeslist = holes.keys()
+ random.shuffle(holeslist)
+ startx, starty = holeslist.pop()
+ # make the hole larger (2x2) towards the center of the board
+ if startx > boards.width//2:
+ startx -= 1
+ if starty > boards.height//2:
+ starty -= 1
+ # initial 2x2 hole
+ for t in rdig(startx, starty, startx+2, starty+2, 0):
+ yield t
+
+ dlist = [
+ (0,0,1,0, 0,-1), (0,0,1,0, 0,0), # right
+ (0,0,0,1, -1,0), (0,0,0,1, 0,0), # bottom
+ (-1,0,0,0, -1,-1), (-1,0,0,0, -1,0), # left
+ (0,-1,0,0, -1,-1), (0,-1,0,0, 0,-1), # top
+ ]
+ while holeslist:
+ random.shuffle(dlist)
+ pending = holeslist
+ holeslist = []
+ progress = 0
+ for x, y in pending:
+ if holes[x, y] != 0:
+ continue
+ for dx1, dy1, dx2, dy2, dx, dy in dlist:
+ x1 = x + dx
+ y1 = y + dy
+ x2 = x1 + 2
+ y2 = y1 + 2
+ result = 0
+ while result == 0:
+ result = joined(x1,y1,x2,y2)
+ if result == 1:
+ # rectangle (x1,y1,x2,y2) is good
+ for t in rdig(x1, y1, x2, y2,
+ dx1<0 or dy1<0 or dx2<0 or dy2<0):
+ yield t
+ progress = 1
+ break
+ x1 += dx1
+ y1 += dy1
+ x2 += dx2
+ y2 += dy2
+ else:
+ # rectangle (x1,y1,x2,y2) is too large for the screen
+ # failure
+ continue
+ break
+ else:
+ # no successful direction found from this point
+ holeslist.append((x, y)) # try again later
+ if not progress:
+ # deadlocked situation, add a new random hole
+ x = random.randrange(2, boards.width-2)
+ y = random.randrange(1, boards.height-1)
+ holeslist.insert(0, (x, y))
+ holes.setdefault((x, y), 0)
+ yield None
+
+ # pattern transformation:
+ # X. ..
+ # ... --> ...
+ # .X .X
+ progress = 1
+ while progress:
+ progress = 0
+ for y in range(1, boards.height-1):
+ for x in range(3, boards.width-3):
+ if (' ' == bget(x, y)
+ == bget(x+1, y)
+ == bget(x-1, y)
+ == bget(x, y+1)
+ == bget(x, y-1)):
+ if '#' == bget(x-1, y-1) == bget(x+1, y+1):
+ curboard.killwall(x-1, y-1)
+ progress = 1
+ elif '#' == bget(x+1, y-1) == bget(x-1, y+1):
+ curboard.killwall(x+1, y-1)
+ progress = 1
+ yield None
+
+# This game is suitable for at least min_players players
+min_players = 1
+
+def run():
+ global curboard
+ import boards
+ from boards import curboard
+ boards.replace_boardgen(Pacman().bgen())
+
+def setup():
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext2/image1.ppm b/bubbob/ext2/image1.ppm
new file mode 100644
index 0000000..1daa864
--- /dev/null
+++ b/bubbob/ext2/image1.ppm
Binary files differ
diff --git a/bubbob/ext2/image2.ppm b/bubbob/ext2/image2.ppm
new file mode 100644
index 0000000..4ed7060
--- /dev/null
+++ b/bubbob/ext2/image2.ppm
Binary files differ
diff --git a/bubbob/ext2/music.wav b/bubbob/ext2/music.wav
new file mode 100644
index 0000000..921322e
--- /dev/null
+++ b/bubbob/ext2/music.wav
Binary files differ
diff --git a/bubbob/ext3/.cvsignore b/bubbob/ext3/.cvsignore
new file mode 100644
index 0000000..553d0ec
--- /dev/null
+++ b/bubbob/ext3/.cvsignore
@@ -0,0 +1,2 @@
+*.py[co]
+image1-[2-9].ppm
diff --git a/bubbob/ext3/__init__.py b/bubbob/ext3/__init__.py
new file mode 100644
index 0000000..f5408c1
--- /dev/null
+++ b/bubbob/ext3/__init__.py
@@ -0,0 +1,367 @@
+from __future__ import generators
+import os, math, random
+import images, gamesrv
+from images import ActiveSprite
+import boards
+from boards import CELL, HALFCELL, bget
+from mnstrmap import GreenAndBlue, Fire
+from bonuses import Bonus
+from player import Dragon, BubPlayer
+import monsters
+from bubbles import Bubble
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+localmap = {
+ 'gala': ('image1-%d.ppm', (0, 0, 32, 32)),
+ }
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+snd_shoot = gamesrv.getsample(os.path.join(LocalDir, 'shoot.wav'))
+
+
+class Ship(ActiveSprite):
+
+ def __init__(self, galaga, bubber, x, y):
+ ico = images.sprget(('gala', bubber.pn))
+ ActiveSprite.__init__(self, ico, x, y)
+ self.galaga = galaga
+ self.bubber = bubber
+ self.gen.append(self.movedown())
+ self.gen.append(self.playing_ship())
+ self.gen.append(self.doomed())
+ self.galaga.ships.append(self)
+
+ def movedown(self):
+ import boards
+ target_y = boards.bheight - self.ico.h
+ while self.y < target_y:
+ yield None
+ self.move(self.x, self.y + 3)
+ self.move(self.x, target_y)
+
+ def playing_ship(self):
+ import boards
+ bubber = self.bubber
+ xmin = HALFCELL
+ xmax = boards.bwidth - HALFCELL - self.ico.w
+ fire = 0
+ while 1:
+ wannago = bubber.wannago(self.dcap)
+ nx = self.x + 2*wannago
+ if nx < xmin:
+ nx = xmin
+ elif nx > xmax:
+ nx = xmax
+ self.move(nx, self.y)
+ if fire:
+ fire -= 1
+ elif bubber.key_fire:
+ self.firenow()
+ fire = 28
+ yield None
+
+ def firenow(self):
+ ico = images.sprget(GreenAndBlue.new_bubbles[self.bubber.pn][0])
+ s = Shot(ico, self.x, self.y)
+ s.d = self
+ s.gen = [s.straightup(self)]
+ self.play(snd_shoot)
+
+ def doomed(self):
+ dangerous = Alien, monsters.MonsterShot
+ while 1:
+ touching = images.touching(self.x+3, self.y+3, 26, 26)
+ for s in touching:
+ if isinstance(s, dangerous):
+ self.kill()
+ return
+ yield None
+ yield None
+
+ def kill(self):
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ images.Snd.Pop.play(1.0, pad=0.0)
+ images.Snd.Pop.play(1.0, pad=1.0)
+ ico = images.sprget(Bubble.exploding_bubbles[0])
+ for i in range(11):
+ s = ActiveSprite(ico,
+ self.x + random.randrange(self.ico.w) - CELL,
+ self.y + random.randrange(self.ico.h) - CELL)
+ s.gen.append(s.die(Bubble.exploding_bubbles))
+ try:
+ self.galaga.ships.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+
+class Shot(Bubble):
+ touchable = 0
+
+ def straightup(self, ship):
+ ymin = -self.ico.h
+ while self.y > ymin:
+ self.step(0, -10)
+ touching = images.touching(self.x+CELL-1, self.y+CELL-1, 2, 2)
+ touching = [s for s in touching if isinstance(s, Alien)]
+ if touching:
+ alien = random.choice(touching)
+ self.gen = []
+ self.touchable = 1
+ self.move(alien.x, alien.y)
+ self.pop([ship])
+ alien.kill()
+ scores = ship.galaga.scores
+ scores[ship.bubber] = scores.get(ship.bubber, 0) + 1
+ ship.bubber.givepoints(100)
+ return
+ yield None
+
+ def popped(self, dragon):
+ return 200
+
+
+class Alien(monsters.Monster):
+ ANGLES = 32
+ SPEED = 5
+ ANGLE_TABLE = [(SPEED * math.cos(a*2.0*math.pi/ANGLES),
+ -SPEED * math.sin(a*2.0*math.pi/ANGLES))
+ for a in range(ANGLES)]
+ touchable = 0
+
+ def __init__(self, galaga, squadron, rank, relativey):
+ centerx = boards.bwidth // 2
+ go_left = squadron % 2
+ dx = (1,-1)[go_left]
+ halfspan = centerx*7//12
+ relativex = - halfspan + 4*CELL*rank
+ if relativex > halfspan:
+ raise StopIteration
+
+ if squadron % 3 == 2:
+ from mnstrmap import Ghosty as mcls
+ else:
+ from mnstrmap import Flappy as mcls
+ mdef = mcls(centerx // CELL - 1, -7, go_left)
+ mdef.left_weapon = mdef.right_weapon = [Fire.drop]
+ monsters.Monster.__init__(self, mdef)
+
+ self.path = [(None, centerx + (dx*centerx)*2//3, boards.bheight//3),
+ (None, centerx - (dx*centerx)*4//5, boards.bheight//6),
+ (galaga, -dx*relativex, -relativey)]
+ self.gen = [self.waiting(rank * 20)]
+ self.in_place = 0
+ galaga.nbmonsters += 1
+
+ def default_mode(self, angle=ANGLES//4):
+ self.touchable = 1
+ speed = self.SPEED
+ relative, tx, ty = self.path[0]
+ fx = self.x
+ fy = self.y
+ ymax = boards.bheight - self.ico.h
+ cont = 1
+ if relative:
+ shoot_prob = 0.0085
+ else:
+ shoot_prob = 0.021
+ while cont:
+ if self.angry:
+ self.kill() # never getting out of a bubble
+ return
+ if relative:
+ dx = relative.globalx + tx
+ dy = relative.globaly + ty
+ else:
+ dx = tx
+ dy = ty
+ dx -= self.x
+ dy -= self.y
+
+ tests = []
+ for a1 in (-1, 0, 1):
+ a1 = (angle+a1) % self.ANGLES
+ testx, testy = self.ANGLE_TABLE[a1]
+ testx -= dx
+ testy -= dy
+ tests.append((testx*testx+testy*testy, a1))
+ ignored, angle = min(tests)
+ if dx*dx+dy*dy > speed*speed:
+ dx, dy = self.ANGLE_TABLE[angle]
+ elif relative:
+ self.in_place = 1
+ if self.y > ymax and relative.ships:
+ for ship in relative.ships[:]:
+ ship.kill()
+ relative.builddelay[ship.bubber] = 9999
+ relative.gameover = 1
+ #x0 = self.x//CELL + 1
+ #if x0 < 2: x0 = 0
+ #if x0 >= boards.width-2: x0 = boards.width-3
+ #bubbles.FireFlame(x0, boards.height-2, None, [-1, 1],
+ # boards.width)
+ else:
+ self.path.pop(0)
+ self.gen.append(self.default_mode(angle))
+ cont = 0
+ fx += dx
+ fy += dy
+ self.move(int(fx), int(fy))
+ if dx and (self.dir > 0) != (dx > 0):
+ self.dir = -self.dir
+ self.resetimages()
+ if random.random() < shoot_prob and self.y >= 0:
+ monsters.DownShot(self)
+ yield None
+
+
+class Galaga:
+ gameover = 0
+
+ def bgen(self):
+ self.scores = {}
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+
+ self.ships = []
+ self.builddelay = {}
+ self.nbmonsters = 0
+ #finish = 0
+ for t in self.frame():
+ t = boards.normal_frame()
+ self.build_ships()
+ yield t
+ #if len(self.ships) == 0:
+ # finish += 1
+ # if finish == 50:
+ # break
+ #else:
+ # finish = 0
+ if (BubPlayer.FrameCounter & 15) == 7:
+ for s in images.ActiveSprites:
+ if isinstance(s, Bubble) and not isinstance(s, Shot):
+ s.pop()
+
+ for t in boards.result_ranking(self.scores, self.nbmonsters):
+ self.build_ships()
+ yield t
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, (Alien, Ship)):
+ s.kill()
+
+ def displaypoints(self, bubber):
+ return self.scores.get(bubber, 0)
+
+ def frame(self):
+ curboard.walls_by_pos.clear()
+ curboard.winds = ['v' * curboard.width] * curboard.height
+ for y in range(len(curboard.walls)):
+ curboard.walls[y] = ' ' * len(curboard.walls[y])
+ l1 = curboard.sprites['walls']
+ l2 = curboard.sprites['borderwalls']
+ while l1 or l2:
+ for l in [l1, l2]:
+ for w in l[:]:
+ w.step(0, 5)
+ if w.y >= boards.bheight:
+ l.remove(w)
+ w.kill()
+ yield None
+
+ self.globalx = boards.bwidth // 2
+ self.globaly = 0
+ shifter = self.shifter()
+ squadrons = len([p for p in BubPlayer.PlayerList if p.isplaying()])
+ squadrons = 3 + (squadrons+1)//3
+ nextsquad = 0
+ relativey = 0
+ squadtime = 0
+ while not self.gameover:
+ yield None
+ #if random.random() < 0.015:
+ # bubbles.sendbubble(bubbles.PlainBubble, top=0)
+ in_place = {0: [], 1: [], 2: []}
+ for s in BubPlayer.MonsterList:
+ if isinstance(s, Alien):
+ in_place[s.in_place].append(s)
+ toohigh = self.globaly - relativey < -3*CELL
+ if in_place[1]:
+ xbounds = [s.x for s in in_place[1]]
+ self.alien_bounds = min(xbounds), max(xbounds)
+ shifter.next()
+ elif toohigh:
+ self.globaly += 1
+ squadtime -= 1
+ if nextsquad >= squadrons:
+ if not (in_place[0] or in_place[1]):
+ break
+ elif squadtime < 0 and not toohigh:
+ squadtime = 200
+ try:
+ rank = 0
+ while 1:
+ Alien(self, nextsquad, rank, relativey)
+ rank += 1
+ except StopIteration:
+ pass
+ nextsquad += 1
+ relativey += 4*CELL
+ for t in range(20):
+ yield None
+
+ def shifter(self):
+ while 1:
+ # go right
+ while self.alien_bounds[1] < boards.bwidth-5*CELL:
+ self.globalx += 2
+ yield None
+ # go down
+ for i in range(3*CELL):
+ self.globaly += 1
+ yield None
+ # go left
+ while self.alien_bounds[0] > 3*CELL:
+ self.globalx -= 2
+ yield None
+ # go down
+ for i in range(3*CELL):
+ self.globaly += 1
+ yield None
+
+ def build_ships(self):
+ for p in BubPlayer.PlayerList:
+ dragons = [d for d in p.dragons if not isinstance(d, Ship)]
+ if dragons and len(p.dragons) == len(dragons):
+ if self.builddelay.get(p):
+ self.builddelay[p] -= 1
+ else:
+ self.builddelay[p] = 75
+ dragon = random.choice(dragons)
+ ship = Ship(self, p, dragon.x, dragon.y)
+ ship.dcap = dragon.dcap
+ p.dragons.append(ship)
+ p.emotic(dragon, 4)
+ for d in dragons:
+ d.kill()
+
+# This game is suitable for at least min_players players
+min_players = 1
+
+def run():
+ global curboard
+ from boards import curboard
+ boards.replace_boardgen(Galaga().bgen())
+
+def setup():
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ if filename.find('%d') >= 0:
+ for p in BubPlayer.PlayerList:
+ images.sprmap[key, p.pn] = (filename % p.pn, rect)
+ else:
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext3/image1-0.ppm b/bubbob/ext3/image1-0.ppm
new file mode 100644
index 0000000..89e9178
--- /dev/null
+++ b/bubbob/ext3/image1-0.ppm
Binary files differ
diff --git a/bubbob/ext3/music.wav b/bubbob/ext3/music.wav
new file mode 100644
index 0000000..4684177
--- /dev/null
+++ b/bubbob/ext3/music.wav
Binary files differ
diff --git a/bubbob/ext3/shoot.wav b/bubbob/ext3/shoot.wav
new file mode 100644
index 0000000..fb2b099
--- /dev/null
+++ b/bubbob/ext3/shoot.wav
Binary files differ
diff --git a/bubbob/ext4/.cvsignore b/bubbob/ext4/.cvsignore
new file mode 100644
index 0000000..553d0ec
--- /dev/null
+++ b/bubbob/ext4/.cvsignore
@@ -0,0 +1,2 @@
+*.py[co]
+image1-[2-9].ppm
diff --git a/bubbob/ext4/__init__.py b/bubbob/ext4/__init__.py
new file mode 100644
index 0000000..1efa86e
--- /dev/null
+++ b/bubbob/ext4/__init__.py
@@ -0,0 +1,440 @@
+from __future__ import generators
+import os, math, random
+import images, gamesrv
+from images import ActiveSprite
+from boards import CELL, HALFCELL, bget
+from mnstrmap import GreenAndBlue
+from bubbles import BubblingEyes, Bubble
+from bonuses import Bonus, points
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+
+localmap = {
+ 't-brick1': ('image1-%d.ppm', (0, 0, 16, 16)),
+ 't-brick2': ('image1-%d.ppm', (16, 0, 16, 16)),
+ }
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+
+
+class BrickEyes(BubblingEyes):
+
+ Patterns = [[(-2,0), (-1,0), (0,0), (1,0)],
+ [(-2,0), (-1,0), (0,0), (0,-1)],
+ [(-1,-1), (-1,0), (0,0), (1,0)],
+ [(-1,0), (0,0), (0,-1), (1,0)],
+ [(-1,-1), (0,-1), (0,0), (1,0)],
+ [(-2,0), (-1,0), (-1,-1), (0,-1)],
+ [(-1,-1), (-1,0), (0,-1), (0,0)]]
+
+ def __init__(self, tetris, bubber, saved_caps, olddragon):
+ BubblingEyes.__init__(self, bubber, saved_caps, olddragon)
+ self.tetris = tetris
+ self.bricks = []
+
+ def playing_bubble(self, oldsprite):
+ import boards
+ self.pat = random.choice(self.Patterns)
+ self.orientation = 1,0
+ xmin = 2 - min([x for x,y in self.pat])
+ xmax = boards.width-3 - max([x for x,y in self.pat])
+ x = int(random.normalvariate(oldsprite.x, boards.bwidth/4))
+ x = (x+HALFCELL) // CELL
+ if x<xmin: x=xmin
+ if x>xmax: x=xmax
+ y = -1
+ self.tx = x
+ self.ty = y
+ self.move((x-1)*CELL, (y-1)*CELL)
+ for i in range(5):
+ yield None
+ self.bricks = [Brick(self.bubber, px, py)
+ for px, py in self.brick_positions()]
+ self.gen.append(self.step_control())
+ self.gen.append(self.rotate_control())
+ self.gen.append(self.fall_control())
+ self.gen.append(self.move_eyes())
+
+ def bottom_up(self):
+ return 0
+
+ def kill(self):
+ for b in self.bricks:
+ b.stop(self.tetris)
+ b.remove()
+ self.bricks = []
+ BubblingEyes.kill(self)
+
+ def brick_positions(self):
+ ox, oy = self.orientation
+ result = []
+ cx = self.tx*CELL - HALFCELL
+ cy = self.ty*CELL - HALFCELL
+ for px, py in self.pat:
+ px = px*CELL + HALFCELL
+ py = py*CELL + HALFCELL
+ result.append((cx+px*ox-py*oy, cy+px*oy+py*ox))
+ return result
+
+ def save_position(self):
+ return self.tx, self.ty, self.orientation
+
+ def restore_position(self, p):
+ self.tx, self.ty, self.orientation = p
+
+ def moved(self, old_position):
+ for b in self.bricks:
+ b.set(' ')
+ try:
+ for px, py in self.brick_positions():
+ if bget(px//CELL, py//CELL) != ' ':
+ self.restore_position(old_position)
+ return 0
+ for b, (px, py) in zip(self.bricks, self.brick_positions()):
+ b.follow(px, py)
+ finally:
+ for b in self.bricks:
+ b.set('!') # note: we need '!' < '#'
+ return 1
+
+ def onground(self):
+ for b in self.bricks:
+ if b.gen: # brick still moving
+ return 0
+ for px, py in self.brick_positions():
+ if bget(px//CELL, py//CELL+1) >= '#':
+ return 1
+ return 0
+
+ def step_control(self):
+ while 1:
+ while not self.bubber.wannago(self.dcap):
+ yield None
+ pos = self.save_position()
+ self.tx += self.bubber.wannago(self.dcap)
+ if self.moved(pos):
+ for i in range(4):
+ yield None
+ yield None
+
+ def fall_control(self):
+ delay = 1
+ while 1:
+ for i in range(delay and 14):
+ if self.bubber.key_fire:
+ break
+ yield None
+ pos = self.save_position()
+ self.ty += 1
+ delay = self.moved(pos)
+ if delay:
+ for i in range(3):
+ yield None
+ elif self.onground() and self.tetris.ready:
+ self.gen = [self.stopping()]
+ yield None
+
+ def rotate_control(self):
+ while 1:
+ while not self.bubber.key_jump:
+ yield None
+ pos = self.save_position()
+ ox, oy = self.orientation
+ self.orientation = oy, -ox
+ if self.moved(pos):
+ for i in range(7):
+ yield None
+ yield None
+
+ def stopping(self):
+ self.move(self.x, -self.ico.h)
+ positions = [(py//CELL, px//CELL) for px, py in self.brick_positions()
+ if py >= 0]
+ positions.sort()
+ positions = [(px, py) for py, px in positions]
+ for b in self.bricks:
+ b.stop(self.tetris)
+ if b.ty < 0:
+ b.remove()
+ self.bricks = []
+ staticbricks = self.tetris.staticbricks
+ pts = 500
+ while 1:
+ for px, py in positions:
+ y = py
+ x1 = px
+ while (x1-1, y) in staticbricks:
+ x1 -= 1
+ if bget(x1-1, y) != '#':
+ continue
+ x2 = px
+ while (x2, y) in staticbricks:
+ x2 += 1
+ if bget(x2, y) != '#':
+ continue
+ if x2-x1 < 2:
+ continue
+ # full line
+ ico = images.sprget(Bubble.exploding_bubbles[0])
+ self.tetris.score[self.bubber] = self.tetris.score.get(
+ self.bubber, 0) + 1
+ xlist = range(x1, x2)
+ for x in xlist:
+ s = ActiveSprite(ico,
+ x*CELL + random.randrange(CELL) - CELL,
+ y*CELL + random.randrange(CELL) - CELL)
+ s.gen.append(s.die(Bubble.exploding_bubbles))
+ s = staticbricks[x, y]
+ points(x*CELL + HALFCELL, y*CELL + HALFCELL, s, pts)
+ s.remove()
+ if pts == 500:
+ self.play(images.Snd.Fruit)
+ elif pts == 4000:
+ self.play(images.Snd.Extralife)
+ else:
+ self.play(images.Snd.Extra)
+ pts *= 2
+ for y in range(py-1, -1, -1):
+ if not [x for x in xlist if (x, y) in staticbricks]:
+ break
+ for t in range(4):
+ yield None
+ if [x for x in xlist if (x, y+1) in staticbricks]:
+ break
+ for x in xlist:
+ if (x, y) in staticbricks:
+ staticbricks[x, y].shiftdown()
+ yield None
+ break
+ else:
+ break
+ if self.tetris.ready < 2:
+ self.gen.append(self.playing_bubble(self))
+
+ def move_eyes(self):
+ while 1:
+ tx = (self.tx-1) * CELL
+ ty = (self.ty-1) * CELL
+ for i in range(3):
+ if tx < self.x:
+ dx = -1
+ elif tx > self.x:
+ dx = +1
+ else:
+ dx = 0
+ if ty > self.y:
+ dy = +1
+ else:
+ dy = 0
+ self.step(2*dx, 2*dy)
+ key = ('eyes', dx, 0)
+ self.seticon(images.sprget(key))
+ yield None
+
+
+class Brick(ActiveSprite):
+
+ def __init__(self, bubber, x, y):
+ ico = images.sprget(('t-brick1', bubber.pn))
+ ActiveSprite.__init__(self, ico, x, y)
+ self.tx = x//CELL
+ self.ty = y//CELL
+ self.bubber = bubber
+
+ def follow(self, x, y):
+ self.tx = x//CELL
+ self.ty = y//CELL
+ self.gen = [self.following(x, y)]
+
+ def following(self, nx, ny):
+ dx = (nx - self.x) / 7.0
+ dy = (ny - self.y) / 7.0
+ for i in range(6, 0, -1):
+ self.move(nx - int(i*dx), ny - int(i*dy))
+ yield None
+ self.move(nx, ny)
+
+ def set(self, c):
+ x, y = self.tx, self.ty
+ if 0 <= x < curboard.width and 0 <= y < curboard.height:
+ line = curboard.walls[y]
+ curboard.walls[y] = line[:x] + c + line[x+1:]
+
+ def stop(self, tetris):
+ self.set('X')
+ self.seticon(images.sprget(('t-brick2', self.bubber.pn)))
+ images.ActiveSprites.remove(self)
+ tetris.staticbricks[self.tx, self.ty] = self
+ self.staticbricks = tetris.staticbricks
+
+ def remove(self):
+ del self.staticbricks[self.tx, self.ty]
+ self.set(' ')
+ gamesrv.Sprite.kill(self)
+
+ def shiftdown(self):
+ del self.staticbricks[self.tx, self.ty]
+ self.set(' ')
+ self.ty += 1
+ self.set('X')
+ self.staticbricks[self.tx, self.ty] = self
+ self.step(0, CELL)
+
+
+class Tetris:
+
+ def bgen(self, limittime = 90.1): # 1:30
+ import boards
+ from player import BubPlayer
+
+ self.score = {}
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+
+ tc = boards.TimeCounter(limittime)
+ self.ready = 0
+ self.staticbricks = {}
+ finished = 0
+ for t in self.frame():
+ t = boards.normal_frame()
+ self.build_eyes()
+ yield t
+ tc.update(t)
+ if tc.time == 0.0:
+ self.ready = 2
+ finished += not self.still_playing()
+ if finished > 16:
+ break
+ if (BubPlayer.FrameCounter & 15) == 7:
+ for s in images.ActiveSprites:
+ if isinstance(s, Bubble):
+ s.pop()
+ elif isinstance(s, Bonus):
+ s.kill()
+
+ tc.restore()
+ for t in boards.result_ranking(self.score):
+ self.remove_eyes()
+ yield t
+ for s in self.staticbricks.values():
+ s.remove()
+
+ def displaypoints(self, bubber):
+ return self.score.get(bubber, 0)
+
+ def frame(self):
+ heights = {1: curboard.height,
+ curboard.width-2: curboard.height}
+ ymax = curboard.height-1
+ maxheight = curboard.height*3//4
+ for x in range(2, curboard.width-2):
+ if bget(x, ymax) == ' ':
+ curboard.putwall(x, ymax)
+ height = 1
+ for y in range(ymax-1, -1, -1):
+ if bget(x, y) == '#':
+ if height == maxheight:
+ curboard.killwall(x, y)
+ else:
+ height += 1
+ heights[x] = height
+ xlist = range(2, curboard.width-2)
+ random.shuffle(xlist)
+ for x in xlist:
+ h = heights[x]
+ x1 = x2 = x
+ while heights[x1-1] == h:
+ x1 -= 1
+ while heights[x2] == h:
+ x2 += 1
+ parts = (x2-x1) // 8
+ if not parts:
+ continue
+ left = 0
+ if heights[x1-1] > h:
+ x1 -= 1
+ left += 1
+ right = parts+1
+ if heights[x2] > h:
+ x2 += 1
+ right -= 1
+ for p in range(left, right):
+ x = x1 + ((x2-x1-1)*p+parts//2)//parts
+ y = ymax
+ for i in range(2):
+ while bget(x, y) == '#':
+ y -= 1
+ if y >= 3:
+ curboard.putwall(x, y)
+ heights[x] += 1
+ curboard.reorder_walls()
+
+ walls_by_pos = curboard.walls_by_pos
+ moves = 1
+ s = 8.0
+ while moves:
+ moves = 0
+ for y in range(curboard.height-3, -1, -1):
+ for x in range(2, curboard.width-2):
+ if ((y,x) in walls_by_pos and
+ (y+1,x) not in walls_by_pos):
+ y0 = y
+ while (y0-1,x) in walls_by_pos:
+ y0 -= 1
+ w = curboard.killwall(x, y0, 0)
+ curboard.putwall(x, y+1, w)
+ moves = 1
+ curboard.reorder_walls()
+ for i in range(int(s)+2):
+ yield None
+ s *= 0.95
+ self.ready = 1
+ while 1:
+ yield None
+
+ def build_eyes(self):
+ from player import BubPlayer
+ for p in BubPlayer.PlayerList:
+ dragons = [d for d in p.dragons if not isinstance(d, BrickEyes)]
+ if dragons and len(p.dragons) == len(dragons):
+ dragon = random.choice(dragons)
+ eyes = BrickEyes(self, p, dragon.dcap, dragon)
+ p.dragons.append(eyes)
+ #p.emotic(dragon, 4)
+ for d in dragons:
+ d.kill()
+
+ def still_playing(self):
+ from player import BubPlayer
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons:
+ if d.gen:
+ return 1
+ return 0
+
+ def remove_eyes(self):
+ from player import BubPlayer
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons:
+ d.kill()
+
+# This game is suitable for at least min_players players
+min_players = 1
+
+def run():
+ global curboard
+ import boards
+ from boards import curboard
+ boards.replace_boardgen(Tetris().bgen())
+
+def setup():
+ from player import BubPlayer
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ if filename.find('%d') >= 0:
+ for p in BubPlayer.PlayerList:
+ images.sprmap[key, p.pn] = (filename % p.pn, rect)
+ else:
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext4/image1-0.ppm b/bubbob/ext4/image1-0.ppm
new file mode 100644
index 0000000..60a86ac
--- /dev/null
+++ b/bubbob/ext4/image1-0.ppm
Binary files differ
diff --git a/bubbob/ext4/music.wav b/bubbob/ext4/music.wav
new file mode 100644
index 0000000..b4b01a4
--- /dev/null
+++ b/bubbob/ext4/music.wav
Binary files differ
diff --git a/bubbob/ext5/.cvsignore b/bubbob/ext5/.cvsignore
new file mode 100644
index 0000000..539da74
--- /dev/null
+++ b/bubbob/ext5/.cvsignore
@@ -0,0 +1 @@
+*.py[co]
diff --git a/bubbob/ext5/__init__.py b/bubbob/ext5/__init__.py
new file mode 100644
index 0000000..ef73f14
--- /dev/null
+++ b/bubbob/ext5/__init__.py
@@ -0,0 +1,289 @@
+from __future__ import generators
+import os, random
+import images, gamesrv
+from images import ActiveSprite
+import boards
+from boards import CELL, HALFCELL, bget
+from monsters import Monster
+from player import Dragon, BubPlayer
+from bubbles import Bubble
+import bonuses
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+localmap = {
+ ('lem-walk', 1,0) : ('image1.ppm', ( 0, 0, 32, 32)),
+ ('lem-walk', 1,1) : ('image1.ppm', ( 32, 0, 32, 32)),
+ ('lem-walk', 1,2) : ('image1.ppm', ( 64, 0, 32, 32)),
+ ('lem-walk', 1,3) : ('image1.ppm', ( 96, 0, 32, 32)),
+ ('lem-walk', 1,4) : ('image1.ppm', (128, 0, 32, 32)),
+ ('lem-walk', 1,5) : ('image1.ppm', (160, 0, 32, 32)),
+ ('lem-walk', 1,6) : ('image1.ppm', (192, 0, 32, 32)),
+ ('lem-walk', 1,7) : ('image1.ppm', (224, 0, 32, 32)),
+ ('lem-fall', 1,0) : ('image1.ppm', (256, 0, 32, 32)),
+ ('lem-fall', 1,1) : ('image1.ppm', (288, 0, 32, 32)),
+ ('lem-fall', 1,2) : ('image1.ppm', (320, 0, 32, 32)),
+ ('lem-fall', 1,3) : ('image1.ppm', (352, 0, 32, 32)),
+
+ ('lem-fall',-1,3) : ('image2.ppm', ( 0, 0, 32, 32)),
+ ('lem-fall',-1,2) : ('image2.ppm', ( 32, 0, 32, 32)),
+ ('lem-fall',-1,1) : ('image2.ppm', ( 64, 0, 32, 32)),
+ ('lem-fall',-1,0) : ('image2.ppm', ( 96, 0, 32, 32)),
+ ('lem-walk',-1,7) : ('image2.ppm', (128, 0, 32, 32)),
+ ('lem-walk',-1,6) : ('image2.ppm', (160, 0, 32, 32)),
+ ('lem-walk',-1,5) : ('image2.ppm', (192, 0, 32, 32)),
+ ('lem-walk',-1,4) : ('image2.ppm', (224, 0, 32, 32)),
+ ('lem-walk',-1,3) : ('image2.ppm', (256, 0, 32, 32)),
+ ('lem-walk',-1,2) : ('image2.ppm', (288, 0, 32, 32)),
+ ('lem-walk',-1,1) : ('image2.ppm', (320, 0, 32, 32)),
+ ('lem-walk',-1,0) : ('image2.ppm', (352, 0, 32, 32)),
+
+ ('lem-jail', 0) : ('image4.ppm', ( 0, 0, 32, 32)),
+ ('lem-jail', 1) : ('image4.ppm', ( 0, 32, 32, 32)),
+ ('lem-jail', 2) : ('image4.ppm', ( 0, 64, 32, 32)),
+ }
+for n in range(16):
+ localmap[('lem-crash', n)] = ('image3.ppm', (32*n, 0, 32, 32))
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+snd_ouch = gamesrv.getsample(os.path.join(LocalDir, 'ouch.wav'))
+
+
+class Lemmy:
+ right = [('lem-walk', 1,n) for n in range(8)]
+ left = [('lem-walk',-1,n) for n in range(8)]
+ jailed = [('lem-jail', n) for n in range(3)]
+
+
+class Lemming(Monster):
+
+ def __init__(self, lemmings, x, y, dir):
+ Monster.__init__(self, Lemmy, x, y, dir, in_list=lemmings.lemlist)
+ self.lemmings = lemmings
+
+ def argh(self, *args, **kwds):
+ self.untouchable()
+ self.gen = [self.jumpout()]
+
+ def resetimages(self):
+ pass
+
+ def touched(self, dragon):
+ if 20 >= abs(self.x - dragon.x) >= 14:
+ if self.x < dragon.x:
+ self.dir = -1
+ else:
+ self.dir = 1
+
+ def in_bubble(self, bubble):
+ self.move(bubble.x, bubble.y)
+ Monster.in_bubble(self, bubble)
+ return -1
+
+ def bubbling(self, bubble):
+ dx = random.randrange(-3, 4)
+ dy = random.randrange(-4, 2)
+ counter = 0
+ while not hasattr(bubble, 'poplist'):
+ if self.y < -CELL and bubble.y > CELL: # bubble wrapped
+ self.leaveboard(bubble)
+ return
+ self.move(bubble.x+dx, bubble.y+dy)
+ yield None
+ if bubble.poplist is None and bubble.y <= -2*CELL+1:
+ self.leaveboard(bubble)
+ return
+ self.setimages(None)
+ self.gen = [self.jumpout()]
+
+ def jumpout(self):
+ # jumping out of the bubble
+ self.seticon(images.sprget(self.mdef.jailed[1]))
+ dxy = [(random.random()-0.5) * 9.0,
+ (random.random()+0.5) * (-5.0)]
+ for n in self.parabolic(dxy):
+ yield n
+ if dxy[1] >= 2.0:
+ break
+ if dxy[0] < 0:
+ self.dir = -1
+ else:
+ self.dir = 1
+ self.touchable = 1
+ self.gen.append(self.falling())
+
+ def falling(self):
+ self.setimages(None)
+ n = 0
+ lemmap = self.lemmings.lemmap
+ while not self.onground():
+ yield None
+ self.move(self.x, (self.y + 4) & ~3,
+ lemmap['lem-fall', self.dir, n&3])
+ n += 1
+ if self.y >= boards.bheight:
+ self.kill()
+ return
+ yield None
+ if n <= 33:
+ self.gen.append(self.walking())
+ else:
+ self.play(snd_ouch)
+ self.untouchable()
+ self.to_front()
+ self.gen = [self.die([('lem-crash', n) for n in range(16)], 2)]
+
+ def walking(self):
+ self.setimages(None)
+ n = 0
+ lemmap = self.lemmings.lemmap
+ y0 = self.y // 16
+ while self.y == y0*16:
+ yield None
+ nx = self.x + self.dir*2
+ x0 = (nx+15) // 16
+ if bget(x0, y0+1) == ' ':
+ if bget(x0, y0+2) == ' ':
+ y0 += 1 # fall
+ elif bget(x0, y0) != ' ':
+ self.dir = -self.dir
+ self.resetimages()
+ continue
+ else: # climb
+ y0 -= 1
+ n2 = 0
+ while self.y > y0*16:
+ self.step(0, -2)
+ if n2:
+ n2 -= 1
+ else:
+ self.seticon(lemmap['lem-walk', self.dir, n&7])
+ n += 1
+ n2 = 2
+ yield None
+ self.move(nx, self.y, lemmap['lem-walk', self.dir, n&7])
+ n += 1
+ yield None
+ yield None
+ self.gen.append(self.falling())
+
+ def onground(self):
+ if self.y & 15:
+ return 0
+ x0 = (self.x+15) // 16
+ y0 = self.y // 16 + 2
+ return bget(x0, y0) != ' ' == bget(x0, y0-1)
+
+ def leaveboard(self, bubble):
+ if hasattr(bubble, 'd'):
+ bubble.play(images.Snd.Extra)
+ score = self.lemmings.score
+ bubber = bubble.d.bubber
+ score[bubber] = score.get(bubber, 0) + 1
+ bonuses.points(bubble.x, bubble.y, bubble.d, 500)
+ self.kill()
+
+ default_mode = walking
+
+
+class Lemmings:
+
+ def bgen(self, limittime = 60.1): # 0:60
+ self.score = {}
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+ self.lemmap = {}
+ for key in localmap:
+ self.lemmap[key] = images.sprget(key)
+
+ tc = boards.TimeCounter(limittime)
+ self.lemlist = []
+ self.lemtotal = 0
+ for t in self.frame():
+ t = boards.normal_frame()
+ yield t
+ tc.update(t)
+ if tc.time == 0.0:
+ break
+
+ tc.restore()
+ for s in self.lemlist[:]:
+ if s.alive:
+ s.kill()
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, Bubble):
+ s.pop()
+ for t in boards.result_ranking(self.score.copy(), self.lemtotal):
+ yield t
+
+ def displaypoints(self, bubber):
+ return self.score.get(bubber, 0)
+
+ def frame(self):
+ windline = '>>' + '^'*(curboard.width-4) + '<<'
+ curboard.winds = [windline] * curboard.height
+
+ countholes = 0
+ ymax = curboard.height-1
+ for x in range(2, curboard.width-2):
+ if bget(x, ymax) == ' ':
+ countholes += 1
+
+ xrange = []
+ try:
+ for delta in range(2, curboard.width):
+ for x in [delta, curboard.width-delta-1]:
+ if x in xrange: raise StopIteration
+ xrange.append(x)
+ except StopIteration:
+ pass
+
+ for x in xrange:
+ if countholes > curboard.width//6 and bget(x, ymax) == ' ':
+ curboard.putwall(x, ymax)
+ curboard.reorder_walls()
+ countholes -= 1
+ for y in range(0, ymax):
+ if bget(x, y) == ' ':
+ break
+ curboard.killwall(x, y)
+ yield None
+
+ testing = {}
+ def addlemming():
+ for x, y in testing.items():
+ if bget(x, y) != ' ' == bget(x, y-1):
+ if x <= curboard.width//2:
+ dir = 1
+ else:
+ dir = -1
+ s = Lemming(self, x*CELL-HALFCELL, (y-2)*CELL, dir)
+ self.lemtotal += 1
+ if y < ymax:
+ testing[x] = y+1
+ else:
+ del testing[x]
+ for x in xrange:
+ testing[x] = 1
+ addlemming()
+ yield None
+ while testing:
+ addlemming()
+ yield None
+
+ while self.lemlist:
+ yield None
+
+# This game is suitable for at least min_players players
+min_players = 1
+
+def run():
+ global curboard
+ from boards import curboard
+ boards.replace_boardgen(Lemmings().bgen())
+
+def setup():
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext5/image1.ppm b/bubbob/ext5/image1.ppm
new file mode 100644
index 0000000..bb36c9c
--- /dev/null
+++ b/bubbob/ext5/image1.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+384 32
+255
+@ÿ
diff --git a/bubbob/ext5/image2.ppm b/bubbob/ext5/image2.ppm
new file mode 100644
index 0000000..6eef883
--- /dev/null
+++ b/bubbob/ext5/image2.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+384 32
+255
+@ÿ
diff --git a/bubbob/ext5/image3.ppm b/bubbob/ext5/image3.ppm
new file mode 100644
index 0000000..5d0eb00
--- /dev/null
+++ b/bubbob/ext5/image3.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+512 32
+255
+
diff --git a/bubbob/ext5/image4.ppm b/bubbob/ext5/image4.ppm
new file mode 100644
index 0000000..8f22db8
--- /dev/null
+++ b/bubbob/ext5/image4.ppm
Binary files differ
diff --git a/bubbob/ext5/music.wav b/bubbob/ext5/music.wav
new file mode 100644
index 0000000..de1efb4
--- /dev/null
+++ b/bubbob/ext5/music.wav
Binary files differ
diff --git a/bubbob/ext5/ouch.wav b/bubbob/ext5/ouch.wav
new file mode 100644
index 0000000..5d3dd8b
--- /dev/null
+++ b/bubbob/ext5/ouch.wav
Binary files differ
diff --git a/bubbob/ext6/.cvsignore b/bubbob/ext6/.cvsignore
new file mode 100644
index 0000000..553d0ec
--- /dev/null
+++ b/bubbob/ext6/.cvsignore
@@ -0,0 +1,2 @@
+*.py[co]
+image1-[2-9].ppm
diff --git a/bubbob/ext6/__init__.py b/bubbob/ext6/__init__.py
new file mode 100644
index 0000000..bd4c4d4
--- /dev/null
+++ b/bubbob/ext6/__init__.py
@@ -0,0 +1,268 @@
+from __future__ import generators
+import os, random
+import images, gamesrv
+from images import ActiveSprite
+import boards
+from boards import CELL, HALFCELL, bget
+from player import Dragon, BubPlayer
+from mnstrmap import Monky
+from bubbles import Bubble
+from bonuses import Bonus
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+localmap = {
+ ('trn-head', 0,-1): ('image1-%d.ppm', (0, 0, 8, 8)),
+ ('trn-head',-1, 0): ('image1-%d.ppm', (0, 8, 8, 8)),
+ ('trn-head', 0, 1): ('image1-%d.ppm', (0,16, 8, 8)),
+ ('trn-head', 1, 0): ('image1-%d.ppm', (0,24, 8, 8)),
+ ('trn', 0,-1, 1, 0): ('image1-%d.ppm', (0,32, 8, 8)),
+ ('trn', 0, 1, 1, 0): ('image1-%d.ppm', (0,40, 8, 8)),
+ ('trn', 1, 0, 0,-1): ('image1-%d.ppm', (0,48, 8, 8)),
+ ('trn', 1, 0, 0, 1): ('image1-%d.ppm', (0,56, 8, 8)),
+ ('trn', 1, 0, 1, 0): ('image1-%d.ppm', (0,64, 8, 8)),
+ ('trn', 0, 1, 0, 1): ('image1-%d.ppm', (0,72, 8, 8)),
+ }
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+snd_crash = gamesrv.getsample(os.path.join(LocalDir, 'crash.wav'))
+
+
+class TronHead(ActiveSprite):
+
+ def __init__(self, tron, bubber, dcap, cx, cy, dir):
+ self.tron = tron
+ self.bubber = bubber
+ self.dcap = dcap
+ self.cx = cx
+ self.cy = cy
+ self.dir = dir
+ self.icons = {}
+ for key in localmap:
+ ico = images.sprget((key, bubber.pn))
+ key = key[1:]
+ self.icons[key] = ico
+ if len(key) == 4:
+ dx1, dy1, dx2, dy2 = key
+ key = -dx2, -dy2, -dx1, -dy1
+ self.icons[key] = ico
+ ActiveSprite.__init__(self, self.icons[self.dir],
+ self.cx*8-2, self.cy*8-2)
+ self.gen.append(self.trailing())
+
+ def forward_step(self, dir):
+ s = gamesrv.Sprite(self.icons[self.dir + dir], self.x, self.y)
+ self.tron.trailsprites.append(s)
+ self.dir = dir
+ self.cx += dir[0]
+ self.cy += dir[1]
+ self.move(self.cx*8-2, self.cy*8-2, self.icons[dir])
+
+ def trailing(self):
+ unoccupied = self.tron.unoccupied
+ bubber = self.bubber
+ # first go straight forward until we enter the playing board itself
+ while (self.cx, self.cy) not in unoccupied:
+ self.forward_step(self.dir)
+ yield None
+ yield None
+ # playing!
+ unoccupied[self.cx, self.cy] = False
+ while True:
+ # turn
+ d = [(bubber.key_left, -1, 0),
+ (bubber.key_right, 1, 0),
+ (bubber.key_jump, 0,-1),
+ (bubber.key_fire, 0, 1)]
+ d.sort()
+ newdir = self.dir
+ if d[-1][0] > d[-2][0]:
+ newdir = d[-1][1:]
+ if (self.dir + newdir) not in self.icons:
+ newdir = self.dir # forbidden 180-degree turn
+ # move one step forward
+ self.forward_step(newdir)
+ # crash?
+ if not unoccupied.get((self.cx, self.cy)):
+ self.crash()
+ return
+ unoccupied[self.cx, self.cy] = False
+ yield None
+ yield None
+
+ def to_front(self):
+ if self.gen:
+ ActiveSprite.to_front(self)
+
+ def crash(self):
+ self.move(self.x - self.dir[0], self.y - self.dir[1],
+ self.icons[self.dir+self.dir])
+ self.to_back()
+ self.play(snd_crash)
+ ico = images.sprget(Monky.decay_weapon[1])
+ s = ActiveSprite(ico, self.x + self.ico.w//2 - CELL,
+ self.y + self.ico.h//2 - CELL)
+ s.gen.append(s.die(Monky.decay_weapon[1:], 4))
+ self.stop()
+
+ def stop(self):
+ del self.gen[:]
+ try:
+ self.tron.trons.remove(self)
+ except ValueError:
+ pass
+
+ def kill(self):
+ self.stop()
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+
+class Tron:
+
+ def bgen(self, limittime = 60.1): # 1:00
+ self.score = {}
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+
+ self.ready = 0
+ self.trons = []
+ self.trailsprites = []
+ self.playerlist = BubPlayer.PlayerList[:]
+ tc = boards.TimeCounter(limittime)
+ for t in self.frame(tc):
+ t = boards.normal_frame()
+ self.build_trons()
+ yield t
+ tc.update(t)
+ if (BubPlayer.FrameCounter & 15) == 7:
+ for s in images.ActiveSprites:
+ if isinstance(s, Bubble):
+ s.pop()
+ elif isinstance(s, Bonus):
+ s.kill()
+
+ self.ready = 0
+ tc.restore()
+ for t in boards.result_ranking(self.score):
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons[:]:
+ d.kill()
+ yield t
+ self.remove_trons()
+
+ def displaypoints(self, bubber):
+ return self.score.get(bubber, 0)
+
+ def build_trons(self):
+ if self.ready == 0:
+ self.remove_trons()
+ return
+ for p in self.playerlist:
+ dragons = [d for d in p.dragons if not isinstance(d, TronHead)]
+ if self.ready < 10 and dragons and len(p.dragons) == len(dragons):
+ self.score.setdefault(p, 0)
+ dragon = random.choice(dragons)
+ x, y, dir = self.select_start_point()
+ head = TronHead(self, p, dragon.dcap, x, y, dir)
+ self.trons.append(head)
+ p.dragons.append(head)
+ #p.emotic(head, 4)
+ for d in dragons:
+ d.kill()
+
+ def remove_trons(self):
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons[:]:
+ d.kill()
+ for s in self.trailsprites:
+ s.kill()
+ del self.trailsprites[:]
+
+ def select_start_point(self):
+ distmin = 12
+ while True:
+ x, y, dir = random.choice(self.start_points)
+ for head in self.trons:
+ if abs(x-head.cx//2) + abs(y-head.cy//2) < distmin:
+ break
+ else:
+ break
+ distmin *= 0.95
+ if (y, x) in curboard.walls_by_pos:
+ curboard.killwall(x, y)
+ x = 2*x+1
+ y = 2*y - dir[1]
+ if dir[1] < 0:
+ y += 2
+ return x, y, dir
+
+ def frame(self, tc):
+ y1 = 1
+ y2 = curboard.height-2
+ while y1 <= y2:
+ for y in [y1, y2]:
+ for x in range(2, curboard.width-2):
+ if (y, x) in curboard.walls_by_pos:
+ curboard.killwall(x, y)
+ yield None
+ y1 += 1
+ y2 -= 1
+
+ self.start_points = []
+ for x in range(4, curboard.width-3):
+ self.start_points.append((x, 0, (0, 1)))
+ self.start_points.append((x, curboard.height-1, (0, -1)))
+
+ while tc.time != 0.0:
+ for y in [0, curboard.height-1]:
+ for x in range(2, curboard.width-2):
+ if (y, x) not in curboard.walls_by_pos:
+ curboard.putwall(x, y)
+ curboard.reorder_walls()
+ self.unoccupied = {}
+ for x in range(5, 2*curboard.width-4):
+ for y in range(3, 2*curboard.height-2):
+ self.unoccupied[x, y] = True
+ random.shuffle(self.playerlist)
+ for i in range(5):
+ yield None
+
+ min_players = 1
+ while self.ready < 20 or len(self.trons) >= min_players:
+ if len(self.trons) >= 2:
+ min_players = 2
+ self.ready += 1
+ yield None
+
+ if len(self.trons) == 1:
+ bubber = self.trons[0].bubber
+ self.score[bubber] += 1
+ bubber.givepoints(100)
+ self.trons[0].stop()
+ self.ready = 99
+
+ for i in range(28):
+ yield None
+ self.ready = 0
+
+# This game is suitable for at least min_players players
+min_players = 2
+
+def run():
+ global curboard
+ from boards import curboard
+ boards.replace_boardgen(Tron().bgen())
+
+def setup():
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ if filename.find('%d') >= 0:
+ for p in BubPlayer.PlayerList:
+ images.sprmap[key, p.pn] = (filename % p.pn, rect)
+ else:
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext6/crash.wav b/bubbob/ext6/crash.wav
new file mode 100644
index 0000000..65499db
--- /dev/null
+++ b/bubbob/ext6/crash.wav
Binary files differ
diff --git a/bubbob/ext6/image1-0.ppm b/bubbob/ext6/image1-0.ppm
new file mode 100644
index 0000000..bb54ce2
--- /dev/null
+++ b/bubbob/ext6/image1-0.ppm
Binary files differ
diff --git a/bubbob/ext6/music.wav b/bubbob/ext6/music.wav
new file mode 100644
index 0000000..8c55b34
--- /dev/null
+++ b/bubbob/ext6/music.wav
Binary files differ
diff --git a/bubbob/ext7/.cvsignore b/bubbob/ext7/.cvsignore
new file mode 100644
index 0000000..553d0ec
--- /dev/null
+++ b/bubbob/ext7/.cvsignore
@@ -0,0 +1,2 @@
+*.py[co]
+image1-[2-9].ppm
diff --git a/bubbob/ext7/__init__.py b/bubbob/ext7/__init__.py
new file mode 100644
index 0000000..2d157ab
--- /dev/null
+++ b/bubbob/ext7/__init__.py
@@ -0,0 +1,399 @@
+from __future__ import generators
+import os, random, math
+import images, gamesrv
+from images import ActiveSprite
+import boards
+from boards import CELL
+from player import Dragon, BubPlayer, scoreboard
+from bubbles import Bubble
+from bonuses import Bonus
+from mnstrmap import PlayerBubbles
+from mnstrmap import Monky
+import bonuses
+from ext6 import snd_crash
+
+LocalDir = os.path.basename(os.path.dirname(__file__))
+
+ANGLE_COUNT = 24
+ANGLE_STEP = 360 / ANGLE_COUNT
+ANGLE_TABLE = {}
+for i in range(ANGLE_COUNT):
+ a = i*ANGLE_STEP*math.pi/180.0
+ ANGLE_TABLE[i*ANGLE_STEP] = (math.cos(a), math.sin(a))
+
+
+localmap = {}
+for i in range(ANGLE_COUNT):
+ localmap['camel', i] = ('image1-%d.ppm', (0, i*36, 36, 36))
+
+music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
+snd_fire = gamesrv.getsample(os.path.join(LocalDir, 'fire.wav'))
+snd_hit = gamesrv.getsample(os.path.join(LocalDir, 'hit.wav'))
+
+
+class Plane(ActiveSprite):
+ lock = None
+
+ def __init__(self, camel, bubber, dcap, x, y, dirhint=None):
+ self.bubber = bubber
+ self.dcap = dcap
+ self.camel = camel
+ self.shotlist = camel.score.setdefault(bubber, {})
+
+ if x < x_min:
+ x = x_min
+ elif x > x_max:
+ x = x_max
+
+ if y < 4*CELL:
+ y = 4*CELL
+ elif y > (curboard.height-4)*CELL - 36:
+ y = (curboard.height-4)*CELL - 36
+
+ if dirhint not in (1, -1):
+ controldelay = 5
+ if x < boards.bwidth//2:
+ dir = 1
+ else:
+ dir = -1
+ else:
+ controldelay = 20
+ if x < boards.bwidth//3:
+ dir = 1
+ elif x >= boards.bwidth*2//3:
+ dir = -1
+ else:
+ dir = dirhint
+ if dir > 0:
+ self.angle = 0
+ self.flipped = False
+ else:
+ self.angle = 180
+ self.flipped = True
+
+ ActiveSprite.__init__(self, self.getico(), x, y)
+ self.fx = self.x
+ self.fy = self.y
+ self.controlgen = self.control(delay=controldelay)
+ self.gen.append(self.fly())
+ self.gen.append(self.controlgen)
+ self.gen.append(self.blink())
+ self.gen.append(self.bob())
+
+ def controlled(self):
+ return self.controlgen in self.gen
+
+ def getico(self):
+ a = self.angle // ANGLE_STEP
+ if self.flipped:
+ if a:
+ a = ANGLE_COUNT-a
+ key = 'vflip', ('camel', a, self.bubber.pn)
+ else:
+ key = 'camel', a, self.bubber.pn
+ return images.sprget(key)
+
+ def blink(self):
+ for i in range(10):
+ yield None
+ yield None
+ self.setdisplaypos(-256, -256)
+ yield None
+ self.setdisplaypos(-256, -256)
+ yield None
+ self.touchable = 1
+
+ def bob(self):
+ f = 3.0
+ for i in range(0, 1080, ANGLE_STEP):
+ self.fy += f * ANGLE_TABLE[i % 360][1]
+ f *= 0.98
+ yield None
+
+## def loosealtitude(self, y0, angle0):
+## if 90 <= angle0 < 270 or (angle0 == 270 and not self.flipped):
+## angledir = -1
+## else:
+## angledir = 1
+## for i in range(0, 180, ANGLE_STEP):
+## if i % (4*ANGLE_STEP) == 0 and not (45 <= angle0 <= 135):
+## angle0 += ANGLE_STEP * angledir
+## angle0 = (angle0 + 360) % 360
+## y0 += 4.0 * ANGLE_TABLE[i][1]
+## if y0 > self.fy:
+## self.fy = y0
+## self.angle = angle0
+## yield None
+
+ def turn(self, dir):
+ self.angle += ANGLE_STEP * dir
+ self.angle = (self.angle + 360) % 360
+
+ def control(self, delay=0):
+ bubber = self.bubber
+ prev_key_jump = 0
+ for i in range(delay):
+ yield None
+ shootdelay = 0
+ while True:
+ wannago = bubber.wannago(self.dcap)
+ self.turn(wannago)
+ if shootdelay:
+ shootdelay -= 1
+ elif bubber.key_fire:
+ x = self.x + self.ico.w//2
+ y = self.y + self.ico.h//2
+ acos, asin = ANGLE_TABLE[self.angle]
+ x += acos * 20
+ y += asin * 20
+ if self.flipped:
+ acos = -acos
+ asin = -asin
+ x -= asin * 5
+ y += acos * 5
+ self.play(snd_fire)
+ Shot(self, int(x), int(y), self.angle, 2)
+ Shot(self, int(x), int(y), self.angle)
+ shootdelay = 7
+ for i in range(2):
+ if bubber.key_jump > prev_key_jump:
+ self.flipped = not self.flipped
+ prev_key_jump = bubber.key_jump
+ yield None
+ for s in self.touching(12):
+ if isinstance(s, Plane) and s is not self:
+ ico = images.sprget(Monky.decay_weapon[1])
+ s1 = ActiveSprite(ico,
+ (self.x+s.x)//2 + self.ico.w//2 - CELL,
+ (self.y+s.y)//2 + self.ico.h//2 - CELL)
+ s1.gen.append(s1.die(Monky.decay_weapon[1:], 4))
+ s1.play(snd_crash)
+ self.gen = [self.godowninflames(s)]
+ s.gen = [s.godowninflames(self)]
+
+ def fly(self, speed=3.3):
+ while True:
+ if (self.y < 0 and not (0 < self.angle < 180) and
+ ((abs(270 - self.angle) < -4*self.y) or random.random() < 0.2)):
+ if (90 <= self.angle < 270 or
+ (self.angle == 270 and not self.flipped)):
+ self.turn(-1)
+ else:
+ self.turn(1)
+ ico = self.getico()
+ acos, asin = ANGLE_TABLE[self.angle]
+ self.fx += acos * speed
+ self.fy += asin * speed
+ self.move(int(self.fx), int(self.fy), ico)
+ if self.x < x_min:
+ self.angle = 2 * ANGLE_STEP
+ self.flipped = not self.flipped
+ self.gen = [self.godowninflames()]
+ self.play(images.Snd.Pop)
+ elif self.x > x_max:
+ self.angle = 180 - 2 * ANGLE_STEP
+ self.flipped = not self.flipped
+ self.gen = [self.godowninflames()]
+ self.play(images.Snd.Pop)
+ elif self.y > y_max:
+ self.gen = [self.crashed()]
+ yield None
+
+ def godowninflames(self, hit_by_plane=None):
+ if hit_by_plane and hit_by_plane in self.shotlist:
+ hittime = self.shotlist[hit_by_plane]
+ if BubPlayer.FrameCounter < hittime + 60:
+ del self.shotlist[hit_by_plane]
+ scoreboard()
+ self.seticon(self.getico())
+ self.gen.append(self.fly())
+ trail = [(self.x, self.y)] * 7
+ ico = images.sprget(PlayerBubbles.explosion[0])
+ s = ActiveSprite(ico, self.x + self.ico.w//2 - CELL,
+ self.y + self.ico.h//2 - CELL)
+ s.gen.append(s.die(PlayerBubbles.explosion))
+ self.bubber.emotic(self, 4)
+ while True:
+ yield None
+ if random.random() < 0.37:
+ ico = images.sprget(Bubble.exploding_bubbles[0])
+ x, y = random.choice(trail)
+ x += random.randint(-10, 10)
+ y += random.randint(-10, 10)
+ s = ActiveSprite(ico, x+2, y+2)
+ s.gen.append(s.die(Bubble.exploding_bubbles))
+ if random.random() < 0.5:
+ yield None
+ if 90 <= self.angle < 270:
+ lst = [0, 0, 0, 0, -1, -1, -1, 1, 1]
+ else:
+ lst = [0, 0, 0, 0, -1, -1, 1, 1, 1]
+ self.turn(random.choice(lst))
+ trail.pop(0)
+ trail.append((self.x, self.y))
+
+ def crashed(self):
+ self.untouchable()
+ self.play(snd_crash)
+ ico = images.sprget(Monky.decay_weapon[1])
+ self.seticon(ico)
+ self.step(self.ico.w//2 - CELL,
+ self.ico.h//2 - CELL)
+ self.gen.append(self.die(Monky.decay_weapon[1:], 4))
+ yield None
+
+ def kill(self):
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+
+class Shot(ActiveSprite):
+
+ def __init__(self, plane, x, y, angle, steps=0):
+ ico = images.sprcharacterget('.')
+ ActiveSprite.__init__(self, ico, x-4, y-12)
+ self.plane = plane
+ self.angle = angle
+ self.gen.append(self.moving(steps))
+
+ def moving(self, steps=0):
+ minx = 2*CELL - 4
+ maxx = (curboard.width-2)*CELL - 4
+ maxy = (curboard.height-1)*CELL - 12
+ fx = self.x
+ fy = self.y
+ dx, dy = ANGLE_TABLE[self.angle]
+ dx *= 7.6
+ dy *= 7.6
+ fx += dx * steps
+ fy += dy * steps
+ for i in range(22-steps):
+ for s in images.touching(self.x+3, self.y+11, 2, 2):
+ if isinstance(s, Plane) and s is not self.plane:
+ self.play(snd_hit)
+ self.kill()
+ if s.controlled():
+ s.gen = [s.godowninflames(self.plane)]
+ self.plane.shotlist[s] = BubPlayer.FrameCounter
+ bonuses.points(self.x + 4 - CELL, self.y + 12 - CELL,
+ self.plane, 100)
+ return
+ fx += dx
+ fy += dy
+ self.move(int(fx), int(fy))
+ if self.x < minx or self.x > maxx or self.y > maxy:
+ break
+ yield None
+ self.kill()
+
+
+class Camel:
+
+ def bgen(self, limittime = 90.1): # 1:30
+ self.score = {}
+ for t in boards.initsubgame(music, self.displaypoints):
+ yield t
+
+ tc = boards.TimeCounter(limittime)
+ for t in self.frame(tc):
+ t = boards.normal_frame()
+ self.build_planes()
+ yield t
+ tc.update(t)
+ if (BubPlayer.FrameCounter & 15) == 7:
+ for s in images.ActiveSprites:
+ if isinstance(s, Bubble):
+ s.pop()
+ elif isinstance(s, Bonus):
+ s.kill()
+
+ tc.restore()
+ score = {}
+ for player, shotlist in self.score.items():
+ score[player] = len(shotlist)
+ for t in boards.result_ranking(score):
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons[:]:
+ d.kill()
+ yield t
+ self.remove_planes()
+
+ def displaypoints(self, bubber):
+ return len(self.score.get(bubber, ()))
+
+ def build_planes(self):
+ for p in BubPlayer.PlayerList:
+ dragons = [d for d in p.dragons if not isinstance(d, Plane)]
+ if dragons and len(p.dragons) == len(dragons):
+ dragon = random.choice(dragons)
+ if dragon.dcap['infinite_shield']:
+ start_position = self.select_start_position()
+ dirhint = None
+ else:
+ start_position = dragon.x-2, dragon.y-2
+ dirhint = getattr(dragon, 'dir', None)
+ plane = Plane(self, p, dragon.dcap,
+ start_position[0], start_position[1], dirhint)
+ p.dragons.append(plane)
+ p.emotic(plane, 4)
+ for d in dragons:
+ d.kill()
+
+ def remove_planes(self):
+ for p in BubPlayer.PlayerList:
+ for d in p.dragons[:]:
+ d.kill()
+
+ def select_start_position(self):
+ planes = [d for p in BubPlayer.PlayerList
+ for d in p.dragons
+ if isinstance(d, Plane)]
+ distmin = 180
+ while True:
+ x = random.choice([x_min, x_max])
+ y = random.randint(2*CELL, (curboard.height-4)*CELL - 36)
+ for d in planes:
+ dist = (x-d.x)*(x-d.x) + (y-d.y)*(y-d.y)
+ if dist < distmin*distmin:
+ break
+ else:
+ return x, y
+ distmin = int(distmin * 0.94)
+
+ def frame(self, tc):
+ y = curboard.height-1
+ for x in range(2, curboard.width-2):
+ if (y, x) not in curboard.walls_by_pos:
+ curboard.putwall(x, y)
+ curboard.reorder_walls()
+ for y in range(0, curboard.height-1):
+ yield None
+ for x in range(2, curboard.width-2):
+ if (y, x) in curboard.walls_by_pos:
+ curboard.killwall(x, y)
+ while tc.time != 0.0:
+ yield None
+
+# This game is suitable for at least min_players players
+min_players = 2
+
+def run():
+ global curboard, x_min, x_max, y_max
+ from boards import curboard
+ x_min = 2*CELL - 3
+ x_max = (curboard.width-2)*CELL - 36 + 3
+ y_max = (curboard.height-1)*CELL - 36 + 7
+ boards.replace_boardgen(Camel().bgen())
+
+def setup():
+ for key, (filename, rect) in localmap.items():
+ filename = os.path.join(LocalDir, filename)
+ if filename.find('%d') >= 0:
+ for p in BubPlayer.PlayerList:
+ images.sprmap[key + (p.pn,)] = (filename % p.pn, rect)
+ else:
+ images.sprmap[key] = (filename, rect)
+setup()
diff --git a/bubbob/ext7/fire.wav b/bubbob/ext7/fire.wav
new file mode 100644
index 0000000..c8531a3
--- /dev/null
+++ b/bubbob/ext7/fire.wav
Binary files differ
diff --git a/bubbob/ext7/hit.wav b/bubbob/ext7/hit.wav
new file mode 100644
index 0000000..36d497a
--- /dev/null
+++ b/bubbob/ext7/hit.wav
Binary files differ
diff --git a/bubbob/ext7/image1-0.ppm b/bubbob/ext7/image1-0.ppm
new file mode 100644
index 0000000..2df7b9b
--- /dev/null
+++ b/bubbob/ext7/image1-0.ppm
Binary files differ
diff --git a/bubbob/ext7/music.wav b/bubbob/ext7/music.wav
new file mode 100644
index 0000000..b45f018
--- /dev/null
+++ b/bubbob/ext7/music.wav
Binary files differ
diff --git a/bubbob/images.py b/bubbob/images.py
new file mode 100644
index 0000000..4de0b56
--- /dev/null
+++ b/bubbob/images.py
@@ -0,0 +1,542 @@
+from __future__ import generators
+import gamesrv, os
+from sprmap import sprmap as original_sprmap
+from patmap import patmap
+import mnstrmap
+import pixmap
+
+KEYCOL = 0x010101
+MAX = 10
+
+ActiveSprites = []
+SpritesByLoc = {}
+
+
+class ActiveSprite(gamesrv.Sprite):
+ touchable = 0
+ imgsetter = None
+ angry = []
+ priority = 0
+
+ def __init__(self, *args):
+ gamesrv.Sprite.__init__(self, *args)
+ if self.priority:
+ ActiveSprites.insert(0, self)
+ else:
+ ActiveSprites.append(self)
+ self.ranges = []
+ self.gen = []
+
+ def kill(self):
+ self.untouchable()
+ del self.gen[:]
+ ActiveSprites.remove(self)
+ gamesrv.Sprite.kill(self)
+
+ def untouchable(self):
+ self.touchable = 0
+ for key in self.ranges:
+ del key[self]
+ del self.ranges[:]
+
+ def play(self, snd, volume=0.8):
+ import boards
+ xmin = 2*boards.CELL
+ xmax = boards.bwidth-4*boards.CELL
+ snd.play(volume, pad=float(self.x-xmin)/(xmax-xmin))
+
+ def setimages(self, gen):
+ if self.imgsetter is not None:
+ try:
+ self.gen.remove(self.imgsetter)
+ except ValueError:
+ pass
+ self.imgsetter = gen
+ if gen is not None:
+ self.gen.append(gen)
+
+ def vertical_warp(self):
+ # short-cut this method to boards.py
+ import boards
+ ActiveSprite.vertical_warp = boards.vertical_warp_sprite
+ self.vertical_warp()
+## if moebius:
+## self.moebius()
+## return moebius
+## def moebius(self):
+## pass
+
+ # common generators
+ def cyclic(self, nimages, speed=5):
+ images = [sprget(n) for n in nimages]
+ speed = range(speed)
+ while 1:
+ for img in images:
+ self.seticon(img)
+ for i in speed:
+ yield None
+
+ def imgseq(self, nimages, speed=5, repeat=1):
+ images = [sprget(n) for n in nimages]
+ for r in range(repeat):
+ for img in images:
+ self.seticon(img)
+ for i in range(speed):
+ yield None
+
+ def die(self, nimages, speed=1):
+ for n in nimages:
+ if n is not None:
+ self.seticon(sprget(n))
+ for i in range(speed):
+ yield None
+ self.kill()
+
+ def straightline(self, dx, dy):
+ fx = self.x + 0.5
+ fy = self.y + 0.5
+ while 1:
+ fx += dx
+ fy += dy
+ self.move(int(fx), int(fy))
+ yield None
+
+ def parabolic(self, dxy, warp=0, gravity=0.3):
+ import boards
+ from boards import CELL
+ nx = self.x
+ ny = self.y
+ dx, dy = dxy
+ xmax = boards.bwidth - 2*CELL - self.ico.w
+ while ny < boards.bheight:
+ nx += dx
+ ny += dy
+ dy += gravity
+ if nx < 2*CELL:
+ nx = 2*CELL
+ dx = abs(dx)
+ elif nx >= xmax:
+ nx = xmax
+ dx = -abs(dx)
+ if warp and (ny < -2*CELL or ny >= boards.bheight):
+ nx, ny = boards.vertical_warp(nx, ny)
+## if moebius:
+## self.moebius()
+## dx = -dx
+ self.move(int(nx), int(ny))
+ dxy[:] = [dx, dy]
+ yield None
+
+ def following(self, other, dx=0, dy=0):
+ while other.alive:
+ self.move(other.x + dx, other.y + dy)
+ yield None
+ self.kill()
+
+ def touchdelay(self, delay):
+ for i in range(delay):
+ yield None
+ self.touchable = 1
+
+ def touching(self, margin=0):
+ return touching(self.x, self.y, self.ico.w, self.ico.h, margin)
+
+ def genangry(self):
+ # do one more step throught all generators of self.gen
+ while 1:
+ glist = self.gen[:]
+ try:
+ for g in glist:
+ if self.alive:
+ g.next()
+ except StopIteration:
+ try:
+ self.gen.remove(g)
+ except ValueError:
+ pass
+ for g in glist[glist.index(g)+1:]:
+ if self.alive:
+ try:
+ g.next()
+ except StopIteration:
+ pass
+ yield None
+
+def touching(x1, y1, w1, h1, margin=0):
+ touch = {}
+ x1 = int(x1)
+ y1 = int(y1)
+ xrange = range(x1>>5, (x1+w1+31)>>5)
+ for y in range(y1>>4, (y1+h1+15)>>4):
+ for x in xrange:
+ touch.update(SpritesByLoc.get((x,y), {}))
+ return [s for s in touch
+ if x1+margin < s.x+s.ico.w and y1+margin < s.y+s.ico.h and
+ s.x+margin < x1+w1 and s.y+margin < y1+h1]
+
+def action(sprlist, len=len):
+ # Main generator dispatch loop
+ for self in sprlist:
+ glist = self.gen + self.angry
+ try:
+ for g in glist:
+ if self.alive:
+ g.next()
+ except StopIteration:
+ try:
+ self.gen.remove(g)
+ except ValueError:
+ pass
+ for g in glist[glist.index(g)+1:]:
+ if self.alive:
+ try:
+ g.next()
+ except StopIteration:
+ pass
+ if self.touchable and self.alive:
+ # record position
+ x = self.x & -8
+ y = self.y & -8
+ if self.touchable != (x, y):
+ self.touchable = x, y
+ for key in self.ranges:
+ del key[self]
+ del self.ranges[:]
+ xrange = range(x>>5, (x+self.ico.w+38)>>5)
+ for y in range(y>>4, (y+self.ico.h+22)>>4):
+ for x in xrange:
+ key = SpritesByLoc.setdefault((x,y), {})
+ key[self] = 1
+ self.ranges.append(key)
+
+def sprget(n, spriconcache={}):
+ try:
+ return spriconcache[n]
+ except KeyError:
+ key = n
+ if isinstance(key, tuple) and key[0] in Transformations:
+ t, n = key
+ transform = Transformations[t]
+ else:
+ transform = transform_noflip
+ filename, rect = sprmap[n]
+ bitmap, rect = transform(filename, rect)
+ if isinstance(n, tuple):
+ n1 = n[0]
+ else:
+ n1 = n
+ if isinstance(n1, int):
+ n1 = n1 % 1000
+ alpha = transparency.get(n1, 255)
+ ico = bitmap.geticon(alpha=alpha, *rect)
+ spriconcache[key] = ico
+ return ico
+
+def transform_noflip(filename, rect):
+ bitmap = gamesrv.getbitmap(filename, KEYCOL)
+ return bitmap, rect
+
+def make_transform(datamap, ptmap):
+ def transform(filename, rect, datamap=datamap, ptmap=ptmap, cache={}):
+ try:
+ bitmap, width, height = cache[filename]
+ except KeyError:
+ f = open(filename, "rb")
+ data = f.read()
+ f.close()
+ width, height, data = pixmap.decodepixmap(data)
+ data = datamap(width, height, data)
+ dummy, dummy, nwidth, nheight = ptmap(0, 0, width, height)
+ data = pixmap.encodepixmap(nwidth, nheight, data)
+ bitmap = gamesrv.newbitmap(data, KEYCOL)
+ cache[filename] = bitmap, width, height
+ #print 'transformed', filename, 'to', nwidth, nheight
+ x, y, w, h = rect
+ x1, y1, dummy, dummy = ptmap(x, y, width, height)
+ x2, y2, dummy, dummy = ptmap(x+w, y+h, width, height)
+ rect = min(x1,x2), min(y1,y2), abs(x2-x1), abs(y2-y1)
+ #print filename, ':', (x,y,w,h), '->', rect
+ return bitmap, rect
+ return transform
+
+Transformations = {
+ '': transform_noflip,
+ 'vflip': make_transform(pixmap.vflip, lambda x,y,w,h: (x,h-y,w,h)),
+ 'hflip': make_transform(pixmap.hflip, lambda x,y,w,h: (w-x,y,w,h)),
+ 'cw': make_transform(pixmap.rotate_cw, lambda x,y,w,h: (h-y,x,h,w)),
+ 'ccw': make_transform(pixmap.rotate_ccw,lambda x,y,w,h: (y,w-x,h,w)),
+ 'rot180': make_transform(pixmap.rotate_180,lambda x,y,w,h: (w-x,h-y,w,h)),
+ }
+
+if 0: # disabled clipping
+ def sprget_subrect(n, subrect):
+ x, y, w, h = subrect
+ filename, (x0, y0, w0, h0) = sprmap[n]
+ key = (n, 'subrect', subrect)
+ sprmap[key] = filename, (x0+x, y0+y, w, h)
+ return sprget(key)
+
+def make_darker(ico, is_dragon, bmpcache={}):
+ bmp, rect = ico.getorigin()
+ try:
+ darkbmp = bmpcache[bmp, is_dragon]
+ except KeyError:
+ image = pixmap.decodepixmap(bmp.read())
+ if is_dragon:
+ translation = pixmap.translation_dragon
+ else:
+ translation = pixmap.translation_darker
+ darkimage = pixmap.make_dark(image, translation)
+ data = pixmap.encodepixmap(*darkimage)
+ darkbmp = gamesrv.newbitmap(data, bmp.colorkey)
+ bmpcache[bmp, is_dragon] = darkbmp
+ return darkbmp.geticon(*rect)
+
+def haspat(n):
+ return n in patmap
+
+def loadpattern(n, keycol=None):
+ if not haspat(n):
+ n = (n[0] % 100,) + n[1:]
+ filename, rect = patmap[n]
+ filename = os.path.join('tmp', filename)
+ bitmap = gamesrv.getbitmap(filename, keycol)
+ return bitmap, rect
+
+def makebkgndpattern(bitmap, (x,y,w,h), darker={}):
+ from boards import CELL
+ try:
+ nbitmap, hscale, vscale = darker[bitmap]
+ except KeyError:
+ data = bitmap.read()
+ width, height, data = pixmap.decodepixmap(data)
+ nwidth, nheight, data = pixmap.makebkgnd(width, height, data)
+ hscale = float(nwidth) / width
+ vscale = float(nheight) / height
+ data = pixmap.encodepixmap(nwidth, nheight, data)
+ nbitmap = gamesrv.newbitmap(data, None)
+ darker[bitmap] = nbitmap, hscale, vscale
+ x = int(x*hscale)
+ y = int(y*vscale)
+ w = int(CELL*hscale)
+ h = int(CELL*vscale)
+ return nbitmap, (x,y,w,h)
+
+def computebiggericon(ico, bigger={}):
+ try:
+ result, computing = bigger[ico]
+ except KeyError:
+ bigger[ico] = None, pixmap.imagezoomer(*ico.getimage())
+ return None
+ if computing is not None:
+ result = computing.next() or computing.next() or computing.next()
+ if not result:
+ return None # still computing
+ w, h, data = result
+ data = pixmap.encodepixmap(w, h, data)
+ result = gamesrv.newbitmap(data, KEYCOL).geticon(0, 0, w, h)
+ bigger[ico] = result, None
+ return result
+
+def biggericon(ico):
+ result = None
+ while result is None:
+ result = computebiggericon(ico)
+ return result
+
+extramap = {
+ 'shield-left': ('extra1.ppm', (0, 0, 32, 32)),
+ 'shield-right': ('extra1.ppm', (0, 32, 32, 32)),
+ 'moebius': ('extra1.ppm', (0, 64, 32, 32)),
+ 'flower': ('extra1.ppm', (0, 96, 32, 32)),
+ 'flower2': ('extra1.ppm', (0, 128, 32, 32)),
+ 'potion4': ('extra1.ppm', (0, 160, 32, 32)),
+ ('glasses', -1):('extra1.ppm', (0, 192, 32, 16)),
+ ('glasses', +1):('extra1.ppm', (0, 208, 32, 16)),
+ 'cactus': ('extra1.ppm', (0, 224, 32, 32)),
+ 'questionmark3':('extra2.ppm', (0, 0, 16, 16)),
+ 'questionmark1':('extra2.ppm', (0, 16, 16, 16)),
+ 'questionmark5':('extra2.ppm', (0, 32, 16, 16)),
+ 'questionmark2':('extra2.ppm', (0, 48, 16, 16)),
+ 'questionmark4':('extra2.ppm', (0, 64, 16, 16)),
+ 'percent': ('extra2.ppm', (0, 80, 16, 16)),
+ 'colon': ('extra2.ppm', (0, 96, 16, 16)),
+ 'gameoverbkgnd':('extra2.ppm', (0, 112, 16, 16)),
+ ('eyes', 0,0): ('extra3.ppm', (0, 0, 32, 32)),
+ ('eyes', 0,-1): ('extra3.ppm', (0, 32, 32, 32)),
+ ('eyes', -1,0): ('extra3.ppm', (0, 64, 32, 32)),
+ ('eyes', -1,-1):('extra3.ppm', (0, 96, 32, 32)),
+ ('eyes', 1,0): ('extra3.ppm', (0, 128, 32, 32)),
+ ('eyes', 1,-1): ('extra3.ppm', (0, 160, 32, 32)),
+ 'eyes-blink': ('extra3.ppm', (0, 192, 32, 32)),
+ ('smstar','blue' ,0): ('extra4.ppm', ( 0, 0, 16, 16)),
+ ('smstar','blue' ,1): ('extra4.ppm', ( 0, 16, 16, 16)),
+ ('smstar','yellow' ,0): ('extra4.ppm', ( 0, 32, 16, 16)),
+ ('smstar','yellow' ,1): ('extra4.ppm', ( 0, 48, 16, 16)),
+ ('smstar','red' ,0): ('extra4.ppm', (16, 0, 16, 16)),
+ ('smstar','red' ,1): ('extra4.ppm', (16, 16, 16, 16)),
+ ('smstar','green' ,0): ('extra4.ppm', (16, 32, 16, 16)),
+ ('smstar','green' ,1): ('extra4.ppm', (16, 48, 16, 16)),
+ ('smstar','magenta',0): ('extra4.ppm', (32, 0, 16, 16)),
+ ('smstar','magenta',1): ('extra4.ppm', (32, 16, 16, 16)),
+ ('smstar','cyan' ,0): ('extra4.ppm', (32, 32, 16, 16)),
+ ('smstar','cyan' ,1): ('extra4.ppm', (32, 48, 16, 16)),
+ ('starbub','blue' ,0): ('extra5.ppm', (0, 0, 32, 32)),
+ ('starbub','blue' ,1): ('extra5.ppm', (0, 32, 32, 32)),
+ ('starbub','blue' ,2): ('extra5.ppm', (0, 64, 32, 32)),
+ ('starbub','yellow' ,0): ('extra5.ppm', (0, 96, 32, 32)),
+ ('starbub','yellow' ,1): ('extra5.ppm', (0,128, 32, 32)),
+ ('starbub','yellow' ,2): ('extra5.ppm', (0,160, 32, 32)),
+ ('starbub','red' ,0): ('extra5.ppm', (0,192, 32, 32)),
+ ('starbub','red' ,1): ('extra5.ppm', (0,224, 32, 32)),
+ ('starbub','red' ,2): ('extra5.ppm', (0,256, 32, 32)),
+ ('starbub','green' ,0): ('extra5.ppm', (0,288, 32, 32)),
+ ('starbub','green' ,1): ('extra5.ppm', (0,320, 32, 32)),
+ ('starbub','green' ,2): ('extra5.ppm', (0,352, 32, 32)),
+ ('starbub','magenta',0): ('extra5.ppm', (0,384, 32, 32)),
+ ('starbub','magenta',1): ('extra5.ppm', (0,416, 32, 32)),
+ ('starbub','magenta',2): ('extra5.ppm', (0,448, 32, 32)),
+ ('starbub','cyan' ,0): ('extra5.ppm', (0,480, 32, 32)),
+ ('starbub','cyan' ,1): ('extra5.ppm', (0,512, 32, 32)),
+ ('starbub','cyan' ,2): ('extra5.ppm', (0,544, 32, 32)),
+ 'sheep-sm': ('extra6.ppm', (0, 0, 32, 32)),
+ 'sheep-big': ('extra6.ppm', (0, 32, 46, 50)),
+ ('emotic', 0): ('extra7.ppm', (0, 0, 8, 8)),
+ ('emotic', 1): ('extra7.ppm', (0, 8, 8, 8)),
+ ('emotic', 2): ('extra7.ppm', (0, 16, 8, 8)),
+ ('emotic', 3): ('extra7.ppm', (0, 24, 8, 8)),
+ ('emotic', 4): ('extra7.ppm', (0, 32, 8, 8)),
+ ('emotic', 5): ('extra7.ppm', (0, 40, 8, 8)),
+ ('emotic', 6): ('extra7.ppm', (0, 48, 8, 8)),
+ ('butterfly', 'jailed', 0): ('butterfly.ppm', (0, 0, 32, 32)),
+ ('butterfly', 'jailed', 1): ('butterfly.ppm', (0, 32, 32, 32)),
+ ('butterfly', 'jailed', 2): ('butterfly.ppm', (0, 64, 32, 32)),
+ ('butterfly', 'dead', 0): ('butterfly.ppm', (0, 96, 32, 32)),
+ ('butterfly', 'dead', 1): ('butterfly.ppm', (0, 128, 32, 32)),
+ ('butterfly', 'dead', 2): ('butterfly.ppm', (0, 160, 32, 32)),
+ ('butterfly', 'dead', 3): ('butterfly.ppm', (0, 192, 32, 32)),
+ ('butterfly', 'fly', 0): ('butterfly.ppm', (0, 224, 32, 32)),
+ ('butterfly', 'fly', 1): ('butterfly.ppm', (0, 256, 32, 32)),
+ 'glue': ('glue.ppm', (0, 0, 32, 32)),
+ 'black': ('black.ppm', (0, 0, 32, 32)),
+ ('sheep',-1, 0): ('sheep.ppm', (0, 0, 32, 32)),
+ ('sheep',-1, 1): ('sheep.ppm', (0, 32, 32, 32)),
+ ('sheep',-1, 2): ('sheep.ppm', (0, 64, 32, 32)),
+ ('sheep',-1, 3): ('sheep.ppm', (0, 96, 32, 32)),
+ ('sheep', 1, 0): ('sheep.ppm', (0, 128, 32, 32)),
+ ('sheep', 1, 1): ('sheep.ppm', (0, 160, 32, 32)),
+ ('sheep', 1, 2): ('sheep.ppm', (0, 192, 32, 32)),
+ ('sheep', 1, 3): ('sheep.ppm', (0, 224, 32, 32)),
+ ('sheep', 'a'): ('sheep.ppm', (2, 263, 7, 8)),
+ ('sheep', 'b'): ('sheep.ppm', (11, 262, 6, 10)),
+ ('sheep', 'c'): ('sheep.ppm', (17, 264, 11, 8)),
+ ('sheep', 'd'): ('sheep.ppm', (18, 272, 11, 7)),
+ ('sheep', 'e'): ('sheep.ppm', (18, 279, 11, 8)),
+ ('sheep', 'f'): ('sheep.ppm', (4, 273, 10, 12)),
+ ('sheep', 'g'): ('sheep.ppm', (19, 257, 11, 8)),
+ }
+hatmap = {
+ ('hat', 0, -1,1):('hat2.ppm',( 0, 0, 32, 48)),
+ ('hat', 0, -1,2):('hat2.ppm',( 32, 0, 32, 48)),
+ ('hat', 0, -1,3):('hat2.ppm',( 64, 0, 32, 48)),
+ ('hat', 0, 1,3):('hat2.ppm',( 96, 0, 32, 48)),
+ ('hat', 0, 1,2):('hat2.ppm',(128, 0, 32, 48)),
+ ('hat', 0, 1,1):('hat2.ppm',(160, 0, 32, 48)),
+ ('hat', 1, -1,1):('hat1.ppm',( 0, 0, 32, 48)),
+ ('hat', 1, -1,2):('hat1.ppm',( 32, 0, 32, 48)),
+ ('hat', 1, -1,3):('hat1.ppm',( 64, 0, 32, 48)),
+ ('hat', 1, 1,3):('hat1.ppm',( 96, 0, 32, 48)),
+ ('hat', 1, 1,2):('hat1.ppm',(128, 0, 32, 48)),
+ ('hat', 1, 1,1):('hat1.ppm',(160, 0, 32, 48)),
+ ('hat', 0) :('hat5.ppm',( 32, 0, 32, 48)),
+ ('hat', 1) :('hat5.ppm',( 0, 0, 32, 48)),
+ }
+
+def generate_sprmap():
+ # check and maybe regenerate the colored image files
+ file = os.path.join('images', 'buildcolors.py')
+ g = {'__name__': '__auto__', '__file__': file}
+ execfile(file, g)
+ # replace the entries 'filename_%d.ppm' by a family of entries,
+ # one for each color
+ sprmap = {}
+ for n, (filename, rect) in (original_sprmap.items() +
+ extramap.items() + hatmap.items()):
+ if filename.find('%d') >= 0:
+ for i in range(MAX):
+ sprmap[n+1000*i] = (os.path.join('images',filename % i), rect)
+ else:
+ sprmap[n] = (os.path.join('images', filename), rect)
+ return sprmap
+sprmap = generate_sprmap()
+
+transparency = {
+ mnstrmap.GreenAndBlue.new_bubbles[0][0]: 0xA0,
+ mnstrmap.GreenAndBlue.new_bubbles[0][1]: 0xB0,
+ mnstrmap.GreenAndBlue.new_bubbles[0][2]: 0xC0,
+ mnstrmap.GreenAndBlue.new_bubbles[0][3]: 0xD0,
+ mnstrmap.GreenAndBlue.normal_bubbles[0][0]: 0xE0,
+ mnstrmap.GreenAndBlue.normal_bubbles[0][1]: 0xE0,
+ mnstrmap.GreenAndBlue.normal_bubbles[0][2]: 0xE0,
+ mnstrmap.DyingBubble.first[0]: 0xD0,
+ mnstrmap.DyingBubble.first[1]: 0xD0,
+ mnstrmap.DyingBubble.first[2]: 0xD0,
+ mnstrmap.DyingBubble.medium[0]: 0xC0,
+ mnstrmap.DyingBubble.medium[1]: 0xC0,
+ mnstrmap.DyingBubble.medium[2]: 0xC0,
+ mnstrmap.DyingBubble.last[0]: 0xB0,
+ mnstrmap.DyingBubble.last[1]: 0xB0,
+ mnstrmap.DyingBubble.last[2]: 0xB0,
+ 'starbub': 0xE0,
+ }
+
+def sprcharacterget(c, filename=os.path.join('images', 'extra8.ppm')):
+ n = ord(c) - 32
+ if 0 <= n < 95:
+ return gamesrv.getbitmap(filename, KEYCOL).geticon(n*8, 0, 8, 15)
+ else:
+ return None
+
+def writestr(x, y, text):
+ result = []
+ for c in text:
+ ico = sprcharacterget(c)
+ if ico is not None:
+ result.append(gamesrv.Sprite(ico, x, y))
+ x += 7
+ return result
+
+def writestrlines(lines):
+ import boards
+ width = boards.bwidth + 9*boards.CELL
+ y = 50
+ for text in lines:
+ if text:
+ writestr((width - 7*len(text)) // 2, y, text)
+ y += 28
+ else:
+ y += 14
+
+
+
+def getsample(fn, freq):
+ return gamesrv.getsample(os.path.join('sounds', fn), freq)
+
+SoundList = ['Pop', 'Jump', 'Die', 'LetsGo', 'Extralife',
+ 'Fruit', 'Extra', 'Yippee', 'Hurry', 'Hell', 'Shh']
+
+class Snd:
+ pass
+
+def loadsounds(freqfactor=1):
+ for key in SoundList:
+ setattr(Snd, key, getsample(key.lower()+'.wav', freqfactor))
+
+loadsounds()
+music_intro = gamesrv.getmusic('music/Snd1-8.wav')
+music_game = gamesrv.getmusic('music/Snd2-8.wav')
+music_potion = gamesrv.getmusic('music/Snd3-8.wav')
+music_modern = gamesrv.getmusic('music/Snd4-8.wav')
+music_old = gamesrv.getmusic('music/Snd5-8.wav')
+music_game2 = gamesrv.getmusic('music/Snd6-8.wav')
+#gamesrv.set_musics([music_intro, music_game], 1)
diff --git a/bubbob/images/.cvsignore b/bubbob/images/.cvsignore
new file mode 100644
index 0000000..7a1612c
--- /dev/null
+++ b/bubbob/images/.cvsignore
@@ -0,0 +1,7 @@
+[1-7]0000_[2-9].ppm
+digits_[2-9].ppm
+dragon_[2-9].ppm
+dragon_bubble_[2-9].ppm
+game_over_[2-9].ppm
+point_[2-9].ppm
+fish_[2-9].ppm
diff --git a/bubbob/images/10000_0.ppm b/bubbob/images/10000_0.ppm
new file mode 100644
index 0000000..2915dc9
--- /dev/null
+++ b/bubbob/images/10000_0.ppm
Binary files differ
diff --git a/bubbob/images/20000_0.ppm b/bubbob/images/20000_0.ppm
new file mode 100644
index 0000000..d1abe4b
--- /dev/null
+++ b/bubbob/images/20000_0.ppm
Binary files differ
diff --git a/bubbob/images/30000_0.ppm b/bubbob/images/30000_0.ppm
new file mode 100644
index 0000000..9869ceb
--- /dev/null
+++ b/bubbob/images/30000_0.ppm
Binary files differ
diff --git a/bubbob/images/40000_0.ppm b/bubbob/images/40000_0.ppm
new file mode 100644
index 0000000..56b3fca
--- /dev/null
+++ b/bubbob/images/40000_0.ppm
Binary files differ
diff --git a/bubbob/images/50000_0.ppm b/bubbob/images/50000_0.ppm
new file mode 100644
index 0000000..765ab0f
--- /dev/null
+++ b/bubbob/images/50000_0.ppm
Binary files differ
diff --git a/bubbob/images/60000_0.ppm b/bubbob/images/60000_0.ppm
new file mode 100644
index 0000000..ed220b8
--- /dev/null
+++ b/bubbob/images/60000_0.ppm
Binary files differ
diff --git a/bubbob/images/70000_0.ppm b/bubbob/images/70000_0.ppm
new file mode 100644
index 0000000..575cd03
--- /dev/null
+++ b/bubbob/images/70000_0.ppm
Binary files differ
diff --git a/bubbob/images/big_bubble.ppm b/bubbob/images/big_bubble.ppm
new file mode 100644
index 0000000..f0e7b7b
--- /dev/null
+++ b/bubbob/images/big_bubble.ppm
@@ -0,0 +1,4 @@
+P6
+64 384
+255
+ÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÿÿÿÿÿÿÿÿÌÿÿÿÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÌÿÿÌÿÿÿÿÿÌÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file
diff --git a/bubbob/images/big_bubble_2.ppm b/bubbob/images/big_bubble_2.ppm
new file mode 100644
index 0000000..8436f6e
--- /dev/null
+++ b/bubbob/images/big_bubble_2.ppm
@@ -0,0 +1,4 @@
+P6
+64 320
+255
+ŒŒŒÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ«««ÿÿÿÿÿÿÿÿÿ˜˜˜üüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèèèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÓÓÓÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¸¸¸ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ===ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ===ÿÿÿÿÿÿ===ÿÿÿÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿOOOÿÿÿÄÄÄÿÿÿÿÿÿÿÿÿÄÄÄ===ÿÿÿÿÿÿÄÄÄ===ÿÿÿÿÿÿÿÿÿ===ÿÿÿÿÿÿÿÿÿÚÚÚÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{{{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚÚÚÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¥¥¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¸¸¸ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¾¾¾ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿKKKÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄÄÄÄÄÄÄÄÄÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄÄÄÄÄÄ{{{{{{{{{{{{iiiÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file
diff --git a/bubbob/images/black.ppm b/bubbob/images/black.ppm
new file mode 100644
index 0000000..c5adbe8
--- /dev/null
+++ b/bubbob/images/black.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+32 32
+255
+ \ No newline at end of file
diff --git a/bubbob/images/blitzy.ppm b/bubbob/images/blitzy.ppm
new file mode 100644
index 0000000..b2e0963
--- /dev/null
+++ b/bubbob/images/blitzy.ppm
Binary files differ
diff --git a/bubbob/images/blitzy_angry.ppm b/bubbob/images/blitzy_angry.ppm
new file mode 100644
index 0000000..464ce2b
--- /dev/null
+++ b/bubbob/images/blitzy_angry.ppm
Binary files differ
diff --git a/bubbob/images/blitzy_shot.ppm b/bubbob/images/blitzy_shot.ppm
new file mode 100644
index 0000000..378c956
--- /dev/null
+++ b/bubbob/images/blitzy_shot.ppm
Binary files differ
diff --git a/bubbob/images/bonus_0.ppm b/bubbob/images/bonus_0.ppm
new file mode 100644
index 0000000..4fa6e4c
--- /dev/null
+++ b/bubbob/images/bonus_0.ppm
Binary files differ
diff --git a/bubbob/images/bonus_1.ppm b/bubbob/images/bonus_1.ppm
new file mode 100644
index 0000000..82ddd25
--- /dev/null
+++ b/bubbob/images/bonus_1.ppm
Binary files differ
diff --git a/bubbob/images/bonus_10.ppm b/bubbob/images/bonus_10.ppm
new file mode 100644
index 0000000..9ffd660
--- /dev/null
+++ b/bubbob/images/bonus_10.ppm
Binary files differ
diff --git a/bubbob/images/bonus_11.ppm b/bubbob/images/bonus_11.ppm
new file mode 100644
index 0000000..1a5c509
--- /dev/null
+++ b/bubbob/images/bonus_11.ppm
Binary files differ
diff --git a/bubbob/images/bonus_12.ppm b/bubbob/images/bonus_12.ppm
new file mode 100644
index 0000000..354a1fe
--- /dev/null
+++ b/bubbob/images/bonus_12.ppm
Binary files differ
diff --git a/bubbob/images/bonus_2.ppm b/bubbob/images/bonus_2.ppm
new file mode 100644
index 0000000..61588b0
--- /dev/null
+++ b/bubbob/images/bonus_2.ppm
Binary files differ
diff --git a/bubbob/images/bonus_3.ppm b/bubbob/images/bonus_3.ppm
new file mode 100644
index 0000000..dd1ff6d
--- /dev/null
+++ b/bubbob/images/bonus_3.ppm
Binary files differ
diff --git a/bubbob/images/bonus_4.ppm b/bubbob/images/bonus_4.ppm
new file mode 100644
index 0000000..c85e926
--- /dev/null
+++ b/bubbob/images/bonus_4.ppm
Binary files differ
diff --git a/bubbob/images/bonus_5.ppm b/bubbob/images/bonus_5.ppm
new file mode 100644
index 0000000..57bd455
--- /dev/null
+++ b/bubbob/images/bonus_5.ppm
Binary files differ
diff --git a/bubbob/images/bonus_6.ppm b/bubbob/images/bonus_6.ppm
new file mode 100644
index 0000000..2ac4a64
--- /dev/null
+++ b/bubbob/images/bonus_6.ppm
Binary files differ
diff --git a/bubbob/images/bonus_7.ppm b/bubbob/images/bonus_7.ppm
new file mode 100644
index 0000000..5de3eaa
--- /dev/null
+++ b/bubbob/images/bonus_7.ppm
Binary files differ
diff --git a/bubbob/images/bonus_8.ppm b/bubbob/images/bonus_8.ppm
new file mode 100644
index 0000000..b1c6c2a
--- /dev/null
+++ b/bubbob/images/bonus_8.ppm
Binary files differ
diff --git a/bubbob/images/bonus_9.ppm b/bubbob/images/bonus_9.ppm
new file mode 100644
index 0000000..8d51608
--- /dev/null
+++ b/bubbob/images/bonus_9.ppm
Binary files differ
diff --git a/bubbob/images/bubble.ppm b/bubbob/images/bubble.ppm
new file mode 100644
index 0000000..7c6af5e
--- /dev/null
+++ b/bubbob/images/bubble.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.1
+32 448
+255
+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™ff¤lh jgœgf]V‡VI‡VI‡VI­qió›xù°ˆý¾“þ×þ–ü½’ø®‡õ¡}ás¯rj[Sásü»ÿؤÿãªÿãªÿߨÿؤÿÌœÿÆ™ü½’ø®‡î–vÃ~n‘_ZÐ…pÿÕ¢ÿ÷¶ÿü¹ÿü¹ÿ÷¶ÿð²ÿà¨ÿÓ ÿÌœÿÇ™ýÁ•ù²Šî–vºxl‘_ZÀ|mÿá©ÿÿãÿÿÿÿÿÿÿÿÖÿû¸ÿí°ÿݧÿÕ¢ÿПÿËœþ×ü»ø®‡ë”u½zl‘_Z jgÿПÿÿñÿÿÿÿÿÿÿÿÿÿÿÈÿ÷¶ÿí°ÿãªÿÙ¤ÿÔ¡ÿÏžÿÇ™ýÁ•û¹÷¨‚ãt¸wk]V[S÷ªƒÿû¸ÿÿÿÿÿÿÿÿÿÿÿÿÿýºÿ÷¶ÿí°ÿå«ÿݧÿÕ¢ÿПÿËœþ×ü½’ù°ˆóœyÞs¯rj‹ZOãtÿð²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿãÿü¹ÿõµÿí°ÿç¬ÿߨÿÕ¢ÿПÿËœÿÆ™ý¾“ú³‹ö¥€ë”uË‚o–db؉qÿá©ÿÿñÿÿÿÿÿÿÿÿÿÿÿãÿÿ»ÿù·ÿô´ÿí°ÿå«ÿߨÿؤÿПÿËœÿÆ™ý¾“ú¶ö¦î–v؉q¯rj‹ZOù°ˆÿýºÿÿÿÿÿÿÿÿÿÿÿñÿÿÈÿü¹ÿõµÿð²ÿé®ÿä«ÿݧÿÕ¢ÿПÿËœÿÆ™ý¾“ú¶÷¨‚ó™wÞsÀ|m’`[Ë‚oÿПÿÿÈÿÿÿÿÿÿÿÿÿÿÿÈÿýºÿù·ÿó³ÿí°ÿç¬ÿá©ÿÛ¥ÿÔ¡ÿÏžÿÉ›þ×ü½’ú³‹÷¨‚ó™wás΄p¤lh‰XLÅnÿÙ¤ÿÿÈÿÿÿÿÿÿÿÿãÿýºÿù·ÿõµÿð²ÿë¯ÿä«ÿߨÿÙ¤ÿÓ ÿÌœÿÇ™þ–û¹ù²Š÷¨‚ó›xãtÐ…p¸wk‰XLÀ|mÿߨÿÿ»ÿÿÿÿÿÿÿÿ»ÿû¸ÿ÷¶ÿñ³ÿí°ÿç¬ÿà¨ÿÛ¥ÿÕ¢ÿÏžÿÉ›þ×ý¾“û·ù°ˆ÷¨‚óœyæ‘tÓ‡qÀ|m‹ZO¸wkÿݧÿü¹ÿÿãÿýºÿû¸ÿ÷¶ÿñ³ÿí°ÿè­ÿãªÿݧÿؤÿÓ ÿÌœÿÇ™þ–ü½’ú¶ø®‡ö¦óœyæ‘tÖˆqÈ€n[S²tjÿÓ ÿ÷¶ÿýºÿø·ÿõµÿñ³ÿí°ÿè­ÿä«ÿà¨ÿÙ¤ÿÔ¡ÿÏžÿËœÿÆ™ýÁ•ü»ú³‹ø¬…ö¥€ó›xæ‘t؉q΄p]V¯rjÿÉ›ÿñ³ÿ÷¶ÿó³ÿð²ÿì¯ÿç¬ÿãªÿà¨ÿÛ¥ÿÕ¢ÿПÿÌœÿÇ™þ×ý¾“û¹ù°ˆ÷¨‚õ¡}ó™wæ‘tãtÖˆq–dbªoiü½’ÿç¬ÿï±ÿè­ÿç¬ÿãªÿà¨ÿݧÿÙ¤ÿÕ¢ÿПÿËœÿÇ™þ×ýÁ•ü»ú³‹÷ªƒõ¢~ó›xë”uãtë”u؉q™ff¤lhø®‡ÿÓ ÿݧÿÛ¥ÿݧÿÛ¥ÿؤÿÔ¡ÿÓ ÿÏžÿÉ›ÿÆ™þ×ýÁ•ü»ú¶ø¬…ö¥€óœyî–vÞsé“uó™wÐ…p]Vœgfõ¡}ý¾“ÿÉ›ÿÏžÿÓ ÿÔ¡ÿÓ ÿÏžÿËœÿÇ™þ×ýÁ•ý¾“ü»û·ù°ˆ÷¨‚ôŸ{ï—vãtãtõ¡}ôŸ{Ã~n‰XL–dbæ‘tú³‹ü½’þ–ÿÇ™ÿËœÿÌœÿÉ›ÿÆ™þ–ü½’ü»û¹û·ù²Š÷ªƒõ¢~ó™wæ‘té“uö¥€ù²Šóœyµvk‰XLÅnö¦ú³‹û¹ý¾“þ–þ×ýÁ•ü½’û¹ú¶ú³‹ù²Šù°ˆ÷ªƒö¥€óœyë”uë”uö¥€ü»ú¶ás–db­qië”u÷¨‚ø®‡ú³‹û·û¹û·ú¶ù²Šø®‡ø¬…÷ªƒ÷¨‚õ¢~ó›xë”uë”uõ¢~û¹ÿÆ™ø®‡Ã~n‹ZOÀ|mï—vö¥€÷ªƒø¬…ø®‡ø®‡ø®‡ø¬…÷¨‚ö¥€õ¢~ôŸ{ó™wé“uæ‘tôŸ{û·þ×ü»ë”uŸig’`[È€nï—vôŸ{õ¡}õ¢~õ¢~ö¥€ö¥€õ¡}ôŸ{ó™wï—vé“ué“uóœyù²ŠýÁ•û¹óœyºxl‹ZO–dbË‚oé“uë”uî–vï—vó™wó™wï—vî–vé“uæ‘të”uóœyø¬…û¹û·ôŸ{Ån‹ZOœgfÅnÖˆqÛ‹rásé“ué“uæ‘tæ‘té“uë”uóœy÷¨‚ù²Šø®‡ó›xË‚o’`[™ffºxlË‚o؉qæ‘tæ‘tæ‘té“uï—vó›xõ¡}ö¥€õ¢~ãtºxl’`[”b_¯rjÀ|mÐ…pÞsãtæ‘të”ué“uæ‘tÞsË‚oªoi‰XL‰XL”b_µvkË‚oÓ‡qÓ‡qÓ‡qË‚o½zl jg‘_Z‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VIvF,‡VI‡VI‡VI„SC‡VI‡VI‡VI‡VIœgfÐ…p÷ªƒü½’ÿÆ™ÿÇ™ÿÆ™ü»÷ªƒó›xÀ|m]Vœgf؉qü½’ÿߨÿÛ¥ÿݧÿÕ¢ÿÔ¡ÿПÿËœÿÇ™ýÁ•ù°ˆë”uŸig‡VI‡VIø®‡ÿù·ÿÿ»ÿõµÿó³ÿï±ÿãªÿÛ¥ÿÕ¢ÿПÿÌœÿÇ™ýÁ•ú³‹æ‘t½zl‡VI‡VIü½’ÿÿÖÿÿÿÿÿÿÿÿãÿü¹ÿô´ÿë¯ÿá©ÿÙ¤ÿÔ¡ÿПÿËœþ–ü»ú³‹ë”uÅn‡VI”b_÷¨‚ÿÿñÿÿÿÿÿÿÿÿÿÿÿÿÿýºÿø·ÿï±ÿå«ÿߨÿؤÿÓ ÿÏžÿÇ™ýÁ•ü½’÷¨‚é“uÓ‡q‡VI…TEï—vÿù·ÿÿãÿÿÿÿÿÿÿÿÿÿÿãÿü¹ÿ÷¶ÿð²ÿé®ÿà¨ÿÙ¤ÿÓ ÿÏžÿÉ›þ×ý¾“ø®‡ôŸ{é“uÀ|m‡VIásÿé®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿñÿÿ»ÿû¸ÿõµÿï±ÿè­ÿá©ÿÙ¤ÿÔ¡ÿÏžÿÉ›þ×ý¾“ú³‹ö¥€ë”u؉q¤lh¤lhÿð²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈÿýºÿõµÿó³ÿí°ÿç¬ÿá©ÿÙ¤ÿÔ¡ÿÏžÿÉ›þ×ü½’ú³‹ö¦ï—vásÀ|m‡VIù°ˆÿø·ÿÿÿÿÿÿÿÿÿÿÿãÿÿÈÿýºÿù·ÿô´ÿï±ÿé®ÿä«ÿߨÿؤÿÓ ÿÌœÿÉ›þ×ü½’ú³‹ö¦ï—vãtÓ‡q¤lh‡VIÿÓ ÿø·ÿÿÿÿÿÿÿÿÿÿÿÈÿýºÿû¸ÿõµÿñ³ÿì¯ÿç¬ÿá©ÿݧÿÕ¢ÿПÿËœÿÇ™þ–û¹ù²Šö¦ó›xãtÓ‡q½zl‡VI‡VIÿߨÿ÷¶ÿÿÿÿÿÿÿÿñÿýºÿû¸ÿõµÿó³ÿí°ÿè­ÿãªÿߨÿÙ¤ÿÔ¡ÿÏžÿÉ›þ×ý¾“û·ù°ˆ÷¨‚ó›xæ‘tÓ‡q΄p‡VI‡VIÿߨÿô´ÿÿãÿÿ»ÿü¹ÿû¸ÿ÷¶ÿñ³ÿí°ÿé®ÿå«ÿà¨ÿÛ¥ÿÕ¢ÿПÿËœÿÆ™þ–ü½’ú¶ø®‡÷¨‚ó›xé“uÓ‡q؉q‡VI‡VIÿÕ¢ÿí°ÿ÷¶ÿù·ÿ÷¶ÿõµÿð²ÿí°ÿé®ÿå«ÿá©ÿݧÿؤÿÓ ÿÏžÿÉ›þ×ýÁ•ü»ú³‹ø¬…ö¦ó›xë”uÓ‡qÞs‡VI‡VIÿÌœÿå«ÿð²ÿô´ÿó³ÿï±ÿë¯ÿç¬ÿãªÿà¨ÿݧÿؤÿÔ¡ÿПÿËœÿÆ™þ–ü½’û·ø®‡÷¨‚õ¢~ï—vé“uæ‘tæ‘t™ff‡VIü»ÿÙ¤ÿä«ÿè­ÿé®ÿç¬ÿãªÿà¨ÿݧÿÙ¤ÿÕ¢ÿÓ ÿÏžÿËœÿÇ™þ×ý¾“û¹ù°ˆ÷¨‚õ¢~óœyë”uÞsó›xé“u–db‡VI÷¨‚ÿÌœÿÕ¢ÿÙ¤ÿݧÿݧÿÛ¥ÿؤÿÕ¢ÿÓ ÿÏžÿËœÿÉ›ÿÆ™þ–ý¾“û¹ù²Š÷ªƒõ¢~óœyî–vÓ‡qó™wõ¡}Û‹r‡VI‡VIó›xû¹þ×ÿËœÿÏžÿÓ ÿÕ¢ÿÓ ÿÏžÿËœÿÇ™þ×þ–ýÁ•ü½’ü»ú³‹ø¬…ö¥€ó›xï—vÓ‡qî–vù°ˆ÷¨‚Ån‡VIÖˆqù²Šü»ý¾“þ×ÿÇ™ÿÌœÿÏžÿÇ™þ×ýÁ•ü½’ü»ü»û¹ú³‹ø®‡ö¦ôŸ{ï—v؉qôŸ{û·ý¾“ôŸ{¯rj‘_Zõ¢~ù°ˆú³‹û¹ü½’ýÁ•þ–ý¾“ü½’û·ú¶ú³‹ù²Šù°ˆø¬…ö¦õ¢~ï—væ‘tóœyû¹ÿÌœû·Ó‡q‡VIÓ‡qõ¢~÷ªƒø®‡ù²Šú³‹ú¶ú¶ú³‹ù°ˆø®‡÷ªƒ÷¨‚ö¦õ¢~óœyæ‘tÞsõ¡}ü»ÿÌœÿÌœö¥€¯rj‡VIÖˆqôŸ{ö¥€÷¨‚÷¨‚÷¨‚÷ªƒ÷ªƒ÷ªƒ÷¨‚ö¥€õ¡}ôŸ{ó™wæ‘tæ‘tóœyû¹ÿÌœÿÇ™÷¨‚Ë‚o‡VI‡VI؉qó™wó™wó›xó›xõ¡}õ¡}õ¡}óœyóœyï—vé“uæ‘të”uõ¡}û·ÿÆ™ýÁ•ôŸ{Û‹r‡VI‡VIÓ‡qÞsÞsãtãtë”uë”ué“ué“uæ‘tæ‘tó›xõ¢~ú³‹ü½’ú¶õ¢~Þs‡VI‡VIºxlÓ‡qÓ‡qæ‘tó›xé“uæ‘té“uó™wóœy÷¨‚ø¬…ù²Š÷¨‚ë”u½zl‡VI‡VIªoiºxlÓ‡qÛ‹rë”uë”uï—vó›xó™wó™wó›xæ‘tÓ‡q‡VI‡VI‡VI™ff²tjË‚oÛ‹rÓ‡qÓ‡qÓ‡qºxlªoi‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VI‡VIºxlÖˆqõ¢~û¹ýÁ•ÿÇ™ÿÆ™þ×û¹ø¬…õ¡}Þsµvk’`[À|mó™wÿÇ™ÿð²ÿõµÿ÷¶ÿó³ÿë¯ÿãªÿݧÿؤÿÏžÿÇ™ü»ôŸ{΄pŸig[Sªoiû·ÿø·ÿÿÿÿÿÿÿÿÿÿÿãÿû¸ÿó³ÿé®ÿãªÿߨÿؤÿÓ ÿËœü»ö¥€Þs²tj‘_Z²tjÿËœÿÿÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈÿø·ÿí°ÿå«ÿߨÿÕ¢ÿÏžÿÇ™þ×ý¾“û·ö¦ãt½zl–dbµvkýÁ•ÿü¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýºÿõµÿí°ÿè­ÿà¨ÿؤÿПÿÌœÿÇ™þ–ü½’ù²Šõ¡}é“uÅn–db²tjû·ÿü¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»ÿù·ÿô´ÿí°ÿç¬ÿà¨ÿÙ¤ÿÓ ÿÏžÿÉ›þ×ý¾“ú¶÷¨‚ó™wÞsºxl‘_Z²tjÿÉ›ÿü¹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿñÿÿÈÿü¹ÿõµÿñ³ÿì¯ÿå«ÿà¨ÿؤÿÓ ÿÏžÿÉ›þ×ý¾“û·ø¬…óœyæ‘tÐ…p­qi‰XLé“uÿí°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿãÿÿÈÿü¹ÿø·ÿó³ÿí°ÿè­ÿãªÿߨÿÕ¢ÿПÿÌœÿÇ™þ×ü½’ú¶ø¬…ôŸ{ë”uÛ‹rÀ|m–db–dbü»ÿõµÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»ÿü¹ÿù·ÿô´ÿð²ÿë¯ÿä«ÿà¨ÿÛ¥ÿÔ¡ÿÏžÿËœÿÆ™þ–ü»ú³‹÷ªƒõ¡}ë”uÞsË‚o¯rj[S–dbþ×ÿù·ÿÿÿÿÿÿÿÿÿÿÿÈÿü¹ÿø·ÿô´ÿð²ÿì¯ÿç¬ÿá©ÿݧÿؤÿÓ ÿÌœÿÉ›þ×ý¾“û¹ù²Š÷ªƒõ¡}î–vÞsÐ…pºxl[S–dbþ×ÿÿ»ÿÿÿÿÿÿÿýºÿû¸ÿø·ÿó³ÿï±ÿë¯ÿç¬ÿãªÿݧÿؤÿÔ¡ÿÏžÿÉ›þ×ýÁ•ü½’ú¶ù°ˆ÷¨‚õ¡}ï—vásÓ‡qÃ~nœgf”b_ü»ÿü¹ÿÿÿÿÿ»ÿ÷¶ÿô´ÿñ³ÿí°ÿé®ÿå«ÿá©ÿߨÿÙ¤ÿÔ¡ÿПÿËœÿÇ™þ–ý¾“û¹ú³‹ø¬…ö¦ôŸ{î–vás؉qÈ€nœgf”b_ù²Šÿ÷¶ÿÿ»ÿø·ÿð²ÿí°ÿé®ÿç¬ÿãªÿà¨ÿݧÿÙ¤ÿÔ¡ÿПÿÌœÿÇ™þ×ýÁ•ü»ú¶ø®‡÷¨‚õ¢~ó›xé“uãtæ‘tÓ‡qœgf‘_Zõ¡}ÿç¬ÿù·ÿë¯ÿä«ÿãªÿá©ÿߨÿÛ¥ÿؤÿÔ¡ÿПÿÌœÿËœÿÇ™þ×ýÁ•ü½’ú¶ù°ˆ÷¨‚õ¡}ó›xë”uásé“uî–vÐ…pœgf[Së”uÿÏžÿè­ÿПÿÔ¡ÿؤÿؤÿÕ¢ÿÓ ÿПÿÌœÿÉ›ÿÆ™þ×þ–ý¾“ü»ú¶ù°ˆ÷¨‚õ¡}ó›xë”uÞsæ‘tôŸ{ó™wÅn]V–dbÛ‹rû·ÿÇ™þ–ÿÆ™ÿËœÿÌœÿÏžÿÌœÿÉ›ÿÆ™þ–ý¾“ü½’ü½’û¹ú¶ù°ˆ÷ªƒõ¢~ó›xë”uãtî–vö¦ø®‡ï—vºxl]V­qiôŸ{ú³‹ú¶û¹ý¾“þ–þ×þ×ýÁ•ü½’û¹ú¶ú¶ú³‹ù²Šø®‡÷¨‚õ¢~ó›xë”ué“uó›xù²Šü½’ø¬…Öˆqœgf]V؉qóœy÷ªƒø®‡ú³‹ú¶û·û·û·ú³‹ù°ˆø®‡ø¬…÷ªƒ÷¨‚ö¥€ôŸ{ó™wé“uë”uõ¡}ú¶þ×ý¾“ó›x½zl‰XL™ff΄pó™wõ¢~÷¨‚÷ªƒ÷ªƒ÷ªƒø¬…÷ªƒ÷¨‚ö¦õ¢~õ¡}ôŸ{ó™wë”uæ‘të”uõ¢~û·þ×ü½’ö¥€È€n’`[”b_Ã~né“uó™wó›xó›xóœyõ¡}õ¡}ôŸ{óœyó›xï—vë”ué“ué“uï—vö¥€û·ý¾“ú¶ôŸ{È€n”b_‘_ZºxlÖˆqÞsásãté“uî–vë”ué“ué“uæ‘tæ‘tî–vóœyö¦ù²Šú¶ø¬…ï—vÀ|m’`[[S­qiÀ|m΄pÖˆqãtë”uæ‘tæ‘té“uë”uï—vôŸ{ö¥€÷¨‚÷¨‚ó™wÓ‡q­qi]V‰XL”b_ªoiºxlË‚o؉qásæ‘té“uë”uë”uë”ué“uÞsÐ…pªoi[S‡VI‰XL]V jg½zlÐ…pÖˆqÖˆqÖˆqÐ…pÃ~nµvk’`[‹ZO‡VI‡VI‡VI–dbŸig–db]V‡VI‡VIY33_66^55[44R..L++L++L++d99ŒPPŸXX±^^¸``µ__¯]]XX‘RR€IIe99Q..€II¬\\ÕffõffõffçffÕffÃccº``¯]]XXˆMMp??S//wDDÐffÿ~~ÿÿŽŽÿ~~ÿqqìffÉeeÃcc¼aa³^^¡YYˆMMk==S//n??ñffÿ­­ÿÆÆÿ½½ÿ©©ÿ‰‰ÿmmãffÐffÇddÀbb¸``¬\\XX†LLm>>S//^55Çddÿ²²ÿææÿëëÿÏÏÿ££ÿ||ÿmmõffÙffÌffÅdd¼aa³^^ª\\—VV‚JJj<<R..Q..™WWÿ‡‡ÿÍÍÿððÿééÿÀÀÿ––ÿ~~ÿnnÿffãffÐffÇddÀbb¸``¯]]ŸXXŽQQHHe99O--‚JJÿqqÿ¶¶ÿÍÍÿ××ÿÌÌÿªªÿŽŽÿ{{ÿnnÿggçffÐffÇddÀbbº``±^^¤ZZ”TT†LLtBBX22|FFñffÿ±±ÿååÿÖÖÿ¿¿ÿ®®ÿ™™ÿ„„ÿwwÿmmÿffçffÕffÇddÀbbº``±^^¦ZZ•UUˆMM|FFe99O--ŸXXÿ‘‘ÿßßÿööÿÔÔÿ²²ÿ  ÿÿ{{ÿqqÿiiúffãffÐffÇddÀbbº``±^^¦ZZ—VV‹OOHHn??U00tBBÇddÿ¡¡ÿêêÿêêÿÁÁÿ¡¡ÿ’’ÿƒƒÿvvÿmmÿggñffÞffÌffÅdd¾bb¸``¯]]¤ZZ—VV‹OO€IIvCC_66N,,q@@Ùffÿ¡¡ÿääÿØØÿ®®ÿ••ÿ……ÿ{{ÿqqÿkkúffçffÙffÉeeÃcc¼aaµ__ª\\¡YY—VVŒPP‚JJwDDj<<N,,n??çffÿ™™ÿÌÌÿ½½ÿœœÿˆˆÿ||ÿttÿmmÿggìffÞffÐffÅdd¾bb¸``±^^¨[[ŸXX—VVŽQQƒKKyEEn??O--j<<ãffÿŒŒÿ¬¬ÿ““ÿ‡‡ÿ~~ÿttÿmmÿhhõffãffÕffÉeeÃcc¼aaµ__¯]]¦ZZXX•UUŽQQƒKKzEEsAAQ..g::Éeeÿ||ÿ’’ÿÿzzÿttÿmmÿhhúffìffÙffÌffÅddÀbbº``³^^¬\\¤ZZ›WW”TTŒPPƒKK|FFvCCR..e99¾bbÿttÿ||ÿuuÿqqÿllÿggõffìffÞffÐffÇddÃcc¼aa¸``±^^ª\\ŸXX—VV‘RR‹OOƒKK‚JJzEEX22b88¯]]ÿggÿppÿhhÿggõffìffãffÙffÐffÇddÀbb¼aa¸``³^^¬\\¤ZZ™WW’SSŒPP†LL‚JJ†LL|FFY33_66XXÉeeãffÞffãffÞffÕffÌffÉeeÅdd¾bbº``¸``³^^¬\\¦ZZ›WW”TTŽQQˆMMHH…KK‹OOwDDR..[44‘RR±^^¾bbÅddÉeeÌffÉeeÅddÀbb¼aa¸``³^^±^^¬\\¨[[ŸXX—VVQQ‰NN‚JJ‚JJ‘RRQQp??N,,X22ƒKK¤ZZ¯]]µ__¼aaÀbbÃcc¾bbº``µ__¯]]¬\\ª\\¨[[¡YY™WW’SS‹OOƒKK…KK”TT¡YYŽQQh;;N,,q@@•UU¤ZZª\\±^^µ__¸``³^^¯]]ª\\¦ZZ¤ZZ¡YYŸXX™WW”TTŽQQ†LL†LL”TT¬\\¦ZZ€IIX22d99†LL—VVXX¤ZZ¨[[ª\\¨[[¦ZZ¡YYXX›WW™WW—VV’SSŒPP†LL†LL’SSª\\º``XXp??O--n??‰NN”TT™WW›WWXXXXXX›WW—VV”TT’SSQQ‹OO…KKƒKKQQ¨[[¸``¬\\†LL\44U00sAA‰NNQQ‘RR’SS’SS”TT”TT‘RRQQ‹OO‰NN…KK…KKŽQQ¡YY³^^ª\\ŽQQk==O--X22tBB…KK†LLˆMM‰NN‹OO‹OO‰NNˆMM…KKƒKK†LLŽQQ›WWª\\¨[[QQq@@O--[44q@@zEE}GG€II…KK…KKƒKKƒKK…KK†LLŽQQ—VV¡YYXXŒPPtBBU00Y33k==tBB|FFƒKKƒKKƒKK…KK‰NNŒPP‘RR”TT’SS‚JJk==U00V11e99n??wDDHH‚JJƒKK†LL…KKƒKKHHtBBb88N,,N,,V11h;;tBByEEyEEyEEtBBm>>^55S//L++L++L++L++L++L++L++L++?##L++L++L++I))L++L++L++L++[44wDD™WW¯]]º``¼aaº``¬\\™WWŒPPn??R..[44|FF¯]]çffÞffãffÐffÌffÇddÀbb¼aa³^^ŸXX†LL\44L++L++XXÿ„„ÿ——ÿzzÿuuÿppõffÞffÐffÇddÃcc¼aa³^^¤ZZƒKKm>>L++L++¯]]ÿ¥¥ÿääÿääÿªªÿŒŒÿyyÿkkñffÙffÌffÇddÀbbµ__¬\\¤ZZ†LLq@@L++V11—VVÿ³³ÿòòÿÿÿÿôôÿÅÅÿ‘‘ÿÿppÿffçffÕffÉeeÅdd¼aa³^^¯]]—VV…KKyEEL++K**‰NNÿ„„ÿªªÿääÿùùÿÝÝÿ®®ÿÿ~~ÿqqÿiiìffÙffÉeeÅdd¾bb¸``±^^XXQQ…KKn??L++€IIÿiiÿÉÉÿääÿÃÃÿÈÈÿ±±ÿ››ÿ‡‡ÿzzÿppÿhhñffÙffÌffÅdd¾bb¸``±^^¤ZZ”TT†LL|FF_66_66ÿrrÿÁÁÿÿÿÿÿÿÿÃÃÿ··ÿŸŸÿ‘‘ÿ{{ÿuuÿmmÿggñffÙffÌffÅdd¾bb¸``¯]]¤ZZ•UU‰NN€IIn??L++ŸXXÿÿææÿýýÿèèÿ¯¯ÿ¤¤ÿ““ÿƒƒÿwwÿppÿiiúffçffÕffÉeeÃcc¾bb¸``¯]]¤ZZ•UU‰NN‚JJyEE_66L++ÉeeÿÿííÿôôÿÈÈÿ¤¤ÿ““ÿˆˆÿ{{ÿttÿllÿggñffãffÐffÇddÀbb¼aaµ__ª\\¡YY•UUŒPP‚JJyEEm>>L++L++çffÿ~~ÿ××ÿââÿµµÿ““ÿˆˆÿzzÿuuÿnnÿhhõffçffÙffÌffÅdd¾bb¸``±^^¨[[ŸXX—VVŒPPƒKKyEEvCCL++L++çffÿyyÿ®®ÿ››ÿŽŽÿ‡‡ÿ||ÿttÿnnÿiiÿffìffÞffÐffÇddÀbbº``µ__¯]]¦ZZXX—VVŒPP…KKyEE|FFL++L++Ðffÿmmÿ||ÿƒƒÿ~~ÿzzÿrrÿmmÿiiÿffñffãffÕffÉeeÅdd¾bb¸``³^^¬\\¤ZZ›WW•UUŒPP†LLyEEHHL++L++ÃccÿffÿqqÿyyÿuuÿppÿkkÿggõffìffãffÕffÌffÇddÀbbº``µ__¯]]¨[[XX—VV’SS‰NN…KKƒKKƒKKY33L++¬\\ÙffúffÿhhÿiiÿggõffìffãffÙffÐffÉeeÅddÀbb¼aa¸``±^^ª\\ŸXX—VV’SSŽQQ†LLHHŒPP…KKX22L++—VVÃccÐffÙffãffãffÞffÕffÐffÉeeÅddÀbb¾bbº``µ__±^^ª\\¡YY™WW’SSŽQQˆMMyEE‹OO‘RR}GGL++L++ŒPPª\\¸``ÀbbÅddÉeeÐffÉeeÅddÀbb¼aa¸``µ__³^^¯]]¬\\¤ZZ›WW”TTŒPP‰NNyEEˆMMŸXX—VVq@@L++zEE¡YY¬\\±^^¸``¼aaÃccÅdd¼aa¸``³^^¯]]¬\\¬\\ª\\¤ZZXX•UUQQ‰NN|FFQQ¨[[±^^QQe99S//’SSŸXX¤ZZª\\¯]]³^^µ__±^^¯]]¨[[¦ZZ¤ZZ¡YYŸXX›WW•UU’SS‰NNƒKKŽQQª\\Ãcc¨[[yEEL++yEE’SS™WWXX¡YY¤ZZ¦ZZ¦ZZ¤ZZŸXXXX™WW—VV•UU’SSŽQQƒKKHH‘RR¬\\ÃccÃcc”TTe99L++zEEQQ”TT—VV—VV—VV™WW™WW™WW—VV”TT‘RRQQ‹OOƒKKƒKKŽQQª\\Ãcc¼aa—VVtBBL++L++|FF‹OO‹OOŒPPŒPP‘RR‘RR‘RRŽQQŽQQ‰NN…KKƒKK†LL‘RR¨[[º``³^^QQ}GGL++L++yEEHHHH‚JJ‚JJ†LL†LL…KK…KKƒKKƒKKŒPP’SS¤ZZ¯]]¦ZZ’SSHHL++L++k==yEEyEEƒKKŒPP…KKƒKK…KK‹OOŽQQ—VV›WW¡YY—VV†LLm>>L++L++b88k==yEE}GG†LL†LL‰NNŒPP‹OO‹OOŒPPƒKKyEEL++L++L++Y33g::tBB}GGyEEyEEyEEk==b88L++L++L++L++L++L++L++L++L++L++L++L++L++L++L++L++L++L++k==zEE’SSª\\³^^¼aaº``¸``ª\\›WW‘RRHHh;;U00n??‹OO¼aaÿrrÿzzÿ||ÿvvÿkkõffãffÕffÅdd¼aa¬\\QQvCC\44Q..b88¨[[ÿ‚‚ÿ¸¸ÿÁÁÿ¼¼ÿªªÿŠŠÿuuÿiiõffçffÕffÉeeÀbb¬\\”TTHHg::S//g::Àbbÿ¡¡ÿ××ÿêêÿÝÝÿÊÊÿ¡¡ÿ€€ÿnnÿffçffÐffÅdd¼aa¸``±^^¨[[•UU‚JJm>>X22h;;³^^ÿÿÎÎÿððÿððÿÔÔÿ··ÿ••ÿzzÿnnÿhhìffÕffÇddÃcc¼aaµ__¯]]¡YY‘RR…KKq@@X22g::¨[[ÿÿ½½ÿÍÍÿÔÔÿÏÏÿ¶¶ÿššÿ……ÿyyÿnnÿggìffÙffÉeeÅdd¾bb¸``±^^¦ZZ—VV‹OOHHk==S//g::¾bbÿŽŽÿÑÑÿííÿÖÖÿ½½ÿ±±ÿžžÿŒŒÿ{{ÿttÿllÿffìffÕffÉeeÅdd¾bb¸``±^^¨[[›WWŽQQƒKKwDDd99N,,…KKÿnnÿ¿¿ÿïïÿððÿÌÌÿ­­ÿŸŸÿÿ€€ÿuuÿnnÿhhõffçffÐffÇddÃcc¼aa¸``¯]]¦ZZ›WWQQ†LL}GGn??X22X22¬\\ÿ{{ÿááÿââÿááÿ¶¶ÿœœÿÿƒƒÿyyÿqqÿkkúffìffÞffÌffÅddÀbbº``µ__¬\\¤ZZ™WW‘RR†LLHHtBBe99Q..X22¸``ÿƒƒÿÛÛÿÓÓÿÈÈÿ  ÿÿ‚‚ÿwwÿrrÿllÿggñffãffÕffÉeeÃcc¾bb¸``±^^ª\\¡YY™WW‘RRˆMMHHwDDk==Q..X22¸``ÿ——ÿÎÎÿÈÈÿ••ÿ‰‰ÿÿvvÿppÿkkÿggõffãffÕffÌffÅdd¾bb¸``³^^¯]]¦ZZŸXX—VV‘RR‰NN€IIyEEp??[44V11¬\\ÿŽŽÿ¼¼ÿœœÿ~~ÿyyÿttÿnnÿiiÿffñffçffÙffÌffÇddÀbb¼aaµ__±^^ª\\¤ZZ›WW•UUQQˆMM€II|FFsAA[44V11¡YYÿ||ÿššÿ€€ÿrrÿnnÿiiÿggõffìffãffÙffÌffÇddÃcc¼aa¸``³^^¬\\¦ZZXX—VV’SSŒPP…KK‚JJƒKKyEE[44S//‘RRÿggÿƒƒÿkkúffõffñffçffÞffÕffÌffÇddÃccÀbb¼aa¸``³^^¯]]¦ZZŸXX—VV‘RRŒPP†LL€II…KKˆMMwDD[44Q..†LLÅddÿhhÇddÌffÕffÕffÐffÉeeÇddÃcc¾bbº``¸``µ__±^^¬\\¦ZZŸXX—VV‘RRŒPP†LLHHƒKKQQ‹OOq@@R..X22}GG¨[[¼aaµ__º``ÀbbÃccÅddÃcc¾bbº``µ__±^^¯]]¯]]ª\\¦ZZŸXX™WW’SSŒPP†LL‚JJˆMM•UUXX‰NNk==R..d99QQ¤ZZ¦ZZª\\±^^µ__¸``¸``³^^¯]]ª\\¦ZZ¦ZZ¤ZZ¡YYXX—VV’SSŒPP†LL…KKŒPP¡YY¯]]›WWzEE[44R..|FFŽQQ™WWXX¤ZZ¦ZZ¨[[¨[[¨[[¤ZZŸXXXX›WW™WW—VV”TTQQ‹OO…KK†LL‘RR¦ZZ¸``±^^ŒPPm>>N,,Y33vCC‹OO’SS—VV™WW™WW™WW›WW™WW—VV•UU’SS‘RRQQ‹OO†LLƒKK†LL’SS¨[[¸``¯]]”TTsAAU00V11p??…KK‹OOŒPPŒPPŽQQ‘RR‘RRQQŽQQŒPP‰NN†LL…KK…KK‰NN”TT¨[[±^^¦ZZQQsAAV11S//k==zEEHH€II‚JJ…KKˆMM†LL…KK…KKƒKKƒKKˆMMŽQQ•UU¡YY¦ZZ›WW‰NNn??U00Q..d99n??vCCzEE‚JJ†LLƒKKƒKK…KK†LL‰NNQQ”TT—VV—VV‹OOyEEd99R..N,,V11b88k==tBB|FF€IIƒKK…KK†LL†LL†LL…KKHHwDDb88Q..L++N,,R..^55m>>wDDzEEzEEzEEwDDp??h;;U00O--L++L++L++X22\44X22R..L++L++?D""B!!@ 8333G##}33°33Ö33ê33å33Ñ33«33‹33a00H$$7a00Ì33ÿ;;ÿFFÿFFÿBBÿ;;ÿ44ï33Ñ33«33o33R)):Y,,ÿ::ÿddÿxxÿwwÿddÿUUÿCCÿ88ÿ44ô33Û33´33o33M&&:P((ÿDDÿššÿººÿ¯¯ÿ——ÿqqÿQQÿ@@ÿ::ÿ66ÿ33ê33Ì33«33j33O'':B!!ÿ66ÿ¡¡ÿààÿææÿÄÄÿÿccÿQQÿFFÿ==ÿ99ÿ55ô33Û33Ç3333b11L&&87¢33ÿnnÿÂÂÿííÿääÿ±±ÿ€€ÿddÿRRÿHHÿ@@ÿ::ÿ66ÿ33ê33Ñ33°3333a00H$$7b11ÿUUÿ¦¦ÿÂÂÿÎÎÿ¿¿ÿ˜˜ÿwwÿ``ÿRRÿIIÿBBÿ::ÿ66ÿ33ï33Ö33¹33”33j33U**=]..ÿDDÿŸŸÿßßÿÌÌÿ°°ÿœœÿ‚‚ÿjjÿ\\ÿQQÿHHÿBBÿ;;ÿ66ÿ33ï33Ö33¾33™33o33]..H$$7°33ÿyyÿ××ÿóóÿËËÿ¡¡ÿ‹‹ÿvvÿ``ÿUUÿMMÿGGÿ@@ÿ::ÿ66ÿ33ï33Ö33¾3333x33a00P((;U**ÿ66ÿŒŒÿååÿååÿ´´ÿŒŒÿ{{ÿiiÿ[[ÿQQÿIIÿDDÿ??ÿ99ÿ55ù33ê33Ñ33¹3333x33a00W++D""5T**ÿ==ÿŒŒÿÞÞÿÐÐÿœœÿÿmmÿ``ÿUUÿNNÿGGÿBBÿ==ÿ88ÿ44ô33å33Ç33´3333}33b11Y,,L&&5P((ÿBBÿ‚‚ÿ¿¿ÿ¯¯ÿ‡‡ÿooÿccÿYYÿQQÿIIÿCCÿ??ÿ::ÿ55ù33ê33Ö33Â33°333333d22Z--P((7L&&ÿ@@ÿssÿ™™ÿ||ÿnnÿddÿYYÿQQÿKKÿFFÿ@@ÿ;;ÿ88ÿ44ô33å33Ñ33¾33«33™3333d22\..T**7J%%ÿ88ÿccÿ{{ÿeeÿ__ÿYYÿQQÿKKÿGGÿCCÿ==ÿ99ÿ55ÿ33ï33Û33Ì33¹33¦33”33}33d22]..W++8H$$ù33ÿYYÿccÿZZÿUUÿPPÿIIÿFFÿCCÿ??ÿ::ÿ66ÿ44ô33ê33Ö33Ç33°3333‹33x33d22b11\..=E""Ñ33ÿIIÿSSÿKKÿIIÿFFÿCCÿ@@ÿ==ÿ::ÿ66ÿ33ô33ê33Û33Ì33¹33¢3333}33j33b11j33]..?D""«33ÿ88ÿ@@ÿ??ÿ@@ÿ??ÿ;;ÿ99ÿ88ÿ55ù33ï33ê33Û33Ì33¾33¦33”3333o33a00f33x33Y,,8@ ‹33Ö33ù33ÿ55ÿ88ÿ99ÿ88ÿ55ÿ33ô33ê33Û33Ö33Ì33Â33°3333†33s33b11b11‹33†33R))5=d22¹33Ñ33å33ô33ÿ33ÿ44ù33ï33å33Ñ33Ì33Ç33Â33´33¢3333x33d22f33”33´3333L&&5T**™33¹33Ç33Ö33å33ê33Û33Ñ33Ç33¾33¹33´33°33¢33”3333j33j33”33Ì33¾33a00=G##j3333«33¹33Â33Ç33Â33¾33´33«33¦33¢333333}33j33j3333Ç33ï33«33R))7P((s33”33¢33¦33«33«33«33¦3333”3333†33x33f33d22†33Â33ê33Ì33j33@ ;T**s33†33‹333333”33”33‹33†33x33s33f33f3333´33Û33Ç3333M&&7=U**f33j33o33s33x33x33s33o33f33d22j3333¦33Ç33Â33†33T**7@ T**\.._//a00f33f33d22d22f33j333333´33«33}33U**;?M&&U**]..d22d22d22f33s33}33‹33”3333b11M&&;;H$$P((Y,,a00b11d22j33f33d22a00U**E""55;L&&U**Z--Z--Z--U**O''B!!:33333333*33323333@ Y,,¢33Ñ33ï33ô33ï33Ì33¢33}33P((8@ ]..Ñ33ÿBBÿ??ÿ@@ÿ::ÿ99ÿ66ÿ33ô33Û33°33j33@ 33«33ÿjjÿÿ__ÿZZÿSSÿFFÿ??ÿ::ÿ66ÿ44ô33Û33¹33d22O''33Ñ33ÿ’’ÿÞÞÿÞÞÿ˜˜ÿssÿ^^ÿNNÿDDÿ==ÿ99ÿ66ÿ33å33Ì33¹33j33T**3;33ÿ¢¢ÿïïÿÿÿÿòòÿ¸¸ÿyyÿeeÿSSÿHHÿBBÿ;;ÿ88ÿ55ô33Û33Ñ3333f33Z--33s33ÿjjÿ˜˜ÿÞÞÿøøÿÖÖÿœœÿxxÿddÿUUÿMMÿCCÿ==ÿ88ÿ55ù33ê33Ö33«33†33f33P((3a00ÿMMÿ½½ÿÞÞÿµµÿ»»ÿŸŸÿ……ÿnnÿ__ÿSSÿKKÿDDÿ==ÿ99ÿ55ù33ê33Ö33¹33”33j33]..D""D""ÿVVÿ´´ÿÿÿÿÿÿÿµµÿ§§ÿŠŠÿyyÿ``ÿZZÿQQÿIIÿDDÿ==ÿ99ÿ55ù33ê33Ñ33¹33™33s33a00P((3°33ÿeeÿààÿýýÿããÿžžÿÿ||ÿiiÿ\\ÿSSÿMMÿGGÿBBÿ;;ÿ88ÿ44ù33ê33Ñ33¹33™33s33b11Z--D""3ÿ88ÿeeÿééÿòòÿ»»ÿÿ||ÿooÿ``ÿYYÿPPÿIIÿDDÿ@@ÿ::ÿ66ÿ33ô33å33Ç33´33™33}33b11Z--O''33ÿBBÿddÿÎÎÿÜÜÿ££ÿ||ÿooÿ__ÿZZÿRRÿKKÿFFÿBBÿ==ÿ99ÿ55ù33ê33Ö33Â33°3333}33d22Z--W++33ÿBBÿ^^ÿœœÿ……ÿwwÿnnÿccÿYYÿRRÿMMÿHHÿCCÿ??ÿ::ÿ66ÿ33ï33å33Ñ33¾33«3333}33f33Z--]..33ÿ::ÿQQÿccÿiiÿddÿ__ÿVVÿQQÿMMÿHHÿDDÿ@@ÿ;;ÿ88ÿ55ù33ê33Û33Ì33¹33¦33™33}33j33Z--a0033ÿ44ÿHHÿUUÿ^^ÿZZÿSSÿNNÿIIÿFFÿCCÿ@@ÿ;;ÿ99ÿ66ÿ33ï33å33Ñ33Â33«333333s33f33d22d22?3Ì33ÿ==ÿGGÿKKÿMMÿIIÿFFÿCCÿ@@ÿ==ÿ::ÿ88ÿ55ÿ33ô33ê33Ö33Ç33°33333333j33a00}33f33=333ÿ44ÿ::ÿ==ÿ@@ÿ@@ÿ??ÿ;;ÿ::ÿ88ÿ55ÿ33ù33ï33å33Ö33Ç33´33¢333333o33Z--x33‹33_//33}33Ç33ê33ÿ33ÿ55ÿ88ÿ::ÿ88ÿ55ÿ33ô33ê33å33Û33Ñ33Ì33¹33¦33”33}33s33Z--o33°3333T**3\..´33Ì33Ö33ê33ô33ÿ44ÿ55ô33ê33Û33Ñ33Ì33Ì33Ç33¹33«33™33†33s33]..†33Â33Ö33†33H$$:33°33¹33Ç33Ñ33Û33å33Ö33Ñ33Â33¾33¹33´33°33¦33™3333s33d2233Ç33ÿ44Â33Z--3Z--33¢33«33´33¹33¾33¾33¹33°33«33¢3333™333333d22a00‹33Ì33ÿ44ÿ44”33H$$3\..†33”33333333¢33¢33¢3333”33‹33†33x33d22d2233Ç33ÿ44ô3333U**33]..x33x33}33}33‹33‹33‹333333s33f33d22j33‹33Â33ï33Û33†33_//33Z--a00a00b11b11j33j33f33f33d22d22}3333¹33Ñ33¾3333a0033M&&Z--Z--d22}33f33d22f33x333333¦33´3333j33O''33E""M&&Z--_//j33j33s33}33x33x33}33d22Z--333?J%%U**_//Z--Z--Z--M&&E""333333333333333333M&&\..33Ç33Û33ô33ï33ê33Ç33¦33‹33a00L&&;P((x33ô33ÿVVÿ__ÿccÿ[[ÿNNÿFFÿ@@ÿ;;ÿ55ô33Ì33†33W++@ 7E""Â33ÿhhÿ¨¨ÿ´´ÿ­­ÿ˜˜ÿrrÿZZÿMMÿFFÿBBÿ;;ÿ88ÿ33Ì33”33a00J%%:J%%ÿ33ÿŒŒÿÎÎÿååÿÖÖÿ¾¾ÿŒŒÿffÿRRÿHHÿBBÿ::ÿ55ô33ê33Ö33Â33™33b11O''=L&&Û33ÿxxÿÃÃÿííÿííÿËËÿ§§ÿÿ__ÿRRÿKKÿCCÿ;;ÿ66ÿ44ô33å33Ñ33´33‹33f33T**=J%%Â33ÿvvÿ¯¯ÿÂÂÿËËÿÄÄÿ¦¦ÿ„„ÿmmÿ^^ÿRRÿIIÿCCÿ==ÿ88ÿ55ù33ê33Ö33¾3333x33a00M&&:J%%ù33ÿwwÿÅÅÿééÿÌÌÿ¯¯ÿŸŸÿ‰‰ÿssÿ``ÿYYÿPPÿHHÿCCÿ;;ÿ88ÿ55ù33ê33Ö33Â33¦3333d22Y,,G##5f33ÿRRÿ°°ÿëëÿííÿ¿¿ÿššÿŠŠÿxxÿffÿZZÿRRÿKKÿFFÿBBÿ::ÿ66ÿ44ô33ê33Ñ33¾33¦33†33j33_//P((==Ì33ÿ``ÿÚÚÿÜÜÿÚÚÿ¦¦ÿ‡‡ÿxxÿiiÿ^^ÿUUÿNNÿGGÿCCÿ??ÿ99ÿ55ÿ33ï33å33Ì33¹33¢33‹33j33a00U**H$$7=ê33ÿiiÿÒÒÿÉÉÿ»»ÿ‹‹ÿvvÿhhÿ\\ÿVVÿPPÿIIÿDDÿ@@ÿ;;ÿ88ÿ44ù33ê33Ö33Ç33´33¢33‹33o33a00Y,,M&&7=ê33ÿÿÃÃÿ»»ÿÿqqÿeeÿ[[ÿSSÿNNÿIIÿFFÿ@@ÿ;;ÿ99ÿ55ù33ê33Û33Ñ33¾33°3333‹33s33a00Z--R))@ ;Ì33ÿwwÿ­­ÿ‡‡ÿddÿ^^ÿYYÿRRÿMMÿHHÿDDÿBBÿ==ÿ99ÿ66ÿ33ô33å33Ö33Ç33¹33¦33™33†33o33a00]..T**@ ;´33ÿccÿ„„ÿffÿVVÿRRÿMMÿIIÿFFÿCCÿ@@ÿ==ÿ99ÿ66ÿ44ô33ê33Û33Ì33¾33«333333}33f33b11d22Z--@ :‹33ÿIIÿiiÿNNÿGGÿFFÿDDÿBBÿ??ÿ;;ÿ99ÿ66ÿ44ÿ33ô33ê33Û33Ñ33¾33°3333‹33}33j33a00f33o33Y,,@ 7j33ÿ55ÿKKÿ66ÿ99ÿ;;ÿ;;ÿ::ÿ88ÿ66ÿ44ù33ï33ê33å33Ö33Ì33¾33°3333‹33}33j33a00d22†33x33T**8=_//Â33ô33å33ï33ÿ33ÿ44ÿ55ÿ44ù33ï33å33Ö33Ñ33Ñ33Ç33¾33°33¢3333}33j33b11o33™33«33s33M&&8G##†33¹33¾33Ç33Ö33å33ê33ê33Û33Ñ33Ç33¾33¾33¹33´33«333333}33j33f33}33´33Ñ33¦33\..@ 8]..33¢33«33¹33¾33Â33Â33Â33¹33°33«33¦33¢3333”33†33x33f33j33‹33¾33ê33Ö33}33O''5?W++x333333¢33¢33¢33¦33¢3333™3333‹33†33x33j33d22j3333Â33ê33Ñ33”33T**;;R))f33x33}33}3333‹33‹33†3333}33s33j33f33f33s33”33Â33Ö33¾33†33T**;:M&&\..a00a00b11f33o33j33f33f33d22d22o3333™33´33¾33¦33s33P((;7G##P((W++\..b11j33d22d22f33j33s33†33”333333x33Z--G##85;E""M&&U**]..a00d22f33j33j33j33f33a00Y,,E""7358B!!O''Y,,\..\..\..Y,,R))L&&;7333=@ =833 \ No newline at end of file
diff --git a/bubbob/images/buildcolors.py b/bubbob/images/buildcolors.py
new file mode 100644
index 0000000..0f7ea01
--- /dev/null
+++ b/bubbob/images/buildcolors.py
@@ -0,0 +1,324 @@
+#! /usr/bin/env python
+import sys, os
+
+if __name__ == '__main__':
+ ThisDir = sys.argv[0]
+else:
+ ThisDir = __file__
+ThisDir = os.path.dirname(os.path.abspath(ThisDir))
+
+### rotate colors
+import colorsys
+COLORS = [#(0, 0.0, 1.0, 1, 1), # vert
+ #(1, 0.0, 1.0, 1, 1), # bleu
+ (1, -0.7, 1.0, 1, 1), # rose
+ (0, -0.2, 1.0, 1, 1), # brun
+ (1, 0.72,1.0,-1, 1), # jaune
+ (0, -0.35,0.85,1, 1), # rouge
+ (0, 0, 0.0, 1, 1), # gris
+ (0, -0.85, 0.9, 1, 1), # cyan (was mauve)
+ #(0, 0.2, 1.0, 1, 1), # turquoise
+ (0, 0.925, 0.95,-1, 1), # bleu fonce
+ #(0, 0.45, 0.5, -0.5, 0.75), # hum
+ (1, 'specialpixelmap'), # vert fonce
+ ]
+MAX = 2 + len (COLORS)
+
+## By ION:
+#
+# Here's the new palette-based method.
+#
+# It's an array [N][320] of 24bit unsigned integers
+# (where N is the total number of color sets including the original one.)
+#
+# That is, you access it like
+#
+# Palettes[(PALETTESIZE * palettenumber)+paletteindex]
+#
+# Activate it by passing a palette file as a cmdline argument.
+#
+# The color mapping could be further sped up
+# by making Palettes an array of bytes rather than ints,
+# at the cost of increased complexity (Palettes [ (PALETTESIZE * 3 * palettenumber) + paletteindex + component])
+#
+
+Palettes = None # currently there is no 'internal' palette since this is experimental.
+
+PALETTESIZE = 960
+
+PaletteIndex = None
+# generate the string:paletteindex lookup table
+def initpalettelut ():
+ global PaletteIndex
+ global COLORS, COLORMAPS
+ # palette 0 is the base palette (green dragon, blue tiger)
+ #
+ # Palette 0 must contain NO duplicate colors.
+ PaletteIndex = {}
+ for i in range (PALETTESIZE):
+ v = Palettes[i]
+ #if v & 0xff == 0 and (v >> 8) & 0xff == 0x87 and (v >> 16) & 0xff == 0:
+ # print 'FOUND'
+ s = "".join ([chr ((v >> shift) & 0xff) for shift in (0,8,16)])
+ PaletteIndex[s] = i
+ # invalidate COLORS, but match the length to the number of alt palettes.
+ COLORS = range ((len (Palettes) / PALETTESIZE) - 1)
+ #print 'COLORS',COLORS
+ COLORMAPS = [{} for n in COLORS]
+ #print 'COLORMAPS',COLORMAPS
+
+def loadpalettesets (filename):
+ global Palettes
+ #import array
+ #Palettes = array.array ('I')
+ Palettes = []
+ assert ((os.path.getsize (filename) % (PALETTESIZE * 3)) == 0)
+ #print os.path.getsize (filename)
+ f = open (filename, 'rb')
+ for i in range (os.path.getsize(filename) / (PALETTESIZE * 3)):
+ for j in range (PALETTESIZE):
+ tmp = f.read (3)
+ val = ord (tmp[0]) | (ord (tmp[1]) << 8) | (ord (tmp[2]) << 16)
+ Palettes.append (val)
+ #debuggest
+ #print len(Palettes)
+ #print len(Palettes) % PALETTESIZE
+ assert (len (Palettes) % PALETTESIZE) == 0
+ #print "Palettes len:",len (Palettes)
+
+def inputfiles ():
+ InputFiles = {
+ os.path.join (ThisDir, os.pardir, 'ext1', 'image1-%d.ppm'): 1,
+ os.path.join (ThisDir, os.pardir, 'ext3', 'image1-%d.ppm'): 1,
+ os.path.join (ThisDir, os.pardir, 'ext4', 'image1-%d.ppm'): 1,
+ os.path.join (ThisDir, os.pardir, 'ext6', 'image1-%d.ppm'): 1,
+ os.path.join (ThisDir, os.pardir, 'ext7', 'image1-%d.ppm'): 1,
+ }
+ d = {}
+ execfile (os.path.join(ThisDir, os.pardir, 'sprmap.py'), d)
+ sprmap = d['sprmap']
+ for key, (filename, rect) in sprmap.items ():
+ if filename.find('%d') >= 0:
+ InputFiles[os.path.join (ThisDir, filename)] = 1
+ return InputFiles.keys ()
+
+# ____________________________________________________________
+
+def pixelmap (r, g, b):
+ r /= 255.0
+ g /= 255.0
+ b /= 255.0
+ h, s, v = colorsys.rgb_to_hsv(r, g, b)
+ h = (h*sign + delta) % 1.0
+ s *= sat
+ v *= lumen
+ r, g, b = colorsys.hsv_to_rgb(h, s, v)
+ return r*255.1, g*255.1, b*255.1
+
+def specialpixelmap (r, g, b):
+ return r * 0.1, g * 0.7, r * 0.5
+
+usingpalette = 0
+
+def palettepixelmap (r, g, b):
+# print max(r,g,b)
+ packed = chr(r) + chr(g) + chr(b)
+ try:
+ index = PaletteIndex[packed]
+ #print 'index %r' % index
+# print 'USING', usingpalette
+ v = thispalette[index] #Palettes[(PALETTESIZE * (usingpalette + 1)) + index]
+# print 'hit! %r' % packed
+# print '-> %r' % (chr(v & 0xff) + chr ((v >> 8) & 0xff) + chr((v >> 16) & 0xff))
+# print '%r : %r' % (Palettes[index], Palettes[PALETTESIZE + index])
+ return v & 0xff, (v >> 8) & 0xff, (v >> 16) & 0xff
+ except KeyError:
+ return r,g,b
+
+def ppmbreak (f):
+ sig = f.readline ().strip ()
+ assert sig == "P6"
+ while 1:
+ line = f.readline ().strip ()
+ if not line.startswith('#'):
+ break
+ wh = line.split ()
+ w, h = map (int, wh)
+ sig = f.readline ().strip()
+ assert sig == "255"
+ data = f.read ()
+ return w, h, data
+
+COLORMAPS = [{} for n in COLORS]
+del n
+
+def paletterotate (imglist, chr=chr, int=int, ord=ord):
+ global thispalette
+ gw, gh, green = imglist[0]
+# assert bw == gw and bh == gh
+ n = 0
+ (_, _, fromimage) = imglist[0]
+ for reserved in COLORS:
+ # is not being entered, the fool.
+# lut = {}
+# for
+ thispalette = Palettes[(PALETTESIZE * (reserved + 1)):(PALETTESIZE * (reserved + 2))]
+ # wot is this? _ means unused?
+# (_, _, otherimage) = imglist[1-n]
+ image = []
+ colormap = COLORMAPS[reserved]
+ append = image.append
+
+ for i in range (0, len(fromimage), 3):
+ rgb1 = fromimage[i:i+3]
+# rgb2 = otherimage[i:i+3]
+# if rgb1 == rgb2:
+# append (rgb1)
+ if rgb1 in colormap:
+ append (colormap[rgb1])
+ else:
+# print 'HI!'
+ r, g, b = ord(rgb1[0]), ord(rgb1[1]), ord(rgb1[2])
+# print '%d,%d,%d ->' % (r,g,b)
+ r, g, b = palettepixelmap (r, g, b)
+# print '%d,%d,%d.' % (r,g,b)
+ newrgb = chr (int (r))+chr (int (g))+chr (int (b))
+ append (newrgb)
+ colormap[rgb1] = newrgb
+ imglist.append((gw, gh, ''.join (image)))
+
+
+def rotate (imglist, chr=chr, int=int, ord=ord):
+ global delta, sat, sign, lumen
+ (bw, bh, blue), (gw, gh, green) = imglist
+ assert bw == gw and bh == gh
+ for reserved in range (len (COLORS)):
+ if len (COLORS[reserved]) == 2:
+ n, fn = COLORS[reserved]
+ fn = globals ()[fn]
+ else:
+ n, delta, sat, sign, lumen = COLORS[reserved]
+ fn = pixelmap
+ (_, _, fromimage) = imglist[n]
+ (_, _, otherimage) = imglist[1-n]
+ image = []
+ colormap = COLORMAPS[reserved]
+ append = image.append
+ for i in range (0, len(fromimage), 3):
+ rgb1 = fromimage[i:i+3]
+ rgb2 = otherimage[i:i+3]
+ if rgb1 == rgb2:
+ append (rgb1)
+ elif rgb1 in colormap:
+ append (colormap[rgb1])
+ else:
+ r, g, b = fn(ord(rgb1[0]), ord(rgb1[1]), ord(rgb1[2]))
+ newrgb = chr(int(r))+chr(int(g))+chr(int(b))
+ append(newrgb)
+ colormap[rgb1] = newrgb
+ imglist.append((bw, bh, ''.join(image)))
+
+def writeout (imglist, namepattern, paletted = False):
+ start = 2
+ if paletted:
+ start = 1
+ for i in range (start, len (imglist)):
+ w, h, data = imglist[i]
+ fn = namepattern % i
+ f = open (fn, 'wb')
+ print >> f, 'P6'
+ print >> f, w, h
+ print >> f, 255
+ f.write (data)
+ f.close ()
+
+
+def convert (name):
+ print >> sys.stderr, 'generating colors for %s...' % name
+ imglist = [ppmbreak (open (name % 0, 'rb'))]
+ paletted = False
+ if Palettes:
+ paletterotate (imglist)
+ paletted = True
+ else:
+ imglist.append(ppmbreak (open (name % 1, 'rb')))
+ rotate (imglist)
+ writeout (imglist, name, paletted)
+
+def updatecheck ():
+ myself = os.path.join (ThisDir, 'buildcolors.py')
+
+ def older (list1, list2):
+ def mtime (name):
+ try:
+ st = os.stat (name)
+ except OSError:
+ return None
+ else:
+ return st.st_mtime
+ list2 = [mtime (name) for name in list2]
+ if None in list2:
+ return 0
+ else:
+ list1 = [mtime(name) for name in list1]
+ list1 = [t for t in list1 if t is not None]
+ return list1 and list2 and max (list1) < min (list2)
+
+ rebuild = {}
+ for filename in inputfiles ():
+ distfiles = [myself, filename % 0]
+ genfiles = [filename % n for n in range (1, MAX)]
+ rebuild[filename] = not older (distfiles, genfiles)
+ return rebuild
+
+
+#try to load palettes first
+tmp = os.path.join (ThisDir, os.pardir, 'images', 'palettes.dat')
+if os.path.exists (tmp):
+ #print 'loading palettes'
+ loadpalettesets (tmp)
+ initpalettelut ()
+else:
+ # from now on we should always use the palette approach;
+ # comment out the following line to restore the old color-rotation code.
+ raise IOError("cannot find the palette file %r" % (tmp,))
+
+
+if __name__ == '__auto__': # when execfile'd from images.py
+ rebuild = updatecheck ().items ()
+ rebuild.sort ()
+ for fn, r in rebuild:
+ if r:
+ convert(fn)
+
+#try:
+# import psyco
+# psyco.bind(rotate)
+#except:
+# pass
+
+if __name__ == '__main__':
+ if sys.argv[1:2] == ['-f']:
+ files = inputfiles ()
+ elif sys.argv[1:2] == ['-c']:
+ for filename in inputfiles ():
+ for n in range (1, MAX):
+ try:
+ os.unlink (filename % n)
+ except OSError:
+ pass
+ else:
+ print 'rm', filename % n
+ sys.exit()
+ else:
+ rebuild = updatecheck ()
+ if 0 in rebuild.values ():
+ print >> sys.stderr, ('%d images up-to-date. '
+ 'Use -f to force a rebuild or -c to clean.' %
+ rebuild.values ().count(0))
+ files = [fn for fn, r in rebuild.items () if r]
+
+ files.sort ()
+ for filename in files:
+ convert (filename)
+
diff --git a/bubbob/images/butterfly.ppm b/bubbob/images/butterfly.ppm
new file mode 100644
index 0000000..a7af2c5
--- /dev/null
+++ b/bubbob/images/butterfly.ppm
Binary files differ
diff --git a/bubbob/images/cream_pie_big.ppm b/bubbob/images/cream_pie_big.ppm
new file mode 100644
index 0000000..827a2e0
--- /dev/null
+++ b/bubbob/images/cream_pie_big.ppm
Binary files differ
diff --git a/bubbob/images/diamond_big_blue.ppm b/bubbob/images/diamond_big_blue.ppm
new file mode 100644
index 0000000..78f7a8c
--- /dev/null
+++ b/bubbob/images/diamond_big_blue.ppm
Binary files differ
diff --git a/bubbob/images/diamond_big_purple.ppm b/bubbob/images/diamond_big_purple.ppm
new file mode 100644
index 0000000..2b0938e
--- /dev/null
+++ b/bubbob/images/diamond_big_purple.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: GIMP PNM Filter Version 1.1
+90 90
+255
+ˆˆˆˆ˜!˜ $  $ §&§§&§¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯§&§§&§ $  $ ˜!˜ˆˆˆˆÀ/ÀÜ3Ìÿ9Ìÿ9ÌÿGÌÿGÌÿQÌÿQÌÿ[Ìÿ[Ìÿ[Ìÿ[ÌÿfÌÿ_Ìÿ_Ìÿ[ÌÿQÌÿGÌÿ9Ìÿ3Ìõ3Ìõ3Ìñ3Ìñ3Ìê3Ìç3Ìà3ÌÜ3ÌÕ3ÌÐ3ÌÎ3ÌÉ2ÉÄ0ÄÀ/À»-»¸,¸¶+¶´+´²*²¯)¯¯)¯¯)¯¯)¯¯)¯• •• •• •À/ÀÜ3Ìÿ9ÌÿXÌÿgÌÿmÏÿoÐÿoÐÿoÐÿpÑÿpÑÿrÒÿtÓÿtÓÿtÓÿtÓÿtÓÿrÒÿpÑÿkÎÿbÌÿQÌÿGÌÿ9Ìÿ6Ìÿ3Ìü3Ìú3Ìõ3Ìñ3Ìñ3Ìç3Ìà3ÌÜ3ÌÐ3ÌÉ2ÉÄ0ÄÀ/À»-»¶+¶´+´²*²¯)¯¯)¯¯)¯¯)¯¡$¡¡$¡• •• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿoÐÿoÐÿpÑÿpÑÿrÒÿrÒÿrÒÿrÒÿrÒÿrÒÿrÒÿpÑÿoÐÿkÎÿfÌÿXÌÿNÌÿ@Ìú3Ìú3Ìú3Ìõ3Ìó3Ìì3Ìç3Ìà3ÌÞ3ÌÜ3ÌÕ3ÌÎ3ÌÉ2ÉÃ0ø,¸¶+¶´+´²*²¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¡$¡¡$¡• •ŠŠ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿtÓÿzÖÿzÖÿzÖÿzÖÿzÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿNÌü3Ìñ3Ìñ3Ìñ3Ìê3ÌÜ3ÌÎ3ÌÎ3ÌÉ2ÉÄ0ÄÀ/ÀÀ/À¿.¿»-»»-»¶+¶¶+¶¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯§&§¡$¡• •ŠŠ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿØÿØÿØÿØÿØÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿJÌü3ÌÜ3ÌÜ3ÌÜ3ÌÎ3ÌÉ2ÉÄ0ÄÄ0ÄÀ/À¿.¿¿.¿»-»»-»¶+¶¶+¶¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯¯)¯ª'ª¡$¡• •ŠŠ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„ÛÿŠÞÿŒßÿ‘áÿ‘áÿŠÞÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿJÌü3Ìç3ÌÞ3ÌÞ3ÌÞ3ÌÜ3Ì×3ÌÕ3ÌÐ3ÌÎ3ÌÉ2ÉÄ0ÄÄ0ÄÄ0ÄÃ0ÃÀ/ÀÀ/À¿.¿¼-¼»-»¸,¸¶+¶¶+¶¶+¶´+´²*²¯)¯¯)¯¯)¯ª'ª¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ¥ëÿ²òÿ¥ëÿ˜åÿ‘áÿŠÞÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿ@Ìø3Ìñ3Ìñ3Ìñ3Ìñ3Ìì3Ìê3Ìç3Ìå3Ìà3ÌÞ3ÌÜ3ÌÜ3Ì×3ÌÐ3ÌÎ3ÌÎ3ÌÉ2ÉÇ1ÇÃ0ÿ.¿¼-¼¼-¼¼-¼¸,¸¶+¶´+´¯)¯ª'ª¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿ¿øÿÄûÿ¿øÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿJÌÿ3Ìø3Ìõ3Ìõ3Ìñ3Ìì3Ìê3Ìç3Ìå3Ìà3ÌÞ3ÌÜ3ÌÜ3Ì×3ÌÐ3ÌÎ3ÌÎ3ÌÌ3ÌÉ2ÉÇ1ÇÃ0ÿ.¿¼-¼¼-¼¸,¸¶+¶´+´¯)¯ª'ª¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿÌÿÿÕÿÿÕÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿJÌÿ@Ìÿ3Ìü3Ìø3Ìõ3Ìõ3Ìó3Ìñ3Ìê3Ìê3Ìç3Ìç3Ìç3Ìå3Ìà3ÌÞ3ÌÞ3Ì×3ÌÐ3ÌÇ1ÇÃ0ÿ.¿¼-¼¼-¼¸,¸¶+¶´+´¯)¯ª'ª¤%¤• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿàÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿGÌÿ9Ìÿ6Ìÿ3Ìÿ3Ìÿ3Ìü3Ìú3Ìõ3Ìõ3Ìõ3Ìõ3Ìõ3Ìó3Ìó3Ìó3Ìñ3Ìê3ÌÞ3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¤%¤• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿçÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿGÌÿ=Ìÿ9Ìÿ6Ìÿ3Ìÿ3Ìÿ3Ìü3Ìú3Ìõ3Ìõ3Ìõ3Ìõ3Ìó3Ìó3Ìñ3Ìñ3Ìñ3ÌÞ3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¤%¤• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿGÌÿ=Ìÿ=Ìÿ9Ìÿ9Ìÿ6Ìÿ3Ìü3Ìü3Ìú3Ìø3Ìø3Ìø3Ìõ3Ìó3Ìó3Ìñ3Ìñ3Ìå3Ì×3ÌÌ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¤%¤• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿGÌÿ@Ìÿ@Ìÿ=Ìÿ9Ìÿ9Ìÿ3Ìÿ3Ìü3Ìü3Ìú3Ìø3Ìø3Ìõ3Ìó3Ìó3Ìñ3Ìñ3Ìå3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿGÌÿ@Ìÿ=Ìÿ=Ìÿ9Ìÿ9Ìÿ3Ìÿ3Ìü3Ìü3Ìú3Ìø3Ìõ3Ìó3Ìó3Ìñ3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯«(«¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿGÌÿ@Ìÿ@Ìÿ=Ìÿ9Ìÿ9Ìÿ6Ìÿ3Ìÿ3Ìü3Ìú3Ìú3Ìõ3Ìõ3Ìó3Ìñ3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯«(«¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿGÌÿ@Ìÿ@Ìÿ=Ìÿ=Ìÿ9Ìÿ9Ìÿ6Ìÿ3Ìÿ3Ìü3Ìú3Ìø3Ìõ3Ìó3Ìñ3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯«(«¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿGÌÿ@Ìÿ@Ìÿ=Ìÿ=Ìÿ9Ìÿ9Ìÿ9Ìÿ6Ìÿ3Ìü3Ìú3Ìø3Ìõ3Ìó3Ìñ3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯«(«¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿNÌÿGÌÿ@Ìÿ@Ìÿ=Ìÿ=Ìÿ9Ìÿ9Ìÿ9Ìÿ3Ìÿ3Ìú3Ìø3Ìõ3Ìó3Ìó3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯«(«¡$¡• •ŽŽ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿNÌÿGÌÿDÌÿ@Ìÿ=Ìÿ=Ìÿ=Ìÿ=Ìÿ9Ìÿ6Ìÿ3Ìü3Ìú3Ìõ3Ìõ3Ìó3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¡$¡• •ŠŠ• •À/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿNÌÿGÌÿDÌÿ@Ìÿ@Ìÿ=Ìÿ9Ìÿ9Ìÿ9Ìÿ6Ìÿ3Ìÿ3Ìú3Ìõ3Ìõ3Ìó3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¡$¡• •ŠŠÀ/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿpÑÿmÏÿiÍÿfÌÿ_ÌÿXÌÿQÌÿNÌÿNÌÿJÌÿGÌÿGÌÿDÌÿDÌÿ@Ìÿ=Ìÿ9Ìÿ9Ìÿ6Ìÿ3Ìü3Ìú3Ìõ3Ìó3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯ª'ª¡$¡• •ŠŠÀ/ÀÜ3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿrÒÿoÐÿmÏÿiÍÿgÌÿfÌÿbÌÿ_Ìÿ[ÌÿXÌÿXÌÿTÌÿQÌÿJÌÿDÌÿ@Ìÿ=Ìÿ9Ìÿ9Ìÿ3Ìü3Ìú3Ìú3Ìó3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯§&§¡$¡• •Ü3Ìÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿrÒÿoÐÿmÏÿiÍÿgÌÿfÌÿbÌÿ_Ìÿ_Ìÿ[ÌÿXÌÿXÌÿTÌÿQÌÿNÌÿJÌÿDÌÿ=Ìÿ9Ìÿ3Ìü3Ìü3Ìú3Ìó3Ìñ3Ìç3Ì×3ÌÎ3ÌÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯¤%¤œ#œŒŒÿ9ÌÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿòÿÿúÿÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿòÿÿçÿÿàÿÿØÿÿÌÿÿ·ôÿ¥ëÿ˜åÿŒßÿ„ÛÿØÿ{ÖÿxÕÿtÓÿrÒÿoÐÿmÏÿkÎÿiÍÿgÌÿbÌÿbÌÿ_Ìÿ_Ìÿ[ÌÿXÌÿXÌÿTÌÿQÌÿQÌÿJÌÿ@Ìÿ9Ìÿ3Ìü3Ìü3Ìú3Ìõ3Ìñ3Ìç3Ì×3ÌÉ2ÉÇ1ÇÃ0ÿ.¿¼-¼¸,¸¶+¶´+´¯)¯¡$¡• •ÿXÌÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿçÿÿçÿÿçÿÿòÿÿòÿÿòÿÿòÿÿòÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿòÿÿòÿÿòÿÿòÿÿòÿÿòÿÿçÿÿçÿÿçÿÿçÿÿàÿÿÒÿÿÊþÿ·ôÿ°ñÿ­ïÿ£êÿžèÿ˜åÿ“âÿŽàÿŠÞÿ†ÜÿƒÚÿØÿ}×ÿ{ÖÿzÖÿxÕÿxÕÿvÔÿvÔÿtÓÿrÒÿpÑÿpÑÿoÐÿkÎÿkÎÿgÌÿfÌÿ[ÌÿXÌÿQÌÿNÌÿJÌÿDÌÿ9Ìÿ6Ìø3Ìó3Ìñ3Ìç3Ìç3ÌÞ3ÌÐ3ÌÉ2ÉÀ/Àª'ªÿmÏÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿçÿÿçÿÿçÿÿòÿÿòÿÿòÿÿòÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿúÿÿòÿÿòÿÿçÿÿçÿÿàÿÿØÿÿØÿÿÒÿÿÐÿÿÍÿÿÄûÿ¿øÿ¿øÿ·ôÿ¶ôÿ²òÿ¥ëÿ¢êÿšæÿ‘áÿàÿŠÞÿ†Üÿ†Üÿ„ÛÿÙÿÙÿØÿ{Öÿ{ÖÿzÖÿvÔÿtÓÿtÓÿpÑÿoÐÿkÎÿgÌÿgÌÿfÌÿ_Ìÿ_Ìÿ_ÌÿTÌÿNÌÿNÌÿ@Ìÿ9Ìÿ9Ìø3Ìñ3ÌÉ2ÉÿtÓÿzÖÿ„Ûÿ˜åÿ²òÿÌÿÿØÿÿàÿÿàÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿçÿÿàÿÿØÿÿØÿÿÒÿÿÒÿÿÍÿÿÌÿÿÄûÿ¿øÿ½÷ÿ·ôÿ²òÿ°ñÿ¥ëÿšæÿ˜åÿ‘áÿŠÞÿŠÞÿ†Üÿ„ÛÿƒÚÿÙÿØÿØÿ{ÖÿzÖÿvÔÿvÔÿtÓÿtÓÿpÑÿkÎÿkÎÿgÌÿfÌÿfÌÿ_Ìÿ_Ìÿ[ÌÿTÌÿNÌÿJÌÿ@Ìÿ9Ìÿ3Ìñ3ÌÜ3ÌÉ2ÉÃ0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿ}×ÿ„ÛÿŠÞÿŽàÿàÿ•ãÿ˜åÿ¥ëÿ¥ëÿ²òÿ´óÿ½÷ÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÁùÿÁùÿ¹õÿ²òÿ«îÿ¥ëÿ¢êÿ‘áÿŒßÿƒÚÿÙÿ{ÖÿzÖÿxÕÿvÔÿvÔÿtÓÿtÓÿrÒÿrÒÿrÒÿpÑÿoÐÿoÐÿkÎÿiÍÿgÌÿ_Ìÿ_ÌÿXÌÿTÌÿQÌÿNÌÿQÌÿ_ÌÿbÌÿfÌÿfÌÿgÌÿiÍÿkÎÿiÍÿgÌÿgÌÿbÌÿXÌÿGÌÿ9ÌÃ0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿ}×ÿ„ÛÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ«îÿ²òÿ¶ôÿÂúÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿ½÷ÿ·ôÿ­ïÿ¢êÿ—äÿ‘áÿˆÝÿƒÚÿ}×ÿxÕÿrÒÿoÐÿkÎÿiÍÿgÌÿfÌÿXÌÿXÌÿTÌÿQÌÿNÌÿNÌÿJÌÿGÌÿ=Ìÿ6Ìü3Ìñ3Ìó3Ìü3Ìÿ3Ìÿ6Ìÿ=ÌÿQÌÿTÌÿ[ÌÿfÌÿiÍÿkÎÿoÐÿrÒÿvÔÿvÔÿvÔÿvÔÿpÑÿpÑÿiÍÃ0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿ}×ÿ„ÛÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ«îÿ²òÿ·ôÿÂúÿÌÿÿÌÿÿÌÿÿÌÿÿÌÿÿ½÷ÿ­ïÿ¢êÿ•ãÿŽàÿ„ÛÿØÿzÖÿvÔÿoÐÿiÍÿ_ÌÿXÌÿQÌÿJÌÿ@Ìÿ9Ìü3Ìø3Ìõ3Ìñ3Ìñ3Ìì3Ìê3Ìç3Ìç3Ìå3Ìà3Ìå3Ìì3Ìó3Ìø3Ìÿ3ÌÿJÌÿTÌÿ[ÌÿiÍÿkÎÿpÑÿvÔÿvÔÿxÕÿxÕÿxÕÿxÕÿpÑÿtÓÿiÍÃ0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿØÿ„ÛÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ«îÿ²òÿ·ôÿÂúÿÌÿÿÌÿÿÌÿÿÌÿÿ¹õÿ¢êÿ•ãÿŒßÿ„ÛÿØÿzÖÿtÓÿoÐÿgÌÿXÌÿJÌÿ@Ìÿ6Ìÿ3Ìø3Ìó3Ìê3Ìå3Ìà3ÌÞ3ÌÜ3Ì×3ÌÕ3ÌÕ3ÌÐ3ÌÐ3ÌÐ3ÌÜ3ÌÞ3Ìê3Ìì3Ìú3ÌÿGÌÿXÌÿgÌÿkÎÿpÑÿvÔÿxÕÿ{Öÿ}×ÿ}×ÿ{ÖÿxÕÿtÓÿpÑÿbÌÃ0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿ²òÿ·ôÿÂúÿÌÿÿÌÿÿÌÿÿ¶ôÿ˜åÿŽàÿ„ÛÿØÿzÖÿtÓÿmÏÿgÌÿTÌÿDÌÿ3Ìü3Ìú3Ìõ3Ìñ3Ìê3Ìå3ÌÞ3ÌÜ3Ì×3ÌÕ3ÌÐ3ÌÐ3ÌÎ3ÌÌ3ÌÌ3ÌÌ3ÌÎ3ÌÜ3ÌÞ3Ìê3Ìõ3ÌÿJÌÿfÌÿkÎÿpÑÿvÔÿ{Öÿ}×ÿØÿØÿ}×ÿ{ÖÿtÓÿpÑÿpÑÿ9ÌÃ0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿ²òÿ·ôÿÂúÿÌÿÿÌÿÿ²òÿ“âÿ„ÛÿØÿzÖÿtÓÿmÏÿgÌÿQÌÿ@Ìú3Ìõ3Ìñ3Ìì3Ìç3Ìà3ÌÞ3ÌÜ3ÌÕ3ÌÐ3ÌÐ3ÌÎ3ÌÌ3ÌÌ3ÌÉ2ÉÉ2ÉÉ2ÉÉ2ÉÌ3ÌÕ3ÌÞ3Ìê3Ìú3ÌÿQÌÿiÍÿpÑÿvÔÿ{Öÿ}×ÿØÿ„ÛÿØÿ}×ÿtÓÿpÑÿpÑÿ9Ì»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿ6Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿ²òÿ·ôÿÂúÿÌÿÿ­ïÿŒßÿØÿzÖÿtÓÿmÏÿgÌÿQÌÿ=Ìõ3Ìê3Ìà3ÌÜ3Ì×3ÌÐ3ÌÌ3ÌÉ2ÉÉ2ÉÇ1ÇÇ1ÇÄ0ÄÄ0ÄÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÇ1ÇÎ3ÌÞ3Ìó3Ìÿ9Ìÿ_ÌÿkÎÿtÓÿ{Öÿ}×ÿŠÞÿŠÞÿ„ÛÿØÿzÖÿpÑÿpÑÿ9Ì»-»Ã0ÃÃ0ÃÿNÌÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿ²òÿ·ôÿÂúÿ§ìÿ„ÛÿzÖÿtÓÿmÏÿgÌÿQÌÿ=Ìó3Ìç3Ì×3ÌÕ3ÌÌ3ÌÇ1ÇÄ0ÄÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÄ0ÄÎ3Ìå3Ìü3ÌÿJÌÿgÌÿpÑÿvÔÿ}×ÿŠÞÿŠÞÿŠÞÿØÿzÖÿtÓÿpÑÿ9Ì»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿmÏÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿ²òÿ·ôÿšæÿØÿvÔÿoÐÿgÌÿQÌÿ=Ìó3Ìå3ÌÕ3ÌÉ2ÉÇ1ÇÃ0ÃÀ/ÀÀ/ÀÀ/ÀÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÉ2ÉÜ3Ìõ3Ìÿ@Ìÿ_ÌÿmÏÿvÔÿ{ÖÿŠÞÿŠÞÿŠÞÿ„Ûÿ{ÖÿtÓÿpÑÿXÌ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿgÌÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿ²òÿ‘áÿ{ÖÿrÒÿgÌÿQÌÿ=Ìó3Ìå3ÌÐ3ÌÃ0ü-¼¸,¸¸,¸¸,¸¸,¸»-»¿.¿Ã0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÄ0ÄÐ3Ìì3Ìÿ6Ìÿ[ÌÿkÎÿrÒÿzÖÿ„ÛÿŠÞÿŠÞÿ„Ûÿ{ÖÿtÓÿpÑÿXÌ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿNÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ­ïÿŽàÿxÕÿmÏÿXÌÿ=Ìó3Ìç3ÌÕ3ÌÃ0ø,¸´+´²*²¯)¯¯)¯²*²´+´¸,¸¿.¿Ã0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÄ0ÄÌ3Ìç3Ìú3ÌÿQÌÿiÍÿpÑÿxÕÿØÿ„Ûÿ„Ûÿ„Ûÿ{ÖÿtÓÿpÑÿXÌ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿ¥ëÿ„ÛÿtÓÿgÌÿJÌø3Ìç3ÌÕ3ÌÄ0Ä»-»´+´®)®«(«§&§§&§ª'ª«(«¯)¯¸,¸À/ÀÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÄ0ÄÉ2Éå3Ìõ3ÌÿGÌÿfÌÿoÐÿvÔÿØÿØÿ„Ûÿ„Ûÿ{ÖÿtÓÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿœçÿƒÚÿpÑÿ[Ìÿ3Ìç3Ì×3ÌÄ0ÄÀ/À¶+¶®)®ª'ª¦&¦¡$¡¡$¡¡$¡¦&¦«(«¸,¸À/ÀÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÉ2Éà3Ìñ3ÌÿDÌÿbÌÿoÐÿrÒÿ{ÖÿØÿØÿØÿ{ÖÿtÓÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿ•ãÿØÿmÏÿNÌì3Ì×3ÌÇ1ÇÀ/À¸,¸¯)¯§&§¤%¤ $ ## $ ¤%¤ª'ª¶+¶¿.¿Ã0ÃÃ0ÃÃ0ÃÃ0ÃÃ0ÃÉ2Éà3Ìñ3Ìÿ6Ìÿ_ÌÿoÐÿrÒÿ{Öÿ{ÖÿØÿØÿ{ÖÿtÓÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿàÿØÿiÍÿ=Ìå3ÌÉ2ÉÀ/À¸,¸¯)¯¦&¦¡$¡™"™˜!˜“ “• •#¡$¡ª'ª¶+¶¿.¿Ã0ÃÃ0ÃÃ0ÃÃ0ÃÉ2Éà3Ìñ3Ìÿ3Ìÿ_ÌÿmÏÿpÑÿzÖÿ{Öÿ{ÖÿØÿzÖÿtÓÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿŽàÿzÖÿ[Ìú3ÌÕ3ÌÀ/À¸,¸¯)¯¦&¦ $ ™"™• •’’ŽŽ˜!˜ $ §&§¶+¶¿.¿Ã0ÃÃ0ÃÃ0ÃÉ2ÉÜ3Ìó3Ìÿ6ÌÿXÌÿiÍÿpÑÿvÔÿzÖÿzÖÿzÖÿvÔÿtÓÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿŠÞÿzÖÿTÌì3ÌÉ2ɸ,¸¯)¯¦&¦ $ ™"™“ “ŒŒŒŒŽŽ“ “#¦&¦´+´¿.¿Ã0ÃÃ0ÃÃ0ÃÕ3Ìñ3Ìÿ6ÌÿXÌÿgÌÿmÏÿvÔÿvÔÿvÔÿvÔÿvÔÿtÓÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿ_ÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿ†ÜÿxÕÿGÌÞ3Ì¿.¿²*²¦&¦ $ ™"™’’ŽŽŠŠ‡‡‡‡ŽŽ“ “œ#œ¦&¦´+´¿.¿Ã0ÃÃ0ÃÉ2ÉÞ3Ìú3ÌÿQÌÿfÌÿmÏÿpÑÿvÔÿvÔÿvÔÿvÔÿrÒÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿbÌÿiÍÿoÐÿtÓÿxÕÿ}×ÿØÿvÔÿ=ÌÎ3̶+¶§&§ $ ™"™ŒŒ‡‡‡‡ŽŽ“ “™"™¦&¦²*²¿.¿Ã0ÃÃ0ÃÕ3Ìñ3Ìÿ9Ìÿ[ÌÿkÎÿpÑÿtÓÿvÔÿvÔÿvÔÿrÒÿpÑÿiÍ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿDÌÿQÌÿbÌÿiÍÿpÑÿtÓÿxÕÿ}×ÿpÑñ3ÌÃ0î)® $ ™"™ŒŒ‡‡‡‡ŒŒ“ “™"™¦&¦¯)¯¿.¿Ã0ÃÃ0Ãà3Ìõ3Ìÿ@ÌÿbÌÿoÐÿpÑÿtÓÿvÔÿtÓÿpÑÿiÍÿXÌ»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ9ÌÿGÌÿQÌÿbÌÿiÍÿpÑÿtÓÿxÕÿoÐê3̼-¼¦&¦™"™ŒŒ‡‡‡‡ŒŒ“ “™"™¦&¦¯)¯¿.¿Ã0ÃÉ2Éà3Ìú3ÌÿDÌÿfÌÿoÐÿrÒÿtÓÿrÒÿmÏÿbÌÿ9Ì»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=ÌÿGÌÿQÌÿbÌÿiÍÿpÑÿtÓÿkÎå3Ì´+´#’’ŒŒ‡‡‡‡ŒŒ“ “™"™¦&¦¯)¯¿.¿Ã0ÃÉ2Éê3Ìÿ3ÌÿDÌÿbÌÿoÐÿpÑÿoÐÿmÏÿbÌÿ9Ì»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=ÌÿGÌÿQÌÿbÌÿiÍÿpÑÿbÌ×3Ì®)®˜!˜ŽŽŒŒ‡‡ŒŒ“ “™"™¦&¦¯)¯¿.¿Ã0ÃÐ3Ìê3Ìÿ9ÌÿGÌÿbÌÿmÏÿmÏÿiÍÿbÌÿ9Ì»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=ÌÿGÌÿTÌÿbÌÿiÍÿQÌÕ3̪'ª“ “ŒŒŠŠ‡‡ŒŒ“ “™"™¦&¦²*²¿.¿É2É×3Ìõ3Ìÿ9ÌÿJÌÿ_ÌÿiÍÿ_ÌÿQÌÿ9Ì»-»Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=ÌÿGÌÿTÌÿbÌÿDÌÐ3̦&¦’’ŒŒˆˆ‡‡ŒŒ“ “™"™¦&¦²*²¿.¿Î3ÌÞ3Ìõ3Ìÿ=ÌÿTÌÿ[ÌÿXÌÿ9Ìì3̧&§Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=ÌÿGÌÿTÌÿ6ÌÌ3̤%¤’’ŒŒ‡‡‡‡ŒŒ“ “™"™¦&¦²*²Ã0ÃÐ3Ìç3Ìø3Ìÿ=ÌÿQÌÿQÌÿ3ÌÕ3̧&§Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=ÌÿGÌø3ÌÉ2ɤ%¤ŒŒŒŒ‡‡‡‡ŒŒ“ “™"™ª'ª¶+¶É2É×3Ìç3Ìø3Ìÿ9Ìÿ=Ìü3ÌÀ/À§&§Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìÿ=Ìñ3ÌÇ1Çœ#œŒŒŒŒ‡‡‡‡ŽŽ“ “™"™ª'ª¶+¶É2ÉÞ3Ìì3Ìú3Ìÿ6Ìø3ÌÀ/À§&§Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìÿ3Ìê3ÌÃ0Ù"™ŒŒŒŒ‡‡‡‡ŽŽ“ “™"™«(«»-»Ð3Ìç3Ìì3Ìó3Ìì3ÌÀ/À§&§Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìú3Ìå3ÌÀ/À™"™ŒŒŒŒ‡‡‡‡ŒŒ“ “™"™«(«»-»Þ3Ìì3Ìì3ÌÞ3̯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3Ìõ3Ìà3Ì¿.¿™"™ŒŒŒŒ‡‡‡‡ŒŒ“ “ $ ¯)¯¿.¿ì3Ìì3Ìì3̯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìó3ÌÞ3̼-¼˜!˜ŒŒŒŒˆˆ‡‡ˆˆŒŒ™"™¦&¦¯)¯Ä0Äì3Ìì3̯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3ÌÜ3Ì»-»˜!˜ŒŒŒŒŠŠˆˆ‡‡‡‡ŠŠ“ “ $ ¯)¯¸,¸Î3Ìì3̯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3Ìñ3Ì×3̸,¸˜!˜ŒŒŒŒŠŠŠŠˆˆ‡‡‡‡‡‡ŠŠŒŒ™"™¤%¤¸,¸Î3ÌÞ3̯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3Ìñ3ÌÕ3̶+¶• •ŒŒŒŒŠŠŠŠŠŠˆˆ‡‡ŠŠŒŒ“ “¤%¤«(«¸,¸Î3̯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3Ìñ3ÌÕ3Ì´+´• •ŒŒŒŒŒŒŠŠŠŠŠŠŒŒŒŒ“ “¤%¤«(«¯)¯Ä0į)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3Ìñ3ÌÐ3̲*²• •ŒŒŒŒŒŒŒŒŒŒŒŒŒŒ#¯)¯¯)¯¸,¸¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3Ìñ3ÌÐ3̲*²• •ŒŒŒŒŒŒŒŒŒŒŒŒ#¯)¯¯)¯¸,¸¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌñ3ÌÐ3̲*²• •ŒŒŒŒŒŒŒŒŒŒ#¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍÿNÌÐ3̯)¯• •ŒŒŒŒŒŒŒŒ• •¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿiÍê3̯)¯• •ŒŒŒŒŒŒ• •¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿiÍÿ6̯)¯• •ŒŒŒŒ• •¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÿX̯)¯• •¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿiÍÕ3Ì• •¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿiÍÿ6̯)¯¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÿX̯)¯¯)¯¯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿiÍÿiÍÕ3̯)¯¯)¯¯)¯˜!˜Ã0ÃÃ0Ãì3ÌÿNÌÿiͯ)¯¯)¯˜!˜Ã0ÃÃ0ÃÃ0ÃÃ0ï)¯˜!˜Ã0ÃÃ0ÃÃ0Ø!˜Ã0à \ No newline at end of file
diff --git a/bubbob/images/diamond_big_red.ppm b/bubbob/images/diamond_big_red.ppm
new file mode 100644
index 0000000..f612f53
--- /dev/null
+++ b/bubbob/images/diamond_big_red.ppm
Binary files differ
diff --git a/bubbob/images/diamond_big_yellow.ppm b/bubbob/images/diamond_big_yellow.ppm
new file mode 100644
index 0000000..7ace2ba
--- /dev/null
+++ b/bubbob/images/diamond_big_yellow.ppm
Binary files differ
diff --git a/bubbob/images/digits_0.ppm b/bubbob/images/digits_0.ppm
new file mode 100644
index 0000000..54cc4e0
--- /dev/null
+++ b/bubbob/images/digits_0.ppm
Binary files differ
diff --git a/bubbob/images/door.ppm b/bubbob/images/door.ppm
new file mode 100644
index 0000000..dbc8b69
--- /dev/null
+++ b/bubbob/images/door.ppm
Binary files differ
diff --git a/bubbob/images/dragon_0.ppm b/bubbob/images/dragon_0.ppm
new file mode 100644
index 0000000..14272ec
--- /dev/null
+++ b/bubbob/images/dragon_0.ppm
Binary files differ
diff --git a/bubbob/images/dragon_bubble_0.ppm b/bubbob/images/dragon_bubble_0.ppm
new file mode 100644
index 0000000..7457ffe
--- /dev/null
+++ b/bubbob/images/dragon_bubble_0.ppm
Binary files differ
diff --git a/bubbob/images/extend.ppm b/bubbob/images/extend.ppm
new file mode 100644
index 0000000..bed1ae8
--- /dev/null
+++ b/bubbob/images/extend.ppm
Binary files differ
diff --git a/bubbob/images/extra1.ppm b/bubbob/images/extra1.ppm
new file mode 100644
index 0000000..bf57813
--- /dev/null
+++ b/bubbob/images/extra1.ppm
Binary files differ
diff --git a/bubbob/images/extra2.ppm b/bubbob/images/extra2.ppm
new file mode 100644
index 0000000..0aa664d
--- /dev/null
+++ b/bubbob/images/extra2.ppm
Binary files differ
diff --git a/bubbob/images/extra3.ppm b/bubbob/images/extra3.ppm
new file mode 100644
index 0000000..42d3030
--- /dev/null
+++ b/bubbob/images/extra3.ppm
Binary files differ
diff --git a/bubbob/images/extra4.ppm b/bubbob/images/extra4.ppm
new file mode 100644
index 0000000..c2b1066
--- /dev/null
+++ b/bubbob/images/extra4.ppm
Binary files differ
diff --git a/bubbob/images/extra5.ppm b/bubbob/images/extra5.ppm
new file mode 100644
index 0000000..d162d9e
--- /dev/null
+++ b/bubbob/images/extra5.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.1
+32 576
+255
+```fffeeeEXo?QhRRRRRRRRRkkk‘‘‘ŸŸŸ¨¨¨¤¨¬Yˆ¸V…µ–šž•••†††lllWWW†††¦¦¦¸¸¸¿¿¿¿¿¿§ºa‘ÀZ‹½™¯§§§žžžwwwZZZ~~~···ÐÐÐÚÚÚÙÙÙÎÏÏw Åe”Â^Ž¾cŽº¬­­ªªª   rrrZZZuuu¾¾¾çççðððíííååå¿ÉÏm›Æd“Á`À]¾˜¤¯¬¬¬¦¦¦žžžŒŒŒtttZZZeee³³³éééùùùúúúòòòãããš´Çm›Æg–Ãa‘À_¿|šµ®®®ªªª¥¥¥šššˆˆˆqqqYYYWWW›››ÕÕÕñññûûûùùùíííÛÜÜ~§ÊnœÆi—Äd“Á`Àc¼®¯¯¬¬¬§§§ŸŸŸ’’’………lllVVVˆˆˆÈÈÈêêêñññôôôñññæææÃÌÑv¢ÊnœÆi—Äe”Â`À]¾›¦°­­­¨¨¨¡¡¡———ŒŒŒ{{{___‚‚‚¾¾¾èèèøøøôôôíííçççÞÞÞ¢¹Ês Ém›Æi—Äe”Âa‘À]¾€š³­­­¨¨¨¢¢¢™™™‚‚‚lllVVVŸŸŸÛÛÛ÷÷÷ýýýôôôéééâââÙÙÙ§ÈpÇk™Åg–Ãd“Á`À]¾eº­­­¨¨¨¢¢¢ššš………uuu\\\{{{³³³âââúúúúúúîîîâââÛÛÛÁÉÍs Ém›Æi—Äf•Âc’Á_¿\Œ½YŠ¼›¤«§§§¡¡¡ššš†††|||fffTTT7^a‘À†°ÒšÁÜ—¾Ú‹´Ô¬Ðz¦Ìv¢ÊpÇlšÅg–Ãe”Âa‘À^Ž¾Z‹½X‰¼V‡¹R‚´O}°Ly«Fr¤@kœ;c•3YŠ!Br;_Še”ƒ­Ð“»Ø¸×…¯Ñ{§Íw£ËqžÈm›Æi—Äe”Âc’Á`À\Œ½YŠ¼WˆºT„¶Q³O}°Ly«Gs¥Al;d–5\)Gq`kxf”Á~©ÎŠ³Ô¬Ðz¦Ìw£ËqžÈm›Æj˜Äg–Ãd“Áa‘À^Ž¾Z‹½X‰¼V‡¹T„¶Q€²N|¯KxªGs¥Al<e—:a’IS_nnn©³{¥Ê€«Ïw£Ët¡ÉqžÈm›Æj˜Äg–Ãe”Âa‘À_¿\Œ½Z‹½WˆºV†¸SƒµP±Mz¬Jw©Fr¤Al@i˜gt‚YYYlll¯¯¯½ÂƆªÈrŸÈpÇlšÅi—Äg–Ãe”Âc’Á`À]¾Z‹½X‰¼WˆºT„¶R‚´O}°Ly«Iu§Eq£Kpšzƒ‹___iii§§§ÁÁÁÅÆÆ’ª¾i—Ãg–Ãe”Âd“Áa‘À`À]¾Z‹½X‰¼WˆºV†¸SƒµP±Mz¬Iv¨Fr£b|˜…‡ˆŒŒŒ‚‚‚```fffžžž´´´»»»ººº§±¹o—¾a‘À_¿^Ž¾\Œ½YŠ¼WˆºWˆºV†¸SƒµQ€²Mz¬Jw©Px£y…‘………ŠŠŠ~~~YYYbbb•••¨¨¨¯¯¯²²²´´´›©´^Ž¾\Œ½Z‹½X‰¼WˆºV†¸T„¶SƒµQ³O}°Ly«Ht¦t„”ˆˆˆˆˆˆ•••”””wwwTTT___‰‰‰¡¡¡§§§«««®®®t•¶Z‹½YŠ¼WˆºV‡¹T„¶SƒµR‚´Q³O}°Mz¬Iv¨Eq£St˜ŠŠŠ———   ’’’oooTTTxxx™™™¡¡¡¥¥¥œ¢¨W‡¹WˆºV†¸T„¶R‚´Q€²P±O}°O}°Mz¬Jw©Gs¥Cn CnŸ‹‘˜¦¦¦¢¢¢†††___kkkŒŒŒšššžžžsŽ©Q³R‚´Q³Q€²O}°N|¯N{­N{­Ly«Iv¨Fr¤Cn Cn Iv¨w‘«­­­žžžwwwVVVuuuŽŽŽ•––S}«Mz¬N|¯N|¯N|¯Mz¬Q|«‚Žš™Lv¥Eq£BmžAlHt¦Q³_‹¸¥¦¦ŒŒŒccc\\\zzzy†”Ht¦Iu§Iv¨Iv¨Jw©T|§ˆ–”””‚‰‘LqœBmžGs¥O}°V†¸R‚´|‰–rrrVVV___Zo‡BmžCn Do¡Dp¢XzžŠŽŽŽŠŠŠ‰‰‰‡ŠZ| Mz¬R‚´Q³Ht¦VkƒVVV@Vr7^<e—?i™bx‘‰ŠŠŠŠŠ‰‰‰‰‰‰ŠŠŠŒŒŒ’’’™ššw¦P}¯Fr¤9a“<Qm2PvJd‚qw~‚‚‚‰‰‰‰‰‰‰‰‰ŠŠŠŽŽŽ‘‘‘•••———–––}ƒŠJd‚0Ms]]]llluuu~~~………ˆˆˆ‰‰‰ŒŒŒŠŠŠ‰‰‰………{{{iiiTTTTTT]]]ooo{{{{{{ttteeeZZZRRRRRRRRRRRRRRRRRRRRRRRRDDDRRRRRRRRR8G\?L]RRRRRRRRRbbb~~~›››§§§Š®X‰¼Xˆº›§›››‘‘‘uuuYYYbbb‚‚‚§§§¼¼¼ººº·¹ºg“¾_¿]¾k’·®®®ªªªŸŸŸŒŒŒcccRRRRRRžžžÔÔÔÞÞÞÎÎÎËËË«¹Ãg–Ãc’Á`À]¾›¦°®®®ªªª¡¡¡‰‰‰tttRRRRRR§§§äääøøøøøøæææØØØŽ¬ÅlšÅf•Âa‘À_¿€›µ°°°«««¦¦¦¡¡¡ŒŒŒxxxRRR]]]šššéééüüüÿÿÿüüüïïïÖØØy¤ÊpÇi—Äe”Âa‘Àd‘½°±±®®®ªªª§§§šššŠŠŠRRRQQQŽŽŽÔÔÔæææøøøþþþöööçççÀÊÑw£ËpÇk™Åe”Âa‘À^Ž¾œ§±¯¯¯¬¬¬¨¨¨žžž”””ŠŠŠuuuRRR†††ÃÃÃðððøøøîîîðððèèèßßߢ¹Êt¡ÉpÇj˜Äf•Âa‘À_¿‚œ´¯¯¯¬¬¬¨¨¨¡¡¡———ŒŒŒ‚‚‚ffffffÉÉÉîîîÿÿÿÿÿÿîîîêêêáááÛÛÛ„©ÈrŸÈm›Æi—Äf•Âa‘À_¿fº¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽ†††uuuRRRŸŸŸÑÑÑùùùÿÿÿùùùèèèãããÜÜÜËÎÐs ÈpÇk™Åg–Ãe”Âa‘À^Ž¾Z‹½Ÿ¨¯¬¬¬§§§¡¡¡™™™ŽŽŽˆˆˆfff:J`v˜¸ƒ©É¡ÄÝ ÄÞ“»Ø‡±Ó¬Ð{§Ív¢ÊqžÈlšÅi—Äf•Âd“Á`À]¾Z‹½X‰¼V‡¹R‚´O}°KxªFr£CmœCh“Db‡:Ja5Ide”Âw£Ë—¾ÚšÁÜŒµÕ¬Ð{§Ít¡ÉrŸÈnœÆj˜Äg–Ãe”Âa‘À_¿\Œ½YŠ¼WˆºT„¶Q³O}°Ly«Fr¤Al;d–9a“4IeRRR¥ºs É‹´Ô„®Ñ~©Îz¦Ìw£ËqžÈnœÆk™Åi—Äe”Âc’Á`À]¾Z‹½WˆºV‡¹T„¶Q€²N|¯Ly«Fr¤Bmž;d–YsQRRRRR···¥µÁz¥Êy¥Ìw£Ët¡ÉpÇm›Æk™Åi—Äf•Âd“Áa‘À^Ž¾\Œ½YŠ¼WˆºV†¸SƒµP±Mz¬KxªFr¤EoŸcuˆ………RRRRRR±±±ÀÀÀºÀÅ„¨ÆrŸÈpÇlšÅi—Äg–Ãe”Âd“Áa‘À_¿]¾Z‹½WˆºV‡¹T„¶Q³N|¯Ly«Iv¨PuŸ|…‰‰‰‰‰‰```RRR¦¦¦¹¹¹¿¿¿ÀÁÁ•¬¿i—Ãg–Ãe”Âd“Áa‘À`À^Ž¾\Œ½Z‹½X‰¼WˆºT„¶R‚´O}°Ly«Iv¨h‚Š‹Œ………‘‘‘ŠŠŠ___RRRššš±±±···¹¹¹»»»§±¸o—¾a‘À`À^Ž¾\Œ½Z‹½YŠ¼WˆºV‡¹T„¶R‚´O}°Mz¬R{§~Š–•••ƒƒƒRRRRRR‘‘‘¥¥¥¬¬¬°°°²²²´´´œªµ^Ž¾\Œ½Z‹½X‰¼WˆºV‡¹V†¸T„¶SƒµP±Mz¬Jw©v‡˜ŽŽŽŸŸŸšššxxxRRR   ¦¦¦¨¨¨¬¬¬®®®t•¶\Œ½X‰¼WˆºV†¸T„¶SƒµSƒµR‚´P±N|¯KxªHt¦Vxœ‚‚‚”””¤¤¤¨¨¨”””lllZZZ–––ŸŸŸ¡¡¡¥¥¥›¡§V†¸V‡¹T„¶T„¶Q³Q€²P±O}°O}°Mz¬KxªIv¨Dp¢Al†”¥¥¥±±±¤¤¤RRR–––›››žžžr¨P±Q€²Q€²P±O}°N|¯N{­Mz«KxªIv¨Gs¥Al?išIu§x’«±±±±±±———lllRRR”””•––R|ªLy«Ly«Mz¬Mz¬Mz¬Q|«‚Žš€Œ˜Lv¥Eq£AlAlGs¥R‚´cŽº¬­­ššš{{{RRRRRR‚‚‚z‡”Eq£Fr¤Fr¤Iu§Iu§Rz¤…Œ“’’’ŽŽŽ~…Kp›Cn Iu§Q³WˆºV†¸~‹˜ƒƒƒRRRRRR]rŠ?iš?iš@kœ@kœVw›†‰ŒŠŠŠŠŠŠ‰‰‰‰‰‰‹Ž‘]£P±T„¶Q€²Iv¨awRRR5Id3Z‹;d–<d•e|”‘‘ŠŠŠ‰‰‰ŠŠŠ’’’ššš›œœw¦Mz«Cn 4[Œ3Hd)DkC[zhnuƒƒƒŒŒŒŒŒŒŽŽŽ‘‘‘‘‘‘‰‰‰u{‚2Hf)DkRRR```nnn{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR%Cm Aq Aq%CmRRRRRRrrr–––¥¥¥¤©X‰¼WˆºWˆºR‚´—•••………ooo\\\uuu®®®ÉÉÉÎÎÎÐÐЧ¸ÅlšÅg–Ãd“Áa‘ÀŽ¡²®®®¦¦¦”””|||cccWWWiii¤¤¤ÓÓÓëëëîîîìììææ昵ÌrŸÈk™Åg–Ãe”Â{œº´´´°°°¦¦¦———………nnnZZZnnn°°°âââôôôúúúöööñññßàà{¦ËnœÆi—Äe”Â`À^¼ª¬­¬¬¬¨¨¨¤¤¤™™™ˆˆˆttt___oooªªªÚÚÚòòòûûûûûûôôôêêêÅÎÓt¡ÉnœÆj˜Äe”Âa‘À]¾š¦±®®®«««§§§   •••ŠŠŠxxx___nnn¤¤¤ÙÙÙíííñññôôôòòòêêêßßߤºÊs ÉnœÆi—Äe”Âa‘À^Ž¾ƒœ³¯¯¯¬¬¬¨¨¨¢¢¢ššš………rrrZZZnnn¯¯¯ÙÙÙóóóúúúôôôíííèèèàààØØØ„©ÈqžÈlšÅi—Äe”Âa‘À^Ž¾j’¹¯¯¯¬¬¬¨¨¨¤¤¤œœœ’’’‰‰‰~~~kkkTTTŠŠŠÇÇÇíííûûûûûûñññçççáááÚÚÚÈÌÏrŸÈnœÆj˜Äg–Ãe”Â`À]¾Z‹½¤©­¬¬¬§§§¢¢¢œœœ”””ŒŒŒƒƒƒuuu______‚–ªŽ­Ç§ÇÜŸÃÜšÁܶՅ¯ÑªÏy¥Ìs ÉpÇlšÅg–Ãe”Âc’Á_¿\Œ½Z‹½WˆºV‡¹SƒµP±Mz¬Iu§FpŸFl—IiObxWWW___}—°y¥Ì˜¿Û•½Ú“»Ø†°Ò~©Îy¥Ìs ÉpÇlšÅi—Äf•Âd“Áa‘À^Ž¾Z‹½YŠ¼WˆºT„¶R‚´O}°Mz¬Iu§Do¡?iš;c•MdWWW___¬¬¬­ÂÑ”¼Ù“»Ø¬Ð}¨Íw£Ës ÉpÇlšÅi—Äg–Ãd“Áa‘À_¿\Œ½YŠ¼WˆºV†¸T„¶Q€²O}°Ly«Iu§Dp¢@j›WpŒvwwbbb]]]¦¦¦ÙÙÙË×݈±Òw£Ës ÉqžÈnœÆk™Åi—Äf•Âe”Âa‘À_¿]¾Z‹½X‰¼V‡¹T„¶R‚´P±Mz¬KxªHt¦Ep h{‚‚‚zzzbbb]]]   ÐÐÐßßßÄÉÍ¥ÅnœÆk™Åi—Äg–Ãe”Âd“Áa‘À_¿]¾Z‹½X‰¼WˆºV†¸SƒµQ€²N|¯Ly«Iv¨Rw¡|…ˆˆˆ‰‰‰bbbZZZ•••ÁÁÁÓÓÓÄÄĽ¾¾’©½f•Âe”Âc’Áa‘À_¿]¾Z‹½Z‹½X‰¼WˆºV†¸T„¶Q€²O}°Ly«Iu§gœŠ‹Œ†††ŠŠŠ~~~bbbWWWŒŒŒ²²²Â³³³¶¶¶¸¸¸¤®¶l•½^Ž¾]¾Z‹½YŠ¼WˆºWˆºV‡¹T„¶SƒµQ€²O}°Ly«Rz¥}‰•ŒŒŒ………‰‰‰”””xxxYYY___ƒƒƒ¤¤¤®®®«««­­­°°°±±±–¥±Z‹½YŠ¼WˆºV‡¹T„¶T„¶T„¶R‚´Q€²O}°Mz¬Iv¨v‡˜ŒŒŒˆˆˆ™™™žžžŽŽŽrrrYYYkkk”””¡¡¡¢¢¢¥¥¥¨¨¨«««p’³WˆºV†¸T„¶R‚´Q€²Q€²P±O}°N|¯Ly«Iv¨Fr¤UvšŠŠŠ‘‘‘   §§§œœœbbbYYY‚‚‚’’’›››žžž¡¡¡–œ¢Q²Q³Q³P±O}°N|¯Mz¬Mz¬Ly«Jw©Ht¦Eq£BmžCnŸˆ–¢¢¢¬¬¬¨¨¨‘‘‘tttTTT```|||–––šššn‰¥Mz¬Mz¬Mz¬Mz¬Ly«KxªKw¨Jv§Ht¦Eq£Cn AlCn Iv¨uª¬¬¬§§§———zzz\\\]]]wwwŠŠŠ‘Jt¢Fr¤Gs¥Iu§Iu§Ht¦Ku¤}‰–z†“GpžBmžBmžDp¢Jw©Q³Y‡¶ž ¡”””zzz]]]ZZZrrrewŠ?iš@j›@kœBmžDo¡Lr~…ŠŠŠ‰‰‰}„ŒMtžGs¥KxªO}°Q€²Mz¬qƒ–uuu\\\WWW>[~5\9a“<e—@kœVwšƒ†‰‰‰‰ŠŠŠŒŒŒŽŽŽŽ‘”^€¤Ly«Ly«Eq£;d–>[~YYY,Fk&Iz.S„9]‰_q…‚‚‚†††‰‰‰ŠŠŠŒŒŒŒŒŒŒŒŒŠŠŠhzŽ@f”.S„#Ev+EjDO\VXYeeettt~~~~~~wwwooo\\\UVVBMZRRRRRR___ccc___YYYRRRRRR```fffeeebbbYYYRRRRRRRRRkkk‘‘‘ŸŸŸ¨¨¨¬¬¬­“r«qžžž•••†††lllWWW†††¦¦¦¸¸¸¿¿¿¿¿¿º¶®½„H¸|E­¨ §§§žžžwwwZZZ~~~···ÐÐÐÚÚÚÙÙÙÐÐЫ„¿…G»D²–q®®®ªªª   rrrZZZuuu¾¾¾çççðððíííåååÖÖÔÄ’S¿„F¼E¹ƒK¯®­¬¬¬¦¦¦žžžŒŒŒtttZZZeee³³³éééùùùúúúòòòãããÊÁ«ÅJÁˆH½‚E¼€D²£®®®ªªª¥¥¥šššˆˆˆqqqYYYWWW›››ÕÕÕñññûûûùùùíííÝÝÝÈ­}ÆŽK‰H¿„F¼Eµ’g°°°¬¬¬§§§ŸŸŸ’’’………lllVVVˆˆˆÈÈÈêêêñññôôôñññæææØØÖÊšUÆŽK‰H¿…G¼E¹I¯®­­­­¨¨¨¡¡¡———ŒŒŒ{{{___‚‚‚¾¾¾èèèøøøôôôíííçççÞÞÞÎƲɓMÅJ‰H¿…G½‚Eº~C°£­­­¨¨¨¢¢¢™™™‚‚‚lllVVVŸŸŸÛÛÛ÷÷÷ýýýôôôéééâââÙÙÙȯƒÆKËIÁˆH¿„F¼Eº~C²’k­­­¨¨¨¢¢¢ššš………uuu\\\{{{³³³âââúúúúúúîîîâââÛÛÛÓÓÓÈšYÅJ‰HÀ†G¾ƒF¼€D¹|C¶L¬¬¬§§§¡¡¡ššš†††|||fffTTTxxx¹¹¹âââøøøõõõçççÜÜÜÕÕÕÊĵÆKÄŒJÁˆH¿…G½‚E»D¹{B·yA« ‘¥¥¥   ššš‘‘‘ˆˆˆ~~~qqqTTT}`I¿ŠOÒ¥]Ù¸tØ·uÒ¬jÍ¢^ÊšUÇ’MÅJ‰H¿…G¾ƒF¼E¹|C¸zB¶xA³s?±p=­m<¨i:Ÿd7•^3‰X0|Q,Z< yV9¿„FÐQÕ¨\Ò TÍšPË–NÇ‘LÅJÊIÁˆH¿„F½‚E»D¹{B·yAµw@³s?°o=¬l;¦i9Ÿd7•^3‹Y0‚T.\@&rf\»GË–NÑŸSË–NÊ”MÇ‘LÅJÊIÁˆH¿…G½‚E¼€D¹|C¹{B¶xAµv@²r>¯n<©j:¥h9žc6•^3Y1…V/[QHlll¯¡‹È”QË–NÈ’LÆKÄŒJ‰HÁˆH¿…G¾ƒF¼Eº~C¹{B·yA¶xA³s?²q>­m<¨i:¢f8b6•^3•_6‡ug___iii§§§¾¸¬Å™\ÊI‰HÁˆH¿…G¿„F½‚E¼Eº~C¹{B·yA¶xAµv@²r>¯n<©j:¤g8žc6˜_4”c>Ž„z‚‚‚```fffžžž´´´»¹·¹œs¾„G¾ƒF½‚E¼€D»D¹|C¸zB¶xA¶xAµv@²r>°o=©j:¥h9Ÿd7™`5mRŠˆ†~~~YYYbbb•••¨¨¨¯¯¯²²²³§•º‡P»D¹|C¹{B·yA¶xAµv@³s?²r>±p=­m<¨i:¡e7œg?}pˆˆˆ•••”””wwwTTT___‰‰‰¡¡¡§§§«««®®®° ‡¹{B¸zB¶xAµw@³s?²r>²q>±p=­m<©j:¤g8b6ziŠŠŠ———   ’’’oooTTTxxx™™™¡¡¡¥¥¥¨¨¨±†Z¶xAµv@³s?²q>°o=¯n<­m<­m<©j:¥h9Ÿd7˜_4—jH———¦¦¦¢¢¢†††___kkkŒŒŒšššžžž¢™°p>²q>±p=°o=­m<¬l;©j:©j:¨i:¤g8žc6˜_4˜_4£g9¥ž“­­­žžžwwwVVVuuuŽŽŽ———¢€`©j:¬l;¬l;¬l;©j:¨i:¥i:¤h:¡e7b6—_4•^3¡e7±p=¯j¦¦¦ŒŒŒccc\\\zzz‹‰¡g<¢f8¤g8¤g8¥h9¥h9¢h<—‡x”„u›d:—_4—_4Ÿd7­m<µv@°tB‘rrrVVV___ƒkX—_4˜_4š`5›a5b6h@‡~ŠŠŠ‹‚z˜e>Ÿd7©j:²q>±p=¡e7hVVVVhJ0S-‹Y0ŽZ1’\2—iFŠ†‚‰‰‰‰‰‰ŠŠŠŒŒŒ’Ž‰¦vO­m<¬l;žc6„U.aF,eE(yO+„U.ŒgL‰‡†‰‰‰‰‰‰ŠŠŠŽŽŽ‘‘‘•••–•”ŸxX”]3yO+`B'bF-r\Juuu~~~………ˆˆˆ‰‰‰ŒŒŒŠŠŠ‰‰‰………{{{nYHW?(TTT]]]ooo{{{{{{ttteeeZZZRRRRRRRRRRRRRRRRRRRRRRRRDDDRRRRRRRRRRB4VF7RRRRRRRRRbbb~~~›››§§§­¨ ·{DµyC¦¡™›››‘‘‘uuuYYYbbb‚‚‚§§§¼¼¼ººº»»»·œv¼€Dº~C±•p®®®ªªªŸŸŸŒŒŒcccRRRRRRžžžÔÔÔÞÞÞÎÎÎËËËÇÆÅÀP¾ƒF¼E¹ƒK°¯®®®®ªªª¡¡¡‰‰‰tttRRRRRR§§§äääøøøøøøæææØØØȾ¨ÄŒJÀ†G½‚E¼€D²¤Ž°°°«««¦¦¦¡¡¡ŒŒŒxxxRRR]]]šššéééüüüÿÿÿüüüïïïÛÛÛÈ­}ÆK‰H¿…G½‚E¶“g²²²®®®ªªª§§§šššŠŠŠRRRQQQŽŽŽÔÔÔæææøøøþþþöööçççÙÙ×Ë›VÆKËI¿…G½‚EºƒJ±°¯¯¯¯¬¬¬¨¨¨žžž”””ŠŠŠuuuRRR†††ÃÃÃðððøøøîîîðððèèèßßßÎƲʔMÆKÊIÀ†G½‚E¼€D±¤¯¯¯¬¬¬¨¨¨¡¡¡———ŒŒŒ‚‚‚ffffffÉÉÉîîîÿÿÿÿÿÿîîîêêêáááÛÛÛȯƒÈ’LÅJ‰HÀ†G½‚E¼€D³“k¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽ†††uuuRRRŸŸŸÑÑÑùùùÿÿÿùùùèèèãããÜÜÜÓÓÓÈšYÆKËIÁˆH¿…G½‚E»D·‚L¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽˆˆˆfffRRR´´´ÑÑÑúúúüüüðððãããÜÜÜÖÖÖÊĵǑLÄŒJ‰HÀ†G¿„F¼Eº~C¹{B®£”«««¥¥¥   ™™™‘‘‘ˆˆˆtttRRRVA/ÀˆJËšTÛ¼wÝÁÖ´rЩfΡ\Ê—SÈ“MÆŽKÊIÁˆH¿…G½‚E¼€D¹|C¸zB¶xA³s?±p=­m<¨i:c7–_7‹\8†^?VF8VF8¿…GÉ“MÖ©]Ó£VÐQÍšPË–NÇ‘LÆŽKËI‰H¿…G¾ƒF¼Eº~C¹{B¶xAµw@³s?°o=¬l;¨i:žc6—_4‰X0Y1WD2RRR·šsÅJË–N͘OË–NÊ”MÆKÅJËI‰HÀ†G¿„F½‚E»D¹|C¸zB¶xAµv@²r>¯n<©j:¦i9žc6˜_4‰X0lQRQQRRR±±±½¬ŽÆ’OÉ“MÈ’LÆKÄŒJ‰HÁˆH¿…G¿„F½‚E¼€Dº~C¹{B¶xAµw@³s?±p=¬l;¨i:¤g8›a5—_6‘wc‰‰‰```RRR¦¦¦¹¹¹¼¶©Á”ZËI‰HÁˆH¿…G¿„F½‚E¼E»D¹|C¹{B·yA¶xA³s?²q>­m<¨i:¤g8Ÿd7˜fAˆ}t‘‘‘ŠŠŠ___RRRššš±±±···¸·¶ºžv¾„G¾ƒF½‚E¼E»D¹|C¹{B¸zB¶xAµw@³s?²q>­m<©j:¤g8Ÿd7—tX}|•••ƒƒƒRRRRRR‘‘‘¥¥¥¬¬¬°°°²²²³§•»‰Q»D¹|C¹{B·yA¶xAµw@µv@³s?²r>¯n<©j:¥h9ži@‘‚tŸŸŸšššxxxRRR   ¦¦¦¨¨¨¬¬¬®®®° ‡¹|C·yA¶xAµv@³s?²r>²r>²q>¯n<¬l;¦i9¡e7“~l‚‚‚”””¤¤¤¨¨¨”””lllZZZ–––ŸŸŸ¡¡¡¥¥¥§§§°…Yµw@³s?³s?±p=°o=¯n<­m<­m<©j:¦i9¤g8›a5•hG’’’¥¥¥±±±¤¤¤RRR–––›››žžž¡˜Ž®n=°o=°o=¯n<­m<¬l;©j:¨i:¦i9¤g8Ÿd7•^3[2¢f8¦Ÿ”±±±±±±———lllRRR”””———¡`¨i:¨i:©j:©j:©j:¨i:¥j=¢h<¡e7b6•^3•^3Ÿd7²q>³•o®®®ššš{{{RRRRRR‚‚‚œg>žc6žc6¢f8¢f8¢f8Ÿg=”‰}‘†{—b:•^3˜_4¢f8±p=¶xA²}L”””ƒƒƒRRRRRRzu[2[2”]3”]3˜_4˜d=Œƒ{ŠŠŠ‰‰‰‹‚zžh?¤g8¯n<³s?°o=¤g8‡yRRRSNIyO+‰X0‰X0•^3nI‹†‰‰‰ŠŠŠ’’’›–§uM­m<¨i:˜_4{P+SNIW?)oI'zR0‡lX‚€ŒŒŒŒŒŒŽŽŽ‘‘‘Ž‘u_‹[5U:W?)RQQ```nnn{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRUF9UI>RRRRRRRRRrrr–––¥¥¥ªªª¯™{¶xA¶xA¦–‚œœœ•••………ooo\\\uuu®®®ÉÉÉÎÎÎÐÐÐÊÉÇÄ‘QÁˆH¿„FºŒW²²²®®®¦¦¦”””|||cccWWWiii¤¤¤ÓÓÓëëëîîîìììæææÎèȒLËIÁˆH¿…G¶ª—´´´°°°¦¦¦———………nnnZZZnnn°°°âââôôôúúúöööñññâââɨqÆŽK‰H¿…G¼E´’h®®®¬¬¬¨¨¨¤¤¤™™™ˆˆˆttt___oooªªªÚÚÚòòòûûûûûûôôôêêêÙÙÔÉ•PÆŽKÊI¿…G½‚E¹I°¯®®®®«««§§§   •••ŠŠŠxxx___nnn¤¤¤ÙÙÙíííñññôôôòòòêêêßßßÍÄ­É“MÆŽK‰H¿…G½‚E»D±¤¯¯¯¬¬¬¨¨¨¢¢¢ššš………rrrZZZnnn¯¯¯ÙÙÙóóóúúúôôôíííèèèàààØØØÇ®‚Ç‘LÄŒJ‰H¿…G½‚E»D´”m¯¯¯¬¬¬¨¨¨¤¤¤œœœ’’’‰‰‰~~~kkkTTTŠŠŠÇÇÇíííûûûûûûñññçççáááÚÚÚÒÒÒÇ›]ÆŽKÊIÁˆH¿…G¼Eº~C·‚L®®®¬¬¬§§§¢¢¢œœœ”””ŒŒŒƒƒƒuuu______¦¦¦ÏÏÏ÷÷÷øøø÷÷÷êêêàààÚÚÚÓÓÓÊÇÁÆLÄŒJÁˆH¿…G¾ƒF¼€D¹|C¹{B­ Ž«©¨¦¦¦¡¡¡›››•••ŒŒŒ………{{{lllWWWeM7¶xAÍœUÛ½xÚ½zÙºxÒ­kϤ`ÌXÈ•QÇ‘LÄŒJ‰HÀ†G¿„F½‚E»D¹{B¸zB¶xA³s?±r@­qB©pE¢nF™jF‘fG‰cGz\C\I7`\Y²‚RÒ¡UÚ²fÙ±eÒ TÏœQË–NÉ“MÆKÄŒJ‰HÁˆH¿„F½‚E¼€D¹|C¸zB¶xAµv@³s?°o=­m<¨i:¢f8›a5’\2‰X0W4eZQ]]]¦¤¢Íµ„Ø®aÓ¤WË–NÉ“MÇ‘LÆŽKËI‰HÀ†G¿…G½‚E¼€Dº~C¹{B·yAµw@³s?²q>¯n<©j:¦i9¡e7š`5’\2fI{wtbbb]]]   ÐÐÐÓÉ­ÌšSÆKÆŽKËI‰HÁˆH¿…G¿„F½‚E¼€Dº~C¹{B·yA¶xAµv@²r>°o=¬l;¨i:¤g8žc6–_6wc‰‰‰bbbZZZ•••ÁÁÁÓÓÓÁ¼±¿“[ÁˆHÀ†G¿…G¾ƒF½‚E¼€Dº~C¹{B¹{B·yA¶xAµv@³s?°o=­m<¨i:¢f8žc6˜hCˆwŠŠŠ~~~bbbWWWŒŒŒ²²²Â³³³µ´³¸›s½‚F¼E»Dº~C¹{B¸zB¶xA¶xAµw@³s?²r>°o=­m<¨i:¢f8c7–sX…ƒ‚‰‰‰”””xxxYYY___ƒƒƒ¤¤¤®®®«««­­­°°°±£¸K¹{B¸zB¶xAµw@³s?³s?³s?²q>°o=­m<©j:¤g8Ÿg>‘€qˆˆˆ™™™žžžŽŽŽrrrYYYkkk”””¡¡¡¢¢¢¥¥¥¨¨¨«««®›‚¶xAµv@³s?²q>°o=°o=¯n<­m<¬l;¨i:¤g8žc6’|iŠŠŠ‘‘‘   §§§œœœbbbYYY‚‚‚’’’›››žžž¡¡¡¢¢¢«U±p=±p=¯n<­m<¬l;©j:©j:¨i:¥h9¡e7b6—_4—jH•••¢¢¢¬¬¬¨¨¨‘‘‘tttTTT```|||–––šššœ“‰©j;©j:©j:©j:¨i:¦i9¤g8¢f8¡e7b6˜_4•^3˜_4£g9¤’¬¬¬§§§———zzz\\\]]]wwwŠŠŠ›wZžc6Ÿd7¢f8¢f8¡e7Ÿd7že;›d:˜_4—_4—_4›a5¥h9±p=¬‹g¢¢¢”””zzz]]]ZZZrrr‚}‘]6’\2”]3—_4š`5˜_4—b:‚wŒw–a9š`5Ÿd7¦i9­m<°o=©m?‹‰uuu\\\WWWr]M|Q,„U.‹Y0”]3˜_4–b;‹‚zŠŠŠŒŒŒ‡~¡j@¥h9¨i:¨i:b6‰X0r]MYYYW?(aA#oI'yO+„U.Ž_;ˆ€z‰‰‰ŠŠŠŒŒŒŒŒŒ‡€–d>[2‡W/oI'[=!V>'XB.]?#lL/upl~~~~~~wwwqlhaD+Z="WA.RRRRRR___ccc___YYYRRRRRR```fffeeebbbnAMRRRRRRRRRkkk‘‘‘ŸŸŸ¨¨¨¬¬¬¬¾ll¡‘‘•••†††lllWWW†††¦¦¦¸¸¸¿¿¿¿¿¿¼¼¼º““Átt´‰‰§§§žžžwwwZZZ~~~···ÐÐÐÚÚÚÙÙÙÐÐÐÇÆÆÄ‚‚ÃwwÀxx­¬¬ªªª   rrrZZZuuu¾¾¾çççðððíííååå×××Á´´Å}}ÄyyÂvv±ŸŸ¬¬¬¦¦¦žžžŒŒŒtttZZZeee³³³éééùùùúúúòòòãããÐÐПŸÇ€€Ä{{Ãxx·ŒŒ®®®ªªª¥¥¥šššˆˆˆqqqYYYWWW›››ÕÕÕñññûûûùùùíííÝÝÝÏÎÎÈ‹‹ÇÅ}}ÄyyÀzz¯®®¬¬¬§§§ŸŸŸ’’’………lllVVVˆˆˆÈÈÈêêêñññôôôñññæææÙÙÙÉ¿¿Ê‡‡ÇÆ~~ÄyyÂvv°¡¡­­­¨¨¨¡¡¡———ŒŒŒ{{{___‚‚‚¾¾¾èèèøøøôôôíííçççÞÞÞÔÔÔÆ©©É††ÇÆ~~Ä{{ÂvvµŽŽ­­­¨¨¨¢¢¢™™™‚‚‚lllVVVŸŸŸÛÛÛ÷÷÷ýýýôôôéééâââÙÙÙÏÏÏÈ‘‘È„„Ç€€Å}}ÄyyÂvv½{{­­­¨¨¨¢¢¢ššš………uuu\\\{{{³³³âââúúúúúúîîîâââÛÛÛÓÓÓÈÀÀɆ†ÇÆÅ||ÃxxÂuuÁss¬  §§§¡¡¡ššš†††|||fffTTT‹``½ŒŒÒ§§ÜµµÛ¯¯Ö¢¢Ò™™Ï’’ÍŽŽÊˆˆÉ……Ç€€Æ~~Ä{{ÃwwÁttÀrr¿oo½jj»ee¹aaµYY²QQ®IIC[y-;YYÆ~~ÒššÙªªØ§§ÓÏ””Íˉ‰É††ÇÆ~~Å||ÄyyÂuuÁssÀpp¾ll¼ii»ee¹aa¶ZZ²RR¯JJ•EZw4Cqqq»™™Ð––Õ¡¡Ò™™Ï’’Íˉ‰É††ÈƒƒÇ€€Å}}Ä{{ÃwwÁttÀrr¿oo¾ll¼ggºdd¸``¶ZZ²RR¯KK¨HKaMSnnn´´´È¸¸ÑššÍÌˉ‰É††ÈƒƒÇ€€Æ~~Ä{{ÃxxÂuuÁttÀpp¿nn½kk»ff¹bb¸^^µYY²RR­PP†mmYYYlll¯¯¯ÊÊÊËÅÅÈ——ʈˆÉ……ÇÇ€€Æ~~Å||ÄyyÂvvÁttÀrrÀpp¾ll½jj»ee¹aa·\\µXX©[[Œ~~___iii§§§ÁÁÁÈÈÈÁÀÀ¾  Æ€€Æ~~Å}}Ä{{ÄyyÂvvÁttÀrrÀpp¿nn½kk»ff¹bb·]]µYYžnnˆ††ŒŒŒ‚‚‚```fffžžž´´´»»»ººº»»»¸¬¬ÀƒƒÃxxÃwwÂuuÁssÀppÀpp¿nn½kk¼gg¹bb¸^^¯bb’………ŠŠŠ~~~YYYbbb•••¨¨¨¯¯¯²²²´´´¶¶¶´ŸŸÂuuÁttÀrrÀpp¿nn¾ll½kk¼ii»ee¹aa¶[[–||ˆˆˆˆˆˆ•••”””wwwTTT___‰‰‰¡¡¡§§§«««®®®°°°¹„„ÁssÀpp¿oo¾ll½kk½jj¼ii»ee¹bb·]]µXX¤aaŠŠŠ———   ’’’oooTTTxxx™™™¡¡¡¥¥¥¨¨¨«££¿pp¿nn¾ll½jj¼gg»ff»ee»ee¹bb¸^^¶ZZ³TT³TT™ŽŽ¦¦¦¢¢¢†††___kkkŒŒŒšššžžž¡¡¡¬……½jj¼ii¼gg»eeºdd¹bb²jj¸bb·]]µYY³TT³TT·]]­„„­­­žžžwwwVVVuuuŽŽŽ———›ššµhhºddºddºdd¹bb¹aa¦vv–””™……±[[³SS²RR¶[[¼ii½tt¥££ŒŒŒccc\\\zzzŽŽŽš……·\\·]]·]]¸^^´cc›ƒƒ”””ŽŽŽŽ‚‚«[[¶ZZ»ee¿nn½jj›||rrrVVV___{{{žjj³TT´UU´WW§hh’ŠŠŽŽŽŠŠŠ‰‰‰ŒŒŒ’®qq½jj¼ii¶[[ MMVVVbbb NN¯KK¬QQ“qqŠŠŠŠŠŠ‰‰‰‰‰‰ŠŠŠŒŒŒ’’’šššŸžž«{{µYY©HKy:IpLXˆ[[}ww‚‚‚‰‰‰‰‰‰‰‰‰ŠŠŠŽŽŽ‘‘‘•••———–––ˆˆˆ‡[[x:I]]]llluuu~~~………ˆˆˆ‰‰‰ŒŒŒŠŠŠ‰‰‰………{{{iiiTTTTTT]]]ooo{{{{{{ttteeeZZZRRRRRRRRRRRRRRRRRRRRRRRRDDDRRRRRRRRRONOc?HTPQRRRRRRbbb~~~›››§§§­­­±””Àpp­††›››‘‘‘uuuYYYbbb‚‚‚§§§¼¼¼ººº»»»¶´´À~~Âvv¿ww­««ªªªŸŸŸŒŒŒcccRRRRRRžžžÔÔÔÞÞÞÎÎÎËËËÈÈȼ©©Å||ÄyyÂvv±œœ®®®ªªª¡¡¡‰‰‰tttRRRRRR§§§äääøøøøøøæææØØØÍÍ͘˜ÆÄ{{Ãxx¸‰‰°°°«««¦¦¦¡¡¡ŒŒŒxxxRRR]]]šššéééüüüÿÿÿüüüïïïÛÛÛÐÎÎʉ‰ÇÆ~~Ä{{Âzz²°°®®®ªªª§§§šššŠŠŠRRRQQQŽŽŽÔÔÔæææøøøþþþöööçççÚÚÚɽ½ÊˆˆÈ„„Æ~~Ä{{Ãww²¢¢¯¯¯¬¬¬¨¨¨žžž”””ŠŠŠuuuRRR†††ÃÃÃðððøøøîîîðððèèèßßßÕÕÕÆ©©ÊˆˆÈƒƒÆÄ{{Ãxxµ¯¯¯¬¬¬¨¨¨¡¡¡———ŒŒŒ‚‚‚ffffffÉÉÉîîîÿÿÿÿÿÿîîîêêêáááÛÛÛÏÏÏÈ––Ɇ†ÇÆÄ{{Ãxx¼¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽ†††uuuRRRŸŸŸÑÑÑùùùÿÿÿùùùèèèãããÜÜÜÒÑÑÉÃÃʈˆÈ„„Ç€€Æ~~Ä{{ÃwwÁtt¯¨¨¬¬¬§§§¡¡¡™™™ŽŽŽˆˆˆffff=G·É§§ßÉÉàÇÇÙ¸¸Ó¨¨ÑžžÏ••ÌŽŽË‰‰É……ÇÆÅ}}ÄyyÂvvÁttÀrr¿oo½jj»ee¸``µYY¯TT§QQ™OOc@IZHM††ÍÛ®®Ü±±Ö¤¤Ò™™Ï””ÌËŠŠÊ‡‡ÈƒƒÇ€€Æ~~Ä{{ÃxxÂuuÁssÀpp¾ll¼ii»ee¹aaµYY²RR¯JJ©HKh<GRRRº¸¸Æ££Ö¢¢ÓœœÐ––Ï’’Íˉ‰Ê‡‡È„„ÇÆ~~Å||ÄyyÂvvÁttÀpp¿oo¾ll¼ggºdd¹aaµYY³SS¯JJ˜ddRQRRRR···ÆÆÆɹ¹Í””ÍÌʈˆÉ††È„„ÇÆÅ}}Ä{{ÃwwÂuuÁssÀpp¿nn½kk»ff¹bb¸``µYY±VVŽkk………RRRRRR±±±ÀÀÀÈÈÈÊÄÄǘ˜ÊˆˆÉ……ÇÇ€€Æ~~Å}}Ä{{ÃxxÂvvÁttÀpp¿oo¾ll¼iiºdd¹aa·]]«``Ž‰‰‰‰‰‰```RRR¦¦¦¹¹¹¿¿¿ÂÂÂÂÁÁ¾  Æ€€Æ~~Å}}Ä{{ÄyyÃwwÂuuÁttÀrrÀpp¾ll½jj»ee¹aa·]]¡uuŒŠŠ………‘‘‘ŠŠŠ___RRRššš±±±···¹¹¹»»»»»»¸««ÁÄyyÃwwÂuuÁttÁssÀpp¿oo¾ll½jj»ee¹bb±ee—„„•••ƒƒƒRRRRRR‘‘‘¥¥¥¬¬¬°°°²²²´´´···´žžÂuuÁttÀrrÀpp¿oo¿nn¾ll½kk»ff¹bb¸^^™ŽŽŽŸŸŸšššxxxRRR   ¦¦¦¨¨¨¬¬¬®®®±±±¹……ÀrrÀpp¿nn¾ll½kk½kk½jj»ffºdd¸``¶[[¦ff‚‚‚”””¤¤¤¨¨¨”””lllZZZ–––ŸŸŸ¡¡¡¥¥¥§§§ª¢¢¿oo¾ll¾ll¼ii¼gg»ff»ee»ee¹bb¸``·]]´WW²RR•ŠŠ¥¥¥±±±¤¤¤RRR–––›››žžž   ªƒƒ¼gg¼gg»ff»eeºdd¹bb²ii¸aa·]]¶ZZ²RR±OO·\\®††±±±±±±———lllRRR”””———š™™´gg¹aa¹bb¹bb¹bb¹aa¦vv•““™……±[[²RR²RR¶ZZ½jjÀxx­««ššš{{{RRRRRR‚‚‚–µYYµYY·\\·\\³aa™€€’’’ŽŽŽŠŠŠ«\\·\\¼iiÀpp¿nn~~ƒƒƒRRRRRR›ee±OO²QQ²QQ¥ccŽ††ŠŠŠŠŠŠ‰‰‰‰‰‰‘‘‘–‘‘±vv¾ll¼gg·]]¦YYRRRRRR”JS¯JJªNN•tt‘‘‘ŠŠŠ‰‰‰ŠŠŠ’’’šššœœœŸžž¨xx³TT‘E^r3@d@I€STtnnƒƒƒŒŒŒŒŒŒŽŽŽ‘‘‘‘‘‘‰‰‰c@Ir3@RRR```nnn{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRYJNr2>YJNRRRRRRrrr–––¥¥¥ªªª®®®¹||Àpp¶vvœœœ•••………ooo\\\uuu®®®ÉÉÉÎÎÎÐÐÐÌÌÌÀ²²Ç€€Å}}Ä{{²¡¡®®®¦¦¦”””|||cccWWWiii¤¤¤ÓÓÓëëëîîîìììæææ×××Æ¢¢È„„Ç€€Æ~~»´´´°°°¦¦¦———………nnnZZZnnn°°°âââôôôúúúöööñññâââÐÏÏÉŠŠÇÆ~~ÄyyÀxx­««¬¬¬¨¨¨¤¤¤™™™ˆˆˆttt___oooªªªÚÚÚòòòûûûûûûôôôêêêÜÜÜȽ½Ê‡‡ÈƒƒÆ~~Ä{{Âvv±¡¡®®®«««§§§   •••ŠŠŠxxx___nnn¤¤¤ÙÙÙíííñññôôôòòòêêêßßßÕÕÕƪªÊ‡‡ÇÆ~~Ä{{Ãwwµ¯¯¯¬¬¬¨¨¨¢¢¢ššš………rrrZZZnnn¯¯¯ÙÙÙóóóúúúôôôíííèèèàààØØØÏÏÏÇ””É……ÇÆ~~Ä{{Ãww¼¯¯¯¬¬¬¨¨¨¤¤¤œœœ’’’‰‰‰~~~kkkTTTŠŠŠÇÇÇíííûûûûûûñññçççáááÚÚÚÑÐÐÇÀÀɇ‡ÈƒƒÇ€€Æ~~ÄyyÂvvÁtt®§§¬¬¬§§§¢¢¢œœœ”””ŒŒŒƒƒƒuuu___d[[­‡‡Ç©©ÞÊÊÞÈÈÝÃÃÖ³³Ò¦¦ÏœœÎ’’ËŒŒÊˆˆÉ……Ç€€Æ~~Å||ÃxxÂuuÁttÀpp¿oo½kk»ff¹bb¶\\±XX©VVUU„VVWWW___¬ŸŸÌššÛ¯¯Ú­­ÙªªÔžžÐ––Α‘ÌŒŒÊˆˆÉ……ÇÆÅ}}Ä{{ÃwwÁttÁssÀpp¾ll½jj»ee¹bb·\\´UU±OO®IIŽUUWWW___¬¬¬ÛÚÚÛÅÅÙªªÒ™™Ð••ÍÌŒŒÊˆˆÉ……ÇÇ€€Å}}Ä{{ÃxxÂuuÁssÀpp¿nn¾ll¼gg»ee¹aa·\\´WW±PP–aawvvbbb]]]¦¦¦ÙÙÙìììÔÈÈÌ’’ÌŒŒË‰‰Ê‡‡È„„ÇÆÆ~~Ä{{ÃxxÂvvÁttÀrr¿oo¾ll½jj»ff¹bb¸``¶[[²WW“qq‚‚‚zzzbbb]]]   ÐÐÐßßßÒÒÒÆÀÀÆ••È„„ÇÇ€€Æ~~Å}}Ä{{ÃxxÂvvÁttÀrrÀpp¿nn½kk¼ggºdd¹aa·]]¬ccŽˆˆˆ‰‰‰bbbZZZ•••ÁÁÁÓÓÓÄÄÄ¿¿¿¾½½½žžÅ~~Å||Ä{{ÃxxÂvvÁttÁttÀrrÀpp¿nn¾ll¼gg»ee¹aa¶\\ ttŒŠŠ†††ŠŠŠ~~~bbbWWWŒŒŒ²²²Â³³³¶¶¶¸¸¸¸¸¸µ§§¿}}ÂvvÁttÁssÀppÀpp¿oo¾ll½kk¼gg»ee¹aa±dd–ƒƒŒŒŒ………‰‰‰”””xxxYYY___ƒƒƒ¤¤¤®®®«««­­­°°°±±±²²²²ÁssÀpp¿oo¾ll¾ll¾ll½jj¼gg»ee¹bb·]]™ŒŒŒˆˆˆ™™™žžžŽŽŽrrrYYYkkk”””¡¡¡¢¢¢¥¥¥¨¨¨«««¬¬¬¶‚‚¿nn¾ll½jj¼gg¼gg»ff»eeºdd¹aa·]]µYY¥ccŠŠŠ‘‘‘   §§§œœœbbbYYY‚‚‚’’’›››žžž¡¡¡¢¢¢¥››¼ii¼ii»ff»eeºdd¹bb¹bb¹aa¸^^¶[[µXX³SS³TT—ŒŒ¢¢¢¬¬¬¨¨¨‘‘‘tttTTT```|||–––ššš›››§||¹bb¹bb¹bb¹aa¸``·]]·\\¶[[µXX³TT²RR³TT·]]­ƒƒ¬¬¬§§§———zzz\\\]]]wwwŠŠŠ‘ŽŽ²\\¶ZZ·\\·\\¶[[µ\\ tt’‚‚˜uu¯VV³SS´WW¸^^¼ii»pp¢  ”””zzz]]]ZZZrrr“pp±PP²QQ³SS´UU¬\\ŠŠŠ‰‰‰‰‰‰……®bb¸``»ee¼gg¹bb—zzuuu\\\WWWkkk›KN©HK¯KK²QQ¦ccŠ„„‰‰‰ŠŠŠŒŒŒŽŽŽ”””—””§zz¸bbµXX¯JJ„JZYYYTRSz;K‰=RD\–\\‚€€†††‰‰‰ŠŠŠŒŒŒŒŒŒŒŒŒŠŠŠ………‚yyRUs9Go5As4AiFPeeettt~~~~~~wwwooo\\\VVVRRRRRRRRR___ccc___YYYRRRRRR```fffeeebbbYYYRRRRRRRRRkkk‘‘‘ŸŸŸ¨¨¨¬¬¬u¤ur ržžž•••†††lllWWW†††¦¦¦¸¸¸¿¿¿¿¿¿°¹°I§IEŸE¡«¡§§§žžžwwwZZZ~~~···ÐÐÐÚÚÚÙÙÙÐÐЋ»‹H©HD¢Dt¨t®®®ªªª   rrrZZZuuu¾¾¾çççðððíííåååÕÖÕX²XG¨GE¥EK¤K­¯­¬¬¬¦¦¦žžžŒŒŒtttZZZeee³³³éééùùùúúúòòòããã³Ç³N°NJ«JE¦ED£D­®®®ªªª¥¥¥šššˆˆˆqqqYYYWWW›››ÕÕÕñññûûûùùùíííÝÝ݇À‡O±OK¬KG¨GE¥Ej¨j°°°¬¬¬§§§ŸŸŸ’’’………lllVVVˆˆˆÈÈÈêêêñññôôôñññæææ×Ø×]¹]O±OK¬KH©HE¥EI£I­¯­­­­¨¨¨¡¡¡———ŒŒŒ{{{___‚‚‚¾¾¾èèèøøøôôôíííçççÞÞ޻˻SµSN°NK¬KH©HE¦EC¡C’¬’­­­¨¨¨¢¢¢™™™‚‚‚lllVVVŸŸŸÛÛÛ÷÷÷ýýýôôôéééâââÙÙÙÀP²PM®MJ«JG¨GE¥EC¡Cn¦n­­­¨¨¨¢¢¢ššš………uuu\\\{{{³³³âââúúúúúúîîîâââÛÛÛÓÓÓ`¸`N°NK¬KIªIF§FD£DC CL L¬¬¬§§§¡¡¡ššš†††|||fffTTTxxx¹¹¹âââøøøõõõçççÜÜÜÕÕջȻP²PM¯MJ«JH©HE¦ED¢DBžBAœA“§“¥¥¥   ššš‘‘‘ˆˆˆ~~~qqqTTT,i,H©H_Ã_xÌxsÊsbÄbY¼YV¸VQ³QN°NK¬KH©HF§FE¥EC CBBA›A?–?=’=<<:Š:7ƒ73{30r0,i, M 1g1G¨G[¾[iÇi^Á^X»XV¸VQ³QN°NL­LJ«JG¨GE¦ED¢DBžBAœA@š@?–?=‘=;Ž;9‰97ƒ73{30s0.n.&P&YlYF£FV¸V]À]V¸VT¶TQ³QN°NL­LJ«JH©HE¦ED£DC CBžBA›A@˜@>•><<:‹:9ˆ96‚63{31u1/p/HWHlllŽªŽV´VV¸VR´RP²PM¯MK¬KJ«JH©HF§FE¥EC¡CBžBAœAA›A?–?>”><<:Š:8…8663{36{6g~g___iii§§§¯¼¯c¶cL­LK¬KJ«JH©HG¨GE¦EE¥EC¡CBžBAœAA›A@˜@>•><<:‹:8†86‚64}4>|>zŠz‚‚‚```fffžžž´´´¸º¸x¯xH¨HF§FE¦ED£DD¢DC CBBA›AA›A@˜@>•>=‘=:‹:9ˆ97ƒ75~5RR†‰†~~~YYYbbb•••¨¨¨¯¯¯²²²—¯—R¦RD¢DC CBžBAœAA›A@˜@?–?>•>=’=<<:Š:7„7?ƒ?p†pˆˆˆ•••”””wwwTTT___‰‰‰¡¡¡§§§«««®®®Š«ŠBžBBBA›A@š@?–?>•>>”>=’=<<:‹:8†866i†iŠŠŠ———   ’’’oooTTTxxx™™™¡¡¡¥¥¥¨¨¨Z ZA›A@˜@?–?>”>=‘=<<<<<<:‹:9ˆ97ƒ74}4H‚H———¦¦¦¢¢¢†††___kkkŒŒŒšššžžžŸ>’>>”>=’==‘=<<;Ž;:‹::‹::Š:8†86‚64}44}49†9”£”­­­žžžwwwVVVuuuŽŽŽ———`”`:‹:;Ž;;Ž;;Ž;:‹::Š::‰::ˆ:7„7664|43{37„7=’=l£l¦¦¦ŒŒŒccc\\\zzz‰‰<…<8…88†88†89ˆ99ˆ9<‡<x‘xuu::4|44|47ƒ7<<@˜@B•B‘rrrVVV___XwX4|44}45~55566@ƒ@~~ŠŠŠz‡z>€>7ƒ7:‹:>”>=’=7„7VtVVVV0[0-k-0s01v12x2FF‚ˆ‚‰‰‰‰‰‰ŠŠŠŒŒŒŠŠOO<<;Ž;6‚6.o.,U,(V(+f+.o.L{L†ˆ†‰‰‰‰‰‰ŠŠŠŽŽŽ‘‘‘•••”–”XŽX3y3+f+'S'-V-JhJuuu~~~………ˆˆˆ‰‰‰ŒŒŒŠŠŠ‰‰‰………{{{HeH(M(TTT]]]ooo{{{{{{ttteeeZZZRRRRRRRRRRRRRRRRRRRRRRRRDDDRRRRRRRRR4K47O7RRRRRRRRRbbb~~~›››§§§¡«¡DžDCœCš¤š›››‘‘‘uuuYYYbbb‚‚‚§§§¼¼¼ººº»»»z®zD£DC¡Cs§s®®®ªªªŸŸŸŒŒŒcccRRRRRRžžžÔÔÔÞÞÞÎÎÎËËËÆÇÆS­SF§FE¥EK¤K®°®®®®ªªª¡¡¡‰‰‰tttRRRRRR§§§äääøøøøøøæææØØØ°Ä°M¯MIªIE¦ED£D‘®‘°°°«««¦¦¦¡¡¡ŒŒŒxxxRRR]]]šššéééüüüÿÿÿüüüïïïÛÛÛ‡À‡P²PK¬KH©HE¦Ek©k²²²®®®ªªª§§§šššŠŠŠRRRQQQŽŽŽÔÔÔæææøøøþþþöööçççØÙØ^º^P²PM®MH©HE¦EJ¤J¯±¯¯¯¯¬¬¬¨¨¨žžž”””ŠŠŠuuuRRR†††ÃÃÃðððøøøîîîðððèèèßß߻˻T¶TP²PL­LIªIE¦ED£D“­“¯¯¯¬¬¬¨¨¨¡¡¡———ŒŒŒ‚‚‚ffffffÉÉÉîîîÿÿÿÿÿÿîîîêêêáááÛÛÛÀR´RN°NK¬KIªIE¦ED£Do§o¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽ†††uuuRRRŸŸŸÑÑÑùùùÿÿÿùùùèèèãããÜÜÜÓÓÓ`¸`P²PM®MJ«JH©HE¦ED¢DL¢L¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽˆˆˆfffRRR´´´ÑÑÑúúúüüüðððãããÜÜÜÖÖֻȻQ³QM¯MK¬KIªIG¨GE¥EC¡CBžB–ª–«««¥¥¥   ™™™‘‘‘ˆˆˆtttRRR8O8e®ed»d†Ï†…Ñ…mÈm^Á^Y¼YT¶TR´RO±OL­LJ«JH©HE¦ED£DC CBBA›A?–?=’=<<:Š:7‚77|78u8?s?8O82N2H©HSµSkÇk`Ã`[¾[X»XV¸VQ³QO±OM®MK¬KH©HF§FE¥EC¡CBžBA›A@š@?–?=‘=;Ž;:Š:6‚64|40r01u12O2RRRu­uN°NV¸VWºWV¸VT¶TP²PN°NM®MK¬KIªIG¨GE¦ED¢DC CBBA›A@˜@>•><<:‹:9‰96‚64}40r0Q~QQRQRRR±±±”·”T´TSµSR´RP²PM¯MK¬KJ«JH©HG¨GE¦ED£DC¡CBžBA›A@š@?–?=’=;Ž;:Š:8†8556|6c…c‰‰‰```RRR¦¦¦¹¹¹­º­_±_M®MK¬KJ«JH©HG¨GE¦EE¥ED¢DC CBžBAœAA›A?–?>”><<:Š:8†87ƒ7AAtƒt‘‘‘ŠŠŠ___RRRššš±±±···¶¸¶{±{H¨HF§FE¦EE¥ED¢DC CBžBBBA›A@š@?–?>”><<:‹:8†87ƒ7X†X|~|•••ƒƒƒRRRRRR‘‘‘¥¥¥¬¬¬°°°²²²˜¯˜S§SD¢DC CBžBAœAA›A@š@@˜@?–?>•><<:‹:9ˆ9@…@t‹tŸŸŸšššxxxRRR   ¦¦¦¨¨¨¬¬¬®®®Š«ŠC CAœAA›A@˜@?–?>•>>•>>”><<;Ž;9‰97„7lŠl‚‚‚”””¤¤¤¨¨¨”””lllZZZ–––ŸŸŸ¡¡¡¥¥¥§§§YŸY@š@?–??–?=’==‘=<<<<<<:‹:9‰98†855G€G’’’¥¥¥±±±¤¤¤RRR–––›››žžžž===‘==‘=<<<<;Ž;:‹::Š:9‰98†87ƒ73{32w28…8•¤•±±±±±±———lllRRR”””———`“`:Š::Š::‹::‹::‹::Š:=‰=<‡<7„7663{33{37ƒ7>”>r¨r®®®ššš{{{RRRRRR‚‚‚>ƒ>6‚66‚68…88…88…8=…=}}{Œ{:~:3{34}48…8=’=A›ALœL”””ƒƒƒRRRRRRu~u2w22w23y33y34}4=={ˆ{ŠŠŠ‰‰‰z‡z?„?8†8<<?–?=‘=8†8y„yRRRIQI+f+0r00r03{3I‡I‰‰‰‰ŠŠŠ’’’™M‘M<<:Š:4}4+h+IQI)M)'^'0h0XzX€‚€ŒŒŒŒŒŒŽŽŽ‘‘‘ŽŽ_„_5t5I)M)QRQ```nnn{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQRQ9O9>P>RRRRRRRRRrrr–––¥¥¥ªªª~§~A›AA›A„¡„œœœ•••………ooo\\\uuu®®®ÉÉÉÎÎÎÐÐÐÈÊÈU°UJ«JG¨GY©Y²²²®®®¦¦¦”””|||cccWWWiii¤¤¤ÓÓÓëëëîîîìììæææ³Ê³R´RM®MJ«JH©Hš²š´´´°°°¦¦¦———………nnnZZZnnn°°°âââôôôúúúöööñññâââz¿zO±OK¬KH©HE¥Ek¨k®®®¬¬¬¨¨¨¤¤¤™™™ˆˆˆttt___oooªªªÚÚÚòòòûûûûûûôôôêêê×Ù×V·VO±OL­LH©HE¦EI£I®°®®®®«««§§§   •••ŠŠŠxxx___nnn¤¤¤ÙÙÙíííñññôôôòòòêêêßß߶ʶSµSO±OK¬KH©HE¦ED¢D’­’¯¯¯¬¬¬¨¨¨¢¢¢ššš………rrrZZZnnn¯¯¯ÙÙÙóóóúúúôôôíííèèèàààØØØ‹¿‹Q³QM¯MK¬KH©HE¦ED¢Dp©p¯¯¯¬¬¬¨¨¨¤¤¤œœœ’’’‰‰‰~~~kkkTTTŠŠŠÇÇÇíííûûûûûûñññçççáááÚÚÚÒÒÒd¸dO±OL­LJ«JH©HE¥EC¡CL¢L®®®¬¬¬§§§¢¢¢œœœ”””ŒŒŒƒƒƒuuu______¦¦¦ÏÏÏ÷÷÷øøø÷÷÷êêêàààÚÚÚÑÒѽǽQ²QM¯MJ«JH©HF§FD£DC CBžB¨¨ª¨¦¦¦¡¡¡›››•••ŒŒŒ………{{{lllWWW<[<h£h‡Á‡­Ö­ Ó –ЖyÆydÁdZ»ZSµSP²PM¯MK¬KIªIG¨GE¦ED¢DBžBBBA›A?–?@”@B‘BEŽEFŠFFƒFG}GGwGClC7T7O^OKžK_Ã_zÍzxÌx^Á^Z½ZV¸VSµSP²PM¯MK¬KJ«JG¨GE¦ED£DC CBBA›A@˜@?–?=‘=<<:Š:8…8552x20r04n4Q`Q]]] ¥ ÄsÊsbÄbV¸VSµSQ³QO±OM®MK¬KIªIH©HE¦ED£DC¡CBžBAœA@š@?–?>”><<:‹:9‰97„75~52x2I{Itytbbb]]]   ÐÐйй[º[P²PO±OM®MK¬KJ«JH©HG¨GE¦ED£DC¡CBžBAœAA›A@˜@>•>=‘=;Ž;:Š:8†86‚66|6c„c‰‰‰bbbZZZ•••ÁÁÁÓÓÓµ¿µ`¯`J«JIªIH©HF§FE¦ED£DC¡CBžBBžBAœAA›A@˜@?–?=‘=<<:Š:8…86‚6CCw„wŠŠŠ~~~bbbWWWŒŒŒ²²²Â³³³´µ´y¯yF¦FE¥ED¢DC¡CBžBBBA›AA›A@š@?–?>•>=‘=<<:Š:8…87‚7X…X‚„‚‰‰‰”””xxxYYY___ƒƒƒ¤¤¤®®®«««­­­°°°’¬’K¢KBžBBBA›A@š@?–??–??–?>”>=‘=<<:‹:8†8>„>q‰qˆˆˆ™™™žžžŽŽŽrrrYYYkkk”””¡¡¡¢¢¢¥¥¥¨¨¨«««…§…A›A@˜@?–?>”>=‘==‘=<<<<;Ž;:Š:8†86‚6iˆiŠŠŠ‘‘‘   §§§œœœbbbYYY‚‚‚’’’›››žžž¡¡¡¢¢¢U™U=’==’=<<<<;Ž;:‹::‹::Š:9ˆ97„7664|4H‚H•••¢¢¢¬¬¬¨¨¨‘‘‘tttTTT```|||–––šššŠ™Š;‹;:‹::‹::‹::Š:9‰98†88…87„7664}43{34}49†9“¢“¬¬¬§§§———zzz\\\]]]wwwŠŠŠZŠZ6‚67ƒ78…88…87„77ƒ7;ƒ;::4}44|44|4559ˆ9=’=hŸh¢¢¢”””zzz]]]ZZZrrr}€}6x62x23y34|45~54}4:~:wˆww‡w9}95~57ƒ79‰9<<=‘=??‰‰uuu\\\WWWMhM,i,.o.0s03y34}4;};z‡zŠŠŠŒŒŒ~~@†@9ˆ9:Š::Š:660r0MhMYYY(M(#S#'^'+f+.o.;x;z…z‰‰‰ŠŠŠŒŒŒŒŒŒ€Š€>>2w2/q/'^'!N!'L'.O.#P#/^/lrl~~~~~~wwwhnh+T+"N".M.RRRRRR___ccc___YYYRRRRRR```fffeeemLmeEeRRRRRRRRRkkk‘‘‘ŸŸŸ¨¨¨¬¦ª¾l¥¼h¤Ÿ˜œ•••†††lllWWW†††¦¦¦¸¸¸¿¿¿¿¿¿»›±Äv«Áo¨±Œ¥§§§žžžwwwZZZ~~~···ÐÐÐÚÚÚÙÙÙÏÎÏÈŠ´Æz¬Ãr©¾u§­¬­ªªª   rrrZZZuuu¾¾¾çççðððíííåååÏÄÍÉ‚²Åx¬ÄuªÂq©°žª¬¬¬¦¦¦žžžŒŒŒtttZZZeee³³³éééùùùúúúòòòãããȦ¿É‚²Ç|®Äv«Ãtª·‰¨®®®ªªª¥¥¥šššˆˆˆqqqYYYWWW›››ÕÕÕñññûûûùùùíííÜÛÜÌ‘ºÊƒ³Ç}®Åx¬ÄuªÀv¨¯®¯¬¬¬§§§ŸŸŸ’’’………lllVVVˆˆˆÈÈÈêêêñññôôôñññæææÑÈÐÍŠ¸Êƒ³Ç}®Æz¬ÄuªÂq©° «­­­¨¨¨¡¡¡———ŒŒŒ{{{___‚‚‚¾¾¾èèèøøøôôôíííçççÞÞÞÊ­Ã̇¶É‚²Ç}®Æz¬Äv«Âq©µ‹§­­­¨¨¨¢¢¢™™™‚‚‚lllVVVŸŸŸÛÛÛ÷÷÷ýýýôôôéééâââÙÙÙË’ºÊ„³È°Ç|®Åx¬ÄuªÂq©½w§­­­¨¨¨¢¢¢ššš………uuu\\\{{{³³³âââúúúúúúîîîâââÛÛÛÍÅÌ̇¶É‚²Ç}®Æ{­Åw«ÃtªÂp¨Án§¬Ÿ¨§§§¡¡¡ššš†††|||fffTTTDÄv«ÔšÃÜ®ÑÛªÏÖŸÇÒ•ÀÏŽ»ÍŠ¸Ê„³É€±Ç|®Æz¬Äv«Ãr©Áo¨Àl§¿j¦½d£»_¡¹[ŸµRœªL˜•I•†@†f.f‰H‰Æz¬Ò—ÁÙ§ÌØ£ÊÓ™Âϼ͋¸Ë…´É‚²Ç}®Æz¬Åw«ÄuªÂp¨Án§Àk¦¾f¤¼c£»_¡¹[Ÿ¶Sœ­M™˜I•ŠCŠh4h}cvÄz¬Ð’½ÕžÆÒ•ÀÏŽ»Í‹¸Ë…´É‚²È~¯Ç|®Åx¬Äv«Ãr©Áo¨Àl§¿j¦¾f¤¼b¢º^¡¸YŸ¶Sœ­M™›J–‘G‘^L^nnn´£¯ÌŽ¹Ñ”¿Í‹¸Ì‰·Ë…´É‚²È~¯Ç|®Æz¬Äv«ÃtªÂp¨Áo¨Àk¦¿i¥½e¤»a¢¹\ ¸XžµRœ­M™¡L•†kYYYlll¯¯¯ÆÀÅÊ—»Ë†µÊ„³É€±Ç}®Ç|®Æz¬Åw«ÄuªÂq©Áo¨Àl§Àk¦¾f¤½d£»_¡¹[Ÿ·VµQ›©U”Œ~ˆ___iii§§§ÁÁÁÆÅÆ¿žµÇ}®Ç|®Æz¬Åx¬Äv«ÄuªÂq©Áo¨Àl§Àk¦¿i¥½e¤»a¢¹\ ·WžµR›žkˆ†ˆŒŒŒ‚‚‚```fffžžž´´´»»»ººº¹¬¶Á¬Äv«ÃtªÃr©Âp¨Án§Àk¦Àk¦¿i¥½e¤¼b¢¹\ ¸Xž¯\™’~Œ………ŠŠŠ~~~YYYbbb•••¨¨¨¯¯¯²²²´´´µ¡¯Ãr©Âp¨Áo¨Àl§Àk¦¿i¥¾f¤½e¤¼c£»_¡¹[Ÿ¶U–zŽˆˆˆˆˆˆ•••”””wwwTTT___‰‰‰¡¡¡§§§«««®®®¸‚¦Áo¨Án§Àk¦¿j¦¾f¤½e¤½d£¼c£»_¡¹\ ·WžµQ›¤]‘ŠŠŠ———   ’’’oooTTTxxx™™™¡¡¡¥¥¥¨Ÿ¦¿j¦Àk¦¿i¥¾f¤½d£¼b¢»a¢»_¡»_¡¹\ ¸Xž¶Sœ³Nš³N™™Ž•¦¦¦¢¢¢†††___kkkŒŒŒšššžžž«¼c£½d£¼c£¼b¢»_¡º^¡¹] ¹] ¹[Ÿ·WžµRœ³Nš³Nš·Wž­‚Ÿ­­­žžžwwwVVVuuuŽŽŽ–•–µaž¹\ º^¡º^¡º^¡¹\ ¶`ž›‡•š†”³Y›µQ›°M™­M™¶U¼c£½q¥¦¥¦ŒŒŒccc\\\zzz•Ž¶U·V·Wž·Wž¸Xž±b›—Œ“”””‘…«V•°M™¶Sœ»_¡¿i¥½d£—‚‘rrrVVV___`ƒ°M™³Nš´Oš´P›§c•‘‹ŽŽŽŠŠŠ‰‰‰ŽˆŒ¨f–¹\ ½d£¼c£¶U[VVVoHoD›J–£K––jŠŠ‰ŠŠŠŠ‰‰‰‰‰‰ŠŠŠŒŒŒ’’’š™š¨›º_¡µRœ’G’iCiq=q„R€s}‚‚‚‰‰‰‰‰‰‰‰‰ŠŠŠŽŽŽ‘‘‘•••———–––‹€‡„Rm;m]]]llluuu~~~………ˆˆˆ‰‰‰ŒŒŒŠŠŠ‰‰‰………{{{iiiTTTTTT]]]ooo{{{{{{ttteeeZZZRRRRRRRRRRRRRRRRRRRRRRRRDDDRRRRRRRRRX>XZDZRRRRRRRRRbbb~~~›››§§§¯“¦Àl§¿k¦¨”¢›››‘‘‘uuuYYYbbb‚‚‚§§§¼¼¼ºººº¸¹ÁzªÃtªÂq©»|¦®®®ªªªŸŸŸŒŒŒcccRRRRRRžžžÔÔÔÞÞÞÎÎÎËËËò¿Ç|®Åw«ÄuªÂq©° «®®®ªªª¡¡¡‰‰‰tttRRRRRR§§§äääøøøøøøæææØØØÇœ»É€±Æ{­Äv«Ãtª·Œ©°°°«««¦¦¦¡¡¡ŒŒŒxxxRRR]]]šššéééüüüÿÿÿüüüïïïØ×ØÍŒ¹Ê„³Ç}®Æz¬Äv«Áw©±°±®®®ªªª§§§šššŠŠŠRRRQQQŽŽŽÔÔÔæææøøøþþþöööçççÑÅÏÍ‹¸Ê„³È°Æz¬Äv«Ãr©±¡­¯¯¯¬¬¬¨¨¨žžž”””ŠŠŠuuuRRR†††ÃÃÃðððøøøîîîðððèèèßßßË­Ã̉·Ê„³È~¯Æ{­Äv«Ãtª¶Ž©¯¯¯¬¬¬¨¨¨¡¡¡———ŒŒŒ‚‚‚ffffffÉÉÉîîîÿÿÿÿÿÿîîîêêêáááÛÛÛÊ–ºË†µÉ‚²Ç}®Æ{­Äv«Ãtª¾x§¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽ†††uuuRRRŸŸŸÑÑÑùùùÿÿÿùùùèèèãããÜÜÜÐÍÏˇ¶Ê„³È°Ç|®Æz¬Äv«Ãr©Áo¨¯£¬¬¬¬§§§¡¡¡™™™ŽŽŽˆˆˆfff\@\»…©Ë•»Ý³ÔÞ²ÕÙ§ÌÔ›ÄÒ•Àϼ͊¸Ë…´É€±Ç}®Æ{­Åx¬ÄuªÂq©Áo¨Àl§¿j¦½d£»_¡¸YŸµR›¬N—œN‘‡N‡]@]_<_Æz¬Í‹¸ÛªÏÜ®ÑÖ ÇÒ•Àϼ̉·Ë†µÊƒ³È~¯Ç|®Æz¬Äv«ÃtªÂp¨Án§Àk¦¾f¤¼c£»_¡¹[ŸµRœ­M™˜I•’G’_<_RRR¼˜±Ì‡¶ÖŸÇÓ˜ÂÐ’½ÏŽ»Í‹¸Ë…´Êƒ³È°Ç}®Æz¬Åw«ÄuªÂq©Áo¨Àk¦¿j¦¾f¤¼b¢º^¡¹[ŸµRœ°M™˜I•˜a‰RQRRRR···Á­¼ÍŽºÎºÍ‹¸Ì‰·Ê„³É‚²È°Ç}®Æ{­Åx¬Äv«Ãr©Âp¨Án§Àk¦¿i¥½e¤»a¢¹\ ¸YŸµRœ±O™Ži„………RRRRRR±±±ÀÀÀžÃÈ•¹Ë†µÊ„³É€±Ç}®Ç|®Æz¬Åx¬Äv«ÃtªÂq©Áo¨Àk¦¿j¦¾f¤¼c£º^¡¹[Ÿ·Wž«\–Ž€Š‰‰‰‰‰‰```RRR¦¦¦¹¹¹¿¿¿ÁÀÁÀ ·Ç}®Ç|®Æz¬Åx¬Äv«ÄuªÃr©Âp¨Áo¨Àl§Àk¦¾f¤½d£»_¡¹[Ÿ·W¡r“ŒŠ‹………‘‘‘ŠŠŠ___RRRššš±±±···¹¹¹»»»¹¬µÁ¬Äv«ÄuªÃr©Âp¨Áo¨Án§Àk¦¿j¦¾f¤½d£»_¡¹\ ±`›—ƒ‘•••ƒƒƒRRRRRR‘‘‘¥¥¥¬¬¬°°°²²²´´´¶¢°Ãr©Âp¨Áo¨Àl§Àk¦¿j¦¿i¥¾f¤½e¤»a¢¹\ ¸Xž™}ŽŽŽŸŸŸšššxxxRRR   ¦¦¦¨¨¨¬¬¬®®®¸‚¦Âp¨Àl§Àk¦¿i¥¾f¤½e¤½e¤½d£»a¢º^¡¸YŸ¶U¦a”‚‚‚”””¤¤¤¨¨¨”””lllZZZ–––ŸŸŸ¡¡¡¥¥¥¨ž¥¾i¥¿j¦¾f¤¾f¤¼c£¼b¢»a¢»_¡»_¡¹\ ¸YŸ·Wž´P›¬M˜•‰‘¥¥¥±±±¤¤¤RRR–––›››žžžª~œ»a¢¼b¢¼b¢»a¢»_¡º^¡¹] ¸\Ÿ¸YŸ·Wž¶Sœ­M™¤K—·V®ƒŸ±±±±±±———lllRRR”””–•–´`¹[Ÿ¹[Ÿ¹\ ¹\ ¹\ ¶`ž›‡•š…“³Y›µQ›­M™­M™¶Sœ½d£¾u§­¬­ššš{{{RRRRRR‚‚‚–€µQ›µRœµRœ·V·V¯_š”ˆ’’’ŽŽŽŽŠªU•³Nš·V¼c£Àk¦¿i¥™„’ƒƒƒRRRRRR’c†¤K—¤K—ªL˜ªL˜¥`“‡‹ŠŠŠŠŠŠ‰‰‰‰‰‰‘Œªi˜»a¢¾f¤¼b¢·Wž–hŠRRR_<_‡A‡˜I•—I”™mŒ‘‘ŠŠŠ‰‰‰ŠŠŠ’’’šššœ›œ¨›¸\Ÿ³Nš‰B‰^;^b4bxLxxjtƒƒƒŒŒŒŒŒŒŽŽŽ‘‘‘‘‘‘‰‰‰ƒw€`;`b4bRRR```nnn{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRc1cd-dd-dc1cRRRRRRrrr–––¥¥¥ª §Àl§Àk¦Àk¦½d£ž“š•••………ooo\\\uuu®®®ÉÉÉÎÎÎÐÐÐƯÀÉ€±Ç|®Åx¬Äv«´—«®®®¦¦¦”””|||cccWWWiii¤¤¤ÓÓÓëëëîîîìììæææ̦ˆµÈ°Ç|®Æz¬½Š¬´´´°°°¦¦¦———………nnnZZZnnn°°°âââôôôúúúöööñññàßàͺʃ³Ç}®Æz¬ÄuªÀr¨­«¬¬¬¬¨¨¨¤¤¤™™™ˆˆˆttt___oooªªªÚÚÚòòòûûûûûûôôôêêêÓÊÒ̉·Êƒ³È~¯Æz¬Äv«Âq©± ¬®®®«««§§§   •••ŠŠŠxxx___nnn¤¤¤ÙÙÙíííñññôôôòòòêêêßßß˯Ä̇¶Êƒ³Ç}®Æz¬Äv«Ãr©µŽ©¯¯¯¬¬¬¨¨¨¢¢¢ššš………rrrZZZnnn¯¯¯ÙÙÙóóóúúúôôôíííèèèàààØØØÊ–ºË…´É€±Ç}®Æz¬Äv«Ãr©¼{§¯¯¯¬¬¬¨¨¨¤¤¤œœœ’’’‰‰‰~~~kkkTTTŠŠŠÇÇÇíííûûûûûûñññçççáááÚÚÚÏÊΈµÊƒ³È~¯Ç|®Æz¬ÄuªÂq©Áo¨®§¬¬¬¬§§§¢¢¢œœœ”””ŒŒŒƒƒƒuuu______«‹¡È¼Ü·ÕܲÓÜ®ÑסÈÓ™ÂÑ“¾ÎºÌ‡¶Ê„³É€±Ç|®Æz¬Åw«ÃtªÂp¨Áo¨Àk¦¿j¦½e¤»a¢¹\ ¶V±Q™§P“–RŠyUxWWW___²ˆ¤ÎºÛ«ÐÚ©ÎÙ§ÌÔšÃÐ’½ÎºÌ‡¶Ê„³É€±Ç}®Æ{­Åx¬Äv«Ãr©Áo¨Án§Àk¦¾f¤½d£»_¡¹\ ·V´Oš¤K—•I•„T€WWW___¬¬¬Ñ·ËÚ¨ÍÙ§ÌÒ•ÀБ½Í‹¸Ì‡¶Ê„³É€±Ç}®Ç|®Åx¬Äv«ÃtªÂp¨Án§Àk¦¿i¥¾f¤¼b¢»_¡¹[Ÿ·V´P›§L˜–^‡wvwbbb]]]¦¦¦ÙÙÙÝÑÜÓœÄÍ‹¸Ì‡¶Ë…´Êƒ³È°Ç}®Æ{­Æz¬Äv«ÃtªÂq©Áo¨Àl§¿j¦¾f¤½d£»a¢¹\ ¸YŸ¶U²P™“o‰‚‚‚zzzbbb]]]   ÐÐÐßßßÍÇÌÇ‘¶Êƒ³È°Ç}®Ç|®Æz¬Åx¬Äv«ÃtªÂq©Áo¨Àl§Àk¦¿i¥½e¤¼b¢º^¡¹[Ÿ·Wž¬^—Ž€Šˆˆˆ‰‰‰bbbZZZ•••ÁÁÁÓÓÓÄÄľ½¾¾´Æ{­Æz¬Åw«Äv«ÃtªÂq©Áo¨Áo¨Àl§Àk¦¿i¥¾f¤¼b¢»_¡¹[Ÿ¶V q“ŒŠ‹†††ŠŠŠ~~~bbbWWWŒŒŒ²²²Â³³³¶¶¶¸¸¸¶©³À~ªÃr©Âq©Áo¨Án§Àk¦Àk¦¿j¦¾f¤½e¤¼b¢»_¡¹[Ÿ±_š–‚ŒŒŒ………‰‰‰”””xxxYYY___ƒƒƒ¤¤¤®®®«««­­­°°°±±±²«Áo¨Án§Àk¦¿j¦¾f¤¾f¤¾f¤½d£¼b¢»_¡¹\ ·Wž™}ŒŒŒˆˆˆ™™™žžžŽŽŽrrrYYYkkk”””¡¡¡¢¢¢¥¥¥¨¨¨«««¶¤Àk¦¿i¥¾f¤½d£¼b¢¼b¢»a¢»_¡º^¡¹[Ÿ·WžµRœ¥_’ŠŠŠ‘‘‘   §§§œœœbbbYYY‚‚‚’’’›››žžž¡¡¡£™ ¼c£¼c£¼c£»a¢»_¡º^¡¹\ ¹\ ¹[Ÿ¸Xž¶UµQ›°M™³N™—Œ“¢¢¢¬¬¬¨¨¨‘‘‘tttTTT```|||–––ššš§y™¹\ ¹\ ¹\ ¹\ ¹[Ÿ¸YŸ·Yž¶X¶UµQ›³Nš­M™³Nš·Wž­ž¬¬¬§§§———zzz\\\]]]wwwŠŠŠ‘Ž²VšµRœ¶Sœ·V·V¶U²Wš—‚”Ž°Q˜°M™°M™´P›¸Xž¼c£»l¤¡Ÿ¡”””zzz]]]ZZZrrrŽk…¤K—§L˜ªL˜°M™´Oš«W–ŽŠŠŠŠ‰‰‰€‰¬X–¶Sœ¸YŸ»_¡¼b¢¹\ ˜xuuu\\\WWW|H|ŠCŠ’G’›J–ªL˜¤a’Š„ˆ‰‰‰ŠŠŠŒŒŒŽŽŽ”“«k˜¹[Ÿ¹[ŸµQ›˜I•|H|YYYc6cp4p};}‡F‡‹d‚‚‚†††‰‰‰ŠŠŠŒŒŒŒŒŒŒŒŒŠŠŠ’nˆ™L’};}j1ja6aZHZYWYeeettt~~~~~~wwwooo\\\VUVXFXRRRRRR___ccc___YYYRRRRRR```fffeeebbbYYYRRRRRRRRRkkk‘‘‘ŸŸŸ¨¨¨¬¬¬€›}š˜žžž•••†††lllWWW†††¦¦¦¸¸¸¿¿¿¿¿¿²¸¸^™•V“¤ªª§§§žžžwwwZZZ~~~···ÐÐÐÚÚÚÙÙÙÐÐЖ´³]š–W• ž®®®ªªª   rrrZZZuuu¾¾¾çççðððíííåååÕÖÖm¤¡\™•Y—‘^—’®¯¯¬¬¬¦¦¦žžžŒŒŒtttZZZeee³³³éééùùùúúúòòòããã¶ÅÅe¡_œ˜Z˜“X–—¨¨®®®ªªª¥¥¥šššˆˆˆqqqYYYWWW›››ÕÕÕñññûûûùùùíííÝÝÝ“·¶f¢ža™\™•Y—‘xŸœ°°°¬¬¬§§§ŸŸŸ’’’………lllVVVˆˆˆÈÈÈêêêñññôôôñññæææ×ØØt«©f¢ža™]š–Y—‘\–‘®¯¯­­­¨¨¨¡¡¡———ŒŒŒ{{{___‚‚‚¾¾¾èèèøøøôôôíííçççÞÞÞ½ÉÉj¦£e¡a™]š–Z˜“V”Ž˜¨§­­­¨¨¨¢¢¢™™™‚‚‚lllVVVŸŸŸÛÛÛ÷÷÷ýýýôôôéééâââÙÙÙ—¸¸g£ŸcŸ›_œ˜\™•Y—‘V”Ž{žœ­­­¨¨¨¢¢¢ššš………uuu\\\{{{³³³âââúúúúúúîîîâââÛÛÛÓÓÓv«¨e¡a™^›—[˜”X–U“]”¬¬¬§§§¡¡¡ššš†††|||fffTTTxxx¹¹¹âââøøøõõõçççÜÜÜÕÕÕ¼ÇÇg£Ÿd œ_œ˜]š–Z˜“W•T’ŒRŠ˜¤¤¥¥¥   ššš‘‘‘ˆˆˆ~~~qqqTTT3a^]š–x³±ˆÀÀ„½½{µ³r­ªn©¦h¤¡e¡a™]š–[˜”Y—‘U“S‘‹Q‰NŠ„M‡‚KƒH{Cyu>qn8jg3a^&GE7`^\™•t¯¬¹·w²°q¬©n©¦h¤¡e¡bžš_œ˜\™•Z˜“W•T’ŒRŠPŽ‡NŠ„L†K‚~H~zCyu>qn9kh6fc,KI\ihY–n©¦v±¯n©¦k§¤h¤¡e¡bžš_œ˜]š–Z˜“X–U“T’ŒQ‰O†N‰ƒL…€I€|G}yCxt>qn:li7heJUTlll•¦¥l¦£n©¦i¥¢g£Ÿd œa™_œ˜]š–[˜”Y—‘V”ŽT’ŒRŠQ‰NŠ„MˆƒKƒH{E{wBws>qnArok{z___iii§§§²ººw©§bžša™_œ˜]š–\™•Z˜“Y—‘V”ŽT’ŒRŠQ‰O†N‰ƒL…€I€|F|xCxt?spHtq}ˆ‡‚‚‚```fffžžž´´´¸ºº…¦¥]™•[˜”Z˜“X–W•U“S‘‹Q‰Q‰O†N‰ƒL†I€|G}yCyu@tq[yw‡‰‰~~~YYYbbb•••¨¨¨¯¯¯²²²¬«dš–W•U“T’ŒRŠQ‰O†NŠ„N‰ƒM‡‚KƒH{DzvKzvtƒˆˆˆ•••”””wwwTTT___‰‰‰¡¡¡§§§«««®®®’¦¥T’ŒS‘‹Q‰PŽ‡NŠ„N‰ƒMˆƒM‡‚KƒI€|F|xBwsn‚€ŠŠŠ———   ’’’oooTTTxxx™™™¡¡¡¥¥¥¨¨¨j–“Q‰O†NŠ„MˆƒL†L…€KƒKƒI€|G}yCyu?spRzw———¦¦¦¢¢¢†††___kkkŒŒŒšššžžž“œœM‡‚MˆƒM‡‚L†KƒK‚~I€|I€|H{F|xCxt?sp?spF|x—  ­­­žžžwwwVVVuuuŽŽŽ———jŠI€|K‚~K‚~K‚~I€|H{H~zG}yDzvBws?ro>qnDzvM‡‚x›™¦¦¦ŒŒŒccc\\\zzzŠŒŒH{xE{wF|xF|xG}yG}yI|y}ŒzŠ‰Ewt?ro?roCyuKƒO†QŠ„ŽrrrVVV___^sr?ro?sp@tqAvrBwsLzw‹ŠŠŠŠ|†…IwtCyuI€|MˆƒM‡‚Dzv[poVVV5VT4c`9kh:mj<olPyvƒ‡‡‰‰‰‰‰‰ŠŠŠŒŒŒ‹Z‡ƒKƒK‚~Cxt6gd2PN.QO2_\6gdTur†ˆˆ‰‰‰‰‰‰ŠŠŠŽŽŽ‘‘‘••••––a†ƒ=pm2_\,NK2QONdcuuu~~~………ˆˆˆ‰‰‰ŒŒŒŠŠŠ‰‰‰………{{{Ka`-HGTTT]]]ooo{{{{{{ttteeeZZZRRRRRRRRRRRRRRRRRRRRRRRRDDDRRRRRRRRR7HG:LKRRRRRRRRRbbb~~~›››§§§¤ªªU’ŒSŠ££›››‘‘‘uuuYYYbbb‚‚‚§§§¼¼¼ººº»»»‡¦¥X–V”Ž€Ÿ®®®ªªªŸŸŸŒŒŒcccRRRRRRžžžÔÔÔÞÞÞÎÎÎËËËÆÇÇhŸœ[˜”Y—‘^—’¯°°®®®ªªª¡¡¡‰‰‰tttRRRRRR§§§äääøøøøøøæææØØزÂÂd œ^›—Z˜“X–˜©©°°°«««¦¦¦¡¡¡ŒŒŒxxxRRR]]]šššéééüüüÿÿÿüüüïïïÛÛÛ“·¶g£Ÿa™]š–Z˜“y ž²²²®®®ªªª§§§šššŠŠŠRRRQQQŽŽŽÔÔÔæææøøøþþþöööçççØÙÙu¬ªg£ŸcŸ›]š–Z˜“]—’°±±¯¯¯¬¬¬¨¨¨žžž”””ŠŠŠuuuRRR†††ÃÃÃðððøøøîîîðððèèèßßß½ÉÉk§¤g£Ÿbžš^›—Z˜“X–™©¨¯¯¯¬¬¬¨¨¨¡¡¡———ŒŒŒ‚‚‚ffffffÉÉÉîîîÿÿÿÿÿÿîîîêêêáááÛÛÛ—¸¸i¥¢e¡a™^›—Z˜“X–|Ÿ¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽ†††uuuRRRŸŸŸÑÑÑùùùÿÿÿùùùèèèãããÜÜÜÓÓÓv«¨g£ŸcŸ›_œ˜]š–Z˜“W•^•‘¯¯¯¬¬¬§§§¡¡¡™™™ŽŽŽˆˆˆfffRRR´´´ÑÑÑúúúüüüðððãããÜÜÜÖÖÖ¼ÇÇh¤¡d œa™^›—\™•Y—‘V”ŽT’Œ›§§«««¥¥¥   ™™™‘‘‘ˆˆˆtttRRR3IHaœ˜s«©–ÈÉŸÌÍÁÀ„·¶|±¯q©§k¦£f¢žbžš_œ˜]š–Z˜“X–U“S‘‹Q‰NŠ„M‡‚KƒH{CxtAsp@mjGmj;LK;LK]š–j¦£€¹¸z´²t¯¬q¬©n©¦h¤¡f¢žcŸ›a™]š–[˜”Y—‘V”ŽT’ŒQ‰PŽ‡NŠ„L†K‚~H{Cxt?ro8jg:li6KJRRR„¤£e¡n©¦p«¨n©¦k§¤g£Ÿe¡cŸ›a™^›—\™•Z˜“W•U“S‘‹Q‰O†N‰ƒL…€I€|H~zCxt?sp8jgYxvQRRRRR±±±œ²²k¥¢j¦£i¥¢g£Ÿd œa™_œ˜]š–\™•Z˜“X–V”ŽT’ŒQ‰PŽ‡NŠ„M‡‚K‚~H{F|xAvr@spi€~‰‰‰```RRR¦¦¦¹¹¹¯¸¸r¤¡cŸ›a™_œ˜]š–\™•Z˜“Y—‘W•U“T’ŒRŠQ‰NŠ„MˆƒKƒH{F|xCyuLxuv€‘‘‘ŠŠŠ___RRRššš±±±···¶¸¸ˆ¨§]™•[˜”Z˜“Y—‘W•U“T’ŒS‘‹Q‰PŽ‡NŠ„MˆƒKƒI€|F|xDyua€~|~~•••ƒƒƒRRRRRR‘‘‘¥¥¥¬¬¬°°°²²²ž««e›—W•U“T’ŒRŠQ‰PŽ‡O†NŠ„N‰ƒL…€I€|G}yL|xyˆ†ŸŸŸšššxxxRRR   ¦¦¦¨¨¨¬¬¬®®®’¦¥U“RŠQ‰O†NŠ„N‰ƒN‰ƒMˆƒL…€K‚~H~zDzvq†„‚‚‚”””¤¤¤¨¨¨”””lllZZZ–––ŸŸŸ¡¡¡¥¥¥§§§i•’PŽ‡NŠ„NŠ„M‡‚L†L…€KƒKƒI€|H~zF|xAvrQxu’’’¥¥¥±±±¤¤¤RRR–––›››žžž’››L…€L†L†L…€KƒK‚~I€|H{H~zF|xCyu>qn;nkF{w˜¡¡±±±±±±———lllRRR”””———jŒ‰H{H{I€|I€|I€|H{K~{I|yDzvBws>qn>qnCyuMˆƒ~ ž®®®ššš{{{RRRRRR‚‚‚JzvCxtCxtE{wE{wE{wI{wŒŠ‰Euq>qn?spE{wM‡‚Q‰[‘Œ”””ƒƒƒRRRRRRv}|;nk;nk=pm=pm?spHws}‡†ŠŠŠ‰‰‰|†…K{wF|xL…€NŠ„L†F|x{‚RRRJPO2_\8jg8jg>qnU{‚ˆ‡‰‰‰ŠŠŠ’’’’˜—Y‡ƒKƒH{?sp3`]JPO-HG.WU7b_^vt‚‚ŒŒŒŒŒŒŽŽŽ‘‘‘f}>mi$DB-HGQRR```nnn{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR<LK@MMRRRRRRRRRrrr–––¥¥¥ªªªˆ¡ŸQ‰Q‰‹œ›œœœ•••………ooo\\\uuu®®®ÉÉÉÎÎÎÐÐÐÈÊÊl¤ _œ˜\™•kš²²²®®®¦¦¦”””|||cccWWWiii¤¤¤ÓÓÓëëëîîîìììæææµÇÇi¥¢cŸ›_œ˜]š– ¯®´´´°°°¦¦¦———………nnnZZZnnn°°°âââôôôúúúöööñññââ⊵³f¢ža™]š–Y—‘yžœ®®®¬¬¬¨¨¨¤¤¤™™™ˆˆˆttt___oooªªªÚÚÚòòòûûûûûûôôôêêê×ÙÙm¨¥f¢žbžš]š–Z˜“\–‘¯°°®®®«««§§§   •••ŠŠŠxxx___nnn¤¤¤ÙÙÙíííñññôôôòòòêêêßß߸ÈÈj¦£f¢ža™]š–Z˜“W•™©¨¯¯¯¬¬¬¨¨¨¢¢¢ššš………rrrZZZnnn¯¯¯ÙÙÙóóóúúúôôôíííèèèàààØØØ–··h¤¡d œa™]š–Z˜“W•} ž¯¯¯¬¬¬¨¨¨¤¤¤œœœ’’’‰‰‰~~~kkkTTTŠŠŠÇÇÇíííûûûûûûñññçççáááÚÚÚÒÒÒy«©f¢žbžš_œ˜]š–Y—‘V”Ž^•‘®®®¬¬¬§§§¢¢¢œœœ”””ŒŒŒƒƒƒuuu______¦¦¦ÏÏÏ÷÷÷øøø÷÷÷êêêàààÚÚÚÓÓÓÄÈÈh£Ÿd œ_œ˜]š–[˜”X–U“T’Œ–¥¤¨ªª¦¦¦¡¡¡›››•••ŒŒŒ………{{{lllWWW<VUQ‰u­«˜ÉÉ™ÉÉ–Çlj»º€´²x®¬n¨¥i¤¡d œa™^›—\™•Z˜“W•T’ŒS‘‹Q‰NŠ„O‰ƒP†R„€S€}QzwPvsNqnHge;POY]]b”x³±‰ÁÁˆÀÀw²°s®«n©¦j¦£g£Ÿd œa™_œ˜\™•Z˜“X–U“S‘‹Q‰O†NŠ„L†KƒH{E{wAvr<ol8jg;gdS^]]]]£¥¥š¾¾„½½{µ³n©¦j¦£h¤¡f¢žcŸ›a™^›—]š–Z˜“X–V”ŽT’ŒRŠPŽ‡NŠ„MˆƒL…€I€|H~zDzv@tq<olQtquxxbbb]]]   ÐÐмÍÍs«©g£Ÿf¢žcŸ›a™_œ˜]š–\™•Z˜“X–V”ŽT’ŒRŠQ‰O†N‰ƒL†K‚~H{F|xCxtAspi}‰‰‰bbbZZZ•••ÁÁÁÓÓÓ¶¾¾s£ _œ˜^›—]š–[˜”Z˜“X–V”ŽT’ŒT’ŒRŠQ‰O†NŠ„L†KƒH{E{wCxtNyvyƒ‚ŠŠŠ~~~bbbWWWŒŒŒ²²²Â³³³³µµ…¥¤[˜“Y—‘W•V”ŽT’ŒS‘‹Q‰Q‰PŽ‡NŠ„N‰ƒL†KƒH{E{wCxt`}‚„„‰‰‰”””xxxYYY___ƒƒƒ¤¤¤®®®«««­­­°°°˜¨§]–‘T’ŒS‘‹Q‰PŽ‡NŠ„NŠ„NŠ„MˆƒL†KƒI€|F|xJ{wu†…ˆˆˆ™™™žžžŽŽŽrrrYYYkkk”””¡¡¡¢¢¢¥¥¥¨¨¨«««¢¡Q‰O†NŠ„MˆƒL†L†L…€KƒK‚~H{F|xCxto„‚ŠŠŠ‘‘‘   §§§œœœbbbYYY‚‚‚’’’›››žžž¡¡¡¢¢¢b‹M‡‚M‡‚L…€KƒK‚~I€|I€|H{G}yDzvBws?roRzw•••¢¢¢¬¬¬¨¨¨‘‘‘tttTTT```|||–––ššš––J€|I€|I€|I€|H{H~zF|xE{wDzvBws?sp>qn?spF|x–ŸŸ¬¬¬§§§———zzz\\\]]]wwwŠŠŠb„€CxtCyuE{wE{wDzvCyuGyvEwt?sp?ro?roAvrG}yM‡‚t—”¢¢¢”””zzz]]]ZZZrrr~€€?pm<ol=pm?ro@tq?spEuqz†…z…„Dtp@tqCyuH~zKƒL†M‚~ŠŒŒuuu\\\WWWQed3a^6gd9kh=pm?spFuq|†…ŠŠŠŒŒŒ‹ŠL}yG}yH{H{Bws8jgQedYYY-HG)MK.WU2_\6gdDpm|ƒ‚‰‰‰ŠŠŠŒŒŒŒŒŒ‚‰ˆIvs;nk7if.WU&IF,GE2JI)KI5XVmqq~~~~~~wwwimm0OM'IF2IHRRRRRR___ccc___YYYRRRRRR \ No newline at end of file
diff --git a/bubbob/images/extra6.ppm b/bubbob/images/extra6.ppm
new file mode 100644
index 0000000..26dbee1
--- /dev/null
+++ b/bubbob/images/extra6.ppm
Binary files differ
diff --git a/bubbob/images/extra7.ppm b/bubbob/images/extra7.ppm
new file mode 100644
index 0000000..9482b77
--- /dev/null
+++ b/bubbob/images/extra7.ppm
@@ -0,0 +1,13 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+8 56
+255
+989USU.,-=;=×××äää»»»€312XXXÍÍÍ×××µµµˆˆˆYYY*)*535¤¤¤ªªª•••rqr978 /-/EDE.,. 0.0dcdÖÖÖÖÕÖRPR «««ØØØÜÜܳ³³878
+
+±±±³³³šššqppjhiƒƒƒsssUUU*)*
+
+utuigi¤£¤ÖÖÖäääZXZ-+,£££ÌÌÌ××ו”•|z{¤¤¤ªªª”“”onnzzzlll ?>???? .-.A@AGEFÓÓÓÅÅÅ›š›qopéééÓÓÓ¤¤¤423KIK···”””"!"‘‘‘ŒŒŒqpqHFG]]]535///
+ 
+MKLcacA?AàààÖÖÖ¤££…„…ÚÚÚÒÒÒ¨¨¨767Ž®®®ªªª‰ˆ‰pppyyy#"#
+
+CCCJII%#$ECDÐÎÏÔÔÔ_]^/./²±²ÞÞÞÚÚÚ¬¬¬  poo›››µµµ´´´•”• *()WWWuuu‡†‡trt&%&=;<mkl\[\645‚¶¶¶ãããÞÞÞ_]_%#$QQQ€€€­­­ÏÏÏÌÌÌnnn,*,hgh£££   312323 \ No newline at end of file
diff --git a/bubbob/images/extra8.ppm b/bubbob/images/extra8.ppm
new file mode 100644
index 0000000..af6288d
--- /dev/null
+++ b/bubbob/images/extra8.ppm
Binary files differ
diff --git a/bubbob/images/fire_drop.ppm b/bubbob/images/fire_drop.ppm
new file mode 100644
index 0000000..dbb706b
--- /dev/null
+++ b/bubbob/images/fire_drop.ppm
Binary files differ
diff --git a/bubbob/images/fire_surface.ppm b/bubbob/images/fire_surface.ppm
new file mode 100644
index 0000000..8ef46ee
--- /dev/null
+++ b/bubbob/images/fire_surface.ppm
Binary files differ
diff --git a/bubbob/images/fish_0.ppm b/bubbob/images/fish_0.ppm
new file mode 100644
index 0000000..4f2040f
--- /dev/null
+++ b/bubbob/images/fish_0.ppm
Binary files differ
diff --git a/bubbob/images/flappy.ppm b/bubbob/images/flappy.ppm
new file mode 100644
index 0000000..0724f84
--- /dev/null
+++ b/bubbob/images/flappy.ppm
Binary files differ
diff --git a/bubbob/images/flapy_angry.ppm b/bubbob/images/flapy_angry.ppm
new file mode 100644
index 0000000..4e71e8b
--- /dev/null
+++ b/bubbob/images/flapy_angry.ppm
Binary files differ
diff --git a/bubbob/images/game_over_0.ppm b/bubbob/images/game_over_0.ppm
new file mode 100644
index 0000000..d76ecae
--- /dev/null
+++ b/bubbob/images/game_over_0.ppm
Binary files differ
diff --git a/bubbob/images/ghost.ppm b/bubbob/images/ghost.ppm
new file mode 100644
index 0000000..424668e
--- /dev/null
+++ b/bubbob/images/ghost.ppm
@@ -0,0 +1,6 @@
+P6
+# CREATOR: GIMP PNM Filter Version 1.1
+32 256
+255
+ßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞßáÞÕ×ÔÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÕ×ÔöøõßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞßáÞÕ×ÔÈÊÇÈÊÇÕ×ÔöøõöøõëîêëîêëîêöøõöøõöøõöøõöøõëîêíÖØìt‰ír‡özŽ÷~ðtŠíiŒÚ¾ÅÕ×Ôöøõöøõëîêëîêöøõöøõöøõöøõöøõöøõëãâõr‘íjír‡özŽ÷~ðtŠïd‰ñ`ÙÅÉßáÞöøõëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêø£·õe‚ïs‰ë‡•ð’œõ˜ó‘îc‰í?|îQØÆÉÕÔÒöøõëîêëîêëîêöøõöøõöøõöøõëîêëîêëîêõzðfî뛠謭ï{“ên‚çIrê<oíW…Ô¡¯öøõëîêëîêëîêëîêëîêëîêëîêëîêßáÞßáÞõníl쇒êÇÁëÔÍìm|ëbvèMmæ9jé0oí-r§©¦ëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×ÔðuŠëf|ó£©ïØÒîéãð\qðVvëDlå.gæ fâ f§©¦ßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇ爚ëYsïi|ó£©ðåØîîçíGjëGkè8hå%då_áZ§©¦²´°ßáÞßáÞÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»Ò°³ò\tí_{ï^vïìÞó£©æ8fæ8få/bå WáIà<²´°²´°Õ×ÔÕ×ÔÈÊDz´°²´°¼¾»ÈÊÇÕ×ÔÈÊDz´°²´°¼¾»â~ìXuëQlïHgë9eå0då,cä'[áEÚ(Ö¼¾»¼¾»ÈÊÇÈÊǼ¾»²´°²´°²´°¼¾»ÈÊǼ¾»¼¾»¼¾»¼¾»¿·¶á\}ìIní<hè/bå&_ä ZâMÝ:ØÓÈÊǼ¾»¼¾»²´°¼¾»¼¾»²´°¼¾»ÈÊÇÈÊÇÈÊǼ¾»¿µµßj‰ì1fç#]åXáLÛ0ÛÝ,‘Õ³±ÈÊÇÈÊǧ©¦²´°²´°ÈÊÇÈÊǼ¾»¼¾»²´°²´°Á’™“6¾JÏGÖ/JÙfgÞÒÐÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°²´°ßáÞßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÈÊǧ©¦§©¦ßáÞßáÞßáÞßáÞßáÞÕ×ÔÈÊÇÈÊÇßáÞßáÞßáÞÕ×ÔÈÊÇÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞöøõöøõöøõÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔöøõëîêëîêßáÞëîêëîêöøõëîê¼¾»¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÈÊǼ¾»ÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°ëîêÕ×Ô¼¾»²´°²´°ÈÊÇÈÊÇÕ×ÔÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÈÊǼ¾»²´°Õ×ÔÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»Õ×ÔÕ×ÔÈÊǼ¾»¼¾»ÈÊÇÈÊÇÈÊDz´°²´°²´°¼¾»¼¾»¼¾»ÈÊÇÕ×ÔÈÊÇÈÊÇÈÊÇÈÊǼ¾»¼¾»²´°²´°ßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»²´°²´°§©¦§©¦²´°²´°²´°²´°¼¾»²´°²´°²´°§©¦ßáÞÈÊǼ¾»¼¾»¼¾»²´°§©¦§©¦²´°²´°²´°Õ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÕ×ÔöøõßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞßáÞÕ×ÔÈÊÇÈÊÇÕ×Ôöøõöøõëîêëîêëîêöøõöøõöøõöøõöøõëîêëëè襨ê|…ñn|ôwŒç Ñ¬²Õ×ÔÕ×Ôöøõöøõëîêëîêöøõöøõöøõöøõöøõöøõëëèô‡›îj}íq…õy÷}Žðt‰ìd†æ}œÖÍÎßáÞöøõëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêø¸Æõeïs‰ë†•ð’œõ˜ó‘îc‰ì?|íP€ÙÀÅÕ×Ôöøõëîêëîêëîêöøõöøõöøõöøõëîêëîêëîêõ”ðfî뛠謭ï{“ên‚çIrê<oîO€Ï±¸öøõëîêëîêëîêëîêëîêëîêëîêëîêßáÞßáÞölíl쇒êÇÁëÔÍìm|ëbvèMmæ9jé0oê6w§©¦ëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×Ôòj‚ëf|ó£©ïØÒîéãð\qðVvëDlå.gæ fâ f§©¦ßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇëyëYsïi|ó£©ðåØîîçíGjëGkè8hå%då_áZ§©¦²´°ßáÞßáÞÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»Ö©­ò\tí_{ï^vïìÞó£©æ8fæ8få/bå WáIà<²´°²´°Õ×ÔÕ×ÔÈÊDz´°²´°¼¾»ÈÊÇÕ×ÔÈÊDz´°²´°¼¾»äzŽìXuëQlïHgë9eå0då,cä'[áEÚ(Ö¼¾»¼¾»ÈÊÇÈÊǼ¾»¼¾»²´°¼¾»¼¾»ÈÊǼ¾»¼¾»¼¾»¼¾»¾¹¸Þd‚ìIní<hè/bå&_ä ZâMÝ:ØÓÈÊǼ¾»¼¾»¼¾»¼¾»ÈÊÇÈÊǼ¾»¼¾»²´°¼¾»ÈÊÇÈÊÇÈÊǼ¾»½¹¸Ùw‘ì1fç#]åXáLÛ0ÛÝ,‘Õ³±ÈÊÇÈÊDz´°¼¾»ÈÊÇÕ×Ô¼¾»ÈÊÇÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°½›ŸÓZyÝ0eÞOÕ!?Ø]^ÞÐÎÕ×ÔÕ×ÔÈÊÇÕ×ÔßáÞßáÞëîêßáÞÈÊDz´°²´°²´°²´°²´°¼¾»Õ×ÔßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÈÊÇÈÊǼ¾»²´°²´°Õ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÈÊÇÈÊÇÕ×Ô¼¾»ÈÊÇÈÊÇÕ×ÔßáÞßáÞÕ×ÔÈÊÇÕ×ÔßáÞöøõöøõöøõÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞöøõöøõöøõöøõöøõöøõëîêëîê²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔöøõëîêëîêßáÞëîêëîêßáÞöøõëîêëîêëîê¼¾»²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÈÊǼ¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔëîêëîêßáÞÕ×ÔÕ×ÔßáÞÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°Õ×Ô¼¾»²´°²´°ÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÈÊǼ¾»²´°ÈÊÇÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔÈÊǼ¾»¼¾»ÈÊÇÈÊÇÈÊDz´°²´°²´°¼¾»¼¾»¼¾»ÈÊÇÕ×ÔÈÊÇÈÊÇÈÊÇÈÊǼ¾»¼¾»²´°²´°ßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»²´°²´°§©¦§©¦²´°²´°²´°²´°¼¾»²´°²´°²´°§©¦ßáÞÈÊǼ¾»¼¾»¼¾»²´°§©¦§©¦²´°²´°²´°Õ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÕ×ÔöøõßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞßáÞÕ×ÔÈÊÇÈÊÇÕ×Ôöøõöøõëîêëîêëîêöøõöøõöøõöøõöøõëîêëëè襨ê|…ñn|ôwŒç Ñ¬²Õ×ÔÕ×Ôöøõöøõëîêëîêöøõöøõöøõöøõöøõöøõëëèô‡›îj}íq…õy÷}Žðt‰ìd†æ}œÖÍÎßáÞöøõëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêø¸Æõeïs‰ë†•ð’œõ˜ó‘îc‰ì?|íP€ÙÀÅÕ×Ôöøõëîêëîêëîêöøõöøõöøõöøõëîêëîêëîêõ”ðfî뛠謭ï{“ên‚çIrê<oîO€Ï±¸öøõëîêëîêëîêëîêëîêëîêëîêëîêßáÞßáÞölíl쇒êÇÁëÔÍìm|ëbvèMmæ9jé0oê6w§©¦ëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×Ôòj‚ëf|ó£©ïØÒîéãð\qðVvëDlå.gæ fâ f§©¦ßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇëyëYsïi|ó£©ðåØîîçíGjëGkè8hå%då_áZ§©¦²´°ßáÞßáÞÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»Ö©­ò\tí_{ï^vïìÞó£©æ8fæ8få/bå WáIà<²´°²´°Õ×ÔÕ×ÔÈÊDz´°²´°¼¾»ÈÊÇÕ×ÔÈÊDz´°²´°¼¾»äzŽìXuëQlïHgë9eå0då,cä'[áEÚ(Ö¼¾»¼¾»ÈÊÇÈÊǼ¾»²´°²´°²´°¼¾»ÈÊǼ¾»¼¾»¼¾»¼¾»¾¹¸Þd‚ìIní<hè/bå&_ä ZâMÝ:ØÓÈÊǼ¾»¼¾»²´°¼¾»¼¾»²´°¼¾»ÈÊÇÈÊÇÈÊǼ¾»½¹¸Ùw‘ì1fç#]åXáLÛ0ÛÝ,‘Õ³±ÈÊÇÈÊǧ©¦²´°²´°ÈÊÇÈÊǼ¾»¼¾»²´°²´°½›ŸŒ3ÃKÙKÖ#@Ø]^ÞÐÎÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°²´°ßáÞßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÈÊǧ©¦§©¦ßáÞßáÞßáÞßáÞßáÞÕ×ÔÈÊÇÈÊÇßáÞßáÞßáÞÕ×ÔÈÊÇÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞöøõöøõöøõÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔöøõëîêëîêßáÞëîêëîêöøõëîê¼¾»¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÈÊǼ¾»ÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°ëîêÕ×Ô¼¾»²´°²´°ÈÊÇÈÊÇÕ×ÔÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÈÊǼ¾»²´°Õ×ÔÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»Õ×ÔÕ×ÔÈÊǼ¾»¼¾»ÈÊÇÈÊÇÈÊDz´°²´°²´°¼¾»¼¾»¼¾»ÈÊÇÕ×ÔÈÊÇÈÊÇÈÊÇÈÊǼ¾»¼¾»²´°²´°ßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»²´°²´°§©¦§©¦²´°²´°²´°²´°¼¾»²´°²´°²´°§©¦ßáÞÈÊǼ¾»¼¾»¼¾»²´°§©¦§©¦²´°²´°²´°Õ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÕ×ÔöøõßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞßáÞÕ×ÔÈÊÇÈÊÇÕ×Ôöøõöøõëîêëîêëîêöøõöøõöøõöøõöøõëîêëäá雟ê}†ðt‚òƒ–啥ε¸Õ×ÔÕ×ÔöøõöøõëîêëîêöøõöøõöøõöøõöøõöøõëäâõsŒîj}íq…õy÷}Žðt‰ìd†æ}œÖÍÎßáÞöøõëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêø§ºõeïs‰ë†•ð’œõ˜ó‘îc‰ì?|íP€ÙÀÅÕ×Ôöøõëîêëîêëîêöøõöøõöøõöøõëîêëîêëîêõ}‘ðfî뛠謭ï{“ên‚çIrê<oîO€Ï±¸öøõëîêëîêëîêëîêëîêëîêëîêëîêßáÞßáÞöm€íl쇒êÇÁëÔÍìm|ëbvèMmæ9jé0oê6w§©¦ëîêßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞÕ×Ôño†ëf|ó£©ïØÒîéãð\qðVvëDlå.gæ fâ f§©¦ßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊÇëzëYsïi|ó£©ðåØîîçíGjëGkè8hå%då_áZ§©¦²´°ßáÞßáÞÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»ÚŸ¦ò\tí_{ï^vïìÞó£©æ8fæ8få/bå WáIà<²´°²´°Õ×ÔÕ×ÔÈÊDz´°²´°¼¾»ÈÊÇÕ×ÔÈÊDz´°²´°¼¼¹çtŠìXuëQlïHgë9eå0då,cä'[áEÚ(Ö¼¾»¼¾»ÈÊÇÈÊǼ¾»§©¦ÈÊÇÈÊǼ¾»¼¾»¼¾»ÁµµÜj†ìJoí=jè0cå&_ä ZâMÝ:ØÓ ÈÊǼ¾»²´°²´°ÈÊÇÈÊÇÈÊÇÈÊǼ¾»¼¾»Ì—¦ÞQ€æ%däYáMÛ0ÚÝ/’Õ­¬ÈÊÇÈÊǼ¾»¼¾»²´°²´°!U%q(nÚÞØÖÕ×ÔÕ×ÔÈÊǧ©¦§©¦ßáÞßáÞßáÞßáÞÕ×ÔÕ×ÔÈÊÇÕ×ÔßáÞßáÞÕ×ÔÈÊÇÈÊÇßáÞßáÞßáÞÕ×ÔÈÊÇÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔëîêëîêßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇëîêßáÞÈÊǼ¾»ÈÊÇÕ×ÔÕ×ÔÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊǼ¾»²´°²´°ëîêÕ×Ô¼¾»²´°²´°ÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»Õ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊǼ¾»²´°ëîêÕ×ÔÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊDz´°¼¾»¼¾»¼¾»¼¾»Õ×ÔÕ×ÔÕ×ÔÈÊÇÈÊǼ¾»¼¾»¼¾»¼¾»Õ×ÔÕ×ÔÈÊǼ¾»¼¾»ÈÊÇÈÊǼ¾»²´°²´°²´°¼¾»¼¾»¼¾»ÈÊÇÕ×ÔÈÊÇÈÊÇÈÊÇÈÊǼ¾»¼¾»²´°²´°ßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»²´°²´°§©¦§©¦²´°²´°²´°²´°¼¾»²´°²´°²´°§©¦ßáÞÈÊǼ¾»¼¾»¼¾»²´°§©¦§©¦²´°²´°²´°ÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞëîêÕ×ÔÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîêÕ×ÔÕ×ÔÈÊÇÕ×ÔßáÞßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞöøõßáÞÕ×ÔØ´¸âŒ›òsŽöl„ê{†å¥¨ßßÜëîêöøõöøõöøõöøõöøõëîêëîêëîêöøõöøõßáÞß××èžëd…ït‰÷}öyŽíq†íj}ð†šëìéëîêöøõöøõöøõöøõöøõëîêëîêöøõöøõÕÖÓÙÀÅîR‚ì?{îc‰ó‘õ˜ð’œë†•ïs‰õeñ¶ÃëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêöøõÏ°¸îPê<oçIrên‚ï{“謭뛠îðfô‚•ßáÞëîêëîêöøõöøõöøõöøõëîêëîêëîêöøõ§©¦ê6wé0oæ9jèMmëbvìm|ëÔÍêÇÁ쇒ílõl€ßáÞßáÞëîêëîêëîêëîêëîêëîêëîêëîêöøõ§©¦â fæ få.gëDlðVvð\qîéãïØÒó£©î€‹ëf|óg€Õ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîê²´°§©¦áZå_å%dè8hëGkíGjîîçðåØó£©ïi|ëYsìvŒÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞ²´°²´°à<áIå Wå/bæ8fæ8fó£©ïìÞï^uí_zò\sؤ©¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔßáÞßáÞ¼¾»¼¾»ÖÚ(áEä'[å,cå0dë9eïHgìRkíYræw‡¼½º²´°²´°ÈÊÇÕ×ÔÈÊǼ¾»²´°²´°ÈÊÇÕ×ÔÕ×Ô¼¾»ÈÊÇÓØÞ<âMä Zå&_è/bí;gîJlâg}¾¸¶¼¾»¼¾»¼¾»¼¾»ÈÊǼ¾»¼¾»²´°²´°¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÔ²®à6”ÜÛ.áJäWæ!^è+eØsŽ¾¸·¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»ÈÊǼ¾»²´°²´°ÈÊÇÕ×ÔÕÖÓßÒ×Úe[Ô$3ÙCà7k„5»™ ²´°²´°¼¾»¼¾»ÈÊÇÈÊǼ¾»¼¾»²´°ÈÊÇÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞ²´°²´°§©¦²´°ÈÊÇÈÊÇÕ×ÔßáÞßáÞÕ×ÔßáÞßáÞ§©¦§©¦ßáÞÕ×ÔÈÊÇÕ×ÔÕ×ÔßáÞÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇöøõöøõöøõÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»²´°ëîêöøõëîêëîêßáÞëîêëîêöøõ²´°²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊǼ¾»ÈÊÇßáÞëîꧩ¦²´°¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊÇÕ×ÔÈÊÇÈÊDz´°²´°¼¾»Õ×ÔßáÞ²´°¼¾»¼¾»¼¾»ÈÊÇÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊDz´°²´°¼¾»¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÈÊǼ¾»²´°²´°²´°§©¦²´°ÈÊÇÈÊÇÈÊǼ¾»¼¾»ÈÊÇÕ×ÔÕ×Ô§©¦§©¦²´°²´°¼¾»²´°²´°²´°²´°§©¦§©¦§©¦²´°¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞ§©¦²´°²´°§©¦§©¦²´°¼¾»¼¾»²´°¼¾»Õ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞëîêÕ×ÔÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîêÕ×ÔÕ×ÔÈÊÇÕ×ÔßáÞßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞöøõßáÞÕ×ÔØ´¸âŒ›òsŽöl„ê{†å¥¨ßßÜëîêöøõöøõöøõöøõöøõëîêëîêëîêöøõöøõßáÞß××èžëd…ït‰÷}öyŽíq†íj}ð†šëìéëîêöøõöøõöøõöøõöøõëîêëîêöøõöøõÕÖÓÙÀÅîR‚ì?{îc‰ó‘õ˜ð’œë†•ïs‰õeñ¶ÃëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêöøõÏ°¸îPê<oçIrên‚ï{“謭뛠îðfô‚•ßáÞëîêëîêöøõöøõöøõöøõëîêëîêëîêöøõ§©¦ê6wé0oæ9jèMmëbvìm|ëÔÍêÇÁ쇒ílõl€ßáÞßáÞëîêëîêëîêëîêëîêëîêëîêëîêöøõ§©¦â fæ få.gëDlðVvð\qîéãïØÒó£©î€‹ëf|óg€Õ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîê²´°§©¦áZå_å%dè8hëGkíGjîîçðåØó£©ïi|ëYsìvŒÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞ²´°²´°à<áIå Wå/bæ8fæ8fó£©ïìÞï^uí_zò\sؤ©¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔßáÞßáÞ¼¾»¼¾»ÖÚ(áEä'[å,cå0dë9eïHgìRkíYræw‡¼½º²´°²´°ÈÊÇÕ×ÔÈÊǼ¾»²´°²´°ÈÊÇÕ×ÔÕ×Ô¼¾»ÈÊÇÓØÞ<âMä Zå&_è/bí;gîJlâg}¾¸¶¼¾»¼¾»¼¾»¼¾»ÈÊǼ¾»¼¾»²´°¼¾»¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÔ²®à6”ÜÛ.áJäWæ!^è+eØsŽ¾¸·¼¾»ÈÊÇÈÊÇÈÊǼ¾»²´°²´°¼¾»¼¾»ÈÊǼ¾»¼¾»¼¾»ÈÊÇÕ×ÔÕÖÓßÒ×Úe[Ô$3ÙCà7kÞjŽ»™ ²´°²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÈÊDz´°Õ×ÔÈÊǼ¾»²´°ÈÊÇÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞÕ×Ô¼¾»²´°²´°²´°²´°¼¾»Õ×ÔëîêßáÞßáÞÕ×ÔÈÊÇÈÊÇÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔßáÞ§©¦²´°²´°¼¾»ßáÞÕ×ÔÈÊÇÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊDz´°¼¾»ßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔöøõöøõöøõÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»²´°Õ×ÔßáÞöøõöøõöøõöøõöøõöøõÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»²´°¼¾»ëîêëîêëîêëîêßáÞëîêëîêßáÞëîêëîêöøõ²´°²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇßáÞÕ×ÔÕ×ÔßáÞëîêëîêÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»ÈÊÇÕ×ÔßáÞ§©¦²´°¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔßáÞßáÞÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊDz´°²´°¼¾»ÈÊDz´°¼¾»¼¾»¼¾»ÈÊÇÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊDz´°²´°¼¾»¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÈÊǼ¾»²´°²´°²´°§©¦²´°ÈÊÇÈÊÇÈÊǼ¾»¼¾»ÈÊÇÕ×ÔÕ×Ô§©¦§©¦²´°²´°¼¾»²´°²´°²´°²´°§©¦§©¦§©¦²´°¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞ§©¦²´°²´°§©¦§©¦²´°¼¾»¼¾»²´°¼¾»Õ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞëîêÕ×ÔÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîêÕ×ÔÕ×ÔÈÊÇÕ×ÔßáÞßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞöøõßáÞÕ×ÔØ´¸âŒ›òsŽöl„ê{†å¥¨ßßÜëîêöøõöøõöøõöøõöøõëîêëîêëîêöøõöøõßáÞß××èžëd…ït‰÷}öyŽíq†íj}ð†šëìéëîêöøõöøõöøõöøõöøõëîêëîêöøõöøõÕÖÓÙÀÅîR‚ì?{îc‰ó‘õ˜ð’œë†•ïs‰õeñ¶ÃëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêöøõÏ°¸îPê<oçIrên‚ï{“謭뛠îðfô‚•ßáÞëîêëîêöøõöøõöøõöøõëîêëîêëîêöøõ§©¦ê6wé0oæ9jèMmëbvìm|ëÔÍêÇÁ쇒ílõl€ßáÞßáÞëîêëîêëîêëîêëîêëîêëîêëîêöøõ§©¦â fæ få.gëDlðVvð\qîéãïØÒó£©î€‹ëf|óg€Õ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîê²´°§©¦áZå_å%dè8hëGkíGjîîçðåØó£©ïi|ëYsìvŒÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞ²´°²´°à<áIå Wå/bæ8fæ8fó£©ïìÞï^uí_zò\sؤ©¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔßáÞßáÞ¼¾»¼¾»ÖÚ(áEä'[å,cå0dë9eïHgìRkíYræw‡¼½º²´°²´°ÈÊÇÕ×ÔÈÊǼ¾»²´°²´°ÈÊÇÕ×ÔÕ×Ô¼¾»ÈÊÇÓØÞ<âMä Zå&_è/bí;gîJlâg}¾¸¶¼¾»¼¾»¼¾»¼¾»ÈÊǼ¾»¼¾»²´°²´°¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÔ²®à6”ÜÛ.áJäWæ!^è+eØsŽ¾¸·¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»ÈÊǼ¾»²´°²´°ÈÊÇÕ×ÔÕÖÓßÒ×Úe[Ô$3ÙCà7k„5»™ ²´°²´°¼¾»¼¾»ÈÊÇÈÊǼ¾»¼¾»²´°ÈÊÇÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞ²´°²´°§©¦²´°ÈÊÇÈÊÇÕ×ÔßáÞßáÞÕ×ÔßáÞßáÞ§©¦§©¦ßáÞÕ×ÔÈÊÇÕ×ÔÕ×ÔßáÞÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇöøõöøõöøõÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊǼ¾»²´°ëîêöøõëîêëîêßáÞëîêëîêöøõ²´°²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÈÊǼ¾»ÈÊÇßáÞëîꧩ¦²´°¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊÇÕ×ÔÈÊÇÈÊDz´°²´°¼¾»Õ×ÔßáÞ²´°¼¾»¼¾»¼¾»ÈÊÇÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÈÊǼ¾»¼¾»¼¾»¼¾»¼¾»ÈÊÇÈÊÇÈÊǼ¾»¼¾»¼¾»ÈÊÇÈÊDz´°²´°¼¾»¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÈÊǼ¾»²´°²´°²´°§©¦²´°ÈÊÇÈÊÇÈÊǼ¾»¼¾»ÈÊÇÕ×ÔÕ×Ô§©¦§©¦²´°²´°¼¾»²´°²´°²´°²´°§©¦§©¦§©¦²´°¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞ§©¦²´°²´°§©¦§©¦²´°¼¾»¼¾»²´°¼¾»Õ×ÔÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞëîêÕ×ÔÕ×ÔÕ×ÔßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîêÕ×ÔÕ×ÔÈÊÇÕ×ÔßáÞßáÞßáÞßáÞëîêëîêëîêëîêëîêëîêßáÞßáÞßáÞöøõßáÞÕ×Ô×½¿âžðwôqˆê€‹å©«ßÝÚëîêöøõöøõöøõöøõöøõëîêëîêëîêöøõöøõßáÞß××è‚Ÿêd…ït‰÷}öyŽíq†íj}ò”ëéæëîêöøõöøõöøõöøõöøõëîêëîêöøõöøõÕÖÓÙÀÅîR‚ì?{îc‰ó‘õ˜ð’œë†•ïs‰õeó£µëîêëîêöøõöøõöøõöøõöøõöøõëîêëîêöøõÏ°¸îPê<oçIrên‚ï{“謭뛠îðföp‡ßáÞëîêëîêöøõöøõöøõöøõëîêëîêëîêöøõ§©¦ê6wé0oæ9jèMmëbvìm|ëÔÍêÇÁ쇒íl÷cxßàÝßáÞëîêëîêëîêëîêëîêëîêëîêëîêöøõ§©¦â fæ få.gëDlðVvð\qîéãïØÒó£©î€‹ëf|ôa{ÕÕÒßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞßáÞëîê²´°§©¦áZå_å%dè8hëGkíGjîîçðåØó£©ïi|ëYsìuŒÈÊÇÕ×ÔÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞßáÞ²´°²´°à<áIå Wå/bæ8fæ8fó£©ïìÞï^uí_zò\sᬲ¼¾»ÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔßáÞßáÞ¼¾»¼¾»ÖÚ(áEä'[å,cå0dë9eïHgìRkíYr¼½º²´°²´°ÈÊÇÕ×ÔÈÊÇÈÊDz´°²´°ÈÊÇÕ×ÔÕ×Ô¼¾»ÈÊÇÓ ØÞ<âMä Zå&_è/bí;hîKlèt‰¾¹·¼¾»²´°¼¾»ÈÊÇÕ×Ô§©¦¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÔ©¥àC˜ÝÜ0áKåUæ"[å7nЉœ¼»¹¼¾»¼¾»ÈÊÇÈÊÇÈÊǧ©¦²´°ÈÊÇÕ×ÔÙ ¾à†¶Ü Õ ÝFáK§>A
+¼š¢º¤§¼¾»¼¾»ÈÊÇÕ×ÔÕ×ÔßáÞÕ×ÔßáÞßáÞ²´°²´°ÈÊÇÈÊÇÕ×ÔßáÞÕ×ÔÕ×ÔßáÞÕ×ÔÈÊÇÕ×ÔßáÞßáÞÕ×ÔßáÞßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×ÔÕ×ÔÕ×ÔßáÞëîêëîê²´°²´°¼¾»ÈÊÇÕ×ÔÕ×ÔÈÊÇÈÊÇÕ×ÔÕ×Ô¼¾»Õ×ÔßáÞÈÊǼ¾»ÈÊÇßáÞëîꧩ¦²´°¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×Ô¼¾»ÈÊÇÈÊÇÕ×ÔÕ×ÔÈÊDz´°²´°¼¾»Õ×Ôëîê²´°¼¾»¼¾»¼¾»ÈÊÇÈÊÇÕ×ÔÕ×ÔÕ×Ô¼¾»¼¾»¼¾»¼¾»²´°ÈÊÇÈÊǼ¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞ²´°²´°¼¾»¼¾»ÈÊÇÈÊÇÈÊÇÈÊÇÕ×ÔÈÊǼ¾»²´°¼¾»²´°²´°²´°¼¾»ÈÊÇÈÊǼ¾»¼¾»ÈÊÇÕ×ÔÕ×Ô§©¦§©¦²´°²´°¼¾»²´°²´°²´°²´°§©¦§©¦§©¦²´°¼¾»¼¾»¼¾»¼¾»ÈÊÇÕ×ÔßáÞ§©¦²´°²´°§©¦§©¦²´°¼¾»²´°²´°ÈÊÇÕ×Ô \ No newline at end of file
diff --git a/bubbob/images/ghosty.ppm b/bubbob/images/ghosty.ppm
new file mode 100644
index 0000000..52ff8d3
--- /dev/null
+++ b/bubbob/images/ghosty.ppm
Binary files differ
diff --git a/bubbob/images/ghosty_angry.ppm b/bubbob/images/ghosty_angry.ppm
new file mode 100644
index 0000000..95f533e
--- /dev/null
+++ b/bubbob/images/ghosty_angry.ppm
Binary files differ
diff --git a/bubbob/images/glue.ppm b/bubbob/images/glue.ppm
new file mode 100644
index 0000000..0723e34
--- /dev/null
+++ b/bubbob/images/glue.ppm
@@ -0,0 +1,4 @@
+P6
+32 32
+255
+áÝÝáÝÝÊ#áÝÝáÝÝÔ#Ô#áÝÝáÝÝÔ#Ô#áÝÝáÝÝè"Ü#Ü#áÝÝáÝÝÿ86ÿ86ÿ86è&Ü#Ü#è"áÝÝáÝÝÿ86ÿ86ÿCCÿCCÿCCí#è&Ü#è"ÿ86áÝÝáÝÝÿ86ÿ86ÿCCÿCCÿ86ÿ86í#è&è"Ü#è"ÿ86ÿCCáááÝÙÙÿ86ÿ86ÿCCÿ86ÿ86è&è&è&è"è"è"è"ÿ86ÿCCÿ86ÝÙÙÿ86ÿ86ÿCCÿ86è"è&è&Ü#Ü#Ü#è"è"è"è"ÿ86ÿ86ÿ86ÿ86ÿCCÿ86è"í#Ü#Ü#Ü#Ô#Ô#Ô#Ô#è"ÿ86ÿ86ÿ86ÿ86ÿCCÿ86è"è&Ü#Ü#Ü#Ô#Ô#Ê#Ê#Ä#¼#¼#ÿ86ÿ86ÿCCÿ86è"è&Ü#Ü#Ô#Ô#Ô#Ê#Ä#¼#¼#ÿ86ÿ86ÿCCÿ86è"è&Ü#Ü#Ô#Ô#Ê#Ä#¼#¼#ÿ86ÿ86ÿCCÿ86è"è&Ü#Ü#Ô#Ô#Ê#Ä#¼#ÿ86ÿ86ÿCCÿ86è"è&Ü#Ü#Ô#Ô#Ê#Ä#¼#æææÿ86ÿCCÿ86è"è&Ü#Ü#Ô#Ô#Ê#Ä#¼#æææÿÿÿÿ96è"è&Ü#Ü#Ô#Ô#Ê#Ä#¼#æææÿÿÿæææè&Ü#Ü#Ô#Ô#Ê#Ä#¼#ÿÿÿÿÿÿæéêáááÜ#Ô#Ô#Ê#Ä#¼#ÿÿÿæéêáááááá×××Ô#Ê#Ä#¼#¾ÀÂæéêááá×××××××××Ä#¼#¾À³¶·©«¬××××××ÍÍÍÍÍÍÍÍ;À³¶·©«¬¾À³¶·©«¬¾À³¶·©«¬ßß«©«¬ßß«ßØŸßß«ßØŸßØŸßØŸßØŸßØŸßØŸßß«ßß«ßß«ßß«ßß«ßß«ßß«ßß«¼±ußØžÿ÷µÿ÷µÿ÷µÿ÷µÿ÷µÿ÷µÿ÷µßØžßß«ßß«ßß«ßß«ßß«ßß«ßß«ßß«ÕÕ£ÕÕ£ÕÕ£ÕÕ£ÕÕ£´ªqçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚçÚ´ªq´ªq´ªq \ No newline at end of file
diff --git a/bubbob/images/gramy.ppm b/bubbob/images/gramy.ppm
new file mode 100644
index 0000000..ad2c757
--- /dev/null
+++ b/bubbob/images/gramy.ppm
Binary files differ
diff --git a/bubbob/images/gramy_angry.ppm b/bubbob/images/gramy_angry.ppm
new file mode 100644
index 0000000..c1672ee
--- /dev/null
+++ b/bubbob/images/gramy_angry.ppm
Binary files differ
diff --git a/bubbob/images/hat1.ppm b/bubbob/images/hat1.ppm
new file mode 100644
index 0000000..7e15239
--- /dev/null
+++ b/bubbob/images/hat1.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+192 48
+255
+###111<<<)))222222111&&&&&&111222222)))<<<111###+++666<<<777111)))333333888===BBBBBB===......$$$.........333333888888===::::::===888888333333.........$$$......===BBBBBB===888333333)))111777<<<666+++:::BBB@@@======888888333333)))...333333666888===BBBBBB===888888............))).........333333888888===BBBBBB======BBBBBB===888888333333.........)))............888888===BBBBBB===888666333333...)))333333888888======@@@BBB:::888BBBBBB@@@======888888333333............333333888===@@@BBBBBB===888888333333.........)))))).........333333888888===BBBBBB======  ======BBBBBB===888888333333.........)))))).........333333888888===BBBBBB@@@===888333333............333333888888======@@@BBBBBB888333888BBBBBB@@@======888888333333...............333333666888===BBBBBB===;;;888888333333333..................333333888888===BBBBBB=========888888=========BBBBBB===888888333333..................333333333888888;;;===BBBBBB===888666333333...............333333888888======@@@BBBBBB888333333888BBBBBBBBB@@@======888888333333.........))).........$$$......333333888===@@@BBBBBB===888888666333333333)))...............333333888888===BBB=========888333------333888=========BBB===888888333333...............)))333333333666888888===BBBBBB@@@===888333333......$$$.........))).........333333888888======@@@BBBBBBBBB888333)))333888BBBBBB@@@======888888888333333.........)))$$$............)))......333333888===BBBBBB===;;;888888333333333..................888888===BBBBBB======888888333000000333888888======BBBBBB===888888..................333333333888888;;;===BBBBBB===888333333......)))............$$$))).........333333888888888======@@@BBBBBB888333)))...333888BBBBBB@@@======888888333333............))))))..................333333888===BBBBBB===888888666333333333...............888===BBB======888888333000...!!!!!!...000333888888======BBB===888...............333333333666888888===BBBBBB===888333333..................))))))............333333888888======@@@BBBBBB888333.....................333888BBBBBB@@@======888888333333.........))))))))))))...............888===BBBBBB===888888333333333.........))).........BBBBBB======888333000...000))))))000...000333888======BBBBBB.........))).........333333333888888===BBBBBB===888...............)))))))))))).........333333888888======@@@BBBBBB888333................................................@@@======888888333333.........))))))))) ...............BBBBBB===888888333333333.........---.........======888888333000000000******000000000333888888======.........---.........333333333888888===BBBBBB............... ))))))))).........333333888888======@@@...............................................................333333.........)))))))))###..................888888333333333.........(((......888888333000000000...******...000000000333888888......(((.........333333333888888..................###))))))))).........333333..................................................................)))))))))###...............333333333.........$$$......888333000000000+++$$$$$$+++000000000333888......$$$.........333333333...............###)))))))))............................................................)))....................................000000+++))))))+++000000....................................)))....................................................................................++++++))))))++++++.............................................................................................)))$$$$$$))).......................................................................................................................................................................................... \ No newline at end of file
diff --git a/bubbob/images/hat2.ppm b/bubbob/images/hat2.ppm
new file mode 100644
index 0000000..bce2b98
--- /dev/null
+++ b/bubbob/images/hat2.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+192 48
+255
+{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{€€€{{{{{{{{{€€€€€€€€€€€€{{{{{{{{{€€€{{{{{{{{{{{{{{{{{{€€€†††ŒŒŒ––––––{{{{{{€€€€€€††††††{{{{{{€€€€€€€€€††††††††††††€€€€€€€€€{{{{{{††††††€€€€€€{{{{{{––––––ŒŒŒ†††€€€{{{{{{{{{{{{€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ{{{{{{€€€€€€††††††ŒŒŒ‘‘‘{{{{{{{{{{{{€€€€€€€€€††††††††††††€€€€€€€€€{{{{{{{{{{{{‘‘‘ŒŒŒ††††††€€€€€€{{{{{{ŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€{{{{{{{{{{{{€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{€€€€€€€€€††††††ŒŒŒ‘‘‘––––––‘‘‘{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{‘‘‘––––––‘‘‘ŒŒŒ††††††€€€€€€€€€{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€{{{{{{{{{{{{€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{uuuuuu{{{{{{€€€€€€††††††‰‰‰ŒŒŒ‘‘‘––––––‘‘‘ŒŒŒŒŒŒ€€€€€€{{{{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘––––––––––––‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{{{{€€€€€€ŒŒŒŒŒŒ‘‘‘––––––‘‘‘ŒŒŒ‰‰‰††††††€€€€€€{{{{{{uuuuuu{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€{{{{{{{{{{{{{{{€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{uuuuuu{{{{{{€€€€€€€€€††††††ŒŒŒ‘‘‘”””––––––‘‘‘ŒŒŒŒŒŒ†††††††††€€€€€€€€€€€€{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘––––––‘‘‘‘‘‘––––––‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{€€€€€€€€€€€€†††††††††ŒŒŒŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒ††††††€€€€€€€€€{{{{{{uuuuuu{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€{{{{{{{{{{{{{{{€€€†††ŒŒŒ–––––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{uuuuuu{{{{{{€€€€€€††††††‰‰‰ŒŒŒ‘‘‘––––––‘‘‘ŒŒŒŒŒŒ†††††††††€€€€€€€€€€€€€€€{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘––––––‘‘‘‘‘‘ŒŒŒŒŒŒ‘‘‘‘‘‘––––––‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{€€€€€€€€€€€€€€€†††††††††ŒŒŒŒŒŒ‘‘‘––––––‘‘‘ŒŒŒ‰‰‰††††††€€€€€€{{{{{{uuuuuu{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””–––––––––ŒŒŒ†††€€€{{{{{{{{{{{{€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{uuuuuu€€€€€€€€€{{{{{{€€€€€€††††††ŒŒŒ‘‘‘”””––––––‘‘‘ŒŒŒŒŒŒ‰‰‰†††††††††€€€€€€€€€{{{{{{€€€€€€€€€€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘––––––‘‘‘‘‘‘‘‘‘ŒŒŒ†††ƒƒƒƒƒƒ†††ŒŒŒ‘‘‘‘‘‘‘‘‘––––––‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€€€€€€€€€€{{{{{{€€€€€€€€€†††††††††‰‰‰ŒŒŒŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒ††††††€€€€€€{{{{{{€€€€€€€€€uuuuuu{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€{{{{{{{{{{{{€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€€€€{{{{{{{{{uuuuuu€€€€€€€€€€€€{{{{{{€€€€€€††††††ŒŒŒ‘‘‘––––––‘‘‘ŒŒŒŒŒŒ†††††††††€€€€€€€€€€€€{{{{{{€€€€€€€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘–––‘‘‘‘‘‘‘‘‘ŒŒŒ††††††ƒƒƒƒƒƒ††††††ŒŒŒ‘‘‘‘‘‘‘‘‘–––‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€€€€€€€{{{{{{€€€€€€€€€€€€†††††††††ŒŒŒŒŒŒ‘‘‘––––––‘‘‘ŒŒŒ††††††€€€€€€{{{{{{€€€€€€€€€€€€uuuuuu{{{{{{{{{€€€€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€{{{{{{€€€€€€€€€€€€€€€€€€€€€†††ŒŒŒ––––––”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{uuuuuu€€€€€€€€€€€€€€€€€€††††††ŒŒŒ‘‘‘––––––‘‘‘ŒŒŒŒŒŒ‰‰‰†††††††††€€€€€€€€€€€€{{{{{{€€€€€€€€€€€€ŒŒŒŒŒŒ‘‘‘––––––‘‘‘‘‘‘ŒŒŒŒŒŒ†††ƒƒƒƒƒƒ}}}€€€€€€}}}ƒƒƒƒƒƒ†††ŒŒŒŒŒŒ‘‘‘‘‘‘––––––‘‘‘ŒŒŒŒŒŒ€€€€€€€€€€€€{{{{{{€€€€€€€€€€€€†††††††††‰‰‰ŒŒŒŒŒŒ‘‘‘––––––‘‘‘ŒŒŒ††††††€€€€€€€€€€€€€€€€€€uuuuuu{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””––––––ŒŒŒ†††€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€”””‘‘‘‘‘‘ŒŒŒŒŒŒ††††††€€€€€€€€€{{{{{{{{{uuuuuu€€€€€€€€€€€€€€€€€€ŒŒŒ‘‘‘––––––‘‘‘ŒŒŒŒŒŒ†††††††††€€€€€€€€€€€€{{{{{{{{{€€€€€€€€€ŒŒŒ‘‘‘–––‘‘‘‘‘‘ŒŒŒŒŒŒ†††ƒƒƒ€€€ƒƒƒ€€€}}}{{{{{{}}}€€€ƒƒƒ€€€ƒƒƒ†††ŒŒŒŒŒŒ‘‘‘‘‘‘–––‘‘‘ŒŒŒ€€€€€€€€€{{{{{{{{{€€€€€€€€€€€€†††††††††ŒŒŒŒŒŒ‘‘‘––––––‘‘‘ŒŒŒ€€€€€€€€€€€€€€€€€€uuuuuu{{{{{{{{{€€€€€€€€€††††††ŒŒŒŒŒŒ‘‘‘‘‘‘”””€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€††††††€€€€€€€€€{{{{{{{{{uuuuuu€€€€€€€€€€€€€€€––––––‘‘‘ŒŒŒŒŒŒ†††††††††€€€€€€€€€€€€{{{{{{{{{€€€€€€€€€––––––‘‘‘‘‘‘ŒŒŒ†††ƒƒƒ€€€ƒƒƒ€€€}}}{{{{{{{{{{{{}}}€€€ƒƒƒ€€€ƒƒƒ†††ŒŒŒ‘‘‘‘‘‘––––––€€€€€€€€€{{{{{{{{{€€€€€€€€€€€€†††††††††ŒŒŒŒŒŒ‘‘‘––––––€€€€€€€€€€€€€€€uuuuuu{{{{{{{{{€€€€€€€€€††††††€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{{{{{{{{{uuuuuu€€€€€€€€€€€€€€€€€€ŒŒŒŒŒŒ†††††††††€€€€€€€€€{{{{{{{{{€€€€€€€€€‘‘‘‘‘‘ŒŒŒŒŒŒ†††ƒƒƒƒƒƒƒƒƒ}}}{{{{{{{{{{{{{{{{{{}}}ƒƒƒƒƒƒƒƒƒ†††ŒŒŒŒŒŒ‘‘‘‘‘‘€€€€€€€€€{{{{{{{{{€€€€€€€€€†††††††††ŒŒŒŒŒŒ€€€€€€€€€€€€€€€€€€uuuuuu{{{{{{{{{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†††††††††€€€€€€€€€{{{{{{{{{€€€€€€ŒŒŒŒŒŒ†††ƒƒƒƒƒƒƒƒƒ€€€}}}{{{{{{{{{{{{{{{{{{}}}€€€ƒƒƒƒƒƒƒƒƒ†††ŒŒŒŒŒŒ€€€€€€{{{{{{{{{€€€€€€€€€†††††††††€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{{{{{{€€€€€€ŒŒŒ†††ƒƒƒƒƒƒƒƒƒ}}}{{{{{{{{{{{{{{{{{{}}}ƒƒƒƒƒƒƒƒƒ†††ŒŒŒ€€€€€€{{{{{{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{{{{{{€€€€€€€€€ƒƒƒƒƒƒ}}}{{{{{{{{{{{{{{{{{{}}}ƒƒƒƒƒƒ€€€€€€€€€{{{{{{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€}}}}}}{{{{{{{{{{{{{{{{{{}}}}}}€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{{{{{{{{{{{{{{{{{{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€{{{{{{€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ \ No newline at end of file
diff --git a/bubbob/images/hat5.ppm b/bubbob/images/hat5.ppm
new file mode 100644
index 0000000..78300f8
--- /dev/null
+++ b/bubbob/images/hat5.ppm
@@ -0,0 +1,5 @@
+P6
+# CREATOR: The GIMP's PNM Filter Version 1.0
+64 48
+255
+{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{...BBBBBB@@@===;;;;;;888888333''' {{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{888===BBBBBB@@@===;;;;;;888888333333...{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{333888===BBBBBB@@@===;;;;;;888888333333......{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{...333888===BBBBBB@@@===;;;;;;888888333333......+++{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{&&&...333888===BBBBBB@@@===;;;;;;888888333333......+++$$${{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{......333888===BBBBBB@@@===;;;;;;888888333333......++++++{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{......333888===BBBBBB@@@===;;;;;;888888333333......++++++{{{{{{€€€€€€†††ŒŒŒ‘‘‘––––––”””‘‘‘ŒŒŒŒŒŒ††††††€€€€€€}}}}}}{{{{{{$$$......333888===BBBBBB@@@===;;;;;;888888333333......++++++$$$€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€(((......333888===BBBBBB@@@===;;;;;;888888333333......++++++(((€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€.............................................................................................................................................................................. \ No newline at end of file
diff --git a/bubbob/images/ice_cyan_big.ppm b/bubbob/images/ice_cyan_big.ppm
new file mode 100644
index 0000000..d22c371
--- /dev/null
+++ b/bubbob/images/ice_cyan_big.ppm
Binary files differ
diff --git a/bubbob/images/ice_violet_big.ppm b/bubbob/images/ice_violet_big.ppm
new file mode 100644
index 0000000..d1d967e
--- /dev/null
+++ b/bubbob/images/ice_violet_big.ppm
Binary files differ
diff --git a/bubbob/images/keys.ppm b/bubbob/images/keys.ppm
new file mode 100644
index 0000000..4d20147
--- /dev/null
+++ b/bubbob/images/keys.ppm
Binary files differ
diff --git a/bubbob/images/level_digits.ppm b/bubbob/images/level_digits.ppm
new file mode 100644
index 0000000..1110f8b
--- /dev/null
+++ b/bubbob/images/level_digits.ppm
Binary files differ
diff --git a/bubbob/images/lightning_large.ppm b/bubbob/images/lightning_large.ppm
new file mode 100644
index 0000000..67ac930
--- /dev/null
+++ b/bubbob/images/lightning_large.ppm
Binary files differ
diff --git a/bubbob/images/lightning_small.ppm b/bubbob/images/lightning_small.ppm
new file mode 100644
index 0000000..cd3cf22
--- /dev/null
+++ b/bubbob/images/lightning_small.ppm
Binary files differ
diff --git a/bubbob/images/monky.ppm b/bubbob/images/monky.ppm
new file mode 100644
index 0000000..01afbae
--- /dev/null
+++ b/bubbob/images/monky.ppm
Binary files differ
diff --git a/bubbob/images/monky_angry.ppm b/bubbob/images/monky_angry.ppm
new file mode 100644
index 0000000..786d39a
--- /dev/null
+++ b/bubbob/images/monky_angry.ppm
Binary files differ
diff --git a/bubbob/images/nasty.ppm b/bubbob/images/nasty.ppm
new file mode 100644
index 0000000..a006bcd
--- /dev/null
+++ b/bubbob/images/nasty.ppm
Binary files differ
diff --git a/bubbob/images/nasty_angry.ppm b/bubbob/images/nasty_angry.ppm
new file mode 100644
index 0000000..58375e1
--- /dev/null
+++ b/bubbob/images/nasty_angry.ppm
Binary files differ
diff --git a/bubbob/images/orcy.ppm b/bubbob/images/orcy.ppm
new file mode 100644
index 0000000..1ddb609
--- /dev/null
+++ b/bubbob/images/orcy.ppm
Binary files differ
diff --git a/bubbob/images/orcy_angry.ppm b/bubbob/images/orcy_angry.ppm
new file mode 100644
index 0000000..59b51c3
--- /dev/null
+++ b/bubbob/images/orcy_angry.ppm
Binary files differ
diff --git a/bubbob/images/palettes.dat b/bubbob/images/palettes.dat
new file mode 100644
index 0000000..5070cbb
--- /dev/null
+++ b/bubbob/images/palettes.dat
Binary files differ
diff --git a/bubbob/images/pastec_big.ppm b/bubbob/images/pastec_big.ppm
new file mode 100644
index 0000000..6805f73
--- /dev/null
+++ b/bubbob/images/pastec_big.ppm
@@ -0,0 +1,44 @@
+P6
+# CREATOR: GIMP PNM Filter Version 1.1
+90 90
+255
+ÿÿÎÿÿÎÿÿÎÿÿÎÿÿÎÿûÌÿ³­ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¥¥ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ  ÿ¦¦ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ¨¨ÿ³­ÿûÌÿÿÎÿÿÎÿÿÎÿÿÎÿÿÐÿÿÌÿÿÌÿÿÌÿÿÌÿùÉÿà½ÿ»«ÿ¢Ÿÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ˜˜ÿ’’ÿ••ÿ››ÿ››ÿ››ÿ››ÿ””ÿ””ÿ’’ÿ˜˜ÿ››ÿ››ÿ››ÿ››ÿ››ÿ››ÿ¢Ÿÿ»«ÿà½ÿùÉÿÿÌÿÿÌÿÿÍÿÿÎÿÿÌÿÿÌÿÿÌÿÿÌÿ÷ÈÿÞ»ÿºªÿ¡ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ——ÿ——ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ——ÿ‹‹ÿÿŠŠÿ””ÿ™™ÿ™™ÿ––ÿŒŒÿ‰‰ÿ‰‰ÿ’’ÿ––ÿ——ÿ™™ÿ™™ÿ™™ÿ™™ÿ¡ÿºªÿÞ»ÿ÷ÈÿÿÌÿÿÌÿÿÌÿÿÎûÿÌûÿÌûÿÌûÿÌûöÇüÛ¹þµ¤ÿ™•ÿ‘‘ÿ’’ÿ““ÿ””ÿ••ÿ••ÿ••ÿ••ÿ••ÿ––ÿ˜˜ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ˜˜ÿ––ÿ••ÿ““ÿ‘‘ÿ‘‘ÿ““ÿ••ÿ••ÿ••ÿÿ’’ÿÿ~~ÿ‹‹ÿÿŽŽÿ‘‘ÿ††ÿ‹‹ÿ~~ÿ„„ÿˆˆÿ‡‡ÿ‹‹ÿ‹‹ÿŒŒÿŒŒÿŒŒÿ•‘þ²¡üÚ·ûöÇûÿÌûÿÌüÿÌÿÿÎîÿÌîÿÌîÿÌîÿÌðôÅôÓ°ù¤“þƒÿyyÿ}}ÿƒƒÿ‡‡ÿˆˆÿˆˆÿˆˆÿˆˆÿŠŠÿŽŽÿ““ÿ˜˜ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ˜˜ÿ““ÿŽŽÿŠŠÿ††ÿ„„ÿˆˆÿŠŠÿˆˆÿˆˆÿˆˆÿ††ÿ„„ÿ~~ÿ„„ÿƒƒÿ€€ÿƒƒÿÿ€€ÿuuÿooÿjjÿeeÿppÿrrÿggþMMÿggÿggþsoù™ˆôÍ«ðóÄîÿÌîÿÌïÿÌýÿÎÝÿÌÝÿÌÝÿÌÛÿËßñÂèÇ¥ôŒ{üb^ÿWWÿ^^ÿiiÿppÿxxÿxxÿyyÿwwÿyyÿ€€ÿ‹‹ÿ’’ÿ––ÿ˜˜ÿ™™ÿ™™ÿ˜˜ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ˜˜ÿ––ÿ••ÿ——ÿ——ÿ’’ÿ‹‹ÿ€€ÿ{{ÿwwÿ}}ÿƒƒÿƒƒÿ}}ÿwwÿvvÿuuþxxþzzþ||ÿÿvvÿkkÿttÿwwÿkkÿTTÿWWÿWWÿMMÿMMÿ))ý ý..ÿ22üC?óud缚ßî¿ÝÿÌÝÿÌÜÿÌÐÿÎÐÿÌÐÿÌÏÿÌÐÿÌÒðÀÞÁžð{kûKGÿ>>ÿ^^ÿWWÿccÿssÿwwÿiiÿiiÿmmÿƒƒÿÿ‡‡ÿ‘‘ÿ““ÿ––ÿ––ÿ——ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ™™ÿ˜˜ÿ––ÿ••ÿ””ÿ‘‘ÿŒŒÿŠŠÿŒŒÿ‹‹ÿˆˆÿ~~ÿ^^ÿhhÿhhÿ}}ÿxxþ‚‚ÿmmÿmmÿmmÿooÿ~~ýqqývvÿllÿwwÿPPÿYYþOOÿRRÿMMÿEEÿUUÿWWÿ55þ þ''ûÿ ú!î\KÞ°Ôë¼ÐÿÌÐÿÌÐÿÌÎÿÎÌÿÌÌÿÌÌÿÌÊÿÊÏî¿Ý¼šîveûD@ÿTTþWWýhhÿ{{ÿnnÿnnÿ\\ÿaaÿxxÿrrþ€€ÿ‚‚ÿ~~ÿŒŒÿŠŠÿŒŒÿ””ÿ––ÿ––ÿ••ÿ––ÿ˜˜ÿ™™ÿ™™ÿ™™ÿ™™ÿ˜˜ÿ––ÿ••ÿ••ÿ••ÿ––ÿ––ÿ••ÿ‘‘ÿŒŒÿ‰‰ÿ‡‡ÿƒƒÿ||ÿ~~ÿˆˆÿÿuuÿccÿIIÿKKÿ]]ÿiiÿxxÿccþrrÿiiÿqqÿuuýmmþxxÿppÿnnÿPPÿAAþþ;;û%%ûÿBBý]]ý__ÿTTÿGGûÿ
+
+ÿÿýýEE÷ñí
+
+Ý Ê È C“
+ßÙ
+
+ú ýõ ûú,,úüü
+
+ÿÿÿúï
+
+ü77ÿ55þ88ÿý--óæîòùû--ùü==õ ÷ úù
+
+û ÷
+æåæÙ
+àé ãàä
+æ ßã
+
+øEEêè
+
+
+ô11êèêéçá
+Ñ
+
+
+ßâ
diff --git a/bubbob/images/peach_big.ppm b/bubbob/images/peach_big.ppm
new file mode 100644
index 0000000..3f2b6ca
--- /dev/null
+++ b/bubbob/images/peach_big.ppm
Binary files differ
diff --git a/bubbob/images/point_0.ppm b/bubbob/images/point_0.ppm
new file mode 100644
index 0000000..c6ef319
--- /dev/null
+++ b/bubbob/images/point_0.ppm
Binary files differ
diff --git a/bubbob/images/red_Hurry_up.ppm b/bubbob/images/red_Hurry_up.ppm
new file mode 100644
index 0000000..6f957ac
--- /dev/null
+++ b/bubbob/images/red_Hurry_up.ppm
Binary files differ
diff --git a/bubbob/images/sheep.ppm b/bubbob/images/sheep.ppm
new file mode 100644
index 0000000..66fe04a
--- /dev/null
+++ b/bubbob/images/sheep.ppm
Binary files differ
diff --git a/bubbob/images/shot.ppm b/bubbob/images/shot.ppm
new file mode 100644
index 0000000..74606ff
--- /dev/null
+++ b/bubbob/images/shot.ppm
Binary files differ
diff --git a/bubbob/images/spinning_drop.ppm b/bubbob/images/spinning_drop.ppm
new file mode 100644
index 0000000..6bb22f9
--- /dev/null
+++ b/bubbob/images/spinning_drop.ppm
Binary files differ
diff --git a/bubbob/images/springy.ppm b/bubbob/images/springy.ppm
new file mode 100644
index 0000000..16277ba
--- /dev/null
+++ b/bubbob/images/springy.ppm
Binary files differ
diff --git a/bubbob/images/springy_angry.ppm b/bubbob/images/springy_angry.ppm
new file mode 100644
index 0000000..07bb7f1
--- /dev/null
+++ b/bubbob/images/springy_angry.ppm
Binary files differ
diff --git a/bubbob/images/star_large.ppm b/bubbob/images/star_large.ppm
new file mode 100644
index 0000000..513c538
--- /dev/null
+++ b/bubbob/images/star_large.ppm
Binary files differ
diff --git a/bubbob/images/sugar_pie_big.ppm b/bubbob/images/sugar_pie_big.ppm
new file mode 100644
index 0000000..e63512b
--- /dev/null
+++ b/bubbob/images/sugar_pie_big.ppm
Binary files differ
diff --git a/bubbob/images/water_flow.ppm b/bubbob/images/water_flow.ppm
new file mode 100644
index 0000000..0950083
--- /dev/null
+++ b/bubbob/images/water_flow.ppm
@@ -0,0 +1,4 @@
+P6
+16 160
+255
+fÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÌÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿÌÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿÿÿÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÌÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿÿÿÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÌÌÿfÌÿÿÿÿÌÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿÌÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿÌÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿÌÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿ™ÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿÌÌÿfÌÿÿÿÿÌÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿÿÿÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÌÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿ™ÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿ™ÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿ™ÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿÿÿÿfÌÿfÌÿfÌÿÿÿÿfÌÿ™ÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÿÿÿfÌÿfÌÿÿÿÿfÌÿfÌÿÌÌÿfÌÿÿÿÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿÌÌÿfÌÿfÌÿfÌÿfÌÿfÌÿfÌÿ \ No newline at end of file
diff --git a/bubbob/images/water_still.ppm b/bubbob/images/water_still.ppm
new file mode 100644
index 0000000..ec040f0
--- /dev/null
+++ b/bubbob/images/water_still.ppm
@@ -0,0 +1,4 @@
+P6
+16 16
+255
+3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÌÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿÌÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÌÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÌÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿÌÌÿ3™ÿ™Ìÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÌÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÌÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿÌÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ \ No newline at end of file
diff --git a/bubbob/images/water_surface.ppm b/bubbob/images/water_surface.ppm
new file mode 100644
index 0000000..fcbbc1a
--- /dev/null
+++ b/bubbob/images/water_surface.ppm
@@ -0,0 +1,4 @@
+P6
+16 64
+255
+ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿÿÿÿÿÿÿÿÿÿÿÿÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ™Ìÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ™Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿfÌÿ3™ÿ3™ÿ3™ÿ™Ìÿ3™ÿ™Ìÿ3™ÿ \ No newline at end of file
diff --git a/bubbob/images/yellow_Hurry_up.ppm b/bubbob/images/yellow_Hurry_up.ppm
new file mode 100644
index 0000000..52682af
--- /dev/null
+++ b/bubbob/images/yellow_Hurry_up.ppm
Binary files differ
diff --git a/bubbob/levels/Arena.bin b/bubbob/levels/Arena.bin
new file mode 100644
index 0000000..73db756
--- /dev/null
+++ b/bubbob/levels/Arena.bin
Binary files differ
diff --git a/bubbob/levels/CompactLevels.py b/bubbob/levels/CompactLevels.py
new file mode 100644
index 0000000..e48908a
--- /dev/null
+++ b/bubbob/levels/CompactLevels.py
@@ -0,0 +1,1902 @@
+#
+# A series of compact levels.
+#
+
+import boarddef, mnstrmap, random
+from boarddef import LNasty, LMonky, LGhosty, LFlappy
+from boarddef import LSpringy, LOrcy, LGramy, LBlitzy
+from boarddef import RNasty, RMonky, RGhosty, RFlappy
+from boarddef import RSpringy, ROrcy, RGramy, RBlitzy
+
+
+class level01(boarddef.Level):
+ a = LNasty
+ b = RNasty
+
+ walls = """
+#############################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## a b ##
+#### ############### ####
+## ##
+## ##
+## ##
+## a b ##
+#### ############### ####
+## ##
+## ##
+## ##
+## a b ##
+#### ############### ####
+## ##
+## ##
+## ##
+## ##
+#############################
+"""
+
+
+class level02(boarddef.Level):
+ a = LNasty
+ b = RNasty
+ g = RGhosty
+
+ walls = """
+#############################
+## # ##
+## # ##
+## # ##
+## # g ##
+## g # ##
+## # ##
+## # ##
+#### ############### ####
+## # # ##
+## # # ##
+## # # ##
+## # a b # ##
+#### ############### ####
+## ##
+## ##
+## ##
+## ##
+#### ####
+## ##
+## ##
+## ##
+## ##
+#############################
+"""
+
+
+class level03(boarddef.Level):
+ a = LNasty
+ b = RNasty
+
+ letter = 1
+
+ walls = """
+######### ### #########
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+##b # # # # a ##
+###### ##### ##### ######
+## ##
+## ##
+## ##
+## #b # # # # a # ##
+## ##### ##### ##### ##
+## # # ##
+## # # ##
+## # # ##
+##b # # a ##
+####### # b # #######
+## ####### ##
+## # ##
+## # ##
+## # ##
+## # ##
+## # ##
+######### ### #########
+""" #|# #|# #|# """
+
+
+class level04(boarddef.Level):
+ a = LNasty
+ b = RNasty
+ g = LOrcy
+
+ fire = 1
+
+ walls = """
+######### ### #########
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+##b # # # # a ##
+###### ##### ##### ######
+## ##
+## ##
+## ##
+## #b # #gg # # a # ##
+## ##### ##### ##### ##
+## # # ##
+## # # ##
+## # # ##
+##b # # a ##
+####### # b # #######
+## ####### ##
+## # ##
+## # ##
+## # ##
+## # ##
+## # ##
+######### ### #########
+""" #|# #|# #|# """
+
+class level05(boarddef.Level):
+ a = LNasty
+ b = RNasty
+ g = LOrcy
+ f = RFlappy
+
+ water = top = letter = 1
+
+ walls = """
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+##b # # # # a ##
+#### # ##### ##### # ####
+## ##
+## ##
+## ##
+## #b # #gg # # a # ##
+## # ### ## ## ### # ##
+## # # ##
+## # # ##
+## # f # ##
+##b # # a ##
+####### # b # #######
+## # ### # ##
+## ##
+## ##
+## ##
+## ##
+## ##
+######### #########
+""" #|# #|# #|# """
+
+ winds = """
+>> <<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+"""
+
+class level06(boarddef.Level):
+ a = LNasty, RNasty, LMonky
+
+ letter = fire = 1
+
+ walls = """
+#############################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ### ##
+## # ## ##
+## ## # ## # ##
+## # # ## # # ##
+## ## # # # # # ##
+## # #### # # #a ##
+## a #### #############
+## #### ##
+#### ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level07(boarddef.Level):
+ g = LGhosty, LGhosty, LGhosty, LGhosty
+ h = RGhosty, RGhosty, RGhosty, RGhosty
+
+ letter = lightning = 1
+
+ walls = """
+## ##
+## ##
+## # # ##
+## # # ##
+## # # ##
+## ### ### ##
+##h # # # # #g ##
+## # # # # # # # ##
+##### #### # #### #####
+## # # # ### # # # ##
+## # # # # # # ##
+## ### # # # ### ##
+## # #### #### # ##
+## # # # # # ##
+## # # # # ##
+## ### ##
+## # ##
+## # # # ##
+## # # # ##
+## # # ##
+## ### ### ##
+## # # # # ##
+## # # # # # # ##
+## #### #### #### #### ##
+""" #|# #|# #|# """
+
+class level08(boarddef.Level):
+ s = LSpringy
+ r = RSpringy
+ f = LFlappy
+ g = RFlappy
+
+ walls = """
+#############################
+## ##
+## g f ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## # ##
+## ##
+## ### ##
+## ##
+## ##### ##
+## ##
+## ####### ##
+## ##
+## ######### ##
+## sssrrr ##
+## ########### ##
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level09(boarddef.Level):
+ m = (LMonky, RMonky,)*5
+ g = LGhosty
+
+ walls = """
+####### #### #### #######
+####### #### #### #######
+## ## ## ##
+## ## ## ##
+## g ## ## g ##
+## ## ## ##
+## ## ## ##
+#### ##### ##### ####
+#### ##### ##### ####
+## ##
+## ## #### #### ## ##
+## ## #### #### ## ##
+## ## ## ##
+#### ## ## ####
+#### ## m ## ####
+## ## ### ## ##
+## ## ## ### ## ## ##
+## ## ## ### ## ## ##
+## ## ### ## ##
+#### ## ## ####
+#### ## ## ####
+## ## ## ##
+## ## #### #### ## ##
+####### #### #### #######
+""" #|# #|# #|# """
+
+class level10(boarddef.Level):
+ n = LNasty
+
+ fire = top = 1
+
+ walls = """
+## ##### ## ##
+## ##nn ## ##
+## ####### ##
+## ##
+## ##
+## ## ##### ##
+## ##nn ## ##
+## ####### ##### ## ##
+## ##nn ## ##
+## ####### ##
+## ##
+## ##
+## ## ##### ##
+## ##nn ## ##
+## ####### ##
+## ##
+## ##### ## ##
+## ##nn ## ##
+## ####### ##
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+ winds = """
+>> <<
+>>> >>v xxx <<<
+>>^ ^<<
+>>^ v<<<^<<
+>>^ ^<<
+>>^ ^<<
+>>^ xxx v<< ^<<
+>>^ ^<<
+>>^>>>v >>v xxx ^<<
+>>^ ^<<
+>>^ >v ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ xxx v<< ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ xxx ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+"""
+
+class level11(boarddef.Level):
+ o = LOrcy
+ p = ROrcy
+
+ letter = fire = water = lightning = top = 1
+
+ walls = """
+## ##
+## ##
+### ###
+## # # ##
+## # # ##
+## # # ##
+## # # # # ##
+## # ##
+## o p o # # #p o p ##
+####### #### # #### #######
+## # ##
+## # ##
+## # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # # # # ##
+## # # ##
+###### ###### ###### ######
+""" #|# #|# #|# """
+
+class level12(boarddef.Level):
+ o = LGramy
+
+ walls = """
+## #### ##
+## # # ##
+## #### ##
+## # # ##
+## # o ##
+## #################
+## # # ##
+## #### ##
+## o # # # ##
+## ######### #### ##
+## # # # # ##
+## #### # o ##
+## # # ############
+## #### # # ##
+## # # #### ##
+## o # # # ##
+############ #### ##
+## # # # # ##
+## #### o # ##
+## # # ######## ##
+## #### ##
+## # # ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level13(boarddef.Level):
+ n = LNasty
+ m = LMonky
+ g = LGhosty
+ f = LFlappy
+ s = LSpringy
+ o = LOrcy
+ r = LGramy
+ b = LBlitzy
+
+ walls = """
+## # #### ##
+## f ## ## #### ##
+## #### ## ## ##
+## #### # ##
+## #### f ###### ##
+## ## ## f ###### ##
+## # #### #### #### ##
+## ###### ###### ##
+## ###### ###### ##
+## #### #### # ##
+## ## ## f ##
+## #### ##
+## f ##
+## #### ##
+## ###### ##
+## #### ###### ##
+## ## ## # #### ##
+## # #### f ## ## ##
+## ###### #### ##
+## ###### ##
+## #### ##
+## ##
+## #### ##
+###### ###### #####
+""" #|# #|# #|# """
+
+class level14(boarddef.Level):
+ o = LOrcy
+ r = LGramy
+
+ walls = """
+#############################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ### ### ##
+## ## ## ##
+## # # ##
+## ### ### ##
+## ## ## ##
+## # # ##
+## ## ## ##
+## ## ## ##
+## ### ### ##
+## ## ## ##
+## ## ## ##
+## ##or or ## ##
+#############################
+""" #|# #|# #|# """
+
+class level15(boarddef.Level):
+ s = LSpringy
+ g = LGhosty
+
+ walls = """
+#############################
+## ##
+## ##
+## ##
+## s s ##
+## s s ##
+## s s ##
+## ###s #####s ### ##
+## # ## ####### ## # ##
+## # ### ### # ##
+## ## ### ### ## ##
+## # ### g ### # ##
+## # ### ### # ##
+## # ### ### # ##
+## # ## ####### ## # ##
+## ### ##### ### ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level16(boarddef.Level):
+ l = LBlitzy, LGramy
+ r = RBlitzy, RGramy
+
+ letter = 1
+
+ walls = """
+#############################
+## l r ##
+## #### ## ##
+## #### ## #### ## ##
+## #### ## #### ## ##
+## l ## ## #### ## ##
+## #### ## ## l ##
+## #### ## ## #### ##
+## ## ## ## #### ##
+## #### ## ## ##
+## #### ## ## #### ## ##
+## ## #### ## ##
+## ## #### ## ##
+## ## #### #### l ## ## ##
+## ## #### ## ## ##
+## ## ## ## ##
+## ## ## r ## ##
+## #### ## ## ## ##
+## #### ## #### ## ## ##
+## ## #### ## ## ##
+## #### ## ## ##
+## #### ## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level17(boarddef.Level):
+ m = LMonky
+ j = RMonky
+ g = LGhosty
+ h = RGhosty
+ b = LBlitzy
+
+ walls = """
+############ ##
+## # ##
+## g # ##
+## ##### # ########### ##
+## # # # # # ##
+## # ## # # g # ##
+## # #j # # ##### # ##
+## # ##### # # # # ##
+## # # # ## # ##
+## #b # # #j # ##
+## ########### # ##### ##
+## # ##
+## # ##
+## ########### # h ##
+## # # # ##### ##
+## # h # # # # ##
+## # ##### # # ## # ##
+## # # # # # m # # ##
+## # ## # # ##### # ##
+## # m # # # # ##
+## ##### # # b # ##
+## # ########### ##
+## b # ##
+############ ##
+""" #|# #|# #|# """
+
+class level18(boarddef.Level):
+ o = (ROrcy,)*10
+
+ walls = """
+############# #############
+## ##
+## # ##
+## ##
+## # # ##
+## ##
+## ##
+## # # ##
+## ##
+## # ####### # ##
+## # # ##
+## ## ## ##
+## # ### ### # ##
+## ## ## ##
+## # # ##
+## # ####### # ##
+## # # ##
+## ## ## ##
+## ###### ###### ##
+## ### ### ##
+## ## o ## ##
+## ## ####### ## ##
+## ## ## ##
+############# #############
+""" #|# #|# #|# """
+
+class level19(boarddef.Level):
+ n = LNasty
+ g = RGhosty
+ f = LFlappy
+ s = LSpringy
+
+ walls = """
+## ### ### ### ##
+## # # # # # # ##
+## # #### #### # ##
+## #n # #n # #n # ##
+## ### ### ### ##
+## # # ##
+## # # ##
+## ### f ### ##
+## # # g # # ##
+## # # f # # ##
+## #n # g #n # ##
+## ### ### ##
+## # # ##
+## # # ##
+## ### ### ### ##
+## # #s # #s # # ##
+## # #### #### # ##
+## #n # #n # #n # ##
+## ### ### ### ##
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level20(boarddef.Level):
+ s = LSpringy, RSpringy
+
+ letter = fire = top = 1
+
+ walls = """
+### ###
+### ###
+## ##
+## ##
+##ss ss # # ss ss ##
+## # # # # # # ##
+## # # ##### # # ##
+## # # # # ##
+## # # # # ##
+## # # ##
+## # # ##
+## # # ##
+## ##
+## ##
+## ##
+## ##
+## # # ##
+## # # ##
+## # # ##
+## ##
+## ##
+## ##
+## ##
+## ######### ######### ##
+""" #|# #|# #|# """
+
+class level21(boarddef.Level):
+ n = (RNasty,)*12
+
+ letter = 1
+
+ walls = """
+#############################
+## ##
+##n ##
+######################### ##
+## ##
+## ##
+## #########################
+## ##
+## ##
+######################### ##
+## ##
+## ##
+## #########################
+## ##
+## ##
+######################### ##
+## ##
+## ##
+## #########################
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level22(boarddef.Level):
+ n = LNasty
+ m = LMonky
+ g = LGhosty
+ f = LFlappy
+ s = LSpringy
+ o = LOrcy
+ r = LGramy
+ b = LBlitzy
+
+ walls = """
+## ### ### ### ##
+## # # # # # # ##
+## # #### #### # ##
+## #o # #b # #r # ##
+## ### ### ### ##
+## # # ##
+## # # ##
+## ### ### ##
+## # # # # ##
+## # # # # ##
+## #g # #f # ##
+## ### ### ##
+## # # ##
+## # # ##
+## ### ### ### ##
+## # # # # # # ##
+## # #### #### # ##
+## #m # #s # #n # ##
+## ### ### ### ##
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level23(boarddef.Level):
+ m = LMonky
+
+ water = top = 1
+
+ walls = """
+###### # # ######
+###### # # ######
+## # m # ##
+## ######## ##### ######## ##
+## ######## ##### ######## ##
+## ######## ##### m ## ##
+## ######## ########### ## ##
+## m ######## ## ##
+## ########### ######## ## ##
+## ########### m ## ##
+## ################ ###### ##
+## ################ ###### ##
+## ######## m ##
+## ######## ############## ##
+## ######## ############## ##
+## ########m ########### ##
+## m ##### ##### m ##
+######## ##### ##### ########
+######## m m ########
+############## ##############
+#############################
+## ##
+## ##
+###### ######### ######
+""" #|# #|# #|# """
+
+class level24(boarddef.Level):
+ g = RGhosty
+ f = RFlappy
+ s = LSpringy
+ t = RSpringy
+
+ walls = """
+#############################
+## ##
+## ##
+## s t ##
+## s t ##
+## s t ##
+###### ######
+## ##
+## ##### ##### ##### ##
+## # # # ##
+## #g # #g ##
+## # #f # ##
+## # # # ##
+## ##
+## ##
+## ##### ##### ##### ##
+## # # # ##
+## # #g # ##
+## #f # #f ##
+## # # # ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level25(boarddef.Level):
+ s = LSpringy
+ t = RSpringy
+
+ letter = lightning = 1
+
+ walls = """
+############# #############
+## ##
+## # ##
+## # ##
+## s t ##
+## # # # # ##
+## # # # # ##
+## ##
+## # ##
+## # ##
+## t s ##
+## # # # # ##
+## # # # # ##
+## ##
+## # ##
+## # ##
+## s t ##
+## # # # # ##
+## # # # # ##
+## ##
+## # ##
+## # ##
+## ##
+############# #############
+""" #|# #|# #|# """
+
+class level26(boarddef.Level):
+ s = LSpringy
+
+ fire = 1
+
+ walls = """
+####### #######
+## ##
+## ##
+##s s ##
+##s ####### ####### s ##
+##s s ##
+#### ####
+## ##
+## ## ## ##
+## ##
+## ## ## ##
+## ##
+## ### ##
+## ##
+##s s ##
+#### ## ## ####
+## ##
+## ##
+## ##
+## ##
+## s s ##
+## ## ## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level27(boarddef.Level):
+ s = LSpringy
+
+ fire = 1
+
+ walls = """
+## ##
+## ##
+## # s # ##
+##s # # s # # s ##
+## # #s # # ##
+## # # # # ##
+### # ###
+## ### ##
+### # ###
+## # # # # # ##
+## #s # # #s # ##
+## # # # # ##
+## # # ##
+## ### ### ##
+## # # ##
+## # # ##
+## s #s # s ##
+## # # # # # # ##
+## # # # # # ##
+## # ### # ##
+## ### # ### ##
+## # # ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level28(boarddef.Level):
+ f = LFlappy
+
+ walls = """
+#############################
+######### ###########
+## ## f #### ##
+##f ## ff #### f ##
+##f f ## ### ####f f ##
+## f ## ### #### f ##
+##### ## ### ###### ##
+##### ## ### ###### ##
+##### ## #### ###### ##
+##### ## #### ###### ##
+## #### ##
+## ##
+## ##
+## ## ######## ### ##
+## ## ######## ### ##
+## ## ######## ### ##
+## ## ### ### ##
+## ## ### ### ##
+## ## ### ### ##
+## ##### ### ##### ##
+## ##### ### ##### ##
+## ### ##
+## ### ##
+#############################
+""" #|# #|# #|# """
+
+class level29(boarddef.Level):
+ f = LFlappy, RFlappy
+
+ top = water = 1
+
+ walls = """
+## ##################### ##
+## ##
+## f f ##
+## ##################### ##
+## ##################### ##
+## ##
+## f f ##
+## ##################### ##
+## ##################### ##
+## ##
+## f f ##
+## ##################### ##
+## ##################### ##
+## ##
+## f f ##
+## ##################### ##
+## ##################### ##
+## ##
+## f f ##
+## ##################### ##
+## ##################### ##
+## ##
+## f f ##
+## ##################### ##
+""" #|# #|# #|# """
+
+class level30(boarddef.Level):
+ g = RGhosty
+ h = RGhosty
+ i = LGhosty
+ j = LGhosty
+
+ walls = """
+#############################
+## ##
+## ##
+## ##
+## ####g # ##
+## ###### ##
+## j ###### ##
+## ####### ##
+## ###### ##
+## ######h ##
+## # #### ##
+## i ##
+## ##
+## ##
+## ####g # ####g # ##
+## ###### ###### ##
+## j ###### j ###### ##
+## ####### ####### ##
+## ###### ###### ##
+## ######h ######h ##
+## # #### # #### ##
+## i i ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level31(boarddef.Level):
+ o = LGramy, RGramy
+ r = LOrcy
+ s = ROrcy
+
+ top = letter = lightning = fire = 1
+
+ walls = """
+## ######## ######## ##
+## ##
+## ##
+## r s ##
+## ######## ######## ##
+##o o ##
+###### ######
+## ##
+##o s # #r o ##
+############# #############
+## ##
+## ##
+## ##################### ##
+## ##
+## ##
+## ####### ####### ##
+## ##
+##o o ##
+########## # # ##########
+## # # ##
+## ## ## ##
+## ##
+## ##
+############# #############
+""" #|# #|# #|# """
+
+class level32(boarddef.Level):
+ n = LNasty, RNasty
+ f = (LFlappy, RFlappy) * 2
+
+ walls = """
+############# #############
+## ## ## ##
+## f ## ##f ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+##n ## ## n ##
+#############################
+#############################
+## ##
+## f ##
+##n n ##
+#############################
+#############################
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+############# #############
+""" #|# #|# #|# """
+
+ winds = """
+>> ^ <<
+>>>>>>>>>>>>> ^ <<<<<<<<<<<<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ xxx ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+"""
+
+class level33(boarddef.Level):
+ monsters = [mnstrmap.Gramy(14, -2, 1),
+ mnstrmap.Gramy(13, -2, 0)] * 10
+ top = 1
+
+ walls = """
+## ## ## ##
+## ## ## ##
+## ## ## ##
+## ####### ####### ##
+## # # ##
+## # # ##
+## # ########### # ##
+## # # ##
+## # # ##
+## ####### ####### ##
+## # # ##
+## # # ##
+## # ########### # ##
+## # # ##
+## # # ##
+## ####### ####### ##
+## # # ##
+## # # ##
+## # ########### # ##
+## # # ##
+## # # ##
+## ####### ####### ##
+## ## ## ##
+############# #############
+""" #|# #|# #|# """
+
+ winds = """
+>> <<
+>>>xxxxxxxxxxxxxxxxxxxxxxx<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+"""
+
+class level34(boarddef.Level):
+ n = LNasty
+ N = RNasty
+ m = LMonky
+ M = RMonky
+ g = LGhosty
+ G = RGhosty
+ o = LOrcy
+ O = ROrcy
+ r = LGramy
+ R = RGramy
+ b = LBlitzy, RBlitzy
+
+ top = water = letter = 1
+
+ walls = """
+##### # ############# # #####
+## # # # ##
+## # # # ##
+## # # # ##
+## r #R ##
+############# # #############
+## # ##
+##G # # # g ##
+## # # # ##
+## # # # ##
+## #O b o # ##
+##### # ############# # #####
+## # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+##M # m ##
+############# # #############
+## # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # n #N # ##
+##### # ############# # #####
+""" #|# #|# #|# """
+
+class level35(boarddef.Level):
+ g = (RGhosty,)*3
+ h = (LGhosty,)*3
+
+ walls = """
+############# #############
+## h ##
+#############################
+##g ##
+#############################
+## h ##
+############# #############
+##g ##
+#############################
+## h ##
+#############################
+##g ##
+############# #############
+## h ##
+#############################
+##g ##
+#############################
+## h ##
+############# #############
+##g ##
+#############################
+## ##
+## ##
+############# #############
+""" #|# #|# #|# """
+
+class level36(boarddef.Level):
+ f = LFlappy
+ r = RSpringy
+
+ top = fire = letter = 1
+
+ walls = """
+## # # # # # # ##
+## f # # r # # f ##
+## # # #f r # # # ##
+## # # r # # ##
+## f # # # f # # # f ##
+## # # # # ##
+## f # # # f # # # f ##
+## # # f # # ##
+## # # # # # # ##
+## f # # f # # f ##
+## # # # f # # # ##
+## # # f # # ##
+## # # # # # # ##
+## # # f f # # ##
+## # # # # # # ##
+## # # # # ##
+## # # # f # # # ##
+## # # # # ##
+## # # # f # # # ##
+## # # f # # ##
+## # # # f # # # ##
+## # # # # ##
+## # # # f # # # ##
+#############################
+""" #|# #|# #|# """
+
+class level37(boarddef.Level):
+ n = LNasty
+ m = RMonky
+ o = (RFlappy,) * 5
+ s = LSpringy
+ t = RSpringy
+
+ walls = """
+####### ######### #######
+## ##
+## ##
+## ##
+## #s t s t # ##
+## ##################### ##
+## # # ##
+## # # ##
+## # o # ##
+## # # ##
+## # # ##
+## # #n m # # ##
+## # ####### # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## # # # ##
+## ##################### ##
+## # # # ##
+## # ##
+## # ##
+## # ##
+####### ######### #######
+""" #|# #|# #|# """
+
+class level38(boarddef.Level):
+ f = LFlappy
+ b = (LBlitzy, RBlitzy) * 2
+
+ water = 1
+
+ walls = """
+############# #############
+## ##
+## ##
+## ##
+## b b b ##
+## ######### ######### ##
+## ######### ######### ##
+## ######### ######### ##
+## ### ### ##
+## ### f f f f ### ##
+## ### ### ##
+## ### ### ### ### ##
+## ### ### ### ### ##
+## ### ### ### ### ##
+## ### ### ### ### ##
+## ### ### ##
+## ### f f f f ### ##
+## ######### ######### ##
+## ######### ######### ##
+## ######### ######### ##
+## ##
+## ##
+## ##
+############# #############
+""" #|# #|# #|# """
+
+class level39(boarddef.Level):
+ monsters = []
+ mnstrclasses = ([mnstrmap.Nasty] * 10 +
+ [None] * 30 +
+ [mnstrmap.Monky] * 7 +
+ [None] * 30 +
+ [mnstrmap.Orcy] * 5)
+ for i in range(len(mnstrclasses)):
+ if mnstrclasses[i]:
+ left = random.randrange(2)
+ x = random.choice([7,14,21])
+ monsters.append(mnstrclasses[i](x-left, -2*i, left))
+
+ walls = """
+###### #### #### ######
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+###### ######### ######
+## ##
+## ##
+## ##
+## ##
+#############################
+""" #|# #|# #|# """
+
+class level40(boarddef.Level):
+ g = LGhosty
+
+ top = fire = 1
+
+ walls = """
+## ##
+## ##
+## # ## ##
+## ## ### # ##
+## # ## ##
+## ##
+## ##
+## # ##
+## # #### ##
+## ### ##
+## # # g ##
+## # ##
+## ### # ##
+## # # ### ##
+## ### g # ##
+## # ##
+## g ##
+## # ## ##
+## # # ### ##
+## ### # ##
+## # g ##
+## g # # ##
+## ## # ## ##
+## # ### ## ## ##
+""" #|# #|# #|# """
+
+ winds = """
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>>>>>>>>>>>vvxvv<<<<<<<<<<<<
+>>>>>>>>>>>>vvxvv<<<<<<<<<<<<
+>>>>>>>>>>>>vxxxv<<<<<<<<<<<<
+>>>>>>>>>>>>vxxxv<<<<<<<<<<<<
+>>>>>>>>>>>>vxxxv<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>>>>>>>>>>>>xxxxx<<<<<<<<<<<<
+>> <<
+"""
+
+class level41(boarddef.Level):
+ f = RFlappy
+ j = LFlappy
+ b = RBlitzy
+ d = LBlitzy
+
+ top = 1
+
+ walls = """
+############# #############
+##b d ##
+##### db #####
+## ############# ##
+## ## ## ##
+### ### jfjfjfjf ### ###
+## ## fjfjfjfj ## ##
+#### ##### ##### ####
+## ##
+### ###
+#### ####
+#b d ##
+###### ######
+### ###
+####### #######
+##### #####
+##b d ##
+########## ###########
+##### #####
+#### ####
+####### #######
+## ##
+## ##
+############# #############
+""" #|# #|# #|# """
+
+ winds = """
+>> <<
+>>v<<<<<<<<<<<<>>>>>>>>>>>v<<
+>>v>>>>>>>>vvvvvvv<<<<<<<<v<<
+>>v>>>>>>>>vvvvvvv<<<<<<<<v<<
+>>v>>>>>>>>xxxxxxx<<<<<<<<v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>v v<<
+>>> vvv <<<
+>>> vvv <<<
+"""
+
+class level42(boarddef.Level):
+ l = LOrcy, LOrcy
+ r = ROrcy, ROrcy
+ b = ROrcy, LOrcy, ROrcy, LOrcy
+
+ top = 1
+ water = 1
+ letter = 1
+
+ walls = """
+## # ##
+## l r ##
+## ## ## ##
+## ##
+## #### ### ##
+## # ##
+## # # ##
+## # # r ##
+## # ## ##
+## r # # ##
+## ## # # ##
+## # # ##
+## r # # ##
+## # # ## # ##
+## # # b # ##
+## # ## # ##
+## # r # ##
+## # ## # ##
+## # ##
+## # # ##
+## b ##
+## ## # ##
+## # ##
+## ### ## # ##
+""" #|# #|# #|# """
+
+ winds = """
+>>>vvvvvvvvvvvvvvvvvvvvvvv<<<
+>>>vvvvvvvvvvvvvvvvvvvvvvv<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+"""
+
+class level43(boarddef.Level):
+ s = LSpringy, RSpringy, LSpringy, RSpringy
+ f = LFlappy
+ g = RFlappy
+
+ walls = """
+## # # ##
+## #g f # ##
+## ##
+## # # ##
+## # # ##
+##s # # # # s ##
+## # # # # ##
+## # # # ##
+## # ##
+## # s #s # ##
+## # # # # # ##
+## # # # # # # # # # ##
+## # # # # # # # # # # # ##
+## # # # # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # ##
+## # # # # # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # # # ##
+## # # # # ##
+## # # # # # # # ##
+## # # # # # # # # # # # # ##
+""" #|# #|# #|# """
+
+class level44(boarddef.Level):
+ o = LGramy
+ p = RGramy
+ g = RFlappy
+ h = LFlappy
+
+ top = 1
+
+ walls = """
+## ##
+## ##
+## ##
+######## ## ##
+## h ## ## ##
+## g ## ## ###### ##
+## h ## ## ## ## ##
+## g ## ## ## ## ##
+## h ## ## ## ## ##
+## g ## ## ## ## ##
+## ## ## ## ## ##
+## ## ## ## ## h ##
+## ## ## ## ## g ##
+## ## ## ## ## h ##
+## ## ## ## ## g ##
+## ###### ## ## h ##
+## ## ## g ##
+## ## ########
+## ##
+## ##
+## ##
+## ##
+## p o p o p o ##
+#############################
+""" #|# #|# #|# """
+
+ winds = """
+>>>vvvvvvvvvvvvvvvvvvvvvvv<<<
+>>>>>>>>vv<<<<<vv<<<>>>vvv<<<
+>>^ vv ^<<
+>>^ vv ^<<
+>>^ vv ^<<
+>>^ vv ^<<
+>>^ vv >< ^<<
+>>^ vv ^^ ^<<
+>>^ vv ^^ ^<<
+>>^ vv ^^ ^<<
+>>^ vv ^^ ^<<
+>>^ vv ^^ ^<<
+>>^ vv ^^ ^<<
+>>^ vv ^^ ^<<
+>>^ >< ^^ ^<<
+>>^ ^<<<>>>^ ^^ ^<<
+>>^ ^^ ^<<
+>>^ ^^ ^<<
+>>^ >>>>>^^<<<<<^<<
+>>^ ^<<
+>>^ ^<<
+>>^vvvvvvvvvvvvvvvvvvvvvvv^<<
+>>^vvvvvvvvvvvvvvvvvvvvvvv^<<
+>>^vvvvvvvvvvvvvvvvvvvvvvv^<<
+"""
+
+class level45(boarddef.Level):
+ b = LBlitzy, RBlitzy
+
+ top = 1
+ water = 1
+
+ walls = """
+## ##
+## ## ## ##
+## ## ## # # ## ## ##
+## # # # # # # ##
+## # # #bb # # # ##
+## #bb # ## ## #bb # ##
+## ## ## ## ## ##
+## ## ## ##
+## ## ## # # ## ## ##
+## # # # # # # ##
+## # # # # # # ##
+## #bb # # # #bb # ##
+## ## ## #bbbb # ## ## ##
+## ## ## ##
+## ## ## ## ## ##
+## # # ## ## # # ##
+## # # # # # # ##
+## #bb # # # #bb # ##
+## ## ## #bb # ## ## ##
+## ## ## ##
+## ##
+## ##
+## ##
+### ##################### ###
+""" #|# #|# #|# """
+
+ winds = """
+>>>vvvvvvvvvvvvvvvvvvvvvvv<<<
+>>>>>>>>>>>>>xxx<<<<<<<<<<<<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+>>xxxxxxxxxxxxxxxxxxxxxxxxx<<
+"""
+
+class level46(boarddef.Level):
+ m = LMonky, RGramy
+ n = LGramy, RMonky
+ g = [RGhosty] * 4
+ h = [LGhosty] * 4
+
+ letter = 1
+ fire = 1
+
+ walls = """
+############# #############
+## ##
+## ##
+## ##
+############# #############
+## ## ## ##
+## g ## ## h ##
+## ## mnmnm ## ##
+## ########### ##
+## ##
+## ##
+## ##
+######## ### ########
+## ## # ## ##
+## ## # ## ##
+##mnm ## # ##mnm ##
+######## ##### ########
+## # ##
+## # ##
+## ####### ##
+## ##
+## ##
+## # # # ##
+############ ############
+""" #|# #|# #|# """
+
+ winds = """
+>>> ^ <<<
+>>>>>>>>>>>>>>^<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ x ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+"""
+
+class level47(boarddef.Level):
+ l = LOrcy
+ r = ROrcy
+ n = LNasty
+ m = RNasty
+
+ top = 1
+ lightning = 1
+
+ walls = """
+## ################### ##
+## ################### ##
+## # ##
+## # ##
+## ##
+## ##
+## ##
+###### #### #### ######
+## # # ##
+## # # ##
+## # # ##
+## ##### ####### ##### ##
+## ##
+## ##
+## ##
+##### ###### ###### #####
+## # # ##
+## # # ##
+##rmr # lr # lnl ##
+######### ####### #########
+## # # # ##
+## # # # ##
+## #rmrmrmr #lnlnlnl # ##
+## ################### ##
+""" #|# #|# #|# """
+
+ winds = """
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>>>>>>vvvvvvvvvvvvvvv<<<<<<<
+>>>>>>>>^^^^^^^^^^^^^<<<<<<<<
+>>>>>>>>^^^^^^^^^^^^^<<<<<<<<
+>>>>>>>>^^^^^^^^^^^^^<<<<<<<<
+>>^ ^<<
+"""
+
+class levelFinal(boarddef.Level):
+
+ walls = """
+############# #############
+## ##
+## ##
+## ##
+## ##
+## ##
+#### ################# ####
+## ### ### ##
+## ## ## ##
+####### ##### #######
+## ##
+## ##
+## ##
+## ##
+######### #########
+## ##
+## ##
+## ##
+########## ##########
+## ##
+## ##
+## ##
+## ##
+############# #############
+""" #|# #|# #|# """
+# nb.: the previous line has no purpose
+# other than helping with wall alignment
diff --git a/bubbob/levels/HouseOfFun.bin b/bubbob/levels/HouseOfFun.bin
new file mode 100644
index 0000000..838344f
--- /dev/null
+++ b/bubbob/levels/HouseOfFun.bin
Binary files differ
diff --git a/bubbob/levels/Levels.bin b/bubbob/levels/Levels.bin
new file mode 100644
index 0000000..fd176d1
--- /dev/null
+++ b/bubbob/levels/Levels.bin
Binary files differ
diff --git a/bubbob/levels/LostLevels.bin b/bubbob/levels/LostLevels.bin
new file mode 100644
index 0000000..a09e365
--- /dev/null
+++ b/bubbob/levels/LostLevels.bin
Binary files differ
diff --git a/bubbob/levels/README.txt b/bubbob/levels/README.txt
new file mode 100644
index 0000000..544b571
--- /dev/null
+++ b/bubbob/levels/README.txt
@@ -0,0 +1,23 @@
+===============================
+How to make your own level sets
+===============================
+
+The .bin files are MacBinary files from the original MacOS9 game. The
+.py files are the levels that we did ourselves. To make new levels, you
+need to make a new .py file in the current directory (bubbob/levels/).
+
+For an example, open CompactLevels.py with any text editor. The
+structure should be fairly simple to understand even without knowledge
+about Python. (Just don't try to look at RandomLevels.py first: it is a
+full Python program that generates the levels completely randomly.)
+
+To get started, copy CompactLevels.py to a new name, like MyLevels.py,
+and start editing it. You can remove or change levels, but the last one
+should always be called LevelFinal and have no monster in it. All other
+levels should have monsters, otherwise they'll be considered as the
+final level.
+
+Also note that all levels need to have the same size (width and height),
+but different level sets can have different sizes. For example, the
+levels in CompactLevels.py are a bit smaller than the ones in the .bin
+files, and the levels in scratch.py are a bit larger.
diff --git a/bubbob/levels/RandomLevels.py b/bubbob/levels/RandomLevels.py
new file mode 100644
index 0000000..5c0f77d
--- /dev/null
+++ b/bubbob/levels/RandomLevels.py
@@ -0,0 +1,362 @@
+#
+# Second try at automatically generating levels that are
+# a bit more related to each other instead of being completely independent.
+#
+
+from __future__ import generators
+import sys, random, math
+from random import uniform, choice, randrange
+
+
+class Parameter(object):
+ def __init__(self, name, rng):
+ self.name = name
+ self.rng = rng
+ def __get__(self, instance, cls):
+ assert self.name not in instance.__dict__
+ value = self.rng()
+ setattr(instance, self.name, value)
+ return value
+
+class ChoiceParameter(Parameter):
+ def __init__(self, name, list):
+ Parameter.__init__(self, name, lambda : choice(list))
+
+class BoolParameter(Parameter):
+ def __init__(self, name, prob=0.5):
+ Parameter.__init__(self, name, lambda : random.random() < prob)
+
+def flat(mean,var):
+ return randrange(mean-var,mean+var+1)
+
+def dice(n,sides,orig=1):
+ result = 0
+ for i in range(n):
+ result += orig+randrange(sides)
+ return result
+
+def fork(choice1, prob, choice2):
+ if random.random() < prob:
+ return choice1()
+ else:
+ return choice2()
+
+MnstrCategory = {
+ "Nasty": 0,
+ "Monky": 0,
+ "Ghosty": 1,
+ "Flappy": 1,
+ "Springy": 2,
+ "Orcy": 0,
+ "Gramy": 0,
+ "Blitzy": 2}
+MnstrNames = MnstrCategory.keys()
+Bonuses = ['letter', 'fire', 'lightning', 'water', 'top']
+
+def mnstrclslist(name):
+ import boarddef
+ classname1 = 'L' + name
+ classname2 = 'R' + name
+ return [getattr(boarddef, classname1), getattr(boarddef, classname2)]
+
+class Shape:
+ basemnstr = ChoiceParameter('basemnstr', MnstrNames)
+ extramnstr = ChoiceParameter('extramnstr', range(4))
+ samemnstr = BoolParameter('samemnstr')
+ baseshape = ChoiceParameter('baseshape', ' ODBGMPRWZS')
+ rooms = BoolParameter('rooms')
+ holes = BoolParameter('holes')
+ lines = ChoiceParameter('lines', ' -/|')
+ platforms = BoolParameter('platforms')
+ platholes = BoolParameter('platholes')
+ platfull = BoolParameter('platfull')
+ mess = ChoiceParameter('mess', ' ....!')
+ closed = BoolParameter('closed', 0.95)
+ bonuses = ChoiceParameter('bonuses', xrange(3**len(Bonuses)))
+ smooth = ChoiceParameter('smooth', range(4))
+ startplats = BoolParameter('startplats', 0.98)
+ makespace = BoolParameter('makespace', 0.8)
+ straightfall = BoolParameter('straightfall', 0.8)
+ mirrored = BoolParameter('mirrored', 0.4)
+ enlargeholes = BoolParameter('enlargeholes', 0.9)
+
+ all_parameters = [name for name in locals().keys()
+ if not name.startswith('_')]
+
+ def __init__(self, shape=None):
+ if shape:
+ self.__dict__.update(shape.__dict__)
+ self.modified = 0
+
+ def set_gens(self, rooms=0, platforms=0, holes=0, smooth=0, mess=' ', lines=' '):
+ self.rooms = rooms
+ self.platforms = platforms
+ self.holes = holes
+ self.smooth = smooth
+ self.mess = mess
+ self.lines = lines
+
+ def reset(self, attrname=None):
+ if attrname:
+ try:
+ del self.__dict__[attrname]
+ except KeyError:
+ pass
+ else:
+ self.__dict__.clear()
+ self.modified = 1
+
+ def __eq__(self, other):
+ return (self.__class__ is other.__class__ and
+ self.__dict__ == other.__dict__)
+
+ def test_similar_parameters(self, prevlist):
+ similarity = 0
+ rprevlist = prevlist[:]
+ rprevlist.reverse()
+ for param in Shape.all_parameters:
+ accum = 0
+ for prev in rprevlist:
+ if getattr(self, param) != getattr(prev, param):
+ break
+ else:
+ accum += 1
+ similarity += accum
+ minimum = min(4*len(prevlist), 7)
+ if not (minimum <= similarity <= 17):
+ self.reset()
+
+ def test_not_too_often(self, prevlist):
+ for param, bad_value, delay in [
+ ('mess', '.', 2),
+ ('mess', '!', 11),
+ ('holes', 1, 1),
+ ]:
+ if getattr(self, param) == bad_value:
+ for prev in prevlist[-delay:]:
+ if getattr(prev, param) == bad_value:
+ self.reset(param)
+
+ def test_mess_hole(self, prevlist):
+ if self.mess == '!':
+ self.holes = 1
+
+ all_tests = [value for (name, value) in locals().items()
+ if name.startswith('test_')]
+
+ def accept(self, lvl):
+ f = lambda d=self.difficulty : randrange(3, 4+int(9*d))
+ lvl.mlist = [(mnstrclslist(self.basemnstr), f)]
+ repeat = choice([2,2,3]) - self.extramnstr
+ if repeat > 1:
+ lvl.mlist *= repeat
+ if self.extramnstr:
+ othermnstr = [name for name in MnstrNames if name!=self.basemnstr]
+ if self.samemnstr:
+ othermnstr = [name for name in othermnstr
+ if MnstrCategory[name]==MnstrCategory[self.basemnstr]]
+ random.shuffle(othermnstr)
+ for name in othermnstr[:self.extramnstr]:
+ lvl.mlist.append((mnstrclslist(name), f))
+
+ lvl.genwalls = []
+
+ if self.baseshape == 'G':
+ lvl.genwalls.append((RandomLevel.grids,
+ uniform(0.7,0.8),
+ uniform(0.7,0.8)))
+ self.set_gens()
+ if self.baseshape == 'P':
+ lvl.genwalls.append((RandomLevel.pegs,
+ uniform(0.1,0.2),
+ uniform(0.45,0.7),
+ choice([0,1,1,1])))
+ self.set_gens(smooth=3)
+ self.closed = random.random() < 0.80
+ if self.baseshape == 'B':
+ nr = choice([0,0,1])
+ lvl.genwalls.append((RandomLevel.bouncers,
+ dice(1, 100) + 250 - nr*200, # length
+ uniform(0.7, 1.7),
+ nr))
+ self.set_gens(smooth=3)
+ if self.baseshape == 'W':
+ nr = dice(1, 3) + 2
+ lvl.genwalls.append((RandomLevel.walkers,
+ dice(2, 100) + 100, # length
+ nr, nr + dice(2, 3),
+ choice([0,1])))
+ self.set_gens()
+ if self.baseshape == 'R':
+ lvl.genwalls.append((RandomLevel.rivers,
+ randrange(3,(lvl.WIDTH-4)/4), # the number of rivers
+ uniform(0.3, 1.4), # the side stepping threshold
+ 10)) # the max side stepping size
+ self.set_gens()
+ if self.baseshape == 'Z':
+ lvl.genwalls.append((RandomLevel.zigzag,))
+ self.set_gens()
+ if self.baseshape == 'M':
+ lvl.genwalls.append((RandomLevel.mondrian,))
+ self.set_gens()
+ if self.baseshape == 'O':
+ lvl.genwalls.append((RandomLevel.remove_joined_blocks,))
+ self.set_gens()
+ if self.baseshape == 'S':
+ lvl.genwalls.append((RandomLevel.platforms_reg,))
+ self.set_gens()
+ self.makespace = random.random() < 0.1
+ if self.closed:
+ self.startplats = 0
+ if self.baseshape == 'D':
+ lvl.genwalls.append((RandomLevel.discrete_blocks,))
+ self.set_gens()
+ self.makespace = random.random() < 0.1
+ if self.closed:
+ self.startplats = 0
+
+ if self.rooms:
+ nr = dice(2, 6)
+ lvl.genwalls.append((RandomLevel.rooms,
+ lambda : flat(9-nr,2), # the half size of the room
+ lambda : uniform(0.8,1.2), # the excentricity of the room
+ nr)) # the number of rooms
+ if self.lines != ' ':
+ rng_angle = {
+ '-': lambda : 0,
+ '/': None, # default
+ '|': lambda : math.pi/2,
+ }
+ lvl.genwalls.append((RandomLevel.lines,
+ lambda : dice(8,3), # line length
+ dice(2,4), # number of lines
+ rng_angle[self.lines]))
+ if self.platforms:
+ nplat = dice(2,4,0)
+ if nplat: space = flat((lvl.HEIGHT-1)/nplat/2,(lvl.HEIGHT-1)/nplat/2-1)
+ else: space = 1
+ if self.platholes:
+ nholes = lambda : dice(1,3)
+ else:
+ nholes = lambda : 0
+ wholes = lambda : dice(2,3)
+ full = self.platfull
+ lvl.genwalls.append((RandomLevel.platforms,
+ (nplat,space), # number of platform and spacing
+ (nholes,wholes), # number of holes and width
+ full)) # full width platform
+ if self.mess != ' ':
+ threshold = {
+ '.': 0.02 + 0.08*random.random(), # normal
+ '!': 0.25 + 0.2 *random.random(), # super-filled
+ }
+ lvl.genwalls.append((RandomLevel.mess, threshold[self.mess]))
+ if self.holes:
+ nh = choice([1,1,2,2,2,3,3,3,4,5])
+ lvl.genwalls.append((RandomLevel.holes,
+ lambda : flat(9-nh,2), # radius of the holes
+ lambda : uniform(0.9,1.1), # excentricity
+ nh, # number of holes
+ lambda : choice([0,0,0,1]))) # circle or rectangle
+ if self.closed:
+ lvl.genwalls.append((RandomLevel.close,))
+ if self.smooth > 0:
+ # smooth away all lone empty spaces
+ lvl.genwalls.append((RandomLevel.smooth, 1.0, 1))
+ # possibly smooth away some lone bricks
+ if self.smooth == 2:
+ lvl.genwalls.append((RandomLevel.smooth, 0.25, 0))
+ elif self.smooth == 3:
+ lvl.genwalls.append((RandomLevel.smooth, 0.75, 0))
+ if self.startplats:
+ lvl.genwalls.append((RandomLevel.startplatform, ))
+ lvl.genwalls.append((RandomLevel.openstartway, ))
+
+ if self.makespace:
+ lvl.genwalls.append((RandomLevel.make_space, ))
+
+ if self.straightfall:
+ lvl.genwalls.append((RandomLevel.prevent_straight_fall, ))
+
+ if self.mirrored:
+ lvl.genwalls.append((RandomLevel.mirror, ))
+
+ if self.enlargeholes:
+ lvl.genwalls.append((RandomLevel.enlarge_tiny_holes, ))
+
+ lvl.genwalls.append((RandomLevel.generate_wind, ))
+ b = self.bonuses
+ for name in Bonuses:
+ setattr(lvl, name, (b % 3) == 1)
+ b = b // 3
+ lvl.autogen_shape = self
+
+
+def generate_shape(prevlist):
+ tests = Shape.all_tests
+ s = Shape()
+ for i in range(100):
+ s1 = Shape(s)
+ random.shuffle(tests)
+ for test in tests:
+ test(s1, prevlist)
+ if not s1.modified and s1 == s:
+ break
+ s = s1
+# else:
+# sys.stdout.write('*')
+ del s.modified
+ return s
+
+def makeshapes(nblevels=25):
+ shapelist = []
+ for i in range(nblevels):
+ s = generate_shape(shapelist)
+ s.difficulty = float(i+1)/nblevels
+ yield s
+ shapelist.append(s)
+ if len(shapelist) == 10:
+ del shapelist[:]
+
+def GenerateLevels():
+# print 'generating levels',
+ Levels = []
+ for s in makeshapes():
+ class level(RandomLevel):
+ WIDTH = 28
+ HEIGHT = 23
+ def enter(self, *args, **kw):
+ result = RandomLevel.enter(self, *args, **kw)
+ params = self.autogen_shape.__dict__.items()
+ params.sort()
+# for keyvalue in params:
+# print '%20s: %s' % keyvalue
+ return result
+ s.accept(level)
+ Levels.append(level)
+# sys.stdout.write('.')
+# sys.stdout.flush()
+# print
+ class levelfinal(RandomLevel):
+ WIDTH = level.WIDTH
+ HEIGHT = level.HEIGHT
+ genwalls = [(RandomLevel.platforms,(5,3),(lambda:flat(2,1),lambda:flat(6,2)),1),
+ (RandomLevel.close,)]
+ Levels.append(levelfinal)
+ return Levels
+
+def GenerateSingleLevel(width, height):
+ [s] = makeshapes(1)
+ class level(RandomLevel):
+ WIDTH = width
+ HEIGHT = height
+ s.accept(level)
+ return level
+
+if __name__ == '__main__':
+ for s in makeshapes():
+ print s.__dict__
+else:
+ rnglevel = {}
+ execfile('levels/rnglevel', rnglevel)
+ RandomLevel = rnglevel['RandomLevel']
diff --git a/bubbob/levels/rnglevel b/bubbob/levels/rnglevel
new file mode 100644
index 0000000..cfa6ec0
--- /dev/null
+++ b/bubbob/levels/rnglevel
@@ -0,0 +1,1276 @@
+from random import *
+from math import *
+
+import boarddef
+from boarddef import LNasty, LMonky, LGhosty, LFlappy
+from boarddef import LSpringy, LOrcy, LGramy, LBlitzy
+from boarddef import RNasty, RMonky, RGhosty, RFlappy
+from boarddef import RSpringy, ROrcy, RGramy, RBlitzy
+
+def flat(mean,var):
+ return randrange(mean-var,mean+var+1)
+
+def dice(n,sides,orig=1):
+ result = 0
+ for i in range(n):
+ result += orig+randrange(sides)
+ return result
+
+def fish(mu):
+ def fact(n):
+ r = 1.
+ for i in range(1,n+1):
+ r *= i
+ return r
+ scale = fact(0)/exp(-mu)
+ dens = []
+ while 1:
+ x = len(dens)
+ dens.append(int(scale*exp(-mu)*pow(mu,x)/fact(x)+0.5))
+ if x > mu and dens[-1] == 0:
+ break
+ table = []
+ x = 0
+ for d in dens:
+ for i in range(d):
+ table.append(x)
+ x += 1
+ return choice(table)
+
+
+class RandomLevel(boarddef.Level):
+ WIDTH = 32
+ HEIGHT = 28
+ MAXTRY = 1000
+ # parameters of the 'mess generator'
+ # mess_prob : the probability that a cell turn into a wall
+
+ def __init__(self,num):
+ if hasattr(self.__class__, 'walls'):
+ #print 'Reusing previously generated level'
+ #print self.__class__.walls
+ self.walls = self.__class__.walls
+ boarddef.Level.__init__(self,num)
+ return
+
+ #print 'Generating a new level'
+ self.reset(fill=False)
+
+ self.windmap = [ [' ' for x in range(self.WIDTH)] for y in range(self.HEIGHT) ]
+
+ if hasattr(self, 'auto'):
+ self.generate()
+ self.do_bonuses()
+
+ for gw in self.genwalls:
+ gw[0](self,*gw[1:])
+
+ if hasattr(self, 'mlist'):
+ self.do_monsters()
+
+ self.dig_vertical_walls()
+ self.do_walls()
+ self.walls = self.__class__.walls
+ #print self.walls
+
+ self.do_winds()
+ self.winds = self.__class__.winds
+
+ boarddef.Level.__init__(self,num)
+
+ def reset(self, fill=False):
+ if fill:
+ w = '#'
+ f = 0
+ else:
+ w = ' '
+ f = 1
+ # map for the walls
+ self.wmap = [ [w for x in range(self.WIDTH)] for y in range(self.HEIGHT) ]
+ # map of the free cells
+ self.fmap = [ [f for x in range(self.WIDTH)] for y in range(self.HEIGHT) ]
+
+ def setw(self,x,y,c='#'):
+ if x > self.WIDTH-1 or x < 0 or y > self.HEIGHT-1 or y < 0:
+ return
+ if self.fmap[y][x]:
+ self.wmap[y][x] = c
+ self.fmap[y][x] = 0
+
+ def getw(self,x,y):
+ if x > self.WIDTH-1 or x < 0 or y > self.HEIGHT-1 or y < 0:
+ return '#'
+ return self.wmap[y][x]
+
+ def clrw(self,x,y):
+ if x > self.WIDTH-1 or x < 0 or y > self.HEIGHT-1 or y < 0:
+ return
+ self.wmap[y][x] = ' '
+ self.fmap[y][x] = 1
+
+ def lockw(self,x,y,c=0):
+ if x > self.WIDTH-1 or x < 0 or y > self.HEIGHT-1 or y < 0:
+ return
+ self.fmap[y][x] = c
+
+ def setwind(self,x,y,c=' '):
+ if x > self.WIDTH-1 or x < 0 or y > self.HEIGHT-1 or y < 0:
+ return
+ self.windmap[y][x] = c
+
+ def getwind(self,x,y):
+ if x > self.WIDTH-1 or x < 0 or y > self.HEIGHT-1 or y < 0:
+ return ' '
+ return self.windmap[y][x]
+
+ def wind_rect(self,x,y,w,h,ccw=0):
+ "Set a wind in a rectangle which will move the bubbles cw or ccw"
+ if w < 1 or h < 1:
+ return
+ if ccw == 1:
+ for dx in range(w):
+ self.setwind(x+dx+1, y, '<')
+ self.setwind(x+dx, y+h, '>')
+ for dy in range(h):
+ self.setwind(x, y+dy, 'v')
+ self.setwind(x+w, y+dy+1, '^')
+ else:
+ for dx in range(w):
+ self.setwind(x+dx, y, '>')
+ self.setwind(x+dx+1, y+h, '<')
+ for dy in range(h):
+ self.setwind(x, y+dy+1, '^')
+ self.setwind(x+w, y+dy, 'v')
+
+ def mirror(self):
+ "Mirror the level vertically."
+ for y in range(self.HEIGHT):
+ for x in range(self.WIDTH/2):
+ self.wmap[y][x] = self.wmap[y][self.WIDTH-x-1]
+
+ def dig_well_until_space(self, x=1, yadj=1):
+ "Digs a well either up or down and stops when it encounters first empty wall space."
+ if yadj == 1:
+ y = 0
+ else:
+ yadj = -1
+ y = self.HEIGHT-1
+ while (y < self.HEIGHT) and (y >= 0):
+ self.clrw(x,y)
+ self.clrw(x+1,y)
+ y += yadj
+ if ((self.getw(x,y) == ' ') and (self.getw(x+1,y) == ' ')):
+ break
+
+ def enlarge_tiny_holes(self):
+ "Makes one-block size holes wider."
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ if self.wmap[y][x] == ' ':
+ single = 0
+ for dx in range(x-1,x+2):
+ for dy in range(y-1,y+2):
+ if self.getw(dy,dx) == '#':
+ single = single + 1
+ if single == 8:
+ if x > (self.WIDTH / 2):
+ self.clrw(x-1,y)
+ else:
+ self.clrw(x+1,y)
+
+ def make_space(self, gens=-1):
+ "Removes walls from a level, to make it more playable."
+ if gens == -1:
+ gens = randint(0,62)+1
+ if gens & 1: # top
+ for x in range(self.WIDTH):
+ self.clrw(x,1)
+ if random() < 0.5:
+ self.clrw(x,2)
+ if gens & 2: # bottom
+ for x in range(self.WIDTH):
+ self.clrw(x,self.HEIGHT-1)
+ if random() < 0.5:
+ self.clrw(x,self.HEIGHT-2)
+ if gens & 4: # middle
+ y = randint(0,self.HEIGHT/10) + (self.HEIGHT/2)
+ for x in range(self.WIDTH):
+ self.clrw(x,y)
+ if random() < 0.5:
+ self.clrw(x,y-1)
+ if random() < 0.5:
+ self.clrw(x,y+1)
+ if gens & 8: # left
+ x = randint(0,self.WIDTH/4)
+ self.dig_well_until_space(x, 1)
+ self.dig_well_until_space(x, -1)
+ if gens & 16: # right
+ x = randint(0,self.WIDTH/4)
+ self.dig_well_until_space(self.WIDTH-x-2, 1)
+ self.dig_well_until_space(self.WIDTH-x-2, -1)
+ if gens & 32: # center
+ self.dig_well_until_space(self.WIDTH/2, 1)
+ self.dig_well_until_space(self.WIDTH/2, -1)
+
+ def generate_wind1(self, rndchoice=1, choices=[' ',' ',' ','x','>','<','^','^','v'], xsize=-1,ysize=-1):
+ """Makes a random wind pattern. Parameters:
+ 0: if 1=randomly select from choices, else select in order
+ 1: a list of the choices that are allowed.
+ 2: horizontal size of wind blocks
+ 3: vertical size of wind blocks
+ """
+ choicenum = 0
+ if xsize == -1:
+ xsize = randint(1, self.WIDTH)
+ if ysize == -1:
+ ysize = randint(1, self.HEIGHT)
+ if xsize < 1:
+ xsize = 1
+ elif xsize > self.WIDTH:
+ xsize = self.WIDTH
+ if ysize < 1:
+ ysize = 1
+ elif ysize > self.HEIGHT:
+ ysize = self.HEIGHT
+ for x in range((self.WIDTH/xsize)+1):
+ for y in range((self.HEIGHT/ysize)+1):
+ if rndchoice == 1:
+ wdir = choice(choices)
+ else:
+ wdir = choices[choicenum]
+ choicenum = (choicenum + 1) % len(choices)
+ for dx in range(xsize+1):
+ for dy in range(ysize+1):
+ self.setwind(x*xsize+dx,y*ysize+dy,wdir)
+ # make sure that the special bubbles can come into screen
+ for x in range(self.WIDTH):
+ self.setwind(x, 0, ' ')
+ self.setwind(x, self.HEIGHT-1, ' ')
+
+ def wind_sidewalls(self):
+ """Make sure the left and side walls have updraft next to them
+ """
+ for y in range(self.HEIGHT):
+ self.setwind(0,y,'^')
+ self.setwind(self.WIDTH-1,y,'^')
+
+ def wind_wallblocking(self, winddirs):
+ """Sets up wind depending on the number of walls around each place.
+ winddirs is an array of 16 wind chars.
+ directions with walls count as: 1=N, 2=E, 4=S, 8=W
+ 16th place is used if there is wall at the position.
+ """
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ walld = 0
+ if self.getw(x,y) == '#':
+ walld = 16
+ else:
+ if self.getw(x,y-1) == '#':
+ walld = walld + 1
+ if self.getw(x+1,y) == '#':
+ walld = walld + 2
+ if self.getw(x,y+1) == '#':
+ walld = walld + 4
+ if self.getw(x-1,y) == '#':
+ walld = walld + 8
+ wnd = winddirs[walld]
+ self.setwind(x,y,wnd)
+
+ def wind_wallblocking256(self, winddirs):
+ """Sets up wind depending on the number of walls around each position.
+ winddirs is an array of 257 wind chars (one of ' x<>^v-'), where '-' means
+ to use '>' or '<', pointing towards center of level.
+ directions with walls count as: 1=N, 2=NE, 4=E, 8=SE, 16=S, 32=SW, 64=W, 128=NW
+ 257th place is use if there is wall at the position.
+ """
+ mdirs = [(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)]
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ windd = 0
+ if self.getw(x,y) == '#':
+ windd = 256
+ else:
+ for d in range(8):
+ dx = x + mdirs[d][0]
+ dy = y + mdirs[d][1]
+ if self.getw(dx, dy) == '#':
+ windd = (1 << d)
+ wd = choice(winddirs[windd])
+ if wd == '-':
+ if x < self.WIDTH / 2:
+ wd = '>'
+ else:
+ wd = '<'
+ self.setwind(x,y, wd)
+
+ def generate_wind(self, gens = -1):
+ """Chooses one of the wind pattern generators and uses that to generate the winds.
+ 0: choose what generator to use.
+ """
+ if gens == -1:
+ gens = choice([1,1,2,3,4,4,4,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19])
+ if gens == 1: # totally random pattern
+ self.generate_wind1()
+ elif gens == 2: # wind "layers"
+ self.generate_wind1(0, ['x','^','^','^'],self.WIDTH,1)
+ self.wind_sidewalls()
+ elif gens == 3: # "wiggly" winds
+ self.generate_wind1(1, ['^','<','^','>'],1,1)
+ self.wind_sidewalls()
+ elif gens == 4: # "normal" wind pattern
+ self.wind_sidewalls()
+ dx = (self.WIDTH/2)
+ if random() < 0.7:
+ dy = 1 # usual height where bubbles collect
+ else:
+ dy = randint(1, self.HEIGHT-1)
+ for x in range(dx-3, dx+3):
+ self.setwind(x,dy,'x')
+ for x in range(dx-2):
+ self.setwind(x,dy,'>')
+ self.setwind(self.WIDTH-x-1,dy,'<')
+ elif gens == 5: # bubbles are stopped by horizontal walls
+ self.wind_sidewalls()
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT-2):
+ if self.getw(x,y) == '#':
+ if self.getw(x,y+1) == ' ':
+ self.setwind(x,y+1,'x')
+ elif gens == 6: # bubbles move next the walls, rotating cw or ccw
+ if random() < 0.5: #clockwise
+ winddirs = [' ','>','v','v','<',' ','<','<','^','>',' ','v','^','>','^','x','x']
+ else:
+ winddirs = [' ','<','^','<','>',' ','^','<','v','v',' ','v','>','>','^','x','x']
+ self.wind_wallblocking(winddirs)
+ elif gens == 7: # bubbles move up in column(s) that zig-zag left and right
+ wid = choice([self.WIDTH, randint(2, self.WIDTH), randint(2, self.WIDTH)])
+ xofs = (self.WIDTH % wid) / 2
+ ofs = choice([0,1])
+ for dx in range(0, self.WIDTH-wid+1, wid):
+ for x in range(wid):
+ for y in range(self.HEIGHT):
+ if (y+ofs) & 1:
+ self.setwind(x+dx+xofs,y,'<')
+ if x == 0:
+ self.setwind(x+dx+xofs,y,'^')
+ else:
+ self.setwind(x+dx+xofs,y,'>')
+ if x == wid-1:
+ self.setwind(x+dx+xofs,y,'^')
+ elif gens == 8: # bubbles move towards the map centerline at random height
+ for x in range(self.WIDTH):
+ y = randint(1, self.HEIGHT-1)
+ if x < (self.WIDTH/2):
+ self.setwind(x,y,'>')
+ else:
+ self.setwind(x,y,'<')
+ self.setwind(x,0,'v')
+ self.setwind(x,self.HEIGHT-1,'^')
+ for y in range(self.HEIGHT):
+ self.setwind(self.WIDTH/2,y,'x')
+ elif gens == 9: # bubbles move towards the side walls at random height
+ for x in range(self.WIDTH):
+ y = randint(1, self.HEIGHT-1)
+ if y & 1:
+ self.setwind(x,y,'>')
+ else:
+ self.setwind(x,y,'<')
+ self.setwind(x,0,'v')
+ self.setwind(x,self.HEIGHT-1,'^')
+ for y in range(self.HEIGHT):
+ self.setwind(0,y,'x')
+ self.setwind(self.WIDTH-1,y,'x')
+ elif gens == 10: # bubbles move up-down
+ ofs = choice([0,1])
+ dir_l = choice(['>', '>', '<'])
+ dir_r = choice(['<', '<', '>'])
+ for x in range(self.WIDTH):
+ if x < (self.WIDTH / 2):
+ self.setwind(x, 0, dir_r)
+ self.setwind(x,self.HEIGHT-1,dir_l)
+ else:
+ self.setwind(x, 0, dir_l)
+ self.setwind(x,self.HEIGHT-1,dir_r)
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ if (x+ofs) & 1:
+ self.setwind(x,y+1,'^')
+ else:
+ self.setwind(x,y-1,'v')
+ elif gens == 11: # bubbles rotate
+ self.wind_sidewalls()
+ for z in range(20):
+ wid = randint(2,self.WIDTH/2)
+ hei = randint(2,self.HEIGHT/2)
+ y = randint(1, self.HEIGHT - hei - 1)
+ x = randint(1, self.WIDTH - wid - 1)
+ ok = 1
+ for dx in range(wid):
+ if self.getwind(x+dx+1, y) != ' ':
+ ok = 0
+ if self.getwind(x+dx, y+hei) != ' ':
+ ok = 0
+ for dy in range(hei):
+ if self.getwind(x, y+dy) != ' ':
+ ok = 0
+ if self.getwind(x+wid, y+dy+1) != ' ':
+ ok = 0
+ if ok == 1:
+ self.wind_rect(x,y,wid,hei, random() < 0.5)
+ elif gens == 12: # bubbles gravitate towards a certain spot
+ dx = randint(1,self.WIDTH-1)
+ dy = randint(1,self.HEIGHT-1)
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ ax = abs(dx - x)
+ ay = abs(dy - y)
+ sx = cmp(dx - x, 0)
+ sy = cmp(dy - y, 0)
+ winds = [' ',' ',' ']
+ if ax < 2 and ay < 2:
+ winds = ['x']
+ else:
+ if sx < 0:
+ winds += ['<']
+ elif sx > 0:
+ winds += ['>']
+ else:
+ if sy > 0:
+ winds = ['v']
+ elif sy < 0:
+ winds = ['^']
+ else:
+ winds = ['x']
+ if sy < 0:
+ winds += ['^']
+ elif sy > 0:
+ winds += ['v']
+ else:
+ if sx > 0:
+ winds = ['>']
+ elif sx < 0:
+ winds = ['<']
+ else:
+ winds = ['x']
+ self.setwind(x,y,choice(winds))
+ elif gens == 13: # bubbles stop at some random positions
+ self.generate_wind1(1, [' ',' ',' ',' ',' ',' ',' ','x'],1,1)
+ self.wind_sidewalls()
+ elif gens == 14: # bubbles move cw and ccw in alternating rectangles
+ m = max(self.WIDTH / 2, self.HEIGHT / 2)
+ cwofs = choice([0,1])
+ thk = choice([1,1,2,2,3,4,randint(1,m/2)])
+ for dx in range(m):
+ cw = ((dx / thk) + cwofs) % 2
+ self.wind_rect(dx,dx, self.WIDTH-(dx*2), self.HEIGHT-(dx*2), cw)
+ elif gens == 15: # bubbles move cw or ccw in rectangles
+ m = max(self.WIDTH / 2, self.HEIGHT / 2)
+ cw = choice([0,1])
+ for dx in range(m):
+ self.wind_rect(dx,dx, self.WIDTH-(dx*2), self.HEIGHT-(dx*2), cw)
+ elif gens == 16:
+ xs = randint(2, (self.WIDTH/2)-1)
+ ys = randint(2, (self.HEIGHT/2)-1)
+ rx = (self.WIDTH / xs) + 1
+ ry = (self.HEIGHT / ys) + 1
+ cwchanges = choice([0,0,0,0,0,0,0,0,1,1,1,1,2,2,3])
+ if cwchanges == 0:
+ cw = random() < 0.5
+ for x in range(rx):
+ if cwchanges == 1:
+ cw = random() < 0.5
+ for y in range(ry):
+ if cwchanges == 2:
+ cw = random() < 0.5
+ maxd = max((xs / 2), (ys / 2))
+ for d in range(maxd):
+ if cwchanges == 3:
+ cw = random() < 0.5
+ self.wind_rect(xs*x+d, ys*y+d, xs-2*d-1, ys-2*d-1, cw)
+ elif gens == 17: # bubbles bounce between walls
+ if random() < 0.5: # horizontal
+ winddirs = [' ',' ','<','<',' ',' ','<','<','>','>',' ',' ','>','>',' ',' ','x']
+ else: # vertical
+ winddirs = [' ','v',' ','v','^',' ','^',' ',' ','v',' ','v','^',' ','^',' ','x']
+ self.wind_wallblocking(winddirs)
+ elif gens == 18: # generate winds based on a random 3x3 matrix ruleset
+ winddirs = []
+ for z in range(257):
+ winddirs.append(choice([' ',' ',' ','x','^','v','-']))
+ winddirs[0] = ' '
+ winddirs[256] = choice(['x',' '])
+ self.wind_wallblocking256(winddirs)
+ elif gens == 19: # bubbles will move downwards in a zig-zag pattern
+ y = 0
+ x1 = randint(0, self.WIDTH-1)
+ while y < self.HEIGHT:
+ x2 = randint(0, self.WIDTH-1)
+ if x1 < x2:
+ self.setwind(x1,y, '>')
+ else:
+ self.setwind(x1,y, '<')
+ dy = choice([1,1,1,2])
+ self.setwind(x2,y, 'v')
+ y += dy
+ x1 = x2
+
+ def smooth(self, threshold, rev):
+ """Remove wall blocks that are surrounded by 4 empty places.
+ 0: probability which a wall cell is turned into space
+ 1: smooth away walls or smooth away empty spaces?
+ """
+ # make a copy of self.wmap and adds '#' at the end of line, for
+ # the overflowing indexing below: [x-1] and [x+1]
+ tmpwmap = [ line + ['#'] for line in self.wmap ]
+ if rev == 0:
+ chr = ' '
+ else:
+ chr = '#'
+ for x in range(self.WIDTH):
+ for y in range(1,self.HEIGHT-1):
+ count = 0
+ if tmpwmap[y+1][x] == chr:
+ count = count + 1
+ if tmpwmap[y-1][x] == chr:
+ count = count + 1
+ if tmpwmap[y][x+1] == chr:
+ count = count + 1
+ if tmpwmap[y][x-1] == chr:
+ count = count + 1
+ if (count >= 4) and (random() < threshold):
+ if rev == 0:
+ self.clrw(x,y)
+ else:
+ self.setw(x,y)
+
+ def mess(self, threshold):
+ """Random fill of the board with walls.
+ Only one argument, the probability that
+ a cell turns out to be a wall.
+ """
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ if random() < threshold:
+ self.setw(x,y)
+
+ def zigzag_lr(self):
+ """Generate the level with random left-right zig-zags.
+ """
+ first = 1
+ self.reset(fill=False)
+ y = choice([0,0,2,3,3,4])
+ while y < (self.HEIGHT-2):
+ if first == 1:
+ first = 0
+ x1 = x2 = randint(2, self.WIDTH-3)
+ else:
+ x2 = randint(2, self.WIDTH-3)
+ while (x2 > (x1-3)) and (x2 < (x1+3)):
+ x2 = randint(2, self.WIDTH-3)
+ for dx in range(min(x1,x2+1), max(x1,x2+1)):
+ self.setw(dx,y)
+ dy = choice([2,2,3,3,3,4])
+ for dy in range(dy+1):
+ self.setw(x2,y+dy)
+ y = y + dy
+ x1 = x2
+
+ def zigzag_ud(self):
+ """Generate the level with random up-down zig-zags.
+ """
+ first = 1
+ self.reset(fill=False)
+ x = -1
+ while x < self.WIDTH:
+ if first == 1:
+ first = 0
+ y1 = y2 = randint(2, self.HEIGHT-1)
+ else:
+ y2 = randint(2, self.HEIGHT-1)
+ while (y2 > (y1-2)) and (y2 < (y1+2)):
+ y2 = randint(2, self.HEIGHT-1)
+ for dy in range(min(y1,y2+1), max(y1,y2+1)):
+ self.setw(x,dy)
+ dx = choice([3,4,4,4,5,6])
+ for dx in range(dx+1):
+ self.setw(x+dx,y2)
+ x = x + dx
+ y1 = y2
+
+ def zigzag(self):
+ """Generate a level with a random zig-zag form.
+ """
+ if random() < 0.5:
+ self.zigzag_lr()
+ else:
+ self.zigzag_ud()
+
+ def platforms(self, (nplat, space), (rng_holes, rng_width), full=1):
+ """Place random platforms.
+ args is a tuple with the following fields:
+ 0: a tuple containing the number of platforms and
+ the minum space between two platforms,
+ 1: a tuple indicating in order:
+ - the rng for the number of holes per platform
+ - the rng for the width of the holes,
+ 2: a flag indicating whether the platform should cross
+ the whole level or not.
+ """
+ plat = []
+ for i in range(nplat):
+ ntry = 100
+ while ntry:
+ y = randint(0,self.HEIGHT-1)
+ found = 0
+ for old in plat:
+ if abs(old-y) <= space:
+ found = 1
+ break
+ if not found:
+ plat.append(y)
+ break
+ ntry -= 1
+ if not ntry:
+ continue # ignore platform
+ if full:
+ x = 0
+ w = self.WIDTH
+ else:
+ x = randint(0,self.WIDTH-1)
+ w = randint(0,self.WIDTH-1)
+ s = choice([-1,1])
+ if s == -1:
+ w = min(w,x)
+ x -= w
+ else:
+ w = min(w,self.WIDTH-x)
+ for x1 in range(x,x+w):
+ self.setw(x1,y)
+ for i in range(rng_holes()):
+ hx = randint(x,x+w)
+ hw = rng_width()
+ for h in range(hx-hw/2,hx+hw/2):
+ self.clrw(h,y)
+
+ def remove_joined_blocks(self):
+ """Removes randomly placed, random sized blocks of walls.
+ The blocks are usually joined, or if not, they're offset so that
+ it's possible to move from one block to another by jumping.
+ """
+ self.reset(fill=True)
+ nrooms = randint(1, 4)
+ while nrooms:
+ nrooms -= 1;
+ x = 0
+ while x < self.WIDTH:
+ wid = randint(2,8)
+ hei = randint(2,6)
+ y = randint(2, self.HEIGHT - hei - 1)
+ for dx in range(wid):
+ for dy in range(hei):
+ self.clrw(x+dx, y+dy)
+ x += wid + choice([-2,-2,-1,-1,0]);
+
+ def discrete_blocks(self, blocks = -1):
+ """Put certain size blocks randomly, but so that they don't touch each other.
+ """
+ self.reset(fill=False)
+ if blocks == -1:
+ if random() < 0.75:
+ blocks = [(4,2),(2,4)] # CompactLevels, level 16
+ if random() < 0.30:
+ blocks.append((2,2))
+ if random() < 0.20:
+ blocks.append((6,2))
+ if random() < 0.10:
+ blocks.append((8,2))
+ else:
+ blocks = []
+ while len(blocks) == 0:
+ for bz in range(10):
+ if random() < 0.3:
+ blocks.append((bz+1,1))
+ ntry = 300
+ while ntry:
+ ntry -= 1
+ doput = 1
+ block = choice(blocks)
+ wid = block[0]
+ hei = block[1]
+ x = randint(0,self.WIDTH-wid-2)
+ y = randint(1,self.HEIGHT-hei-3)
+ for dx in range(x,x+wid+2):
+ for dy in range(y,y+hei+2):
+ if self.getw(dx,dy) == '#':
+ doput = 0
+ if doput:
+ for dx in range(x+1,x+wid+1):
+ for dy in range(y+1,y+hei+1):
+ self.setw(dx,dy)
+
+ def lines(self, rng_len, nlines, rng_angle=None):
+ """Generate a set of lines in any direction. It takes three
+ arguments, a rng for the length the lines, the number of lines,
+ and a rng for the angle.
+ """
+ if rng_angle is None:
+ rng_angle = lambda : choice([0]+[pi/i for i in range(3,21)]+[-pi/i for i in range(3,21)])
+ for i in range(nlines):
+ len = rng_len()
+ angle = rng_angle()
+ ntry = self.MAXTRY
+ while ntry:
+ sx = randint(0,self.WIDTH-1)
+ sy = randint(0,self.HEIGHT-1)
+ dx = int(sx + len*cos(angle) + 0.5)
+ dy = int(sy + len*sin(angle) + 0.5)
+ if dx < self.WIDTH and dy < self.HEIGHT and dx >= 0 and dy >= 0:
+ break
+ ntry -= 1
+ if ntry == 0:
+ break
+ if abs(dx-sx) > abs(dy-sy):
+ for x in range(dx-sx+1):
+ y = (2*(dy-sy)*x/(dx-sx)+1)/2
+ self.setw(sx+x,sy+y)
+ else:
+ for y in range(dy-sy+1):
+ x = (2*(dx-sx)*y/(dy-sy)+1)/2
+ self.setw(sx+x,sy+y)
+
+ def rooms(self, rng_radius, rng_e, n_rooms):
+ """Generate rooms. It takes the following arguments:
+ 0: the rng for the radius of the room
+ 1: the rng for the excentricity of the room
+ 2: the number of rooms
+ """
+ for i in range(n_rooms):
+ cx = randint(0,self.WIDTH-1)
+ cy = randint(0,self.HEIGHT-1)
+ r = rng_radius()
+ e = rng_e()*1.0
+ left = cx-int(r*e+0.5)
+ right = cx+int(r*e+0.5)
+ top = cy-int(r/e+0.5)
+ bottom = cy+int(r/e+0.5)
+ for x in range(left,right+1):
+ self.setw(x,top)
+ self.setw(x,bottom)
+ for y in range(top,bottom+1):
+ self.setw(left,y)
+ self.setw(right,y)
+ for x in range(left+1,right):
+ for y in range(top+1,bottom):
+ self.lockw(x,y)
+
+ def holes(self, rng_radius, rng_e, n_holes, rng_rect):
+ """Generate a set of holes in the level. It takes four args:
+ 0: the rng for the radius of the holes
+ 1: the rng for the excentricity of the holes
+ 2: the number of holes
+ 3: the rng for the shape of the hole 0 for circular, 1 for rectangular
+ """
+ for i in range(n_holes):
+ cx = randint(0,self.WIDTH-1)
+ cy = randint(0,self.HEIGHT-1)
+ r = rng_radius()
+ e = rng_e()*1.0
+ rect = rng_rect()
+ for x in range(cx-int(r*e+0.5),cx+int(r*e+0.5)+1):
+ for y in range(cy-int(r/e+0.5),cy+int(r/e+0.5)+1):
+ if not rect and (((x-cx)/e)**2+((y-cy)*e)**2) > r**2:
+ continue
+ self.clrw(x,y)
+
+ def grids(self, horizchance, vertchance):
+ """Generate a level with a grid of horizontal and vertical lines
+ 0: gaussian chance of each horizontal line part
+ 1: gaussian chance of each vertical line part
+ """
+ self.reset(fill=False)
+ xsize = choice([3,3,3,4,4,4,4,5,6])
+ ysize = choice([2,3,3,4,4,4,4,5])
+ xofs = choice([-1,0,1])
+ yofs = choice([-1,0,1])
+ for x in range((self.WIDTH/xsize)+1):
+ for y in range((self.HEIGHT/ysize)+1):
+ dx = x*xsize + xofs
+ dy = y*ysize + yofs
+ if gauss(0,1) > horizchance:
+ for i in range(0,xsize+1):
+ self.setw(dx+i,dy)
+ if gauss(0,1) > vertchance:
+ for i in range(0,ysize+1):
+ self.setw(dx,dy+i)
+
+ def pegs(self, pegchance, posadj, thick):
+ """Generate a level by putting pegs
+ 0: gaussian level of a peg appearance
+ 1: gaussian level of peg position adjustment
+ """
+ self.reset(fill=False)
+ xdist = choice([3,3,3,4,4,5]) # distance between pegs
+ ydist = choice([2,3,3,3,4,5]) # distance between pegs
+ if not thick:
+ xdist = xdist - randint(0,1)
+ ydist = ydist - randint(0,1)
+ xadj = randint(0,4) - 2
+ yadj = randint(0,4) - 2
+ for x in range(self.WIDTH / xdist):
+ for y in range(self.HEIGHT / ydist):
+ if gauss(0,1) > pegchance:
+ dx = x * xdist + xadj
+ dy = y * ydist + yadj
+ if gauss(0,1) > posadj:
+ dx = dx + randint(0,2) - 1
+ dy = dy + randint(0,2) - 1
+ self.setw(dx,dy)
+ if thick:
+ self.setw(dx+1,dy)
+ self.setw(dx,dy+1)
+ self.setw(dx+1,dy+1)
+
+ def mondrian(self, x1=2,y1=2,x2=-1,y2=-1, horiz=-1, mindepth=3):
+ """Generate a level that looks a bit like a Piet Mondrian painting, or
+ different sized rectangles stacked on top of each other.
+ 0-3: the size of the area to be split
+ 4: whether the first split is horizontal or vertical
+ 5: minimum number of splits to do
+ """
+ if horiz == -1:
+ horiz = choice([0,1])
+ if x2 == -1:
+ x2 = self.WIDTH-2
+ if y2 == -1:
+ y2 = self.HEIGHT-2
+ if (abs(x2-x1) < 6) or (abs(y2-y1) < 5):
+ return
+ mindepth = mindepth - 1
+ if horiz == 1:
+ horiz = 0
+ dy = randint(y1+2,y2-2)
+ for dx in range(min(x1,x2),max(x1,x2)):
+ self.setw(dx,dy)
+ if (random() < 0.75) or (mindepth > 0):
+ self.mondrian(x1,y1,x2,dy, horiz, mindepth)
+ if (random() < 0.75) or (mindepth > 0):
+ self.mondrian(x1,dy,x2,y2, horiz, mindepth)
+ else:
+ horiz = 1
+ dx = randint(x1+3,x2-3)
+ for dy in range(min(y1,y2),max(y1,y2)):
+ self.setw(dx,dy)
+ if (random() < 0.75) or (mindepth > 0):
+ self.mondrian(x1,y1,dx,y2, horiz, mindepth)
+ if (random() < 0.75) or (mindepth > 0):
+ self.mondrian(dx,y1,x2,y2, horiz, mindepth)
+
+ def bouncers(self, length, diradj, rev):
+ """Generate a level using a down and left or right moving walker
+ 0: how many steps does the walker take
+ 1: gaussian level, how often to change moving from left to right
+ 2: fill empty level with wall or reverse?
+ """
+ if rev == 0:
+ self.reset(fill=True)
+ else:
+ self.reset(fill=False)
+ x = randint(0,self.WIDTH-2)
+ y = randint(0,self.HEIGHT-2)
+ lorr = choice([1, -1]) # move left or right
+ for i in range(length):
+ if rev == 0:
+ self.clrw(x,y)
+ self.clrw(x+1,y)
+ self.clrw(x,y+1)
+ self.clrw(x+1,y+1)
+ else:
+ self.setw(x,y)
+ self.setw(x+1,y)
+ x = x + lorr
+ y = y + 1
+ if y > self.HEIGHT:
+ y = 0
+ if x > self.WIDTH - 2:
+ x = self.WIDTH - 2
+ lorr = -lorr
+ elif x < 0:
+ x = 0
+ lorr = -lorr
+ if gauss(0,1) > diradj:
+ lorr = -lorr
+
+
+ def walkers(self, length, minturn, maxturn, isbig):
+ """Generate a level with a walker
+ 0: length of the walker: how many steps it walks
+ 1: minimum length it walks straight, before turning
+ 2: maximum length it walks straight, before turning
+ 3: is the trail is 1 or 2 blocks high
+ """
+ # We start from a full wall
+ self.reset(fill=True)
+ x = randint(0,self.WIDTH-2)
+ y = randint(0,self.HEIGHT-2)
+ dir = randint(0,4)
+ dlen = 0
+ for i in range(length):
+ self.clrw(x,y)
+ self.clrw(x+1,y)
+ if isbig == 1:
+ self.clrw(x,y+1)
+ self.clrw(x+1,y+1)
+ dlen = dlen + 1
+ if dir == 0:
+ x = x - 2
+ if x < 0:
+ x = self.WIDTH-2
+ elif dir == 1:
+ y = y - 1
+ if y < 0:
+ y = self.HEIGHT
+ elif dir == 2:
+ x = x + 2
+ if x > (self.WIDTH - 2):
+ x = 0
+ else:
+ y = y + 1
+ if y > self.HEIGHT:
+ y = 0
+ if dlen > randint(minturn, maxturn):
+ # turn 90 degrees
+ dir = (dir + choice([1,3])) % 4
+ dlen = 0
+
+ def rivers(self, n_flow, side_threshold, side_shift):
+ """Generate flow paths by digging a big wall. The arguments are:
+ 0: the number of parallel flow to dig in the wall
+ 1: side_threshold is a gausian level for doing a step aside
+ 2: side_shift is the maximal size of the side step.
+ """
+ # We start from a full wall
+ self.reset(fill=True)
+ for x in [0, self.WIDTH-2]+[randint(3,self.WIDTH-5) for f in range(max(0, n_flow-2))]:
+ for y in range(self.HEIGHT):
+ self.clrw(x,y)
+ self.clrw(x+1,y)
+ g = gauss(0,1)
+ if abs(g) > side_threshold:
+ # We want to move aside, let's find which side is the best:
+ if self.WIDTH/4 < x < 3*self.WIDTH/4:
+ side = random() > 0.5
+ t = random()
+ if t > x*4/self.WIDTH:
+ side = 1
+ elif t > (self.WIDTH-x)*4/self.WIDTH:
+ side = -1
+ side_step = randint(1,side_shift)
+ if side > 0:
+ for i in range(x+2, min(x+2+side_step,self.WIDTH-1)):
+ self.clrw(i,y)
+ x = max(0,min(x+side_step, self.WIDTH-2))
+ else:
+ for i in range(max(x-side_step,0),x):
+ self.clrw(i,y)
+ x = max(x-side_step, 0)
+
+ def platforms_reg(self):
+ """Generate random platforms at regular y-intervals.
+ """
+ self.reset(fill=False)
+ yadjs = [-2,-1,0,0,0,0,0,0,0,0,1,2]
+ y = randint(2,4)
+ yinc = randint(2,6)
+ yincadj = choice(yadjs)
+ ymax = self.HEIGHT-choice([1,1,1,1,1,2,2,2,3,3,4])-1
+ while y < ymax:
+ holes = randint(choice([0,1,1,1,1]),7)
+ for x in range(0, self.WIDTH):
+ self.setw(x,y)
+ for i in range(holes):
+ x = randint(0, self.WIDTH-2)
+ self.clrw(x,y)
+ self.clrw(x+1,y)
+ y = y + yinc
+ yinc = yinc + yincadj
+ if yinc < 2:
+ yinc = 2
+ yincadj = choice(yadjs)
+ if yinc > 6:
+ yinc = 6
+ yincadj = choice(yadjs)
+
+ def startplatform(self):
+ "Make sure there's free space with wall underneath for dragon start positions"
+ hei = choice([1,1,1,2,2,3])
+ lft = choice([0,1])
+ wid = choice([3,3,3,4,5])
+ for x in range(lft, wid):
+ self.setw(x,self.HEIGHT-1)
+ self.setw(self.WIDTH-x-1,self.HEIGHT-1)
+ for y in range(hei+1):
+ self.clrw(x,self.HEIGHT-2-y)
+ self.clrw(self.WIDTH-x-1,self.HEIGHT-2-y)
+
+ def openstartway(self):
+ "Make sure there is a way from the starting position to the center of the level. Reduces player frustrations."
+ gen = choice([0,0,0,1])
+ if gen == 0: # horizontal open space to middle of level
+ ypos = choice([1,1,1,1,1,2,2,3,4,randint(1,self.HEIGHT/2)])
+ hei = choice([1,1,1,2])
+ for x in range(self.WIDTH/2):
+ for y in range(hei):
+ self.clrw(x, self.HEIGHT-1-ypos-y)
+ ypos = choice([1,1,1,1,1,2,2,3,4,randint(1,self.HEIGHT/2)])
+ hei = choice([1,1,1,2])
+ for x in range(self.WIDTH/2):
+ for y in range(hei):
+ self.clrw(self.WIDTH-x-1, self.HEIGHT-1-ypos-y)
+ elif gen == 1: # open way diagonally to NW or NS, third of a way to the level width
+ ypos = choice([1,1,1,1,1,2,2,3,4])
+ wid = choice([2,2,2,2,2,3,3,4])
+ for x in range(self.WIDTH/3):
+ for z in range(wid):
+ self.clrw(x+z, self.HEIGHT-1-x-ypos)
+ ypos = choice([1,1,1,1,1,2,2,3,4])
+ wid = choice([2,2,2,2,2,3,3,4])
+ for x in range(self.WIDTH/2):
+ for z in range(wid):
+ self.clrw(self.WIDTH-x-1-z, self.HEIGHT-1-x-ypos)
+
+ def close(self):
+ "Just close the level with floor and roof"
+ for x in range(self.WIDTH):
+ self.setw(x,0)
+ self.setw(x,self.HEIGHT)
+
+ def largest_vertical_hole(self, x):
+ "Returns the (start, stop) of the largest range of holes in column x."
+ if not (0 <= x < self.WIDTH):
+ return (0, 0)
+ ranges = []
+ best = 0
+ length = 0
+ for y in range(self.HEIGHT+1):
+ if y < self.HEIGHT and self.getw(x,y) == ' ':
+ length += 1
+ elif length > 0:
+ if length > best:
+ del ranges[:]
+ best = length
+ if length == best:
+ ranges.append((y-length, y))
+ length = 0
+ return choice(ranges or [(0, 0)])
+
+ def dig_vertical_walls(self):
+ "Check that no vertical wall spans the whole height of the level"
+ vwall = []
+ for x in range(self.WIDTH):
+ spaces = 0
+ for y in range(self.HEIGHT-1): # ignore bottom line spaces
+ spaces += self.getw(x,y) == ' '
+ if spaces == 0 or (random() < 0.4**spaces):
+ vwall.append(x)
+ shuffle(vwall)
+ for x in vwall:
+ # look for the longest continuous space in each of the two
+ # adjacent columns, and extend these to the current column
+ def dig(y1, y2):
+ for y in range(y1, y2):
+ self.clrw(x, y)
+ return y1 < y2 and y1 < self.HEIGHT-1
+ progress = False
+ for col in [x-1, x+1]:
+ y1, y2 = self.largest_vertical_hole(col)
+ progress |= dig(y1, y2)
+ while not progress:
+ progress |= dig(randint(0, self.HEIGHT-1),
+ randint(0, self.HEIGHT-1))
+
+ def prevent_straight_fall(self):
+ """Make platforms that prevent falling straight from top to bottom, but
+ still leave space for moving.
+ """
+ falls = []
+ for x in range(self.WIDTH):
+ for y in range(self.HEIGHT):
+ if self.getw(x,y) == '#':
+ break
+ else:
+ falls = falls + [x]
+ y = oldy = -10
+ for x in falls:
+ while (y < oldy+2) and (y > oldy-2):
+ y = randint(2, self.HEIGHT-2)
+ for dy in range(y-1,y+2):
+ for dx in range(x-3, x+4):
+ self.clrw(dx,dy)
+ self.setw(x-1,y)
+ self.setw(x+1,y)
+ self.setw(x,y)
+ oldy = y
+
+ def do_monsters(self):
+ """Create monsters based on the requested settings.
+ mlist is a list of monster setting. Each item is a tuple with:
+ 0: the list of monster to uses (each item might be a tuple)
+ 1: the rng for the number of monsters to pick in the list.
+ """
+ current = 'a'
+ for ms in self.mlist:
+ n_monsters = ms[1]()
+ for idx in range(n_monsters):
+ self.__class__.__dict__[current] = choice(ms[0])
+ ntry = self.MAXTRY
+ while ntry:
+ x = randint(0,self.WIDTH-2)
+ y = randint(0,self.HEIGHT-1)
+
+ if self.getw(x,y) == self.getw(x+1,y) == ' ':
+ self.wmap[y][x] = current
+ break
+ ntry -= 1
+ current = chr(ord(current)+1)
+
+ def do_walls(self):
+ "Build the actual walls map for the game."
+ self.__class__.walls = ''
+ for y in range(self.HEIGHT-1):
+ self.__class__.walls += '##'
+ for x in range(self.WIDTH):
+ self.__class__.walls += self.wmap[y][x]
+ self.__class__.walls += '##\n'
+ self.__class__.walls += '##'
+ for x in range(self.WIDTH):
+ if self.getw(x,0) == '#' or self.getw(x,self.HEIGHT-1) == '#':
+ self.__class__.walls += '#'
+ else:
+ self.__class__.walls += ' '
+ self.__class__.walls += '##\n'
+
+ def do_winds(self):
+ "Build the actual wind map for the game."
+ self.__class__.winds = ''
+ for y in range(self.HEIGHT):
+ self.__class__.winds += '>>'
+ for x in range(self.WIDTH):
+ self.__class__.winds += self.windmap[y][x]
+ self.__class__.winds += '<<' + '\n'
+
+ def do_bonuses(self):
+ self.__class__.letter = choice([0,1])
+ self.__class__.fire = choice([0,1])
+ self.__class__.lightning = choice([0,1])
+ self.__class__.water = choice([0,1])
+ self.__class__.top = choice([0,1])
+
+ def generate(self):
+ "Generate random level settings."
+ assert 0, "--- THIS IS NO LONGER REALLY USED ---"
+ self.mlist = [([
+ LNasty, LMonky, LGhosty, LFlappy, LSpringy, LOrcy, LGramy, LBlitzy,
+ RNasty, RMonky, RGhosty, RFlappy, RSpringy, ROrcy, RGramy, RBlitzy,
+ ],lambda : flat(12,4))]
+ gens = choice([512,512,256,256,128,128,64,64,32,32,16,16,16,16,16,16,20,20,8,8,8,8,4,4,4,4,2,2,2,2,1,1,3,5,6,7])
+ self.genwalls = []
+ if gens & 512:
+ print 'Using grids generator'
+ self.genwalls.append((RandomLevel.grids,
+ uniform(0.0, 0.1),
+ uniform(0.0, 0.1)))
+ if gens & 256:
+ # generate using pegs
+ print 'Using the pegs generator'
+ self.genwalls.append((RandomLevel.pegs,
+ uniform(0.1,0.2),
+ uniform(0.45,0.7),
+ choice([0,1,1,1])))
+ if gens & 128:
+ # generate using a bouncer
+ nr = choice([0,0,1])
+ print 'Using the bouncer generator'
+ self.genwalls.append((RandomLevel.bouncers,
+ dice(1, 100) + 250 - nr*200, # length
+ uniform(0.7, 1.7),
+ nr))
+ if gens & 64:
+ # generate using a walker
+ print 'Using the walker generator'
+ nr = dice(1, 3) + 2
+ self.genwalls.append((RandomLevel.walkers,
+ dice(2, 100) + 100, # length
+ nr, nr + dice(2, 3), # straight walk min, max len
+ choice([0,1])))
+ if gens & 32:
+ # generate rivers
+ print 'Using the rivers generator'
+ self.genwalls.append((RandomLevel.rivers,
+ randrange(2,(self.WIDTH-4)/5), # the number of rivers
+ uniform(0.7, 1.7), # the side stepping threshold
+ 6)) # the max side stepping size
+ if gens & 16:
+ # generate rooms
+ print 'Using the romms generator'
+ nr = choice([1,2,2,2,3,3,4,5])
+ self.genwalls.append((RandomLevel.rooms,
+ lambda : flat(9-nr,2), # the half size of the room
+ lambda : uniform(0.8,1.2), # the excentricity of the room
+ nr)) # the number of rooms
+ if gens & 8:
+ # generate a holes generator
+ # as this is interesting only if the level is filled somehow
+ print 'Using the holes generator'
+ self.genwalls.append((RandomLevel.mess,1-uniform(0.2,0.5)))
+ nh = choice([1,1,2,2,2,3,3,3,4,5])
+ self.genwalls.append((RandomLevel.holes,
+ lambda : flat(9-nh,2), # radius of the holes
+ lambda : uniform(0.9,1.1), # excentricity
+ nh, # number of holes
+ lambda : choice([0,0,0,1]))) # circle or rectangle
+ if gens & 4:
+ # generate a lines generator
+ print 'Using the lines generator'
+ self.genwalls.append((RandomLevel.lines,
+ lambda : dice(7,3), # line length
+ dice(2,3))) # number of lines
+ if gens & 2:
+ # generate a platforms generator
+ print 'Using the platforms generator'
+ nplat = dice(2,4,0)
+ if nplat: space = flat((self.HEIGHT-1)/nplat/2,(self.HEIGHT-1)/nplat/2-1)
+ else: space = 1
+ nholes = lambda : dice(1,3,0)
+ wholes = lambda : dice(2,3)
+ full = randrange(2)
+ self.genwalls.append((RandomLevel.platforms,
+ (nplat,space), # number of platform and spacing
+ (nholes,wholes), # number of holes and width
+ full)) # full width platform
+ if gens & 1:
+ # generate a mess generator
+ print 'Using the mess generator'
+ if gens & ~2:
+ offset = 0
+ scale = 0.05
+ else:
+ offset = 0.05
+ scale = 0.10
+ self.genwalls.append((RandomLevel.mess,offset+random()*scale))
+ if random() < 0.2:
+ self.genwalls.append((RandomLevel.close,))
+ if random() < 0.90:
+ self.genwalls.append((RandomLevel.startplatform,))
+ self.genwalls.append((RandomLevel.generate_wind, ))
+
+
+Levels = []
+for i in range(25):
+ class level(RandomLevel):
+ auto = 1
+ Levels.append(level)
+
+class levelfinal(RandomLevel):
+ genwalls = [(RandomLevel.platforms,(4,3),(lambda:flat(1,1),lambda:flat(4,2)),1)]
+Levels.append(levelfinal)
diff --git a/bubbob/levels/scratch.py b/bubbob/levels/scratch.py
new file mode 100644
index 0000000..a958a28
--- /dev/null
+++ b/bubbob/levels/scratch.py
@@ -0,0 +1,2301 @@
+#
+# An experimental level set.
+#
+
+import boarddef
+from boarddef import LNasty, LMonky, LGhosty, LFlappy
+from boarddef import LSpringy, LOrcy, LGramy, LBlitzy
+from boarddef import RNasty, RMonky, RGhosty, RFlappy
+from boarddef import RSpringy, ROrcy, RGramy, RBlitzy
+
+
+class level01(boarddef.Level):
+ a = LNasty
+ b = RNasty
+
+ walls = """
+####################################
+## ##
+## ##
+## ##
+## ##
+## ##
+## a a b b ##
+#### ###################### ####
+## ##
+## ##
+## ##
+## a a b b ##
+#### ###################### ####
+## ##
+## ##
+## ##
+## a a b b ##
+#### ###################### ####
+## ##
+## ##
+## ##
+## a a b b ##
+#### ###################### ####
+## ##
+## ##
+## ##
+## ##
+####################################
+"""
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level02(boarddef.Level):
+ letter = 1
+ lightning = 1
+ top = 0
+
+ a = LNasty
+ b = RNasty
+
+ walls = """
+## ##
+## ##
+##b ##
+############## ##############
+## a ##
+############# #############
+##b ##
+############ ############
+## a ##
+########### ###########
+##b ##
+########## ##########
+## a ##
+######### #########
+##b ##
+######## ########
+## a ##
+####### #######
+##b ##
+###### ## ######
+## a ##
+##### ### ### #####
+## ##
+#### #### #### #### ####
+## ##
+### ######## ###
+## ##
+############ ############
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>x<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>x<<
+>> <<
+>> <<
+>>>>>>>>>>>>>>^ ^<<<<<<<<<<<<<<
+>> <<
+>>>>>>>>>>>>>^ ^<<<<<<<<<<<<<
+>> <<
+>>>>>>>>>>>>^ ^<<<<<<<<<<<<
+>> <<
+>>>>>>>>>>>^ ^<<<<<<<<<<<
+>> <<
+>>>>>>>>>>^ ^<<<<<<<<<<
+>> <<
+>>>>>>>>>^ ^<<<<<<<<<
+>> <<
+>>>>>>>>^ ^<<<<<<<<
+>> <<
+>>>>>>>^ ^<<<<<<<
+>> <<
+>>>>>>^ ^<<<<<<
+>> <<
+>>>>>^ ^<<<<<
+>> <<
+>>>>^ ^<<<<
+>> <<
+>>>^ ^<<<
+>> <<
+"""
+
+
+class level03(boarddef.Level):
+ letter = 1
+ fire = 0
+ lightning = 0
+ water = 1
+ top = 1
+
+ a = LNasty, RNasty
+ b = ()
+ c = LMonky, RMonky
+
+ walls = """
+## ##
+## ##
+## # # ##
+## b ##
+## # ##
+## a # # ##
+## # # ##
+## ##
+## b a b ##
+## # # # ##
+## c ##
+## # # c ##
+## # b # ##
+## ##
+## # # # ##
+## ##
+## # # ##
+## # a # ##
+## # b # ##
+## ##
+## c # ##
+## # # b # ##
+## # ##
+## # ##
+## # ##
+## # # ##
+## # ##
+##### # #####
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level04(level03):
+ a = LNasty, RNasty
+ b = LGhosty, RGhosty
+ c = ()
+
+class level05(level03):
+ a = LSpringy, RSpringy
+ b = LSpringy, RSpringy
+ c = ()
+
+
+
+class level06(boarddef.Level):
+ letter = 1
+ fire = 0
+ lightning = 1
+ water = 0
+ top = 0
+
+ a = LSpringy
+ b = RSpringy
+ c = LBlitzy
+
+ walls = """
+## ############## ##
+## ##
+## ##
+## b bcbcbcb bb a a acacaca a ##
+################ ################
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## ########### ########### ##
+## bababa ##
+## ########################## ##
+## ##
+## ##
+## ##
+########## ##########
+## ##
+## ##
+## ##
+## ##
+## ##
+###### ############## ######
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>>>>>>>>>>>>>>>^^^^<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<<<<<<<<<<<<>>>>>>>>>>>>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level07(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 1
+
+ a = LSpringy
+ b = RSpringy
+
+ walls = """
+############ ############# ####
+### ##
+### b ##
+## b ##
+#### b ##
+## # b ##
+## #b ##
+## # ###### ##
+## # b ##
+## # b ##### ##
+## # b ##
+## # b ##
+## # b ##
+## ### ##
+## # ##
+## # b ##
+## # b ##
+## # b ##
+## # b ##
+## #b ##
+## # ##
+## # ##
+## # ##
+## # ##
+## # ##
+## # ##
+## # ##
+############ ############# ####
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>v v<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvv v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>vvvvv v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>>>>>v v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> v v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> v v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> v v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> xxx v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> v<<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<
+>>> ^<<
+>vv ^<<
+>vv ^<<
+>vv ^<<
+>vv ^<<
+>vv ^<<
+>vv ^<<
+>vv ^<<
+>vv ^<<
+>vvvvvvvvvvvvvvvv ^<<
+>vvvvvvvvvvvvvvvv ^<<
+>vvvvvvvvvvvvvvvv ^<<
+>vvvvvvvvvvvvvvvv ^<<
+"""
+
+
+class level08(boarddef.Level):
+ letter = 1
+ fire = 1
+ lightning = 1
+ water = 0
+ top = 0
+
+ a = b = c = d = e = LFlappy
+
+ walls = """
+## ##
+## ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## a b c d e ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## e d c b a ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## a b c d e ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## e d c b a ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## a b c d e ##
+#### ## ## ## ## ## ####
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level09(level08):
+
+ a = ()
+ b = RGhosty
+ c = RBlitzy
+ d = ()
+ e = LBlitzy
+
+
+class level10(boarddef.Level):
+ top = 1
+
+ a = RSpringy
+ b = LSpringy
+ c = LSpringy
+ d = RSpringy
+ e = LSpringy
+
+ walls = """
+## # # # # # # ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## a b c d e ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## e d c b a ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## a b c d e ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## e d c b a ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## # # # # # # ##
+## ##
+#### ## ## ## ## ## ####
+## ##
+## # # # # # # ##
+## ##
+#### ## ## ## ## ## ####
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+
+class level11(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 1
+ water = 0
+ top = 0
+
+ a = LSpringy
+ b = RFlappy
+ c = RSpringy
+
+ walls = """
+####################################
+## ##
+## ##
+## ##
+## ##
+## ##
+## b b b b b ##
+## ##
+## b b b b b b ##
+## ##
+## ##
+## ##
+## ##
+####################################
+## ##
+## ##
+## ##
+## ##
+## ##
+## a ##
+## c ##
+## a ##
+## c ##
+## a ##
+## c ##
+## a ##
+## c ##
+####################################
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+
+class level12(level11):
+ letter = 0
+ fire = 1
+ lightning = 0
+ water = 0
+ top = 1
+
+ a = LGhosty
+ b = RFlappy
+ c = RGhosty
+
+
+class level13(boarddef.Level):
+ letter = 1
+ fire = 0
+ lightning = 1
+ water = 0
+ top = 1
+
+ a = LFlappy
+ b = RFlappy
+
+ walls = """
+########## ###### ##########
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+## a ###### b ##
+## ###### ##
+## b ###### a ##
+## ###### ##
+## a ###### b ##
+## ###### ##
+## b ###### a ##
+## ###### ##
+## a ###### b ##
+## ##
+## b ###### a ##
+## ###### ##
+## a ###### b ##
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+## ###### ##
+########## ###### ##########
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> xxxx <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>> <<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>> <<
+"""
+
+
+class level14(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ a = RSpringy
+ b = ()
+
+ walls = """
+####################################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+###a # # #a # ##
+## # # #a # #a # # # # ##
+## # # # # # # # # # ##
+## ## ## ## ## ###
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+####################################
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level15(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 1
+ top = 0
+
+ f = LFlappy
+ b = RBlitzy
+ a = LOrcy, RMonky
+ g = RGramy
+ h = LGhosty
+ n = LNasty
+ s = LSpringy
+
+ walls = """
+############# ###### f f #########
+## a #b f f f ##
+## # ###### #### #######
+## # a a # # ##
+## ####### ## # # g ##
+## # # # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## # n n # n # ##
+## # #### ### ##
+## # # ##
+## s # # ##
+## s # h # ##
+## # # s # h # ##
+## # s # # ##
+## # #### ## # ##
+## # # ##### ### ## ##
+## # # ##
+## # b # ##
+## #### ##
+## ##
+## ##
+## ###### ##
+## # # ##
+## # # ##
+############# ###### f #########
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>>>vvvv<<<<<<<<<<<<<<
+>>^ vvvv x<<
+>>^ >vvvv< ^<<
+>>^ > xxxx < ^<<
+>>^ >>>>vvv<<< ^<<
+>>^ vv< >>>>>>>>>^<<
+>>^ xx ^ ^<<
+>>^ ^ ^<<
+>>^ ^ ^<<
+>>^ >^ ^<<
+>>^ ^< >^ ^<<
+>>^ ^<<<<<<<>>>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^< ^<<
+>>^ ^< xx ^<<
+>>^ ^ ^^<<<<<<<<<<<< ^<<
+>>^ ^ ^ ^<<
+>>^ ^< >^ ^<<
+>>^ ^<<>>^ ^<<
+>>^ ^<<
+>>^ ^^^^^^ ^<<
+>>^ ^<>vvvv<>^ ^<<
+>>^ ^<>vvvv<>^ ^<<
+>> ^<>vvvv<>^ <<
+"""
+
+
+class level16(boarddef.Level):
+ letter = 1
+ fire = 1
+ lightning = 1
+ water = 0
+ top = 1
+
+ a = LGhosty, RGhosty
+
+ walls = """
+## # ##
+## ## ## ##
+## a a ###### ##
+## ###### # ##
+## ######### ##
+## ############ ##
+## # # ########## a ##
+## a ### ######## ##
+## #### ### ####### ##
+## ######### ### a ##
+## ######### ### # ##
+## ############## ##
+## ############## ##
+## ####### ###### ##
+## ###### ###### a ##
+## ##### ##### ##
+## #### #### a ##
+## ##### ### ##
+## ########### a ##
+## a ### #### ##
+## ## ###### ##
+## ## ##### ##
+## #### ## ##
+## ####### ##
+## ##### ##
+## # #### ##
+## ##
+####################################
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>vvvvvvvvvvvvvvvvvvvv<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level17(boarddef.Level):
+ letter = 1
+ fire = 1
+ lightning = 0
+ water = 1
+ top = 0
+
+ a = LGhosty, RFlappy
+
+ walls = """
+## ##### # ##
+## ######## # ##
+## ####### a # # ##
+## ####### # # ##
+## ###### a ## # ##
+## ##### ## # ##
+## ###### # a ### # ####
+## ##### # # ####
+## ########### ####### # #####
+## #### ##### # ## #### # #####
+## #### ###### ### ### # # ###
+## ## # ## # ## ###### # ####
+## ### # ### ## # ####
+## ### ## #### # ## ####
+## ### a ## ### # # ###
+## ### ## ### ## # ###
+## ### ## ### a ## # ###
+## #### # ## ### # ###
+## #### # ### ### # ##
+## ### ### #### # ##
+## #### a ## # #### ##
+## ### ####### ###
+### ### ############ ## # ###
+### ## ########### #### ##
+### ### ### ########## ##
+### ### ################ ##
+## ## ############# ##
+### # ####### ##
+"""
+
+
+class level18(boarddef.Level):
+ letter = 1
+ fire = 0
+ lightning = 0
+ water = 1
+ top = 0
+
+ b = LBlitzy, RBlitzy
+ a = LOrcy
+
+ walls = """
+################# #################
+## # ## ## # ##
+## # ## ## # ##
+## # b b ## ## b b # ##
+## # ####### ####### # ##
+## #b # a #b # ##
+## ## #### ## ##
+## # a # ##
+## # # ##
+## a ##
+## #### ##
+## #a # ##
+## # # ##
+## a ##
+## #### ##
+## # a # ##
+## # # ##
+## a ##
+## #### ##
+## #a # ##
+## # # ##
+## a ##
+## #### ##
+## # a # ##
+## ## ## ##
+## ## ## ##
+## ## ## ##
+################# #################
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>>^^<<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ > ^^ < ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level19(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ a = LNasty
+ b = LMonky
+ c = LOrcy
+ d = LGramy
+
+ walls = """
+####################################
+####################################
+####################################
+####################################
+####################################
+####################################
+####################################
+####################################
+## ##
+## # # ##
+## # # ##
+## # # # # ##
+## # # # # ##
+## # # # # ##
+## # # # # # # ##
+## # # # # # # ##
+## # # # # # # ##
+## # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## # # # # # # # # ##
+## #a #b #c #d #c #b #a # ##
+####################################
+""" # [] [] [] # """
+
+ winds = """
+>>xxxxxxxxx>>>>xxxxxx<<<<xxxxxxxxx<<
+>>^ >>> ^ ^<<
+>>^ ^ <<< ^<<
+>>^ ^<<
+>>^ >> << ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level20(boarddef.Level):
+ letter = 1
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 1
+
+ a = RGhosty
+ b = RNasty
+ c = LSpringy
+ d = LFlappy
+
+ walls = """
+## ## # # # ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## ### # # ## ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## ## # # # ### ##
+## # # ##
+## # a d d d # ##
+## # a # ##
+## # a c # ##
+## # c # ##
+## # b b b c # ##
+## ### # # # ## ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## ## # # ### ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+################ # # ###############
+""" # [] [] [] # """
+
+ winds = """
+>>v v< v v v>v v<<
+>>v v<v>vxvv>v v<<
+>>v v<vvvvvv>v v<<
+>>v v<vvvvvv>v v<<
+>>v v<>v>v<v>v v<<
+>>v v<< v x <>>v v<<
+>>v v<vvvvvvvv>v v<<
+>>v v<vvvvvvvv>v v<<
+>>v v<vvvvvvvv>v v<<
+>>v v<v<v<v<v<>v v<<
+>>v v<<v x x v >>v v<<
+>>v v<vvvvvvvvvv>v v<<
+>>v v<vvvvvvvvvv>v v<<
+>>v v<vvvvvvvvvv>v v<<
+>>v v<vvvvvvvvvv>v v<<
+>>v v<vvvvvvvvvv>v v<<
+>>v v<>>v<v<v<<<>v v<<
+>>v v<< v x x v>>v v<<
+>>v v<v>>vvvvv>v v<<
+>>v v<vvvvvvvv>v v<<
+>>v v<vvvvvvvv>v v<<
+>>v v<>v>v<v<<>v v<<
+>>v v<<v x < >>v v<<
+>>v v<vvvvvv>v v<<
+>>v v<vvvvvv>v v<<
+>>v v<vvvvvv>v v<<
+>>v v<>v>v<v>v v<<
+>> v< v v v>v <<
+"""
+
+
+class level21(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ s = LSpringy, LSpringy
+ f = LFlappy, LFlappy
+ g = RGhosty, RGhosty
+
+ walls = """
+########### ## ##########
+## ## s ## ##
+## ##f ## ## ##
+## ## ## ## ##
+## ## ## s ## ##
+## # ## ##
+## ## s ## ##
+## ## #### ##
+## ## ## ## ##
+## ## s ## ## ##
+## ## ## ## ##
+## ## ## ## ##
+## ## s ## ## ##
+## ## ## ## ##
+## ## ## ## ##
+## ##s g ## ##
+## ## ## ## ##
+## ## ## ## ##
+## ## ## f ## ##
+## # ## ##
+## ## g ## ##
+## ## #### ##
+## ## ## ## ##
+## ## f ## ## ##
+## ## ## ## ##
+## ## ## ## ##
+## ## g ## ## ##
+########### ## ##########
+""" # [] [] [] # """
+
+
+class level22(level21):
+ water = 1
+ letter = 1
+
+ s = RBlitzy, RBlitzy
+ f = LOrcy, LOrcy
+ g = LGramy, LGramy
+
+
+class level23(boarddef.Level):
+ letter = 1
+ fire = 1
+ lightning = 1
+ water = 1
+ top = 0
+
+ a = LNasty
+ b = RMonky
+ c = LGramy
+ d = ROrcy
+ s = LSpringy
+
+ walls = """
+## ##
+## ### ####### ############## # ##
+## # # a # b # # # ##
+## # ### # ### # ##### # ### # ##
+## #d # # # # # # c # # # ##
+## ### # # # ### # ##### ### # ##
+## # # # # s # # d # # ##
+## ### # # ### #### ####### ### ##
+## #c # # # # a # # ##
+## ### # ### # ########## ### # ##
+## # # # # # # # b # c # # ##
+## # ### # ### # ### ### ### # ##
+## # b # #s # # # ##
+## # ####### # ######## # ##### ##
+## # # a # # # a # d # ##
+## ### ##### ### # ##### ### # ##
+## # d # # # b # # # ##
+## ### # ####### # ##### # ### ##
+## # # # cs # # # # ##
+## ### ### ######## # ### ### # ##
+## # # # b # # #c # # ##
+## # ### # ##### ##### # ### # ##
+## # d # # # # # # ##
+## ### # # # # ########## ### # ##
+## # # # # # a # # # ##
+## # ####### ################ # ##
+## ##
+####################################
+"""
+
+
+class level24(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ n = LOrcy
+ a = RGhosty, LFlappy
+
+ walls = """
+####################################
+## ## ## # # # # # # # ### ##
+## ## # # # # # # # ## # # # ##
+## ## ### # #a # n # # # ##
+## ## # # a #a # ## # ### ##
+## ## # a # ### ##
+###### # # #a # ########
+###### n #a # # ######
+##### # # # #######
+####### # n #####
+#### # # #######
+##### #a ####
+##### # # # #######
+##### # # ####
+#### # # #####
+###### # # #####
+#### # # ### ####
+####### # ######
+##### # # # # #####
+##### ## # # ######
+####### # # # # # #######
+####### # # # # ######
+######## # # # # # # ########
+######## ## # # # # #########
+########## # # # # # ## ##########
+## ###### # # # # # ###### ##
+## ##
+####################################
+"""
+
+ winds = """
+>> <<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ > xx < ^<<
+>>^ > xx < ^<<
+>>^ > xxx x < ^<<
+>>^ > xxx xxx < ^<<
+>>^ > x x< ^<<
+>>^ >x ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level25(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ b = RGhosty
+ a = LGhosty
+
+ walls = """
+####################################
+## ##
+## ##
+## ####################### ##
+## # ##
+## a a a a a a # ##
+## ################## # ##
+## # # # ##
+## # a a a a a a #b # ##
+## # ########## # ## ##
+## # # # # # ##
+## #b #a a a #b # # ##
+## ## # ##### ## # ##
+## # # # # ##
+## # # b b b b #b # ##
+## # ########### ## ##
+## # # # ##
+## #b b b b b b # ##
+## ###### b ###### ##
+## ####### ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+####################################
+""" # [] [] [] # """
+
+
+class level26(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ n = LNasty
+ m = RNasty
+ a = LGramy
+ g = LGhosty
+
+ walls = """
+## ####### ####### ##
+## ### ########## ### ##
+## g ## ## g ##
+##n m n m n ########## m n m n m ##
+############### ###############
+## ################ ##
+## #### #### ##
+## ############ ##
+## ###### ## ## ###### ##
+## ### ######### ### ##
+## ### # ## ### ##
+##nmnmnm ### ########## ###mnmnmn ##
+###############nmnmn ###############
+##### ################ #####
+############# #############
+## ## ## ## ## ##
+## ####### ####### ##
+## # ################ # ##
+## ########### ########### ##
+## # # ############ # # ##
+## ####### ## ## ####### ##
+## # m n # ######## # n m # ##
+## ####### # # ####### ##
+## ######## ##
+## # m # ##
+## ######## ##
+## ##
+####################################
+""" # [] [] [] # """
+
+
+class level27(boarddef.Level):
+ letter = 1
+ fire = 1
+ lightning = 1
+ water = 0
+ top = 0
+
+ n = LNasty
+ m = RMonky
+
+ walls = """
+########### ###### # ##
+## # # ##
+## ######### # # n ##
+## # ###########
+## # nnn ##
+## ######### ##
+## ##
+## nnn # ##
+## ####### ## # ##
+## # # ##
+## # # ##
+## #nnn mmmm # # m ##
+## ##### ###### ###########
+## ##
+## ##
+## ####### ##
+## ### # ##
+## # # ##
+## nnn # # n ##
+## ###### ###########
+## ##
+## #mmmmmm ##
+## ######## ##
+## ##
+## ######## ### # ##
+## # # ##
+## nnn # # ##
+########### ###### ###########
+""" # [] [] [] # """
+
+
+class level28(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 1
+ top = 1
+
+ m = LMonky
+ g = LGramy
+ h = LGhosty
+ f = LFlappy
+ b = LSpringy
+ a = RSpringy
+
+ walls = """
+## # # ##
+## b # #a ##
+## b # #a ##
+## ##### ##### ##
+## # # ##
+## # # ##
+## # # ##
+## # # ##
+## ##
+## ##
+## ##
+## # # ##
+###### ##### # # # # # ##
+## m m m # # ##
+## ######### # # h f h f ##
+## g g g # # ##
+####### ###### # h f ##
+## m m m # # f ##
+## ########### # # h h h ##
+## g g g # # f f ##
+######## ####### # # # # # # # ##
+## ##
+## ##
+## ##
+####### ##### ## ##### #######
+## # ## # ##
+## # # ##
+################# #################
+""" # [] [] [] # """
+
+ winds = """
+>>v v<<
+>>v v<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+class level29(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 0
+
+ m = LMonky, RMonky
+ g = LGramy, RGramy
+ o = LOrcy, ROrcy
+ n = LNasty, RNasty
+
+ walls = """
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+################# #################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## # m g o nn o g m # ##
+## ######################## ##
+## ##
+""" # [] [] [] # """
+
+ winds = """
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>>xxxxxxxxxxxxxxx xxxxxxxxxxxxxxx<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>x>>>>v>>>>v<>v<<<<v<<<<x<<<<<<
+vvvvvvxxxxxxxxxxxxxxxxxxxxxxxxvvvvvv
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+"""
+
+class level29(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 1
+
+ a = LNasty
+ b = RNasty
+ g = RGhosty
+ r = RGramy
+ m = LMonky
+
+ walls = """
+## ############################
+## ##
+##### ##
+##### ##
+##### ##
+##### ##
+##### # ##
+##### a b a ba ab a ba # ##
+################################ ##
+## ##
+## ##
+## ## g ##
+## ## ##
+## ## ##
+## ## ##
+## ## ##
+## ## br a rb a rb a r a ##
+## #############################
+## ## ##
+## ## ## ## ##
+##### ## mm ##mm ## ##
+############################## ##
+##### ## ## ## ##
+##### ## ## ##
+##### ##
+## ##
+## ##
+## ################################
+""" # [] [] [] # """
+
+ winds = """
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ^<<
+>>vv>>>>>>>>>>>>>>>>>>>>>>>>>>>> v<<
+>>vv <<
+>>vv <<
+>>vv vvvvvvvvvvvvvvvvvvvvvv <<
+>>vv vvvvvvvvvvvvvvvvvvvvvv <<
+>>vv vvvvvvvvvvvvvvvvvvvvvv <<
+>>vv vvvvvvvvvvvvvvvvvvvvvv <<
+>>vv vvvvvvvvvvvvvvvvvvvvvv <<
+>>vv vvvvvvvvvvvvvvvvvvvvvv <<
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>> <<
+"""
+
+
+class level30(boarddef.Level):
+ letter = 1
+ fire = 1
+ lightning = 1
+ water = 0
+ top = 1
+
+ a = RNasty
+
+ walls = """
+####################################
+####################################
+## ##### ## ##### ##
+## a ##### a ## a ##### a ##
+####################################
+####################################
+####################################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+###### ######
+## ##
+## ##
+## ##
+## ##
+####################################
+""" # [] [] [] # """
+
+ winds = """
+>> <<
+>> <<
+>>>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<<<
+>>>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<<<
+>>>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<<<
+>>>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<<<
+>>> <<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>>vv<<<<<<<<<<<<<<<<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>> <<
+"""
+
+
+class level31(level30):
+ fire = 0
+ lightning = 0
+
+
+class level32(boarddef.Level):
+ n = LNasty
+ m = LMonky
+ o = LOrcy
+ g = LGramy
+ h = LGhosty
+ f = LFlappy
+ s = LSpringy
+ b = LBlitzy
+
+ walls = """
+#################### ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ########
+## ##
+## ##
+## # ##
+## ####### ##
+## ##
+## ##
+## # ##
+## ####### ##
+## ##
+## ##
+## ##
+## # # ##
+## # # # ##
+## # # # n ##
+## # # ######
+## # # # ##
+## # # # n n ##
+## # #########
+## # ##
+## # ##
+#################### ##
+""" # [] [] [] # """
+
+ winds = """
+>>>>>>>>>>>>>>>>xxxx<<vvvvvvvvvvvv>v
+>>^ ^<<vvvvvvvvvv>v
+>>^ ^<<vvvvvvvv>v
+>>^ ^<<vvvvvv>v
+>>^ ^<<vvvv>v
+>>^ ^<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ vvvvvvvvvvvvvv>v
+>>^ vvvvvvvvvvvvvv>v
+>> vvvvvvvvvvvvvv>v
+"""
+
+
+class level33(boarddef.Level):
+ m = LMonky, RMonky
+ g = LGramy, RGramy
+ o = LOrcy, ROrcy
+ n = LNasty, RNasty
+
+ walls = """
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+################# #################
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ##
+## ## m g o nn o g m ## ##
+## ######################## ##
+## ##
+""" # [] [] [] # """
+
+ winds = """
+v<vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv>v
+v<vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv>v
+v<vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv>v
+v<vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv>v
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>> <<
+>>xxxxxxxxxxxxxxx xxxxxxxxxxxxxxx<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>v>>>>v>>>>v<>v<<<<v<<<<v<<<<<<
+>>>>>>x>>>>v>>>>v<>v<<<<v<<<<x<<<<<<
+v<vvvvxxxxxxxxxxxxxxxxxxxxxxxxvvvv>v
+v<vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv>v
+"""
+
+
+class level34(boarddef.Level):
+ letter = 0
+ fire = 0
+ lightning = 0
+ water = 0
+ top = 1
+
+ a = RBlitzy
+ b = LBlitzy
+
+ walls = """
+## # ## # ## # ## # ##
+## ## # ba # ## ##
+## ### b#### #### ### ##
+## # # # # b # # ##
+## #### # # #### ##
+## #######b # # ####### ##
+## # # #b # ##
+## # # # # ##
+## # # # # ##
+## #b # # # ##
+## # # #b # ##
+## # # ##
+## # # # # ##
+## #b # # # ##
+## # # #b # ##
+## # # # # ##
+## # # # # ##
+## #b # # # ##
+## # # #b # ##
+## # # # # ##
+## # # # # ##
+## #b # # # ##
+## # # #b # ##
+## ####### # # ####### ##
+## #### # # #### ##
+## #### #### #### #### ##
+## #### b #### ##
+## ## # # ## ##
+""" # [] [] [] # """
+
+ winds = """
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>xxxxxvvvvvvvvvvvvvvvvvvvvvvxxxxx<<
+"""
+
+
+class level35(boarddef.Level):
+ s = LNasty
+ o = LNasty, LNasty, RNasty, RNasty
+ a = RBlitzy
+ b = LBlitzy
+
+ walls = """
+####################################
+## ###s ### ##
+##s ## b ######## a ##s ##
+####################################
+## ##
+## ##
+## ##
+## ## ##
+## #### ##
+## ######## ##
+## ############ ##
+## ###### ##
+## ########## ##
+## ################ ##
+## ########## ##
+## ############## ##
+## ################## ##
+## ## ##
+## ## ##
+## #### ##
+## ###### ##
+## ## ## ## ##
+## ## ## ## ##
+## ##
+## ##
+## ##
+## #o o o o o o o o o o o o # ##
+####################################
+""" # [] [] [] # """
+
+ winds = """
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<
+>>>>>>>>>>>>>> <<<<<<<<<<<<<<
+>>>>>>>>>>>>> <<<<<<<<<<<<<
+>>>>>>>>>>>> <<<<<<<<<<<<
+>>>>>>>>>>> <<<<<<<<<<<
+>>>>>>>>>> <<<<<<<<<<
+>>>>>>>>> <<<<<<<<<
+>>>>>>>> <<<<<<<<
+>>>>>>> <<<<<<<
+>>>>>> <<<<<<
+>>>>> <<<<<
+>>>> <<<<
+>>> <<<
+>>vv<<<<<<<<<<<<<<>>>>>>>>>>>>>>vv<<
+>>vv<<<<<<<<<<<<<<>>>>>>>>>>>>>>vv<<
+>>vv<<<<<<<<<<<<<<>>>>>>>>>>>>>>vv<<
+>>vv<<<<<<<<<<<<<<>>>>>>>>>>>>>>vv<<
+>>vv<<<<<<<<<<<<<<>>>>>>>>>>>>>>vv<<
+>>vv<<<<<<<<<<<<<<>>>>>>>>>>>>>>vv<<
+>>vv<<<<<<^^^^^^^^^^^^^^^^>>>>>>vv<<
+>>vv<<<<^^^^^^^^^^^^^^^^^^^^>>>>vv<<
+>>xx<<^^^^^^^^^^^^^^^^^^^^^^^^>>xx<<
+>>xx<<<<<<<<<<<<<<>>>>>>>>>>>>>>xx<<
+"""
+
+
+class level36(boarddef.Level):
+ s = LSpringy
+ b = LBlitzy
+
+ walls = """
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+## b b ##
+## s ##
+""" # [] [] [] # """
+
+ winds = """
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>>>>>>>>>>>>>>>xxxx<<<<<<<<<<<<<<<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>>^ ^<<
+>> <<
+"""
+
+
+class level37(boarddef.Level):
+ n = LNasty
+ m = LMonky
+
+ walls = """
+################# #################
+## m m m m m # ## #m m m m m ##
+## #n n n n n # #n n n n n # ##
+################# #################
+## ####### ####### ##
+## ################ ##
+## ############################ ##
+## # #### #### # ##
+## #### ##### ##### #### ##
+## #### ################ #### ##
+## #### ################ #### ##
+## ######### ######### ##
+## ### ############## ### ##
+## #### # ############ # #### ##
+## #### ############ #### ##
+############ #### ############
+############ ## ############
+## # ## ## # ##
+## ############ ############ ##
+## # ##### ##### # ##
+## ############################ ##
+## ############################ ##
+## ######## ######## ##
+######## ## ## ########
+######## # #### # ########
+## # #### # ##
+## ## ### ### ## ##
+###### ################ ######
+""" # [] [] [] # """
+
+ winds = """
+>>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<<
+>>xx<<<<<<<<<<<<<<>>>>>>>>>>>>>>xx<<
+>>xx^^^^^^^^^^^^^^^^^^^^^^^^^^^^xx<<
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vv>>>>vvvvvvvvvvvvvvvvvvvv<<<<vv>v
+v<vvvv<>vvvvvvvvvvvvvvvvvvvv<>vvvv>v
+v<vvvv>>vvvvvvvvvvvvvvvvvvvv<<vvvv>v
+v<vv>vvvvvvvvvvvvvvvvvvvvvvvvvv>vv>v
+v<vv>vvvvvvvvvvvvvvvvvvvvvvvvvv>vv>v
+v>vv<vvvvvvvvvvvvvvvvvvvvvvvvvv>vv<v
+v>vv<vvvvvvvvvvvvvvvvvvvvvvvvvv>vv<v
+v>vv<vvvvvvvvvvvvvvvvvvvvvvvvvv>vv<v
+v>vv<vvvvvvvvvvvvvvvvvvvvvvvvvv>vv<v
+v>vv<vvvvvvvvvvvvvvvvvvvvvvvvvv>vv<v
+v>vv<vvvvvvvvvvvvvvvvvvvvvvvvvv>vv<v
+v>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<v
+v>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<v
+v>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<v
+v>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<v
+v>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv<v
+"""
+
+class level38(boarddef.Level):
+ g = RGhosty
+
+ walls = """
+####################################
+## # ##
+## g # g ##
+## # ##
+## # g ##
+## # ##
+## ### ##### ## ##
+## ### ######## ## ##
+## #### ### ###### ##
+## ### ### ####### ### ##
+## ## ########## ##
+## # ######## ##
+## # g ##
+## g ########## ##
+## ########### ##
+## ########## ## ##
+## ############ ## ##
+## ### ############ ## ##
+## #### ########## ### ##
+## ##### ######## ## ##
+## #### ###### ##
+## ##### ##
+## ######## ##
+## ##
+## ##
+## ##
+## ##
+####################################
+"""
+
+
+class levelFinal(boarddef.Level):
+
+ walls = """
+################ ################
+## ##
+## ##
+## ### ### ##
+## ##
+## ### ### ##
+## ##
+## ### ### ##
+## ##
+## ### ### ##
+## ##
+##### #####
+## ##
+## ### ### ##
+## ##
+## ### ### ##
+## ##
+## ### ### ##
+## ##
+## ### ### ##
+## ##
+####################################
+## ##
+## ##
+## ##
+## ##
+## ##
+################ ################
+""" # [] [] [] # """
+# nb.: the previous line has no purpose
+# other than helping with wall alignment
+
+ winds = """
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>>>>>>>>>>>>>>>>^^^^<<<<<<<<<<<<<<<<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+>> ^^^^ <<
+"""
diff --git a/bubbob/macbinary.py b/bubbob/macbinary.py
new file mode 100644
index 0000000..616bce0
--- /dev/null
+++ b/bubbob/macbinary.py
@@ -0,0 +1,260 @@
+import struct
+
+
+def padto(n, m):
+ return (n+m-1) & ~(m-1)
+
+def resourceclass(rtype):
+ return globals().get(rtype.strip() + 'Resource', Resource)
+
+
+class TypeList:
+
+ def __init__(self, type, fmap, fdata, namebase, start, count):
+ self.type = type
+ self.fmap = fmap
+ self.fdata = fdata
+ self.namebase = namebase
+ self.start = start
+ self.count = count
+ self.ids = None
+
+ def resources(self):
+ if self.ids is None:
+ ResourceCls = resourceclass(self.type)
+ d = {}
+ self.fmap.seek(self.start)
+ for resid, resname, resattr, resofshi, resofslo in [
+ struct.unpack(">HHBBHxxxx", self.fmap.read(12))
+ for i in range(self.count)]:
+ if resname == 0xffff:
+ name = None
+ else:
+ self.fmap.seek(self.namebase + resname)
+ namelen, = struct.unpack(">B", self.fmap.read(1))
+ name = self.fmap.read(namelen)
+ assert resid not in d
+ d[resid] = ResourceCls(self.type, resid, name, resattr,
+ self.fdata, resofslo + (resofshi<<16))
+ self.ids = d
+ return self.ids
+
+ def __getitem__(self, id):
+ return self.resources()[id]
+
+ def keys(self):
+ return self.resources().keys()
+
+ def values(self):
+ return self.resources().values()
+
+ def items(self):
+ return self.resources().items()
+
+ def namedict(self):
+ return dict([(r.name, r) for r in self.resources().values() if r.name is not None])
+
+
+class MacBinary:
+
+ def __init__(self, f):
+ if type(f) is type(''):
+ f = open(f, 'rb')
+ self.f = f
+ self.f.seek(0x53)
+ self.dataforksize, self.resforksize = struct.unpack(">ll", self.f.read(8))
+ self.loadresources()
+
+ def getdata(self):
+ self.f.seek(0x80)
+ return self.f.read(self.dataforksize)
+
+ def loadresources(self):
+ f = Subfile(self.f, padto(0x80 + self.dataforksize, 0x80), self.resforksize)
+ ofsdata, ofsmap, lendata, lenmap = struct.unpack(">llll", f.read(16))
+ fdata = Subfile(f, ofsdata, lendata)
+ fmap = Subfile(f, ofsmap, lenmap)
+ fmap.seek(24)
+ ofstype, ofsname = struct.unpack(">HH", fmap.read(4))
+ self.dtypes = {}
+ fmap.seek(ofstype)
+ numtypes, = struct.unpack(">H", fmap.read(2))
+ numtypes = numtypes + 1
+ for rtype, num, ofsref in [struct.unpack(">4sHH", fmap.read(8))
+ for i in range(numtypes)]:
+ assert rtype not in self.dtypes
+ self.dtypes[rtype] = TypeList(rtype, fmap, fdata, ofsname,
+ ofstype + ofsref, num + 1)
+
+ def __getitem__(self, rtype):
+ return self.dtypes[rtype]
+
+ def types(self):
+ return self.dtypes
+
+ def keys(self):
+ return self.dtypes.keys()
+
+ def values(self):
+ return self.dtypes.values()
+
+ def items(self):
+ return self.dtypes.items()
+
+
+class Subfile:
+ def __init__(self, f, start, length):
+ if start < 0:
+ raise ValueError, 'negative position'
+ if isinstance(f, Subfile):
+ if start + length > f.length:
+ raise ValueError, 'subfile out of bounds'
+ f, start = f.f, f.start+start
+ self.f = f
+ self.start = start
+ self.length = length
+ self.position = 0
+ def read(self, size=None):
+ if size is None or self.position + size > self.length:
+ size = self.length - self.position
+ if size <= 0:
+ return ''
+ self.f.seek(self.start + self.position)
+ self.position = self.position + size
+ return self.f.read(size)
+ def seek(self, npos):
+ if npos < 0:
+ raise ValueError, 'negative position'
+ self.position = npos
+
+
+class Resource:
+
+ def __init__(self, type, id, name, attr, srcfile, srcofs):
+ self.type = type
+ self.id = id
+ self.name = name
+ self.attr = attr
+ self.srcfile = srcfile
+ self.srcofs = srcofs
+
+ def subfile(self):
+ self.srcfile.seek(self.srcofs)
+ length, = struct.unpack(">l", self.srcfile.read(4))
+ return Subfile(self.srcfile, self.srcofs + 4, length)
+
+ def load(self):
+ return self.subfile().read()
+
+
+class RGBImage:
+ def __init__(self, w, h, data):
+ assert len(data) == 3*w*h
+ self.w = w
+ self.h = h
+ self.data = data
+
+
+def loadcolormap(f):
+ size, = struct.unpack(">xxxxxxH", f.read(8))
+ size = size + 1
+ d = {}
+ for index, r, g, b in [struct.unpack(">HHHH", f.read(8)) for i in range(size)]:
+ assert index not in d, 'duplicate color index'
+ d[index] = r/256.0, g/256.0, b/256.0
+ return d
+
+def image2rgb(image):
+ # returns (w, h, data)
+ h = len(image)
+ result1 = []
+ for line in image:
+ for r, g, b in line:
+ result1.append(chr(int(r)) + chr(int(g)) + chr(int(b)))
+ return len(image[0]), len(image), ''.join(result1)
+
+
+class clutResource(Resource):
+ # a color table
+ def gettable(self):
+ return loadcolormap(self.subfile())
+
+
+class ppatResource(Resource):
+ # a pattern
+ def getimage(self):
+ f = self.subfile()
+ pattype, patmap, patdata = struct.unpack(">Hll", f.read(10))
+ if pattype != 1:
+ raise ValueError, 'Pattern type not supported'
+ f.seek(patmap)
+ (rowBytes, h, w, packType, packSize,
+ pixelType, pixelSize, cmpCount, cmpSize, pmTable) = (
+ struct.unpack(">xxxxHxxxxHHxxHlxxxxxxxxHHHHxxxxlxxxx", f.read(50)))
+ isBitmap = (rowBytes & 0x8000) != 0
+ rowBytes &= 0x3FFF
+ if packType != 0:
+ raise ValueError, 'packed image not supported'
+ if pixelType != 0 or cmpCount != 1:
+ raise ValueError, 'direct RGB image not supported'
+ assert cmpSize == pixelSize and pixelSize in [1,2,4,8]
+ f.seek(pmTable)
+ colormap = loadcolormap(f)
+ bits_per_pixel = pixelSize
+ pixels_per_byte = 8 // bits_per_pixel
+ image = []
+ f.seek(patdata)
+ for y in range(h):
+ line = f.read(rowBytes)
+ imgline = []
+ for x in range(w):
+ n = x//pixels_per_byte
+ idx = ((ord(line[n]) >> ((pixels_per_byte - 1 - x%pixels_per_byte) * bits_per_pixel))
+ & ((1<<bits_per_pixel)-1))
+ imgline.append(colormap[idx])
+ image.append(imgline)
+ return image
+
+
+class LEVLResource(Resource):
+ # bub & bob level
+ WIDTH = 32
+ HEIGHT = 25
+ MONSTERS = 30
+ WALLS = { 1:'#', 0:' '}
+ WINDS = { 0:' ', 1:'>', 2:'<', 3:'v', 4:'^', 5:'x', 0x66:' '}
+ FLAGS = ['flag0', 'letter', 'fire', 'lightning', 'water', 'top', 'flag6', 'flag7']
+
+ def getlevel(self, mnstrlist):
+ f = self.subfile()
+ result = {}
+
+ walls = []
+ for y in range(self.HEIGHT):
+ line = f.read(self.WIDTH//8)
+ line = [self.WALLS[(ord(line[x//8]) >> (x%8)) & 1]
+ for x in range(self.WIDTH)]
+ walls.append(''.join(line))
+ result['walls'] = '\n'.join(walls)
+
+ winds = []
+ for y in range(self.HEIGHT):
+ line = f.read(self.WIDTH)
+ line = [self.WINDS[ord(v)] for v in line]
+ winds.append(''.join(line))
+ result['winds'] = '\n'.join(winds)
+
+ monsters = []
+ for i in range(self.MONSTERS):
+ x,y,monster_type,f1,f2,f3 = struct.unpack(">BBBBBB", f.read(6))
+ if monster_type != 0:
+ assert f1 == 0, f1
+ cls = mnstrlist[monster_type-1]
+ monsters.append(cls(x=x, y=y, dir=f2, player=f3))
+ result['monsters'] = monsters
+
+ result['level'], = struct.unpack('>H', f.read(2))
+ for i in range(8):
+ result[self.FLAGS[i]] = ord(f.read(1))
+
+ return result
diff --git a/bubbob/mnstrmap.py b/bubbob/mnstrmap.py
new file mode 100644
index 0000000..88e531d
--- /dev/null
+++ b/bubbob/mnstrmap.py
@@ -0,0 +1,397 @@
+
+class Monster1:
+ def __init__(self, x, y, dir, player=0):
+ self.x = x
+ self.y = y
+ self.dir = (1, -1)[dir]
+ self.player = player
+
+def nrange(start,n):
+ return range(start,start+n)
+
+class Nasty(Monster1):
+ right = nrange(239,4)
+ left = nrange(243,4)
+ jailed = nrange(247,3)
+ dead = nrange(253,4)
+ right_angry = nrange(800,4)
+ left_angry = nrange(804,4)
+
+class Monky(Monster1):
+ right = nrange(265,4)
+ left = nrange(269,4)
+ jailed = nrange(273,3)
+ dead = nrange(279,4)
+ left_weapon = right_weapon = nrange(451,1) # FIXME
+ decay_weapon = nrange(452,4) # FIXME
+ right_angry = nrange(808,4)
+ left_angry = nrange(812,4)
+
+class Ghosty(Monster1):
+ right = nrange(291,4)
+ left = nrange(295,4)
+ jailed = nrange(299,3)
+ dead = nrange(305,4)
+ right_angry = nrange(816,4)
+ left_angry = nrange(820,4)
+
+class Flappy(Monster1):
+ right = nrange(317,4)
+ left = nrange(321,4)
+ jailed = nrange(325,3)
+ dead = nrange(331,4)
+ right_angry = nrange(824,4)
+ left_angry = nrange(828,4)
+
+class Springy(Monster1):
+ right = nrange(343,4)
+ left = nrange(347,4)
+ jailed = nrange(351,3)
+ dead = nrange(357,4)
+ right_jump = nrange(369,2)
+ left_jump = nrange(371,2)
+ right_angry = nrange(832,4)
+ left_angry = nrange(836,4)
+ right_jump_angry = nrange(840,2)
+ left_jump_angry = nrange(842,2)
+
+class Orcy(Monster1):
+ right = nrange(373,4)
+ left = nrange(377,4)
+ jailed = nrange(381,3)
+ dead = nrange(387,4)
+ left_weapon = nrange(456,4)
+ right_weapon = nrange(460,4)
+ decay_weapon = nrange(456,0) # FIXME
+ right_angry = nrange(844,4)
+ left_angry = nrange(848,4)
+
+class Gramy(Monster1):
+ right = nrange(399,4)
+ left = nrange(403,4)
+ jailed = nrange(407,3)
+ dead = nrange(413,4)
+ left_weapon = right_weapon = nrange(472,4) # FIXME
+ right_angry = nrange(852,4)
+ left_angry = nrange(856,4)
+
+class Blitzy(Monster1):
+ right = left = nrange(425,4)
+ jailed = nrange(429,3)
+ dead = nrange(435,4)
+ right_angry = left_angry = nrange(860,4)
+ left_weapon = right_weapon = nrange(476,1) # FIXME
+
+class Ghost:
+ left = nrange(443,4)
+ right = nrange(447,4)
+
+class PlayerBubbles:
+ appearing = nrange(952,5)
+ bubble = nrange(910,3)
+ explosion = nrange(913,3)
+ left_weapon = nrange(464,4)
+ right_weapon = nrange(468,4)
+ decay_weapon = []
+
+class LetterBubbles:
+ Extend = nrange(128,3) # FIXME
+ eXtend = nrange(136,3)
+ exTend = nrange(144,3)
+ extEnd = nrange(152,3)
+ exteNd = nrange(160,3)
+ extenD = nrange(168,3)
+
+class DyingBubble:
+ first = nrange(163,3)
+ medium = nrange(171,3)
+ last = nrange(155,3)
+
+class Fire:
+ ground = nrange(490,4)
+ drop = 489
+
+class Lightning:
+ fired = 488
+
+class Water:
+ h_flow = 900
+ v_flow = 901
+ start_right = 902
+ start_left = 904
+ bottom = 903
+ top = 905
+ tl_corner = 906
+ bl_corner = 907
+ br_corner = 908
+ tr_corner = 909
+
+class Flood:
+ waves = nrange(140,4)
+ fill = 495
+
+class MiscPoints:
+ pink_100 = 496
+
+class DigitsMisc:
+ digits_mask = nrange(519,10)
+ digits_border = nrange(920,10)
+ digits_white = nrange(930,10)
+
+class PotionBonuses:
+ coin = 477
+ flower = 478
+ trefle = 479
+ rainbow = 480
+ green_note = 481
+ blue_note = 692
+
+
+class Bonuses:
+ monster_bonuses = [
+ (593,1000), # banana
+ (594,2000), # peach
+ (595,3000), # quince
+ (596,4000), # pastec
+ (597,5000), # wine
+ (598,6000), # ananas
+ (599,8000) # diamond
+ ]
+
+ door = 139 # Lots of diamonds
+
+ red_potion = 637 #\ .
+ green_potion = 638 # > Clean the level and fill the top 5 lines with one of the PotionBonuses.
+ yellow_potion = 639 #/
+
+ kirsh = 600
+ icecream1 = 601 # NOT_USED
+ erdbeer = 602
+ fish1 = 603
+ tomato = 604
+ donut = 605
+ apple = 606
+ corn = 607
+ icecream2 = 608 # NOT_USED
+ radish = 609
+
+ cyan_ice = 610 #\ .
+ violet_ice = 611 #|
+ peach2 = 612 # > Produced from the bubbles after a wand.
+ pastec2 = 613 #|
+ cream_pie = 614 #|
+ sugar_pie = 615 #/
+
+ brown_wand = 620 #\ .
+ yellow_wand = 621 #|
+ green_wand = 622 # > Bubbles turn into bonus of the previous set after
+ violet_wand = 623 # > the death of the last enemy plus a mega-bonus.
+ blue_wand = 624 #|
+ red_wand = 625 #/
+
+ violet_chest = 626 #\ .
+ blue_chest = 627 # > Bubbles turn into diamonds plus after the death
+ red_chest = 628 # > of the last enemy plus a mega-diamond
+ yellow_chest = 629 #/
+
+ shoe = 631 # speed player movements
+ grenade = 632 # put fire everywhere
+
+ brown_umbrella = 633 # fire rain
+ grey_umbrella = 634 # water rain
+ violet_umbrella = 635 # spinning balls rain
+
+ clock = 636 # time travel
+ coffee = 641 # Speed player's movements and fire rate.
+ book = 642 # Produces stars the middle-top going in any direction which kill the enemy upon contact.
+ heart_poison = 643 # Froze the enemy and they are now killed on contact.
+ gold_crux = 644 # become a bubble
+ red_crux = 645 # become a monster
+ blue_crux = 646 # become a monster
+ extend = 647 # Give 100'000 Points to the player and finish the level.
+
+ ring = 640 # lord of the ring
+ green_pepper = 648 # hot stuff!
+ orange_thing = 649 # slippy
+ aubergine = 650 # rear gear
+ carrot = 651 # angry monsters
+ rape = 652 # auto-fire
+ white_carrot = 653 # fly
+ chickpea = 654 # shield
+ mushroom = 655 # pinball mode
+ egg = 656 # players permutation
+ chestnut = 657 # variation of frames per second
+ green_thing = 658 # sugar bomb
+ icecream3 = 659 # \ each icecream becomes two of the
+ icecream4 = 660 # \ next kind, which scores more points
+ icecream5 = 661 # / that's a lot of points in total
+ icecream6 = 662 # /
+ softice1 = 663 # shoot farther
+ softice2 = 665 # shoot nearer1
+ french_fries = 664 # shoot 10 lightning bubbles
+ custard_pie = 666 # shoot faster
+ lollipop = 667 # invert left and right
+ cocktail = 668 # short-lived bubbles
+ ham = 669 # wall builder
+ bomb = 670 # explodes the structure of the level
+ beer = 671 # shoot 10 water bubbles
+ emerald = 672 # mega points
+ fish2 = 673 # mega blitz
+ sapphire = 681 # mega points
+ ruby = 682 # mega points
+ tin = 674 # angry (double-speed) player
+ hamburger = 675 # shoot 10 fire bubbles
+ insect = 676 # walls fall down
+ blue_necklace = 677 # player ubiquity
+ violet_necklace = 679 # monster ubiquity
+ butterfly = 678 # lunar gravity
+ conch = 680 # complete water flood
+ yellow_sugar = 630 # from a bonbon bomb
+ blue_sugar = 691 # from a bonbon bomb
+
+class Diamonds:
+ # Produced from the bubbles after last enemy is killed and a chest or wand has been caught.
+ violet = 616
+ blue = 617
+ red = 618
+ yellow = 619
+
+class Stars:
+ # Effect of the book. Kill monsters on contact.
+ blue = nrange(940,2)
+ yellow = nrange(942,2)
+ red = nrange(944,2)
+ green = nrange(946,2)
+ magenta = nrange(948,2)
+ cyan = nrange(950,2)
+ COLORS = ['blue', 'yellow', 'red', 'green', 'magenta', 'cyan']
+
+class SpinningBalls:
+ free = nrange(482,4)
+ bubbled = nrange(486,2) # NOT_USED
+
+class BigImages:
+ cyan_ice = 10 # Megabonus produced after a wand
+ violet_ice = 11
+ peach2 = 12
+ pastec2 = 13
+ cream_pie = 14
+ sugar_pie = 15
+ violet = 16
+ blue = 17
+ red = 18
+ yellow = 19
+ blitz = 30
+ hurryup = nrange(31,2)
+
+class birange:
+ def __init__(self, a,b,n):
+ self.a = a
+ self.n = n
+ def __getitem__(self, pn):
+ return range(self.a + 1000*pn, self.a + 1000*pn + self.n)
+
+class bidict:
+ def __init__(self, a,b):
+ self.a = a.items()
+ def __getitem__(self, pn):
+ pn *= 1000
+ d = {}
+ for key, value in self.a:
+ d[key] = value + pn
+ return d
+
+class GreenAndBlue:
+ water_bubbles = birange(182,185,3)
+ fire_bubbles = birange(176,554,3)
+ light_bubbles = birange(179,557,3)
+ normal_bubbles = birange(188,195,3)
+ new_bubbles = birange(191,203,4)
+ players = birange(210,226,13)
+ jumping_players = birange(683,687,4)
+ new_players = birange(693,696,3)
+ numbers = birange(499,509,10) # FIXME: already seen below
+ comming = birange(693,696,3)
+ points = bidict({
+ 100: 529,
+ 150: 530,
+ 200: 531,
+ 250: 532,
+ 300: 533,
+ 350: 534,
+ 500: 535,
+ 550: 536,
+ 600: 537,
+ 650: 538,
+ 700: 539,
+ 750: 540,
+ 800: 541,
+ 850: 542,
+ 900: 543,
+ 950: 544,
+ 1000: 545,
+ 2000: 546,
+ 3000: 547,
+ 4000: 548,
+ 5000: 549,
+ 6000: 550,
+ 7000: 551,
+ 8000: 552,
+ 9000: 553,
+ 10000: 20,
+ 20000: 21,
+ 30000: 22,
+ 40000: 23,
+ 50000: 24,
+ 60000: 25,
+ 70000: 26,
+ },{
+ 100: 561,
+ 150: 562,
+ 200: 563,
+ 250: 564,
+ 300: 565,
+ 350: 566,
+ 500: 567,
+ 550: 568,
+ 600: 569,
+ 650: 570,
+ 700: 571,
+ 750: 572,
+ 800: 573,
+ 850: 574,
+ 900: 575,
+ 950: 576,
+ 1000: 577,
+ 2000: 578,
+ 3000: 579,
+ 4000: 580,
+ 5000: 581,
+ 6000: 582,
+ 7000: 583,
+ 8000: 584,
+ 9000: 585,
+ 10000: 90,
+ 20000: 91,
+ 30000: 92,
+ 40000: 93,
+ 50000: 94,
+ 60000: 95,
+ 70000: 96,
+ })
+ gameover = birange(497,498,1)
+ digits = birange(499,509,10)
+ fish = birange(700,707,7)
+
+class Butterfly(Monster1):
+ right = [('butterfly', 'fly', n) for n in range(2)]
+ left = [Bonuses.insect, Bonuses.butterfly]
+ jailed = [('butterfly', 'jailed', n) for n in range(3)]
+ dead = [('butterfly', 'dead', n) for n in range(4)]
+
+class Sheep(Monster1):
+ right = [('sheep', 1, n) for n in range(4)]
+ left = [('sheep',-1, n) for n in range(4)]
+ right_angry = right
+ left_angry = left
+
diff --git a/bubbob/monsters.py b/bubbob/monsters.py
new file mode 100644
index 0000000..88983d0
--- /dev/null
+++ b/bubbob/monsters.py
@@ -0,0 +1,937 @@
+from __future__ import generators
+import random
+import gamesrv
+import images
+import boards
+from boards import *
+from images import ActiveSprite
+from mnstrmap import GreenAndBlue, Bonuses, Ghost
+from player import BubPlayer
+import bonuses
+
+
+class Monster(ActiveSprite):
+ touchable = 1
+ special_prob = 0.2
+ shootcls = None
+ vx = 2
+ vy = 0
+ vdir = -1
+ is_ghost = 0
+ MonsterBonus = bonuses.MonsterBonus
+
+ def __init__(self, mnstrdef, x=None, y=None, dir=None, in_list=None):
+ self.mdef = mnstrdef
+ self.ptag = None
+ if dir is None: dir = mnstrdef.dir
+ if x is None: x = mnstrdef.x*CELL
+ if y is None: y = mnstrdef.y*CELL
+ self.dir = dir
+ ActiveSprite.__init__(self, images.sprget(self.imgrange()[0]), x, y)
+ self.gen.append(self.waiting())
+ if in_list is None:
+ in_list = BubPlayer.MonsterList
+ self.in_list = in_list
+ self.in_list.append(self)
+ self.no_shoot_before = 0
+ #images.ActiveSprites.remove(self)
+
+ def unlist(self):
+ try:
+ self.in_list.remove(self)
+ return 1
+ except ValueError:
+ return 0
+
+ def kill(self):
+ self.unlist()
+ ActiveSprite.kill(self)
+
+ def tagdragon(self):
+ lst = bonuses.getvisibledragonlist()
+ if lst:
+ return random.choice(lst)
+ else:
+ return None
+
+ def imgrange(self):
+ if self.is_ghost:
+ if self.dir > 0:
+ return Ghost.right
+ else:
+ return Ghost.left
+ elif self.angry:
+ if self.dir > 0:
+ return self.mdef.right_angry
+ else:
+ return self.mdef.left_angry
+ else:
+ if self.dir > 0:
+ return self.mdef.right
+ else:
+ return self.mdef.left
+
+ def imgrange1(self):
+ # normally this is self.imgrange()[1]
+ lst = self.imgrange()
+ return lst[len(lst) > 1]
+
+ def resetimages(self, is_ghost=0):
+ self.is_ghost = is_ghost
+ if self.gen:
+ self.setimages(self.cyclic(self.imgrange(), 3))
+ else: # frozen monster
+ self.seticon(images.sprget(self.imgrange()[0]))
+
+ def blocked(self):
+ if self.dir < 0:
+ x0 = (self.x-1)//16
+ else:
+ x0 = (self.x+33)//16
+ y0 = self.y // 16 + 1
+ y1 = (self.y + 31) // 16
+ return bget(x0,y0) == '#' or bget(x0,y1) == '#'
+
+ def tryhstep(self):
+ if self.blocked():
+ self.dir = -self.dir
+ self.resetimages()
+ return 0
+ else:
+ self.step(self.vx*self.dir, 0)
+ return 1
+
+ def vblocked(self):
+ if self.vdir < 0:
+ y0 = self.y//16
+ else:
+ y0 = (self.y+1)//16 + 2
+ x0 = self.x // 16
+ x1 = self.x // 16 + 1
+ x2 = (self.x+31) // 16
+ return bget(x0,y0) == '#' or bget(x1,y0) == '#' or bget(x2,y0) == '#'
+
+ def tryvstep(self):
+ if self.vblocked():
+ self.vdir = -self.vdir
+ return 0
+ else:
+ self.step(0, self.vy*self.vdir)
+ self.vertical_warp()
+ return 1
+
+ def waiting(self, delay=20):
+ for i in range(delay):
+ yield None
+ self.resetimages()
+ self.gen.append(self.default_mode())
+
+ def overlapping(self):
+ if self.in_list is BubPlayer.MonsterList:
+ for s in self.in_list:
+ if (-6 <= s.x-self.x <= 6 and -6 <= s.y-self.y < 6 and
+ #s.dir == self.dir and s.vdir == self.vdir and
+ s.vx == self.vx and s.vy == self.vy and
+ (not s.angry) == (not self.angry)):
+ return s is not self
+ return 0
+
+ def walking(self):
+ while onground(self.x, self.y):
+ yield None
+ if random.random() < 0.2 and self.overlapping():
+ yield None
+ x1 = self.x
+ if self.dir > 0:
+ x1 += self.vx
+ if (x1 & 15) < self.vx and random.random() < self.special_prob:
+ self.move(x1 & -16, self.y)
+ if self.special():
+ return
+ self.tryhstep()
+ if self.seedragon():
+ self.gen.append(self.hjumping())
+ else:
+ self.gen.append(self.falling())
+
+ def seedragon(self, dragon=None):
+ dragon = dragon or self.tagdragon()
+ if dragon is None:
+ return False
+ return abs(dragon.y - self.y) < 16 and self.dir*(dragon.x-self.x) > 0
+
+ def special(self):
+ dragon = self.tagdragon()
+ if dragon is None:
+ return 0
+ if self.seedragon(dragon) and self.shoot():
+ return 1
+ if dragon.y < self.y-CELL:
+ #and abs(dragon.x-self.x) < 2*(self.y-dragon.y):
+ for testy in range(self.y-2*CELL, self.y-6*CELL, -CELL):
+ if onground(self.x, testy):
+ if random.random() < 0.5:
+ ndir = self.dir
+ elif dragon.x < self.x:
+ ndir = -1
+ else:
+ ndir = 1
+ self.gen.append(self.vjumping(testy, ndir))
+ return 1
+ return 0
+
+ def shooting(self, pause):
+ for i in range(pause):
+ yield None
+ self.shootcls(self)
+ yield None
+ self.gen.append(self.default_mode())
+
+ def shoot(self, pause=10):
+ if (self.shootcls is None or
+ self.no_shoot_before > BubPlayer.FrameCounter):
+ return 0
+ else:
+ self.gen.append(self.shooting(pause))
+ self.no_shoot_before = BubPlayer.FrameCounter + 29
+ return 1
+
+ def falling(self):
+ bubber = getattr(self, 'bubber', None)
+ while not onground(self.x, self.y):
+ yield None
+ ny = self.y + 3
+ if (ny & 15) > 14:
+ ny = (ny//16+1)*16
+ elif (ny & 15) < 3:
+ ny = (ny//16)*16
+ nx = self.x
+ if nx < 32:
+ nx += 1 + (self.vx-1) * (bubber is not None)
+ elif nx > boards.bwidth - 64:
+ nx -= 1 + (self.vx-1) * (bubber is not None)
+ elif bubber:
+ dx = bubber.wannago(self.dcap)
+ if dx and dx != self.dir:
+ self.dir = dx
+ self.resetimages()
+ self.setimages(None)
+ if dx and not self.blocked():
+ nx += self.vx*dx
+ self.seticon(images.sprget(self.imgrange1()))
+ self.move(nx, ny)
+ if self.y >= boards.bheight:
+ self.vertical_warp()
+ if bubber:
+ nextgen = self.playing_monster
+ else:
+ nextgen = self.walking
+ self.gen.append(nextgen())
+
+## def moebius(self):
+## self.dir = -self.dir
+## self.resetimages()
+## if hasattr(self, 'dcap'):
+## self.dcap['left2right'] *= -1
+
+ def hjumping(self):
+ y0 = self.y
+ vspeed = -2.2
+ ny = y0-1
+ while ny <= y0 and not self.blocked():
+ self.move(self.x+2*self.dir, int(ny))
+ yield None
+ vspeed += 0.19
+ ny = self.y + vspeed
+ self.gen.append(self.default_mode())
+
+ def vjumping(self, limity, ndir):
+ self.setimages(None)
+ yield None
+ self.dir = -self.dir
+ self.seticon(images.sprget(self.imgrange()[0]))
+ for i in range(9):
+ yield None
+ self.dir = -self.dir
+ self.seticon(images.sprget(self.imgrange()[0]))
+ for i in range(4):
+ yield None
+ self.dir = ndir
+ self.seticon(images.sprget(self.imgrange1()))
+ for ny in range(self.y-4, limity-4, -4):
+ self.move(self.x, ny)
+ if ny < -32:
+ self.vertical_warp()
+ yield None
+ self.resetimages()
+ self.gen.append(self.default_mode())
+
+ def regular(self):
+ return self.still_playing() and self.touchable and not self.is_ghost
+
+ def still_playing(self):
+ return (self.in_list is BubPlayer.MonsterList and
+ self in self.in_list)
+
+ def touched(self, dragon):
+ if self.gen:
+ self.killdragon(dragon)
+ if self.is_ghost and not hasattr(self, 'bubber'):
+ self.gen = [self.default_mode()]
+ self.resetimages()
+ else:
+ self.argh(getattr(self, 'poplist', None)) # frozen monster
+
+ def killdragon(self, dragon):
+ dragon.die()
+
+ def in_bubble(self, bubble):
+ if not hasattr(self.mdef, 'jailed'):
+ return
+ self.untouchable()
+ self.angry = []
+ bubble.move(self.x, self.y)
+ if not hasattr(bubble, 'withmonster'):
+ bubble.to_front()
+ self.to_front()
+ img = self.mdef.jailed
+ self.gen = [self.bubbling(bubble)]
+ self.setimages(self.cyclic([img[1], img[2], img[1], img[0]], 4))
+
+ def bubbling(self, bubble):
+ counter = 0
+ while not hasattr(bubble, 'poplist'):
+ self.move(bubble.x, bubble.y)
+ yield None
+ counter += 1
+ if counter == 50 and hasattr(self, 'bubber'):
+ bubble.setimages(bubble.bubble_red())
+ if bubble.poplist is None:
+ self.touchable = 1
+ self.angry = [self.genangry()]
+ self.resetimages()
+ self.gen.append(self.default_mode())
+ else:
+ previous_len = len(BubPlayer.MonsterList)
+ self.argh(bubble.poplist)
+ dragon = bubble.poplist[0]
+ if dragon is not None:
+ if previous_len and not BubPlayer.MonsterList:
+ points = 990
+ else:
+ points = 90
+ dragon.bubber.givepoints(points)
+
+ def argh(self, poplist=None, onplace=0):
+ if self not in self.in_list:
+ return
+ if not poplist:
+ poplist = [None]
+ poplist.append(self)
+ level = len(poplist) - 2
+ bonuses.BonusMaker(self.x, self.y, self.mdef.dead, onplace=onplace,
+ outcome=(self.MonsterBonus, level))
+ self.kill()
+
+ def freeze(self, poplist):
+ # don't freeze monsters largely out of screen, or they'll never come in
+ if self.regular() and -self.ico.h < self.y < boards.bheight:
+ self.gen = []
+ self.poplist = poplist
+
+ def flying(self):
+ blocked = 0
+ while 1:
+ if random.random() < 0.2 and self.overlapping():
+ yield None
+ hstep = self.tryhstep()
+ vstep = self.tryvstep()
+ if hstep or vstep:
+ blocked = 0
+ elif blocked:
+ # blocked! go up or back to the play area
+ if self.x < 32:
+ self.step(self.vy, 0)
+ elif self.x > boards.bwidth - 64:
+ self.step(-self.vy, 0)
+ else:
+ self.step(0, -self.vy)
+ self.vertical_warp()
+ else:
+ blocked = 1
+ yield None
+
+ def becoming_monster(self, big=0, immed=0):
+ if big:
+ self.is_ghost = 1
+ self.seticon(images.sprget(self.imgrange()[0]))
+ images.Snd.Hell.play()
+ for i in range(5):
+ ico = self.ico
+ self.seticon(self.bubber.icons[11 + immed, self.dir])
+ yield None
+ yield None
+ self.seticon(ico)
+ yield None
+ yield None
+ self.resetimages(is_ghost=big)
+ self.gen.append(self.playing_monster())
+
+ def become_monster(self, bubber, saved_caps, big=0, immed=0):
+ self.timeoutgen = self.back_to_dragon()
+ self.default_mode = self.playing_monster
+ self.bubber = bubber
+ self.dcap = saved_caps
+ self.gen = [self.becoming_monster(big, immed)]
+
+ def back_to_dragon(self):
+ for t in range(259):
+ yield None
+ if bonuses.getdragonlist():
+ yield None
+ yield None
+ yield None
+ from player import Dragon
+ d = Dragon(self.bubber, self.x, self.y, self.dir, self.dcap)
+ d.dcap['shield'] = 50
+ self.bubber.dragons.append(d)
+ self.kill()
+
+ def playing_monster(self):
+ if self.timeoutgen not in self.gen:
+ self.gen.append(self.timeoutgen)
+ bubber = self.bubber
+ while self.is_ghost:
+ # ghost
+ self.angry = []
+ key, dx, dy = max([(bubber.key_left, -1, 0),
+ (bubber.key_right, 1, 0),
+ (bubber.key_jump, 0, -1),
+ (bubber.key_fire, 0, 1)])
+ if key:
+ if dx and self.dir != dx:
+ self.dir = dx
+ self.resetimages(is_ghost=1)
+ nx = self.x + 10*dx
+ ny = self.y + 9*dy
+ if nx < 0: nx = 0
+ if nx > boards.bwidth-2*CELL: nx = boards.bwidth-2*CELL
+ if ny < -CELL: ny = -CELL
+ if ny > boards.bheight-CELL: ny = boards.bheight-CELL
+ self.move(nx, ny)
+ yield None
+ if self.vy:
+ # flying monster
+ while 1:
+ dx = bubber.wannago(self.dcap)
+ if dx and dx != self.dir:
+ self.dir = dx
+ self.resetimages()
+ if bubber.key_jump and bubber.key_jump > bubber.key_fire:
+ dy = self.vdir = -1
+ elif bubber.key_fire:
+ dy = self.vdir = 1
+ else:
+ dy = 0
+ hstep = dx and self.tryhstep()
+ vstep = dy and self.tryvstep()
+ if dx and dy and not (hstep or vstep):
+ # blocked?
+ self.dir = -self.dir
+ self.vdir = -self.vdir
+ blocked = self.blocked() and self.vblocked()
+ self.dir = -self.dir
+ self.vdir = -self.vdir
+ if blocked:
+ # completely blocked! accept move or force back to
+ # play area
+ if self.x < 32:
+ self.step(self.vy, 0)
+ elif self.x > boards.bwidth - 64:
+ self.step(-self.vy, 0)
+ else:
+ self.step(self.vx*dx, self.vy*dy)
+ self.vertical_warp()
+ yield None
+ elif not isinstance(self, Springy):
+ # walking monster
+ jumping_y = 0
+ imgsetter = self.imgsetter
+ while onground(self.x, self.y) or jumping_y:
+ dx = bubber.wannago(self.dcap)
+ if dx and dx != self.dir:
+ self.dir = dx
+ self.resetimages()
+ imgsetter = self.imgsetter
+ if dx and not self.blocked():
+ self.step(self.vx*dx, 0)
+ if not jumping_y:
+ self.setimages(imgsetter)
+ else:
+ self.seticon(images.sprget(self.imgrange1()))
+ self.setimages(None)
+ else:
+ self.setimages(None)
+ dx = 0
+ yield None
+ if not jumping_y:
+ wannafire = bubber.key_fire
+ wannajump = bubber.key_jump
+ if wannafire and self.shoot(1):
+ return
+ if wannajump:
+ jumping_y = CELL
+ if jumping_y:
+ self.step(0, -4)
+ if self.y < -32:
+ self.vertical_warp()
+ if onground(self.x, self.y):
+ jumping_y = 0
+ else:
+ jumping_y -= 1
+ self.gen.append(self.falling())
+ else:
+ # springy
+ if not onground(self.x, self.y):
+ self.gen.append(self.falling())
+ return
+ prevx = self.x
+ for t in self.walking():
+ dx = bubber.wannago(self.dcap)
+ if dx:
+ if dx != self.dir:
+ self.dir = dx
+ self.resetimages()
+ if self.blocked() and (self.x-prevx)*dx <= 0:
+ dx = 0
+ self.move(prevx + self.vx*dx, self.y)
+ yield None
+ prevx = self.x
+
+ def become_ghost(self):
+ self.gen = [self.ghosting()]
+ self.resetimages(is_ghost=1)
+
+ def ghosting(self):
+ counter = 0
+ while counter < 5:
+ for i in range(50):
+ yield None
+ dragon = self.tagdragon()
+ if dragon is None:
+ counter += 1
+ else:
+ counter = 0
+ px, py = dragon.x, dragon.y
+ if abs(px-self.x) < abs(py-self.y):
+ dx = 0
+ if py > self.y:
+ dy = 1
+ else:
+ dy = -1
+ else:
+ dy = 0
+ if px > self.x:
+ dx = 1
+ else:
+ dx = -1
+ self.dir = dx
+ self.resetimages(is_ghost=1)
+ dx *= 10
+ dy *= 9
+ distance = 1E10
+ while 1:
+ self.angry = []
+ self.step(dx, dy)
+ yield None
+ dist1 = (px-self.x)*(px-self.x)+(py-self.y)*(py-self.y)
+ if dist1 > distance:
+ break
+ distance = dist1
+ self.angry = []
+ self.gen = [self.default_mode()]
+ self.resetimages()
+
+ default_mode = falling
+
+
+def argh_em_all():
+ poplist = [None]
+ for s in images.ActiveSprites[:]:
+ if isinstance(s, Monster):
+ s.argh(poplist)
+
+def freeze_em_all():
+ poplist = [None]
+ for s in images.ActiveSprites:
+ if isinstance(s, Monster):
+ s.freeze(poplist)
+
+
+class MonsterShot(ActiveSprite):
+ speed = 6
+ touchable = 1
+
+ def __init__(self, owner, dx=CELL, dy=0):
+ self.owner = owner
+ self.speed = owner.dir * self.speed
+ if owner.dir < 0:
+ nimages = owner.mdef.left_weapon
+ else:
+ nimages = owner.mdef.right_weapon
+ ActiveSprite.__init__(self, images.sprget(nimages[0]),
+ owner.x, owner.y + dy)
+ self.step((owner.ico.w - self.ico.w) // 2,
+ (owner.ico.h - self.ico.h) // 2)
+ if not self.blocked():
+ self.step(dx*owner.dir, 0)
+ if len(nimages) > 1:
+ self.setimages(self.cyclic(nimages, 3))
+ self.gen.append(self.moving())
+
+ def blocked(self):
+ if self.speed < 0:
+ x0 = (self.x-self.speed-8)//16
+ else:
+ x0 = (self.x+self.ico.w+self.speed-8)//16
+ y0 = (self.y+8) // 16 + 1
+ return not (' ' == bget(x0,y0) == bget(x0+1,y0))
+
+ def moving(self):
+ while not self.blocked():
+ yield None
+ self.step(self.speed, 0)
+ self.hitwall()
+
+ def hitwall(self):
+ self.untouchable()
+ self.gen.append(self.die(self.owner.mdef.decay_weapon, 2))
+
+ def touched(self, dragon):
+ dragon.die()
+
+
+class BoomerangShot(MonsterShot):
+ speed = 8
+
+ def hitwall(self):
+ self.gen.append(self.moveback())
+
+ def moveback(self):
+ owner = self.owner
+ if self.speed > 0:
+ nimages = owner.mdef.left_weapon
+ else:
+ nimages = owner.mdef.right_weapon
+ self.setimages(self.cyclic(nimages, 3))
+ while (owner.x-self.x) * self.speed < 0:
+ yield None
+ self.step(-self.speed, 0)
+ if self.blocked():
+ break
+ self.kill()
+
+class FastShot(MonsterShot):
+ speed = 15
+
+
+class DownShot(MonsterShot):
+
+ def __init__(self, owner):
+ MonsterShot.__init__(self, owner, 0, CELL)
+
+ def moving(self):
+ while self.y < boards.bheight:
+ yield None
+ self.step(0, 7)
+ self.kill()
+
+
+##class DragonShot(MonsterShot):
+## speed = 8
+
+## def __init__(self, owner):
+## MonsterShot.__init__(self, owner)
+## self.untouchable()
+## self.gen.append(self.touchdelay(4))
+
+## def touched(self, dragon):
+## if dragon is not self.owner:
+## if dragon.bubber.bonbons == 0:
+## dragon.die()
+## else:
+## from player import scoreboard
+## from bonuses import Sugar1, Sugar2
+## from bonuses import BonusMaker
+## if random.random() < 0.2345:
+## start = 1
+## else:
+## start = 0
+## loose = min(2, dragon.bubber.bonbons)
+## for i in range(start, loose):
+## cls = random.choice([Sugar1, Sugar2])
+## BonusMaker(self.x, self.y, [cls.nimage],
+## outcome=(cls,))
+## dragon.bubber.bonbons -= loose
+## scoreboard()
+## dragon.dcap['shield'] = 25
+## self.owner.play(images.Snd.Yippee)
+## self.kill()
+
+## def blocked(self):
+## return self.x < -self.ico.w or self.x >= gamesrv.game.width
+## #return self.x < CELL or self.x >= boards.bwidth - 3*CELL
+
+
+class Nasty(Monster):
+ pass
+
+class Monky(Monster):
+ shootcls = MonsterShot
+
+class Ghosty(Monster):
+ default_mode = Monster.flying
+ vy = 2
+
+class Flappy(Monster):
+ default_mode = Monster.flying
+ vy = 1
+
+class Springy(Monster):
+ spring_down = 0
+ def imgrange(self):
+ if self.spring_down and not self.is_ghost:
+ if self.angry:
+ if self.dir > 0:
+ r = self.mdef.right_jump_angry
+ else:
+ r = self.mdef.left_jump_angry
+ else:
+ if self.dir > 0:
+ r = self.mdef.right_jump
+ else:
+ r = self.mdef.left_jump
+ return [r[self.spring_down-1]]
+ else:
+ return Monster.imgrange(self)
+ def walking(self):
+ self.spring_down = 1
+ self.resetimages()
+ for t in range(2+self.overlapping()):
+ yield None
+ self.spring_down = 2
+ self.resetimages()
+ for t in range(4+2*self.overlapping()):
+ yield None
+ self.spring_down = 1
+ self.resetimages()
+ for t in range(2+self.overlapping()):
+ yield None
+ self.spring_down = 0
+ self.resetimages()
+ g = 10.0/43
+ vy = -20*g
+ yf = self.y
+ for t in range(40):
+ yf += vy
+ vy += g
+ if self.blocked():
+ self.dir = -self.dir
+ self.resetimages()
+ nx = self.x + self.dir*self.vx
+ if self.y//16 < int(yf)//16:
+ if onground(self.x, (self.y//16+1)*16):
+ break
+ if onground(nx, (self.y//16+1)*16):
+ self.move(nx, self.y)
+ break
+ nx, yf = vertical_warp(nx, yf)
+ self.move(nx, int(yf))
+## if moebius:
+## self.moebius()
+ yield None
+ self.gen.append(self.falling())
+
+class Orcy(Monster):
+ shootcls = FastShot
+
+class Gramy(Monster):
+ shootcls = BoomerangShot
+ vx = 3
+
+class Blitzy(Monster):
+ shootcls = DownShot
+ vx = 3
+
+ def seedragon(self, dragon=None):
+ return 0
+
+ def special(self):
+ if random.random() < 0.3:
+ self.shootcls(self)
+ return 0
+
+ def shoot(self, pause=0):
+ # no pause (only used when controlled by the player)
+ if self.no_shoot_before > BubPlayer.FrameCounter:
+ pass
+ else:
+ self.shootcls(self)
+ self.no_shoot_before = BubPlayer.FrameCounter + 29
+ return 0
+
+MonsterClasses = [c for c in globals().values()
+ if type(c)==type(Monster) and issubclass(c, Monster)]
+MonsterClasses.remove(Monster)
+
+
+class Butterfly(Monster):
+ MonsterBonus = bonuses.IceMonsterBonus
+ fly_away = False
+
+ def waiting(self, delay=0):
+ return Monster.waiting(self, delay)
+
+ def imgrange(self):
+ self.angry = []
+ return Monster.imgrange(self)
+
+ def killdragon(self, dragon):
+ if self.is_ghost:
+ Monster.killdragon(self, dragon)
+ else:
+ self.fly_away = True, dragon.x
+
+ def flying(self):
+ repeat = 0
+ while 1:
+ r = random.random()
+ if self.x < 64:
+ bump = self.dir < 0
+ elif self.x > boards.bwidth - 64:
+ bump = self.dir > 0
+ elif self.fly_away:
+ wannago = self.x - self.fly_away[1]
+ if self.x < 100:
+ wannago = 1
+ elif self.x > boards.bwidth - 100:
+ wannago = -1
+ bump = self.dir * wannago < 0
+ if repeat:
+ self.fly_away = False
+ repeat = 0
+ else:
+ repeat = 1
+ else:
+ bump = r < 0.07
+ if bump:
+ self.dir = -self.dir
+ self.resetimages()
+ elif r > 0.92:
+ self.vdir = -self.vdir
+ self.step(self.dir * (2 + (r < 0.5)), self.vdir * 2)
+ self.vertical_warp()
+ if not repeat:
+ yield None
+
+ default_mode = flying
+
+
+class Sheep(Monster):
+
+ def playing_monster(self):
+ from bonuses import Bonus
+ bubber = self.bubber
+ vy = None
+ imgsetter = self.imgsetter
+ poplist = [None]
+ while 1:
+ dx = bubber.wannago(self.dcap)
+ if dx and dx != self.dir:
+ self.dir = dx
+ self.resetimages()
+ imgsetter = self.imgsetter
+ if dx and vy is None:
+ self.setimages(imgsetter)
+ else:
+ self.setimages(None)
+ if vy is not None:
+ if vy < 0:
+ n = 1
+ else:
+ n = 3
+ self.seticon(images.sprget(self.imgrange()[n]))
+ if dx and not self.blocked():
+ self.step(self.vx*dx, 0)
+ yield None
+ impulse = 0.0
+ wannajump = bubber.key_jump
+ if vy is not None:
+ vy += 0.33
+ if vy > 12.0:
+ vy = 12.0
+ yf = self.y + yfp + vy
+ yfp = yf - int(yf)
+ delta = int(yf) - self.y
+ if delta > 0:
+ by_y = {}
+ for s in images.ActiveSprites:
+ if isinstance(s, Bonus) and s.touchable:
+ if abs(s.x - self.x) <= 22:
+ by_y[s.y] = s
+ for monster in BubPlayer.MonsterList:
+ if abs(monster.x - self.x) <= 22:
+ if monster.regular():
+ by_y[monster.y] = monster
+ for ny in range(self.y - 1, self.y + delta + 1):
+ self.move(self.x, ny)
+ self.vertical_warp()
+ if onground(self.x, self.y):
+ poplist = [None]
+ impulse = vy
+ vy = None
+ break
+ key = self.y + 29
+ if key in by_y:
+ s = by_y[key]
+ if isinstance(s, Monster):
+ self.play(images.Snd.Extra)
+ s.argh(poplist)
+ elif isinstance(s, Bonus):
+ s.reallytouched(self)
+ yfp = 0.0
+ vy = -3.3
+ break
+ else:
+ self.step(0, delta)
+ self.vertical_warp()
+ if vy is None:
+ if onground(self.x, self.y):
+ if wannajump:
+ yfp = 0.0
+ vy = - max(1.0, impulse) - 2.02
+ impulse = 0.0
+ self.play(images.Snd.Jump)
+ else:
+ yfp = vy = 0.0
+ if impulse > 8.1:
+ break
+ self.play(images.Snd.Pop)
+ for n in range(2):
+ for letter in 'abcdefg':
+ ico = images.sprget(('sheep', letter))
+ nx = self.x + random.randrange(-1, self.ico.w - ico.w + 2)
+ ny = self.y + random.randrange(0, self.ico.h - ico.h + 2)
+ dxy = [random.random() * 5.3 - 2.65, random.random() * 4 - 4.4]
+ s = images.ActiveSprite(ico, nx, ny)
+ s.gen.append(s.parabolic(dxy))
+ s.gen.append(s.die([None], random.randrange(35, 54)))
+ self.move(-99, 0)
+ for t in range(68):
+ yield None
+ self.kill()
+
+ default_mode = falling = playing_monster
+
+ def argh(self, *args, **kwds):
+ pass
diff --git a/bubbob/music/Snd1-8.wav b/bubbob/music/Snd1-8.wav
new file mode 100644
index 0000000..f4962cf
--- /dev/null
+++ b/bubbob/music/Snd1-8.wav
Binary files differ
diff --git a/bubbob/music/Snd2-8.wav b/bubbob/music/Snd2-8.wav
new file mode 100644
index 0000000..c59f1e8
--- /dev/null
+++ b/bubbob/music/Snd2-8.wav
Binary files differ
diff --git a/bubbob/music/Snd3-8.wav b/bubbob/music/Snd3-8.wav
new file mode 100644
index 0000000..dda70f0
--- /dev/null
+++ b/bubbob/music/Snd3-8.wav
Binary files differ
diff --git a/bubbob/music/Snd4-8.wav b/bubbob/music/Snd4-8.wav
new file mode 100644
index 0000000..509afad
--- /dev/null
+++ b/bubbob/music/Snd4-8.wav
Binary files differ
diff --git a/bubbob/music/Snd5-8.wav b/bubbob/music/Snd5-8.wav
new file mode 100644
index 0000000..b4bfabc
--- /dev/null
+++ b/bubbob/music/Snd5-8.wav
Binary files differ
diff --git a/bubbob/music/Snd6-8.wav b/bubbob/music/Snd6-8.wav
new file mode 100644
index 0000000..0f2bad5
--- /dev/null
+++ b/bubbob/music/Snd6-8.wav
Binary files differ
diff --git a/bubbob/patmap.py b/bubbob/patmap.py
new file mode 100644
index 0000000..3b00252
--- /dev/null
+++ b/bubbob/patmap.py
@@ -0,0 +1,177 @@
+patmap = {(0, 0, 0): ('pat03.ppm', (0, 120, 24, 24)),
+ (1, 0, 0): ('pat01.ppm', (0, 24, 24, 24)),
+ (2, 0, 0): ('pat09.ppm', (0, 0, 24, 24)),
+ (2, 'l'): ('pat14.ppm', (0, 160, 40, 32)),
+ (3, 0, 0): ('pat06.ppm', (0, 120, 24, 24)),
+ (3, 'l'): ('pat17.ppm', (0, 32, 40, 32)),
+ (4, 0, 0): ('pat04.ppm', (0, 48, 24, 24)),
+ (4, 'l'): ('pat15.ppm', (0, 192, 40, 32)),
+ (5, 0, 0): ('pat01.ppm', (0, 216, 24, 24)),
+ (5, 'l'): ('pat19.ppm', (0, 96, 40, 32)),
+ (6, 0, 0): ('pat09.ppm', (0, 144, 24, 24)),
+ (6, 'l'): ('pat18.ppm', (0, 96, 40, 32)),
+ (7, 0, 0): ('pat07.ppm', (0, 24, 24, 24)),
+ (8, 0, 0): ('pat02.ppm', (0, 48, 24, 24)),
+ (9, 0, 0): ('pat09.ppm', (0, 216, 24, 24)),
+ (9, 'l'): ('pat11.ppm', (0, 64, 40, 32)),
+ (10, 0, 0): ('pat07.ppm', (0, 96, 24, 24)),
+ (10, 'l'): ('pat14.ppm', (0, 96, 40, 32)),
+ (11, 0, 0): ('pat05.ppm', (0, 24, 24, 24)),
+ (11, 'l'): ('pat13.ppm', (0, 32, 40, 32)),
+ (12, 0, 0): ('pat02.ppm', (0, 216, 24, 24)),
+ (12, 'l'): ('pat18.ppm', (0, 0, 40, 32)),
+ (13, 0, 0): ('pat00.ppm', (0, 72, 24, 24)),
+ (13, 'l'): ('pat18.ppm', (0, 128, 40, 32)),
+ (14, 0, 0): ('pat07.ppm', (0, 192, 24, 24)),
+ (15, 0, 0): ('pat05.ppm', (0, 192, 24, 24)),
+ (15, 'l'): ('pat18.ppm', (0, 32, 40, 32)),
+ (16, 0, 0): ('pat06.ppm', (0, 96, 24, 24)),
+ (16, 'l'): ('pat11.ppm', (0, 32, 40, 32)),
+ (17, 0, 0): ('pat04.ppm', (0, 0, 24, 24)),
+ (17, 'l'): ('pat14.ppm', (0, 64, 40, 32)),
+ (18, 0, 0): ('pat01.ppm', (0, 168, 24, 24)),
+ (18, 'l'): ('pat13.ppm', (0, 0, 40, 32)),
+ (19, 0, 0): ('pat09.ppm', (0, 96, 24, 24)),
+ (19, 'l'): ('pat16.ppm', (0, 192, 40, 32)),
+ (20, 0, 0): ('pat07.ppm', (0, 0, 24, 24)),
+ (20, 'l'): ('pat15.ppm', (0, 160, 40, 32)),
+ (21, 0, 0): ('pat04.ppm', (0, 192, 24, 24)),
+ (21, 'l'): ('pat19.ppm', (0, 64, 40, 32)),
+ (22, 0, 0): ('pat02.ppm', (0, 120, 24, 24)),
+ (22, 'l'): ('pat17.ppm', (0, 224, 40, 32)),
+ (23, 0, 0): ('pat10.ppm', (0, 24, 24, 24)),
+ (23, 'l'): ('pat12.ppm', (0, 64, 40, 32)),
+ (24, 0, 0): ('pat05.ppm', (0, 0, 24, 24)),
+ (24, 'l'): ('pat17.ppm', (0, 96, 40, 32)),
+ (25, 0, 0): ('pat02.ppm', (0, 168, 24, 24)),
+ (25, 'l'): ('pat15.ppm', (0, 64, 40, 32)),
+ (26, 0, 0): ('pat00.ppm', (0, 24, 24, 24)),
+ (26, 0, 1): ('pat00.ppm', (0, 0, 24, 24)),
+ (26, 1, 0): ('pat01.ppm', (0, 120, 24, 24)),
+ (26, 1, 1): ('pat08.ppm', (0, 144, 24, 24)),
+ (26, 'l'): ('pat13.ppm', (0, 224, 40, 32)),
+ (27, 0, 0): ('pat07.ppm', (0, 216, 24, 24)),
+ (27, 'l'): ('pat17.ppm', (0, 64, 40, 32)),
+ (28, 0, 0): ('pat05.ppm', (0, 168, 24, 24)),
+ (28, 'l'): ('pat15.ppm', (0, 224, 40, 32)),
+ (29, 0, 0): ('pat03.ppm', (0, 72, 24, 24)),
+ (30, 0, 0): ('pat00.ppm', (0, 216, 24, 24)),
+ (30, 'l'): ('pat17.ppm', (0, 160, 40, 32)),
+ (31, 0, 0): ('pat08.ppm', (0, 168, 24, 24)),
+ (31, 'l'): ('pat12.ppm', (0, 32, 40, 32)),
+ (32, 0, 0): ('pat08.ppm', (0, 0, 24, 24)),
+ (33, 0, 0): ('pat05.ppm', (0, 144, 24, 24)),
+ (33, 'l'): ('pat12.ppm', (0, 192, 40, 32)),
+ (34, 0, 0): ('pat03.ppm', (0, 48, 24, 24)),
+ (35, 0, 0): ('pat00.ppm', (0, 168, 24, 24)),
+ (35, 'l'): ('pat14.ppm', (0, 128, 40, 32)),
+ (36, 0, 0): ('pat07.ppm', (0, 120, 24, 24)),
+ (36, 'l'): ('pat16.ppm', (0, 96, 40, 32)),
+ (37, 0, 0): ('pat06.ppm', (0, 72, 24, 24)),
+ (38, 0, 0): ('pat03.ppm', (0, 216, 24, 24)),
+ (38, 'l'): ('pat11.ppm', (0, 128, 40, 32)),
+ (39, 0, 0): ('pat01.ppm', (0, 96, 24, 24)),
+ (40, 0, 0): ('pat08.ppm', (0, 48, 24, 24)),
+ (41, 0, 0): ('pat04.ppm', (0, 24, 24, 24)),
+ (41, 'l'): ('pat12.ppm', (0, 160, 40, 32)),
+ (42, 0, 0): ('pat01.ppm', (0, 192, 24, 24)),
+ (43, 0, 0): ('pat05.ppm', (0, 120, 24, 24)),
+ (43, 1, 0): ('pat08.ppm', (0, 72, 24, 24)),
+ (44, 0, 0): ('pat07.ppm', (0, 48, 24, 24)),
+ (45, 0, 0): ('pat04.ppm', (0, 216, 24, 24)),
+ (45, 'l'): ('pat16.ppm', (0, 224, 40, 32)),
+ (46, 0, 0): ('pat02.ppm', (0, 144, 24, 24)),
+ (46, 'l'): ('pat16.ppm', (0, 64, 40, 32)),
+ (47, 0, 0): ('pat10.ppm', (0, 72, 24, 24)),
+ (47, 'l'): ('pat19.ppm', (0, 224, 40, 32)),
+ (48, 0, 0): ('pat00.ppm', (0, 120, 24, 24)),
+ (48, 'l'): ('pat12.ppm', (0, 128, 40, 32)),
+ (49, 0, 0): ('pat08.ppm', (0, 96, 24, 24)),
+ (49, 'l'): ('pat11.ppm', (0, 192, 40, 32)),
+ (50, 0, 0): ('pat06.ppm', (0, 24, 24, 24)),
+ (50, 'l'): ('pat16.ppm', (0, 128, 40, 32)),
+ (51, 0, 0): ('pat03.ppm', (0, 144, 24, 24)),
+ (51, 'l'): ('pat14.ppm', (0, 0, 40, 32)),
+ (52, 0, 0): ('pat01.ppm', (0, 72, 24, 24)),
+ (52, 'l'): ('pat15.ppm', (0, 0, 40, 32)),
+ (53, 0, 0): ('pat09.ppm', (0, 48, 24, 24)),
+ (53, 'l'): ('pat16.ppm', (0, 32, 40, 32)),
+ (54, 0, 0): ('pat06.ppm', (0, 192, 24, 24)),
+ (54, 'l'): ('pat19.ppm', (0, 192, 40, 32)),
+ (55, 0, 0): ('pat04.ppm', (0, 96, 24, 24)),
+ (55, 'l'): ('pat18.ppm', (0, 160, 40, 32)),
+ (56, 0, 0): ('pat09.ppm', (0, 120, 24, 24)),
+ (56, 'l'): ('pat18.ppm', (0, 64, 40, 32)),
+ (57, 0, 0): ('pat00.ppm', (0, 144, 24, 24)),
+ (57, 'l'): ('pat12.ppm', (0, 96, 40, 32)),
+ (58, 0, 0): ('pat04.ppm', (0, 168, 24, 24)),
+ (59, 0, 0): ('pat02.ppm', (0, 72, 24, 24)),
+ (59, 'l'): ('pat14.ppm', (0, 192, 40, 32)),
+ (60, 0, 0): ('pat10.ppm', (0, 48, 24, 24)),
+ (60, 'l'): ('pat13.ppm', (0, 96, 40, 64)),
+ (61, 0, 0): ('pat07.ppm', (0, 168, 24, 24)),
+ (62, 0, 0): ('pat05.ppm', (0, 96, 24, 24)),
+ (62, 'l'): ('pat19.ppm', (0, 128, 40, 32)),
+ (63, 0, 0): ('pat03.ppm', (0, 0, 24, 24)),
+ (63, 'l'): ('pat20.ppm', (0, 0, 40, 32)),
+ (64, 0, 0): ('pat02.ppm', (0, 96, 24, 24)),
+ (64, 'l'): ('pat13.ppm', (0, 64, 40, 32)),
+ (65, 0, 0): ('pat10.ppm', (0, 0, 24, 24)),
+ (65, 'l'): ('pat19.ppm', (0, 32, 40, 32)),
+ (66, 0, 0): ('pat07.ppm', (0, 144, 24, 24)),
+ (66, 'l'): ('pat17.ppm', (0, 192, 40, 32)),
+ (67, 0, 0): ('pat05.ppm', (0, 72, 24, 24)),
+ (67, 'l'): ('pat11.ppm', (0, 224, 40, 32)),
+ (68, 0, 0): ('pat03.ppm', (0, 24, 24, 24)),
+ (68, 'l'): ('pat15.ppm', (0, 32, 40, 32)),
+ (69, 0, 0): ('pat00.ppm', (0, 96, 24, 24)),
+ (69, 'l'): ('pat13.ppm', (0, 192, 40, 32)),
+ (70, 0, 0): ('pat08.ppm', (0, 192, 24, 24)),
+ (70, 'l'): ('pat17.ppm', (0, 0, 40, 32)),
+ (71, 0, 0): ('pat06.ppm', (0, 0, 24, 24)),
+ (72, 0, 0): ('pat00.ppm', (0, 192, 24, 24)),
+ (72, 'l'): ('pat15.ppm', (0, 128, 40, 32)),
+ (73, 0, 0): ('pat08.ppm', (0, 120, 24, 24)),
+ (74, 0, 0): ('pat06.ppm', (0, 48, 24, 24)),
+ (75, 0, 0): ('pat03.ppm', (0, 192, 24, 24)),
+ (76, 0, 0): ('pat01.ppm', (0, 144, 24, 24)),
+ (76, 'l'): ('pat11.ppm', (0, 0, 40, 32)),
+ (77, 0, 0): ('pat09.ppm', (0, 72, 24, 24)),
+ (77, 'l'): ('pat14.ppm', (0, 32, 40, 32)),
+ (78, 0, 0): ('pat06.ppm', (0, 216, 24, 24)),
+ (78, 'l'): ('pat13.ppm', (0, 160, 40, 32)),
+ (79, 0, 0): ('pat04.ppm', (0, 144, 24, 24)),
+ (79, 'l'): ('pat16.ppm', (0, 160, 40, 32)),
+ (80, 0, 0): ('pat05.ppm', (0, 48, 24, 24)),
+ (81, 0, 0): ('pat02.ppm', (0, 192, 24, 24)),
+ (81, 'l'): ('pat17.ppm', (0, 128, 40, 32)),
+ (82, 0, 0): ('pat00.ppm', (0, 48, 24, 24)),
+ (82, 'l'): ('pat12.ppm', (0, 0, 40, 32)),
+ (83, 0, 0): ('pat08.ppm', (0, 24, 24, 24)),
+ (83, 'l'): ('pat11.ppm', (0, 96, 40, 32)),
+ (84, 0, 0): ('pat05.ppm', (0, 216, 24, 24)),
+ (84, 'l'): ('pat14.ppm', (0, 224, 40, 32)),
+ (84, 'r'): ('pat18.ppm', (0, 224, 40, 32)),
+ (85, 0, 0): ('pat03.ppm', (0, 96, 24, 24)),
+ (86, 0, 0): ('pat01.ppm', (0, 0, 24, 24)),
+ (87, 0, 0): ('pat08.ppm', (0, 216, 24, 24)),
+ (88, 0, 0): ('pat03.ppm', (0, 168, 24, 24)),
+ (88, 'l'): ('pat20.ppm', (0, 32, 40, 32)),
+ (89, 0, 0): ('pat01.ppm', (0, 48, 24, 24)),
+ (90, 0, 0): ('pat09.ppm', (0, 24, 24, 24)),
+ (90, 'l'): ('pat12.ppm', (0, 224, 40, 32)),
+ (91, 0, 0): ('pat06.ppm', (0, 144, 24, 24)),
+ (92, 0, 0): ('pat04.ppm', (0, 120, 24, 24)),
+ (92, 'l'): ('pat18.ppm', (0, 192, 40, 32)),
+ (93, 0, 0): ('pat02.ppm', (0, 24, 24, 24)),
+ (93, 'l'): ('pat11.ppm', (0, 160, 40, 32)),
+ (94, 0, 0): ('pat09.ppm', (0, 192, 24, 24)),
+ (94, 'l'): ('pat19.ppm', (0, 0, 40, 32)),
+ (95, 0, 0): ('pat07.ppm', (0, 72, 24, 24)),
+ (95, 'l'): ('pat15.ppm', (0, 96, 40, 32)),
+ (96, 0, 0): ('pat06.ppm', (0, 168, 24, 24)),
+ (97, 0, 0): ('pat04.ppm', (0, 72, 24, 24)),
+ (97, 'l'): ('pat16.ppm', (0, 0, 40, 32)),
+ (98, 0, 0): ('pat02.ppm', (0, 0, 24, 24)),
+ (98, 'l'): ('pat19.ppm', (0, 160, 40, 32)),
+ (99, 0, 0): ('pat09.ppm', (0, 168, 24, 24))}
diff --git a/bubbob/player.py b/bubbob/player.py
new file mode 100644
index 0000000..3a336a9
--- /dev/null
+++ b/bubbob/player.py
@@ -0,0 +1,1213 @@
+from __future__ import generators
+import random, math, time
+import gamesrv
+import images
+import boards
+import bubbles
+from boards import *
+from images import ActiveSprite
+from mnstrmap import GreenAndBlue, LetterBubbles, PlayerBubbles
+from mnstrmap import DigitsMisc
+
+KEEPALIVE = 5*60 # seconds
+CheatDontDie = 0
+
+
+class Dragon(ActiveSprite):
+ priority = 1
+ mdef = PlayerBubbles
+ fly_counter = 0
+## glueddown = None
+
+ DCAP = {
+ 'hspeed': 1,
+ 'firerate': 2,
+ 'shootthrust': 8.0,
+ 'infinite_shield': 1,
+ 'shield': 50,
+ 'gravity': 0.21,
+ 'bubbledelay': 0,
+ 'shootbubbles': (),
+ 'pinball': 0,
+## 'nojump': 0,
+ 'autofire': 0,
+ 'ring': 0,
+ 'hotstuff': 0,
+ 'left2right': 1,
+ 'slippy': 0,
+ 'vslippy': 0.0,
+ 'lookforward': 1,
+ 'fly': 0,
+ 'jumpdown': 0,
+ 'flower': 1,
+ 'bigflower': None,
+ 'overlayglasses': 0,
+ 'teleport': 0,
+ 'breakwalls': 0,
+ 'carrying': (),
+ 'key_left': 'key_left',
+ 'key_right': 'key_right',
+ 'key_jump': 'key_jump',
+ 'key_fire': 'key_fire',
+ }
+ SAVE_CAP = {'hspeed': 1,
+ 'firerate': 2,
+ 'shootthrust': 8.0 / 1.5,
+ 'flower': -1}
+
+ def __init__(self, bubber, x, y, dir, dcap=DCAP):
+ self.bubber = bubber
+ self.dir = dir
+ icobubber = dcap.get('bubbericons', bubber)
+ ActiveSprite.__init__(self, icobubber.icons[0, dir], x, y)
+ self.fire = 0
+ self.up = 0.0
+ self.watermoveable = 0
+ self.dcap = dcap.copy()
+ self.dcap.update(self.bubber.pcap)
+ BubPlayer.DragonList.append(self)
+ self.gen.append(self.normal_movements())
+ self.overlaysprite = None
+ self.overlayyoffset = 4
+ self.hatsprite = None
+ self.hatangle = 1
+ self.isdying = 0
+ self.lifegained = 0
+ self.playing_fish = False
+ if BubPlayer.SuperSheep:
+ self.become_monster('Sheep', immed=1)
+ elif BubPlayer.SuperFish:
+ self.become_fish()
+
+ def kill(self):
+ try:
+ BubPlayer.DragonList.remove(self)
+ except ValueError:
+ pass
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+ if self.hatsprite is not None:
+ if self.hatsprite.alive:
+ self.hatsprite.kill()
+ self.hatsprite = None
+
+ def die(self):
+ if (self in BubPlayer.DragonList and not self.dcap['shield']
+ and not CheatDontDie):
+ BubPlayer.DragonList.remove(self)
+ self.gen = [self.dying()]
+ self.play(images.Snd.Die)
+ #wasting = boards.curboard.wastingplay
+ #if wasting is not None:
+ # wasting[self.bubber] = len(wasting)
+
+ def dying(self, can_loose_letter=1):
+ self.isdying = 1
+ if self.hatsprite is not None:
+ if self.hatsprite.alive:
+ dxy = [3*self.dir, -7]
+ self.hatsprite.gen = [self.hatsprite.parabolic(dxy)]
+ self.hatsprite = None
+ lst = [bonus for timeout, bonus in self.dcap['carrying']
+ if hasattr(bonus, 'buildoutcome')]
+ #if random.random() > 0.2]
+ if lst:
+ # loose some bonuses
+ from bonuses import BonusMaker
+ for bonus in lst:
+ self.bubber.givepoints(-bonus.points)
+ BonusMaker(self.x, self.y, [bonus.nimage],
+ outcome=bonus.buildoutcome())
+ elif self.bubber.letters and random.random() > 0.59 and can_loose_letter:
+ # loose a letter
+ lst = range(6)
+ random.shuffle(lst)
+ for l in lst:
+ lettername = bubbles.extend_name(l)
+ if lettername in self.bubber.letters:
+ s = self.bubber.letters[lettername]
+ del self.bubber.letters[lettername]
+ if isinstance(s, ActiveSprite):
+ s.kill()
+ scoreboard()
+ s = bubbles.LetterBubble(self.bubber.pn, l)
+ s.move(self.x, self.y)
+ break
+ icons = self.getcurrenticons()
+ for i in range(2, 32):
+ mode = 5 + ((i>>1) & 3)
+ self.seticon(icons[mode, self.dir])
+ yield None
+ self.kill()
+ if not self.bubber.dragons:
+ self.bubber.bubberdie()
+ # self.bubber.badpoints = self.bubber.points // 3
+
+ def killing(self):
+ self.kill()
+ if 0:
+ yield None
+
+ def carrybonus(self, bonus, timeout=500):
+ timeout += BubPlayer.FrameCounter
+ lst = list(self.dcap['carrying'])
+ lst.append((timeout, bonus))
+ lst.sort()
+ self.dcap['carrying'] = lst
+
+ def listcarrybonuses(self):
+ return [bonus for timeout, bonus in self.dcap['carrying']]
+
+## def moebius(self):
+## self.dir = -self.dir
+## self.dcap['left2right'] *= -1
+
+ def monstervisible(self):
+ return not self.dcap['ring'] and not self.dcap['shield']
+
+ def getcurrenticons(self, imgtransform=''):
+ if self.playing_fish:
+ imgtransform = 'fish'
+ icobubber = self.dcap.get('bubbericons', self.bubber)
+ try:
+ return icobubber.transformedicons[imgtransform]
+ except KeyError:
+ icons = icobubber.transformedicons[imgtransform] = {}
+ icobubber.loadicons(imgtransform)
+ return icons
+
+ def normal_movements(self):
+ yfp = 0.0
+ hfp = 0
+ angryticks = 0
+ mytime = 0
+ privatetime = 0
+ while 1:
+ dcap = self.dcap
+ self.poplist = [self]
+ carrying = dcap['carrying']
+ while carrying and carrying[0][0] < BubPlayer.FrameCounter:
+ timeout, bonus = carrying.pop(0)
+ if bonus.endaction:
+ bonus.endaction(self)
+ del bonus
+
+ bubber = self.bubber
+ wannafire = bubber.getkey(dcap, 'key_fire')
+ wannajump = bubber.getkey(dcap, 'key_jump')
+ wannago = bubber.wannago(dcap)
+ bottom_up = self.bottom_up()
+ onground1 = (onground,underground)[bottom_up]
+
+ if dcap['autofire']:
+ wannafire = 1
+ if dcap['pinball']:
+ wannajump = 1
+ if dcap['pinball'] > 1:
+ if self.up:
+ self.up *= 0.982 ** dcap['pinball']
+ if dcap['hotstuff']:
+ if not wannago:
+ if self.dir * (random.random()-0.07) < 0:
+ wannago = -1
+ else:
+ wannago = 1
+ wannafire = 1
+ if self.fire > (11 // dcap['hotstuff']):
+ self.fire = 0
+## if dcap['hotstuff'] > 1 and random.random() < 0.4:
+## from bubbles import FireDrop
+## FireDrop(self.x + HALFCELL, self.y + HALFCELL)
+ if wannago:
+ self.dir = wannago * dcap['lookforward']
+ if self.x & 1:
+ self.step(self.dir, 0)
+ if dcap['slippy']:
+ vx = dcap['vslippy']
+ if wannago:
+ vx += wannago * 0.05
+ else:
+ vx *= 0.95
+ if vx < 0.0:
+ wannago = -1
+ else:
+ wannago = 1
+ dcap['vslippy'] = vx
+ mytime = (mytime+dcap['lookforward']) % 12
+ hfp += abs(vx)
+ else:
+ hfp += dcap['hspeed']
+
+## if self.glueddown:
+## if wannajump or not dcap['nojump']:
+## del self.glueddown
+## else:
+## # glued gliding movements
+## self.step(self.x & 1, self.y & 1)
+## if wannago:
+## mytime = (mytime+dcap['lookforward']) % 12
+## else:
+## hfp = 0
+## while hfp > 0 and self.glueddown:
+## gx, gy = self.glueddown
+## dx = wannago*gy
+## dy = -wannago*gx
+## x0 = (self.x + gx + dx) // CELL + 1
+## y0 = (self.y + gy + dy) // CELL + 1
+## if ' ' == bget(x0+dx, y0+dy) == bget(x0+dx-gx, y0+dy-gy):
+## self.step(2*dx, 2*dy)
+## # detached from this wall?
+## x1 = (self.x + gx + dx) // CELL + 1
+## y1 = (self.y + gy + dy) // CELL + 1
+## if (' ' == bget(x1-dx+gx, y1-dy+gy)
+## == bget(x0 +gx, y0 +gy)
+## == bget(x0+dx+gx, y0+dy+gy)):
+## if bget(x0-dx+gx, y0-dy+gy) != ' ':
+## # rotate around the corner
+## self.glueddown = -dx, -dy
+## self.step(2*gx, 2*gy)
+## else:
+## del self.glueddown
+## elif bget(x0-gx, y0-gy) == ' ':
+## # attach to the wall into which we are running
+## if (self.x*dx | self.y*dy) % CELL != 0:
+## if ((((self.x-2*dx)*dx | (self.y-2*dy)*dy) &
+## ((self.x-4*dx)*dx | (self.y-4*dy)*dy))
+## % CELL == 0):
+## self.step(-2*dx, -2*dy)
+## else:
+## del self.glueddown
+## else:
+## self.glueddown = dx, dy
+## else:
+## del self.glueddown
+## self.vertical_warp()
+## hfp -= 0.82 # slightly faster than usual
+ # normal left or right movements
+ breakwalls = dcap['breakwalls']
+ while hfp > 0:
+ hfp -= 1
+ dir = 0
+ if wannago == -1:
+ x0 = (self.x+1)//CELL
+ y0 = (self.y+4 - bottom_up*(CELL+4)) // CELL + 1
+ y0bis = (self.y+CELL-1) // CELL + 1 - bottom_up
+ if bget(x0,y0) == ' ' == bget(x0,y0bis):
+ dir = -1
+ elif breakwalls:
+ self.breakwalls(x0, y0bis, -1)
+ elif wannago == 1:
+ x0 = (self.x-3)//CELL + 2
+ y0 = self.y // CELL + 1 - bottom_up
+ y0bis = (self.y+CELL-1) // CELL + 1 - bottom_up
+ if bget(x0,y0) == ' ' == bget(x0,y0bis):
+ dir = +1
+ elif breakwalls:
+ self.breakwalls(x0, y0bis, 1)
+ self.step(2*dir, 0)
+ if dir:
+ mytime = (mytime+dcap['lookforward']) % 12
+ else:
+ f = - dcap['vslippy'] * (dcap['slippy']+1)/3.0
+ dcap['vslippy'] = max(min(f, 10.0), -10.0)
+ hfp = 0
+ onbubble = None
+ if not dcap['infinite_shield']:
+ touching = images.touching(self.x+1, self.y+1, 30, 30)
+ touching.reverse()
+ for s in touching:
+ if s.touched(self):
+ onbubble = s
+ elif bubber.key_left or bubber.key_right or bubber.key_jump or bubber.key_fire:
+ dcap['infinite_shield'] = 0
+
+ dir = self.dir
+ icons = self.getcurrenticons('vflip' * bottom_up)
+
+ if self.playing_fish:
+ mode = self.one_fish_frame(onground1, bottom_up)
+ elif self.up:
+ # going up
+ mode = 9
+ self.up -= dcap['gravity']
+ if (self.up,-self.up)[bottom_up] < 4.0:
+ self.up = 0.0
+ mode = 10
+ else:
+ ny = self.y + yfp - self.up
+ self.move(self.x, int(ny))
+ yfp = ny - self.y
+ self.vertical_warp()
+ if wannago and dcap['teleport']:
+ for t in self.teleport(wannago, icons, 0):
+ yield t
+ else:
+ # going down or staying on ground
+ if wannajump and onbubble:
+ ground = True
+ onbubble.dragon_jumped = True, bottom_up
+ else:
+ ground = onground1(self.x, self.y)
+ if ground:
+ if wannajump:
+ self.play(images.Snd.Jump)
+ if dcap['jumpdown'] and not onbubble:
+ self.step(0, (1, -1)[bottom_up])
+ mode = 10
+ bubber.emotic(self, 4)
+ else:
+ yfp = 0.0
+ self.up = (7.5,-7.5)[bottom_up]
+ mode = 9
+ else:
+ mode = mytime // 4
+ if wannago and dcap['teleport']:
+ for t in self.teleport(wannago, icons):
+ yield t
+ else:
+ mode = 10
+ if dcap['fly']:
+ self.fly_counter += 1
+ if self.fly_counter < dcap['fly']:
+ ny = self.y
+ else:
+ del self.fly_counter
+ ny = self.y+(1,-1)[bottom_up]
+ else:
+ ny = (self.y+(4,-1)[bottom_up]) & ~3
+ nx = self.x
+ if nx < 32:
+ nx += 2
+ elif nx > boards.bwidth - 64:
+ nx -= 2
+ self.move(nx, ny)
+ self.vertical_warp()
+ if wannago and dcap['teleport']:
+ for t in self.teleport(wannago, icons, 0):
+ yield t
+
+ if wannafire and not self.fire:
+ self.firenow()
+ self.hatangle = 1
+ if self.fire:
+ if self.fire <= 5:
+ mode = 3
+ self.hatangle = 2
+ elif self.fire <= 10:
+ mode = 4
+ self.hatangle = 3
+ self.fire += 1
+ if self.fire >= 64 // dcap['firerate']:
+ self.fire = 0
+
+ s = dcap['shield']
+ if s:
+ if dcap['infinite_shield'] and s < 20:
+ s += 4
+ s -= 1
+ if dcap['overlayglasses']:
+ self.overlayyoffset = ({3: 2, 4: 0,
+ 9: 3, 10: 5}.get(mode, 4)
+ + self.playing_fish * 2)
+ elif s & 2:
+ mode = 11
+ dcap['shield'] = s
+ if dcap['ring']:# and random.random() > 0.1:
+ if dcap['ring'] > 1:
+ mode = 12
+ else:
+ mode = 11
+ self.seticon(icons[mode, dir])
+ self.watermoveable = not wannajump
+
+ privatetime += BubPlayer.PlayersPrivateTime
+ while privatetime >= 100:
+ yield None
+ privatetime -= 100
+
+ if self.angry:
+ if angryticks == 0:
+ s = ActiveSprite(icons[11, self.dir], self.x, self.y)
+ s.gen.append(s.die([None], speed=10))
+ angryticks = 6
+ angryticks -= 1
+ #if BubPlayer.Moebius and BubPlayer.FrameCounter % 5 == 0:
+ # s = ActiveSprite(icons[11, -self.dir],
+ # boards.bwidth - 2*CELL - self.x, self.y)
+ # s.gen.append(s.die([None], speed=2))
+
+ def teleport(self, wannago, icons, max_delta_y=CELL):
+ #if self.dcap['shield']:
+ # return
+ from bonuses import Bonus, Megabonus
+ best_dx = boards.bwidth
+ centerx = self.x + self.ico.w // 2
+ basey = (self.y + self.ico.h + 8) & ~15
+ for s in images.ActiveSprites:
+ if (isinstance(s, Bonus) and s.touchable
+ and abs(s.y+s.ico.h - basey) <= max_delta_y
+ and s.is_on_ground()):
+ dx = (s.x + (wannago < 0 and s.ico.w)) - centerx
+ if dx*wannago > 0:
+ dx = abs(dx) + CELL
+ if dx < best_dx:
+ best_dx = dx
+ best = s
+ if not (42 <= best_dx < boards.bwidth):
+ return
+ self.play(images.Snd.Shh)
+ self.up = 0.0
+ s = best
+ dx = best_dx
+ basey = s.y+s.ico.h
+ desty = basey - self.ico.h
+ dy_dx = float(desty - self.y) / dx
+ self.dir = wannago
+ ico = images.make_darker(icons[0, wannago], True)
+ # speed up
+ fx = self.x
+ fy = self.y
+ curdx = 0.0
+ stepx = 2.0
+ t = 0
+ while 1:
+ if curdx < 0.5*dx:
+ stepx *= 1.13
+ else:
+ stepx /= 1.13
+ fx += wannago * stepx
+ fy += dy_dx * stepx
+ curdx += stepx
+ if curdx >= dx or stepx < 2.0:
+ fx += wannago * (dx - curdx)
+ break
+ self.move(int(fx), int(fy), ico)
+ # make the target bonus bounce a bit
+ if s.alive:
+ dy = (t & 7) * 4
+ if dy > 16:
+ dy = 32-dy
+ s.move(s.x, basey - s.ico.h - dy)
+ t += 1
+ yield None
+ self.move(int(fx), desty)
+ self.dcap['shield'] = 50
+
+ def breakwalls(self, x, y0, dir):
+ if self.dcap['breakwalls'] > BubPlayer.FrameCounter:
+ return # wait before breaking more walls
+ if not (2 <= x < boards.curboard.width-2):
+ return
+ ys = []
+ for y in (y0, y0-1):
+ if 0 <= y < boards.curboard.height and bget(x, y) == '#':
+ ys.append(y)
+ if len(ys) == 2:
+ from bonuses import DustStar
+ dir *= self.dcap['hspeed']
+ for y in ys:
+ w = boards.curboard.killwall(x, y)
+ s = ActiveSprite(w.ico, w.x, w.y)
+ dxy = [dir+random.random()-0.5,
+ -random.random()*3.0]
+ DustStar(w.x, w.y, dxy[0], dxy[1], big=0)
+ s.gen.append(s.parabolic(dxy))
+ self.dcap['breakwalls'] = BubPlayer.FrameCounter + 40
+
+ def enter_new_board(self):
+ self.playing_fish = False
+ self.lifegained = 0
+
+ def become_fish(self):
+ self.playing_fish = True
+ icons = self.getcurrenticons()
+ self.seticon(icons[11, self.dir])
+
+ def one_fish_frame(self, onground1, bottom_up):
+ if self.bubber.getkey(self.dcap, 'key_jump'):
+ # swimming up
+ self.step(0, (-2, 2)[bottom_up])
+ else:
+ if random.random() < 0.05:
+ bubbles.FishBubble(self)
+ if not onground1(self.x, self.y):
+ # swimming down
+ ny = (self.y+(2,-1)[bottom_up]) & ~1
+ self.move(self.x, ny)
+ self.vertical_warp()
+ return ((BubPlayer.FrameCounter // 3) % 6) * 0.5
+
+ def to_front(self):
+ ActiveSprite.to_front(self)
+ if self.dcap['overlayglasses']:
+ ico = images.sprget(('glasses', self.dir))
+ y = self.y + self.overlayyoffset
+ if self.overlaysprite is None or not self.overlaysprite.alive:
+ self.overlaysprite = images.ActiveSprite(ico, self.x, y)
+ else:
+ self.overlaysprite.to_front()
+ self.overlaysprite.move(self.x, y, ico)
+ self.overlaysprite.gen = [self.overlaysprite.die([None])]
+
+ def bottom_up(self):
+ return self.dcap['gravity'] < 0.0
+
+ def watermove(self, x, y):
+ # for WaterCell.flooding()
+ if self in BubPlayer.DragonList and self.watermoveable:
+ self.watermoveable = 0
+ self.move(x, y)
+ self.up = 0.0
+ if self.dcap['shield'] < 6:
+ self.dcap['shield'] = 6
+ if self.fire <= 10:
+ self.fire = 11
+
+ def become_monster(self, clsname, big=0, immed=0):
+ if self in BubPlayer.DragonList:
+ BubPlayer.DragonList.remove(self)
+
+ import monsters, mnstrmap
+ mcls = getattr(monsters, clsname)
+ mdef = getattr(mnstrmap, clsname)
+ m = mcls(mdef, self.x, self.y, self.dir, in_list=self.bubber.dragons)
+ m.become_monster(self.bubber, self.dcap, big, immed)
+ self.seticon(m.ico)
+ self.gen = [self.killing()]
+
+ def become_bubblingeyes(self, bubble):
+ if self in BubPlayer.DragonList:
+ self.bubber.emotic(self, 4)
+ BubPlayer.DragonList.remove(self)
+
+ import bubbles
+ bubble.to_front()
+ m = bubbles.BubblingEyes(self.bubber, self.dcap, bubble)
+ self.bubber.dragons.append(m)
+ self.gen = [self.killing()]
+ return 1
+ else:
+ return 0
+
+ def firenow(self):
+ self.fire = 1
+ #if boards.curboard.wastingplay is None:
+ shootbubbles = self.dcap['shootbubbles']
+ special_bubbles = shootbubbles and shootbubbles.pop()
+ thrustfactors = None
+ N = self.dcap['flower']
+ if N == 1:
+ angles = [0]
+ elif N > 1:
+ angles = [i*(2.0*math.pi/N) for i in range(N)]
+ self.dcap['flower'] = N*2//3
+ elif N > -16: # triple fire, possibly cumulative
+ angles = [0]
+ for i in range(1, -N+1):
+ angles.append(i * 0.19)
+ angles.append(i * -0.19)
+ else: # heptuple fire
+ c = 0.17
+ a = math.sqrt(1-c+c*c)
+ alpha = math.atan2(math.sqrt(3)/2*c, 1-c/2)
+ b = math.sqrt(1+c+c*c)
+ beta = math.atan2(math.sqrt(3)/2*c, 1+c/2)
+ angles = [0, 0, 0, alpha, -alpha, beta, -beta]
+ thrustfactors = [1, 1-c, 1+c, a, a, b, b]
+ dir = self.dir
+ x = self.x
+## if self.glueddown:
+## gx, gy = self.glueddown
+## dir = dir*gy
+## if not dir:
+## dir = 1
+## delta = self.dir*gx * math.pi/2
+## angles = [angle-delta for angle in angles]
+## x -= 16
+ if self.dcap['hotstuff'] > 1:
+ base = (random.random()-0.5)*math.pi
+ angles = [a + base for a in angles]
+ if self.dcap['bigflower'] is not None:
+ N = 45
+ angle = BubPlayer.FrameCounter - self.dcap['bigflower']
+ if not (0 <= angle < N):
+ self.dcap['bigflower'] = BubPlayer.FrameCounter
+ angle = 0
+ angles = [-angle * (2.0*math.pi/N) * self.dir]
+ thrustfactors = None
+ self.fire = max(1, 64 // self.dcap['firerate'] - 2)
+ if self.dcap['autofire'] >= 1:
+ self.dcap['autofire'] -= 1
+ if not thrustfactors:
+ thrustfactors = [None] * len(angles)
+ import bonuses
+ for angle, thrustfactor in zip(angles, thrustfactors):
+ args = (self, x + 4*dir, self.y, dir,
+ special_bubbles, angle, thrustfactor,
+ self.dcap['shootthrust'])
+ bonuses.record_shot(args)
+ bubbles.DragonBubble(*args)
+ #else:
+ # from monsters import DragonShot
+ # DragonShot(self)
+
+class BubPlayer(gamesrv.Player):
+ # global state
+ FrameCounter = 0
+ PlayerList = []
+ DragonList = []
+ MonsterList = []
+ LimitScore = 0
+ LimitScoreColor = None
+ LimitTime = None
+ PlayersPrivateTime = 100
+ SuperSheep = False
+ SuperFish = False
+ #HighScore = 0
+ #HighScoreColor = None
+
+ INIT_BOARD_CAP = {
+ #'LatestLetsGo': -999,
+ 'BubblesBecome': None,
+ 'MegaBonus': None,
+ 'BaseFrametime': 1.0,
+ 'LeaveBonus': None,
+## 'Moebius': 0,
+ 'OverridePlayerIcon': None,
+ 'DisplayPoints': None,
+ 'SuperSheep': False,
+ 'SuperFish' : False,
+ }
+ TRANSIENT_DATA = ('_client', 'key_left', 'key_right',
+ 'key_jump', 'key_fire', 'pn', 'nameicons',
+ 'icons', 'transformedicons',
+ 'standardplayericon', 'iconnames')
+
+ FISH_MODE_MAP = {0: ('', 0), # swim
+ 0.5: ('', 1),
+ 1: ('', 2),
+ 1.5: ('', 3),
+ 2: ('', 2),
+ 2.5: ('', 1),
+ 3: ('', 4), # lancer de bulle
+ 4: ('', 5),
+ 5: ('', 3), # mort
+ 6: ('cw', 3),
+ 7: ('rot180', 3),
+ 8: ('ccw', 3),
+ 9: ('', 3), # saut, montant
+ 10: ('', 3), # saut, descend
+ 11: ('', 6), # shielded
+ 12: 'black',
+ }
+
+ def __init__(self, n):
+ self.pn = n
+ self.icons = {}
+ self.transformedicons = {'': self.icons}
+ self.standardplayericon = images.sprget(GreenAndBlue.players[n][3])
+ self.iconnames = {
+ (0, -1): GreenAndBlue.players[n][0], # walk
+ (0, +1): GreenAndBlue.players[n][3],
+ (1, -1): GreenAndBlue.players[n][1],
+ (1, +1): GreenAndBlue.players[n][4],
+ (2, -1): GreenAndBlue.players[n][2],
+ (2, +1): GreenAndBlue.players[n][5],
+ (3, -1): GreenAndBlue.players[n][6], # lancer de bulle
+ (3, +1): GreenAndBlue.players[n][8],
+ (4, -1): GreenAndBlue.players[n][7],
+ (4, +1): GreenAndBlue.players[n][9],
+ (5, -1): GreenAndBlue.players[n][0], # mort
+ (5, +1): GreenAndBlue.players[n][0],
+ (6, -1): GreenAndBlue.players[n][11],
+ (6, +1): GreenAndBlue.players[n][10],
+ (7, -1): GreenAndBlue.players[n][12],
+ (7, +1): GreenAndBlue.players[n][12],
+ (8, -1): GreenAndBlue.players[n][10],
+ (8, +1): GreenAndBlue.players[n][11],
+ (9, -1): GreenAndBlue.jumping_players[n][2], # saut, montant
+ (9, +1): GreenAndBlue.jumping_players[n][3],
+ (10,-1): GreenAndBlue.jumping_players[n][0], # saut, descend
+ (10,+1): GreenAndBlue.jumping_players[n][1],
+ (11,-1): 'shield-left', # shielded
+ (11,+1): 'shield-right',
+ (12,-1): 'black', # totally invisible
+ (12,+1): 'black',
+ }
+ self.nameicons = []
+ self.team = -1
+ self.reset()
+
+ def reset(self):
+ self.letters = {}
+ #self.bonbons = 0
+ self.points = 0
+ self.nextextralife = gamesrv.game.extralife
+ self.lives = boards.get_lives()
+ self.lifegained = 0
+ #self.badpoints = 0
+ self.pcap = {}
+ self.dragons = []
+ self.keepalive = None
+ self.stats = {'bubble': 0, 'die': 0}
+
+ def loadicons(self, flip):
+ icons = self.transformedicons[flip]
+ if flip == 'fish':
+ for dir in (-1, 1):
+ for key, value in self.FISH_MODE_MAP.items():
+ if value == 'black':
+ flip = ''
+ else:
+ flip, index = value
+ value = GreenAndBlue.fish[self.pn][index]
+ if dir > 0:
+ flip = flip or 'hflip'
+ icons[key, dir] = images.sprget((flip, value))
+ else:
+ for key, value in self.iconnames.items():
+ icons[key] = images.sprget((flip, value))
+
+ def setplayername(self, name):
+ name = name.strip()
+ for t in [0, 1]:
+ if name.endswith('(%d)' % (t+1)):
+ self.team = t
+ name = name[:-3].strip()
+ #print "New player in team", t, "with name", name
+ break
+ else:
+ self.team = -1
+ #print "New player with no team:", name
+ icons = [images.sprcharacterget(c) for c in name]
+ self.nameicons = [ico for ico in icons if ico is not None][:16]
+ self.nameicons.reverse()
+ scoreboard()
+
+ def playerjoin(self):
+ n = self.pn
+ if not self.icons:
+ self.loadicons(flip='')
+ self.keepalive = None
+ if self.points or self.letters:
+ print 'New player continues at position #%d.' % n
+ else:
+ print 'New player is at position #%d.' % n
+ self.reset()
+ self.key_left = 0
+ self.key_right = 0
+ self.key_jump = 0
+ self.key_fire = 0
+ players = [p for p in BubPlayer.PlayerList
+ if p.isplaying() and p is not self]
+ self.enterboard(players)
+ scoreboard()
+ #if BubPlayer.LatestLetsGo < BubPlayer.FrameCounter - 30:
+ images.Snd.LetsGo.play()
+ #BubPlayer.LatestLetsGo = BubPlayer.FrameCounter
+
+ def playerleaves(self):
+ print 'Closing position #%d.' % self.pn
+ self.savecaps()
+ self.zarkoff()
+ self.keepalive = time.time() + KEEPALIVE
+ scoreboard()
+
+ def sameteam(self, other):
+ return self.team != -1 and self.team == other.team
+
+ def enterboard(self, players):
+ players = [p for p in players if not p.sameteam(self)]
+ leftplayers = [p for p in players if p.start_left]
+ rightplayers = [p for p in players if not p.start_left]
+ self.start_left = (len(leftplayers) + random.random() <
+ len(rightplayers) + random.random())
+ self.lifegained = 0
+
+ def savecaps(self):
+ self.pcap = {}
+ dragons = self.dragons
+ if dragons:
+ for key, minimum in Dragon.SAVE_CAP.items():
+ self.pcap[key] = max(minimum,
+ max([d.dcap[key] for d in dragons]))
+
+ def zarkoff(self):
+ for d in self.dragons[:]:
+ d.kill()
+ del self.dragons[:]
+
+ def zarkon(self):
+ if self.key_left + self.key_right >= 1999997:
+ for dragon in self.dragons:
+ self.emotic(dragon, 6)
+ self.key_left = self.key_right = 900000
+ if self.key_left: self.key_left -= 1
+ if self.key_right: self.key_right -= 1
+ if self.key_jump: self.key_jump -= 1
+ if self.key_fire: self.key_fire -= 1
+ #if self.badpoints and not (self.FrameCounter & 7):
+ # percent = (int(self.points*0.0000333)+1) * 100
+ # decr = min(self.badpoints, percent)
+ # self.badpoints -= decr
+ # self.givepoints(-decr)
+ if boards.curboard and not self.dragons and self.lives != 0:
+ #wasting = boards.curboard.wastingplay
+ #if wasting is not None and self in wasting:
+ # return
+ if self.start_left:
+ x0 = 3*CELL
+ dir = 1
+ else:
+ x0 = boards.bwidth - 5*CELL
+ dir = -1
+ y = boards.bheight - 3*CELL
+ for x in [x0, x0+4*dir, x0+8*dir, x0+12*dir, x0+16*dir,
+ x0-4*dir, x0-8*dir, x0]:
+ if onground(x,y):
+ for d in BubPlayer.DragonList:
+ if d.y == y and abs(d.x-x) <= 5:
+ break
+ else:
+ break
+ self.dragons.append(Dragon(self, x, y, dir))
+ for key in self.pcap.keys():
+ if key not in ('teleport', 'jumpdown'):
+ del self.pcap[key]
+
+ def kLeft(self):
+ if self.key_left <= 1:
+ self.key_left = 1000000
+ def kmLeft(self):
+ self.key_left = (self.key_left == 1000000)
+ def kRight(self):
+ if self.key_right <= 1:
+ self.key_right = 1000000
+ def kmRight(self):
+ self.key_right = (self.key_right == 1000000)
+ def kJump(self):
+ if self.key_jump <= 1:
+ self.key_jump = 1000000
+ def kmJump(self):
+ self.key_jump = (self.key_jump == 1000000)
+ def kFire(self):
+ if self.key_fire <= 1:
+ self.key_fire = 1000000
+ def kmFire(self):
+ self.key_fire = (self.key_fire == 1000000)
+
+ def bubberdie(self):
+ self.stats['die'] += 1
+ if self.lives is not None and self.lives > 0:
+ self.lives -= 1
+ scoreboard()
+
+ def getkey(self, dcap, key_name):
+ return getattr(self, dcap[key_name])
+
+ def setkey(self, dcap, key_name, value):
+ setattr(self, dcap[key_name], value)
+
+ def wannago(self, dcap):
+ return dcap['left2right'] * cmp(self.getkey(dcap, 'key_right'),
+ self.getkey(dcap, 'key_left'))
+
+ def turn_single_shot(self, dcap):
+ for name in ('key_left', 'key_right'):
+ n = self.getkey(dcap, name)
+ if n < 999997 and n != 1:
+ self.setkey(dcap, name, 0)
+ wannago = self.wannago(dcap)
+ for name in ('key_left', 'key_right'):
+ self.setkey(dcap, name, 0)
+ return wannago
+
+ def givepoints(self, points):
+ self.points += points
+ if self.points < 0:
+ self.points = 0
+ while self.points >= self.nextextralife:
+ if self.lives is not None and self.lives > 0:
+ if gamesrv.game.lifegainlimit is None or self.lifegained < gamesrv.game.lifegainlimit:
+ if self.dragons:
+ dragon = random.choice(self.dragons)
+ dragon.play(images.Snd.Extralife)
+ else:
+ images.Snd.Extralife.play()
+ self.lives += 1
+ self.lifegained += 1
+ self.nextextralife += gamesrv.game.extralife
+ if self.LimitScoreColor is not None and self.points >= self.LimitScore:
+ boards.replace_boardgen(boards.game_over(), 1)
+ #if self.points > BubPlayer.HighScore:
+ # BubPlayer.HighScore = self.points
+ # BubPlayer.HighScoreColor = self.pn
+ scoreboard()
+
+ def giveletter(self, l, promize=100000):
+
+ #logf = open('log', 'a')
+ #print >> logf 'giveletter %d:' % self.pn, l
+ #logf.close()
+
+ lettername = bubbles.extend_name(l)
+ if lettername not in self.letters:
+ self.letters[lettername] = 1
+## nimage = getattr(LetterBubbles, lettername)
+## x0, y0 = self.infocoords()
+## s = images.ActiveSprite(images.sprget(nimage[1]), x0+l*(CELL-1), y0 - 3*CELL)
+## s.gen.append(s.cyclic([nimage[1], nimage[2], nimage[1], nimage[0]], 7))
+ scoreboard()
+ if len(self.letters) == 6:
+ import monsters
+ monsters.argh_em_all()
+ import bonuses
+ if self.dragons:
+ for i in range(3):
+ dragon = random.choice(self.dragons)
+ bonuses.starexplosion(dragon.x, dragon.y, 1)
+ for lettername in self.letters:
+ dragon = random.choice(self.dragons)
+ nimages = getattr(LetterBubbles, lettername)
+ bonuses.Parabolic2(dragon.x, dragon.y, nimages)
+ dragon = random.choice(self.dragons)
+ dragon.play(images.Snd.Extralife)
+ music = [images.music_old]
+ boards.replace_boardgen(boards.last_monster_killed(460, music))
+ self.givepoints(promize)
+
+ def emotic(self, dragon, strenght):
+ bottom_up = hasattr(dragon, 'bottom_up') and dragon.bottom_up()
+ vshift = getattr(dragon, 'up', 0.0)
+ for i in range(7):
+ angle = math.pi/6 * i
+ dx, dy = -math.cos(angle), -math.sin(angle)
+ nx = random.randrange(3,12)*dx
+ ny = random.randrange(3,9)*dy - 12
+ if bottom_up:
+ dy = -dy
+ ny = -ny
+ e = ActiveSprite(images.sprget(('vflip'*bottom_up, ('emotic', i))),
+ int(dragon.x + 8 + nx),
+ int(dragon.y + 8 + ny - vshift))
+ e.gen.append(e.straightline((3.3+random.random())*dx, (2.3+random.random())*dy))
+ e.gen.append(e.die([None], strenght))
+
+
+def upgrade(p):
+ p.__class__ = BubPlayer
+ p.key_left = 0
+ p.key_right = 0
+ p.key_jump = 0
+ p.key_fire = 0
+ p.dragons = []
+
+
+def xyiconumber(digits, x, y, pts, lst, width=7):
+ if pts >= 10**width:
+ pts = 10**width-1
+ for l in range(width):
+ ico = images.sprget(digits[pts % 10])
+ lst.append((x + (ico.w+1)*(width-1-l), y, ico))
+ pts = pts//10
+ if not pts:
+ break
+ return lst[-1][0]
+
+def scoreboard(reset=0, inplace=0, compresslimittime=0):
+ endgame = 1
+ if reset:
+ for p in BubPlayer.PlayerList:
+ if inplace:
+ for s in p.letters.values():
+ if isinstance(s, ActiveSprite):
+ s.kill()
+ if len(p.letters) == 6:
+ p.letters.clear()
+ for key in p.letters:
+ p.letters[key] = 2
+ brd = boards.curboard
+ if not brd or not gamesrv.sprites_by_n:
+ return
+ lst = []
+ bubblesshown = {}
+ plist = []
+ teamslist = [[], []]
+ teamspoints = [0, 0]
+ for p in BubPlayer.PlayerList:
+ if p.isplaying():
+ if p.lives != 0:
+ endgame = 0
+ else:
+ if not p.keepalive:
+ continue
+ if p.keepalive < time.time():
+ p.reset()
+ continue
+ if BubPlayer.DisplayPoints is not None:
+ points = BubPlayer.DisplayPoints(p)
+ else:
+ points = p.points
+ if p.team == -1:
+ plist.append((points, p, None))
+ else:
+ teamslist[p.team].append((points,p))
+ teamspoints[p.team] += points
+ teamslist[0].sort()
+ teamslist[1].sort()
+ plist.append((teamspoints[0], None, teamslist[0]))
+ plist.append((teamspoints[1], None, teamslist[1]))
+ plist.sort()
+ x0 = boards.bwidth
+ y0 = boards.bheight
+ for score, p, t in plist:
+ if p:
+ if p.lives == 0:
+ ico = images.sprget(GreenAndBlue.gameover[p.pn][0])
+ elif p.icons:
+ if p.isplaying():
+ mode = 0
+ else:
+ mode = 11
+ ico = BubPlayer.OverridePlayerIcon or p.icons[mode, -1]
+ lst.append((x0+9*CELL-ico.w, y0-ico.h, ico))
+ #if boards.curboard.wastingplay is None:
+ for l in range(6):
+ name = bubbles.extend_name(l)
+ if name in p.letters:
+ x, y = x0+l*(CELL-1), y0-3*CELL
+ imglist = getattr(LetterBubbles, name)
+ ico = images.sprget(imglist[1])
+ if gamesrv.game.End in (0, 1):
+ s = p.letters[name]
+ if (isinstance(s, ActiveSprite) and
+ BubPlayer.FrameCounter <= s.timeout):
+ s.move(x, y)
+ bubblesshown[s] = 1
+ continue
+ if s == 1:
+ s = ActiveSprite(ico, x, y)
+ s.setimages(s.cyclic([imglist[0], imglist[1],
+ imglist[2], imglist[1]]))
+ s.timeout = BubPlayer.FrameCounter + 500
+ p.letters[name] = s
+ bubblesshown[s] = 1
+ continue
+ lst.append((x, y, ico))
+ ## else:
+ ## ico = images.sprget(Bonuses.blue_sugar)
+ ## lst.append((x0+12, y0-3*CELL-8, ico))
+ ## xyiconumber(DigitsMisc.digits_white, x0-19, y0-3*CELL+5,
+ ## p.bonbons, lst)
+ xyiconumber(GreenAndBlue.digits[p.pn], x0+2, y0-18, score, lst)
+ if p.lives is not None and p.lives > 0:
+ xyiconumber(DigitsMisc.digits_white, x0+7*CELL, y0-18,
+ p.lives, lst, width=2)
+ x = x0+13*HALFCELL
+ for ico in p.nameicons:
+ x -= 7
+ lst.append((x, y0-35, ico))
+ y0 -= 7*HALFCELL
+ else: # Team
+ for pscore, p in t:
+ if p.lives == 0:
+ ico = images.sprget(GreenAndBlue.gameover[p.pn][0])
+ elif p.icons:
+ if p.isplaying():
+ mode = 0
+ else:
+ mode = 11
+ ico = BubPlayer.OverridePlayerIcon or p.icons[mode, -1]
+ lst.append((x0+9*CELL-ico.w, y0-ico.h, ico))
+ for l in range(6):
+ name = bubbles.extend_name(l)
+ if name in p.letters:
+ x, y = x0+l*(CELL-1), y0-2*CELL
+ imglist = getattr(LetterBubbles, name)
+ ico = images.sprget(imglist[1])
+ if gamesrv.game.End in (0, 1):
+ s = p.letters[name]
+ if (isinstance(s, ActiveSprite) and
+ BubPlayer.FrameCounter <= s.timeout):
+ s.move(x, y)
+ bubblesshown[s] = 1
+ continue
+ if s == 1:
+ s = ActiveSprite(ico, x, y)
+ s.setimages(s.cyclic([imglist[0], imglist[1],
+ imglist[2], imglist[1]]))
+ s.timeout = BubPlayer.FrameCounter + 500
+ p.letters[name] = s
+ bubblesshown[s] = 1
+ continue
+ lst.append((x, y, ico))
+ x = x0+13*HALFCELL
+ for ico in p.nameicons:
+ x -= 7
+ lst.append((x, y0-19, ico))
+ y0 -= 4*HALFCELL
+ if t != []:
+ xyiconumber(GreenAndBlue.digits[t[-1][1].pn], x0+2, y0-18, score, lst)
+ ico = images.sprget(('hat', p.team, -1, 1))
+ lst.append((x0+9*CELL-ico.w, y0-ico.h+16, ico))
+ y0 -= 5*HALFCELL
+ for p in BubPlayer.PlayerList:
+ for name, s in p.letters.items():
+ if isinstance(s, ActiveSprite) and s not in bubblesshown:
+ p.letters[name] = 2
+ s.kill()
+ compressable = len(lst)
+ #if BubPlayer.HighScoreColor is not None:
+ # x = xyiconumber(GreenAndBlue.digits[BubPlayer.HighScoreColor],
+ # x0+2*CELL, HALFCELL, BubPlayer.HighScore, lst)
+ # ico = images.sprget(GreenAndBlue.players[BubPlayer.HighScoreColor][3])
+ # lst.append((x-5*HALFCELL, 1, ico))
+ if BubPlayer.LimitScoreColor is not None:
+ xyiconumber(GreenAndBlue.digits[BubPlayer.LimitScoreColor],
+ x0+2*CELL, HALFCELL, BubPlayer.LimitScore, lst)
+ if BubPlayer.LimitTime is not None:
+ seconds = int(BubPlayer.LimitTime)
+ xyiconumber(DigitsMisc.digits_white, x0+2*CELL, HALFCELL,
+ seconds // 60, lst, width=3)
+ ico = images.sprget('colon')
+ lst.append((x0+5*CELL-1, HALFCELL+1, ico))
+ seconds = seconds % 60
+ ico = images.sprget(DigitsMisc.digits_white[seconds // 10])
+ lst.append((x0+6*CELL, HALFCELL, ico))
+ ico = images.sprget(DigitsMisc.digits_white[seconds % 10])
+ lst.append((x0+6*CELL+ico.w, HALFCELL, ico))
+ ymin = HALFCELL + ico.h
+ elif compresslimittime:
+ ico = images.sprget(DigitsMisc.digits_white[0])
+ ymin = HALFCELL + ico.h
+ else:
+ ymin = 0
+ if not brd.bonuslevel:
+ if brd.num < 99:
+ xyiconumber(DigitsMisc.digits_white, 2, 2, brd.num+1, lst, width=2)
+ else:
+ xyiconumber(DigitsMisc.digits_white, 2, 2, brd.num+1, lst, width=3)
+
+ # compress the scoreboard vertically if it doesn't fit
+ ymin += HALFCELL
+ if y0 < ymin:
+ factor = float(boards.bheight-ymin) / (boards.bheight-y0)
+ shift = ymin - y0*factor + 0.5
+ for i in range(compressable):
+ x, y, ico = lst[i]
+ lst[i] = x, int((y+ico.h)*factor+shift)-ico.h, ico
+
+ brd.writesprites('scoreboard', lst)
+
+ if gamesrv.game.End in (0, 1):
+ gamesrv.game.End = endgame
+
+
+# initialize global board data
+def reset_global_board_state():
+ for key, value in BubPlayer.INIT_BOARD_CAP.items():
+ setattr(BubPlayer, key, value)
+reset_global_board_state()
diff --git a/bubbob/ranking.py b/bubbob/ranking.py
new file mode 100644
index 0000000..d4a4d22
--- /dev/null
+++ b/bubbob/ranking.py
@@ -0,0 +1,391 @@
+from __future__ import generators
+import random
+import boards, images, gamesrv
+from boards import CELL, HALFCELL
+from mnstrmap import DigitsMisc, Flood, GreenAndBlue
+from bubbles import Bubble
+from bonuses import Points
+from player import BubPlayer
+
+MARGIN = 22
+VMARGIN = 12
+
+class RPicture:
+ def __init__(self):
+ self.icons = []
+ def put(self, ico, dx=0, dy=0):
+ self.icons.append((dx, dy, ico))
+ def getsize(self):
+ if self.icons:
+ return (max([dx+ico.w for dx, dy, ico in self.icons]) + MARGIN,
+ max([dy+ico.h for dx, dy, ico in self.icons]))
+ else:
+ return 0, 0
+ def render(self, x, y):
+ return [gamesrv.Sprite(ico, x+dx, y+dy) for dx, dy, ico in self.icons]
+
+class RPoints:
+ def __init__(self, bubber, nbpoints):
+ self.bubber = bubber
+ self.nbpoints = nbpoints
+ def getsize(self):
+ return 0, 0
+ def render(self, x, y):
+ Points(x, y, self.bubber.pn, self.nbpoints)
+ return []
+
+class RNumber(RPicture):
+ map = {'%': 'percent'}
+ for digit in range(10):
+ map[str(digit)] = DigitsMisc.digits_white[digit]
+
+ def __init__(self, text):
+ RPicture.__init__(self)
+ x = 0
+ for c in text:
+ ico = images.sprget(self.map[c])
+ self.put(ico, dx=x)
+ x += ico.w+1
+
+class RText(RPicture):
+ def __init__(self, text, margin=VMARGIN):
+ RPicture.__init__(self)
+ x = 0
+ for c in text:
+ ico = images.sprcharacterget(c)
+ if ico is not None:
+ self.put(ico, dx=x)
+ x += 7
+ self.margin = margin
+ def getsize(self):
+ w, h = RPicture.getsize(self)
+ h -= (VMARGIN-self.margin)
+ return w, h
+
+
+def linesize(line):
+ width = MARGIN
+ height = 0
+ for item in line:
+ w, h = item.getsize()
+ width += w
+ if h > height:
+ height = h
+ return width, height
+
+def display(lines, timeleft, bgen=None, black=0):
+ waves = []
+ if lines:
+ totalwidth = 0
+ totalheight = 0
+ for line in lines:
+ w, h = linesize(line)
+ if w > totalwidth:
+ totalwidth = w
+ totalheight += h
+ heightmargin = (boards.bheight-2*CELL - totalheight) // (len(lines)+1)
+ if heightmargin > VMARGIN:
+ heightmargin = VMARGIN
+ totalheight += heightmargin * (len(lines)+1)
+
+ # size in number of CELLs
+ cwidth = (totalwidth+CELL-1) // CELL
+ cheight = (totalheight+CELL-1) // CELL
+
+ x0 = ((boards.width - cwidth) // 2) * CELL + HALFCELL
+ y0 = ((boards.height - cheight) // 2) * CELL + HALFCELL
+ extras = boards.curboard.sprites.setdefault('ranking', [])
+ #while extras:
+ # extras.pop().kill()
+ # yield 0.12
+ vspeed = -4
+ while extras:
+ nextras = []
+ for s in extras:
+ s.step(0, vspeed)
+ if s.y + s.ico.h <= 0:
+ s.kill()
+ else:
+ nextras.append(s)
+ extras[:] = nextras
+ yield 1
+ vspeed -= 1
+
+ # draw the box filled with water
+ original_y0 = y0
+ wallicon = boards.patget((boards.curboard.num, 0, 0), images.KEYCOL)
+ if black:
+ fillicon = images.sprget('gameoverbkgnd')
+ waveicons = [wallicon]
+ y0 = boards.bheight+CELL
+ else:
+ fillicon = images.sprget(Flood.fill)
+ waveicons = [images.sprget(n) for n in Flood.waves]
+ for y in range(y0-CELL, y0+cheight*CELL+CELL, CELL):
+ w = gamesrv.Sprite(wallicon, x0-CELL, y)
+ extras.append(w)
+ for x in range(x0, x0+cwidth*CELL, CELL):
+ w = gamesrv.Sprite(wallicon, x, y0+cheight*CELL)
+ extras.append(w)
+ w = gamesrv.Sprite(waveicons[-1], x, y0-CELL)
+ extras.append(w)
+ waves.append(w)
+ for y in range(y0, y0+cheight*CELL, CELL):
+ w = gamesrv.Sprite(fillicon, x, y)
+ extras.append(w)
+ for y in range(y0-CELL, y0+cheight*CELL+CELL, CELL):
+ w = gamesrv.Sprite(wallicon, x0+cwidth*CELL, y)
+ extras.append(w)
+
+ # draw the individual items inside
+ y = y0 + totalheight
+ lines.reverse()
+ for line in lines:
+ linew, lineh = linesize(line)
+ x = x0 + MARGIN
+ y -= (lineh + heightmargin)
+ for item in line:
+ w, h = item.getsize()
+ extras += item.render(x, y+(lineh-h)//2)
+ x += w
+
+ vspeed = 0
+ while y0 > original_y0:
+ vspeed = max(vspeed-1, original_y0 - y0)
+ y0 += vspeed
+ for s in extras:
+ s.step(0, vspeed)
+ yield 1
+
+ while timeleft > 0.0:
+ if waves:
+ ico = waveicons.pop(0)
+ waveicons.append(ico)
+ for w in waves:
+ w.seticon(ico)
+ for i in range(2):
+ if bgen is None:
+ t = boards.normal_frame()
+ else:
+ try:
+ t = bgen.next()
+ except StopIteration:
+ timeleft = 0.0
+ break
+ timeleft -= t
+ yield t
+
+# ____________________________________________________________
+
+def ranking_picture(results, maximum, givepoints):
+ if maximum is None:
+ maximum = 0
+ for n in results.values():
+ maximum += n
+ maximum = maximum or 1
+ ranking = []
+ teamrank = [0, 0]
+ teamplayers = [[], []]
+ for p, n in results.items():
+ if p.team != -1:
+ teamrank[p.team] += n
+ teamplayers[p.team].append((n,p))
+ else:
+ ranking.append((n, random.random(), p))
+ teamplayers[0].sort()
+ teamplayers[0].reverse()
+ teamplayers[1].sort()
+ teamplayers[1].reverse()
+ if teamplayers[0] != []:
+ ranking.append((teamrank[0], random.random(), teamplayers[0]))
+ if teamplayers[1] != []:
+ ranking.append((teamrank[1], random.random(), teamplayers[1]))
+ ranking.sort()
+ ranking.reverse()
+
+ nbpoints = givepoints and ((len(ranking)+1)//2)*10000
+ lines = []
+ for (n, dummy, bubber), i in zip(ranking, range(len(ranking))):
+ pic = RPicture()
+ if isinstance(bubber, list):
+ fraction = (nbpoints//(10*len(bubber))) * 10
+ total = fraction * len(bubber)
+ for n, bub in bubber:
+ bub.givepoints(fraction)
+ bubber = bubber[0][1]
+ pic.put(images.sprget(('hat', bubber.team)))
+ else:
+ if len(ranking) == 1:
+ icon = 0
+ elif i == 0:
+ icon = 10
+ elif i == len(ranking) - 1:
+ icon = 9
+ else:
+ icon = 0
+ pic.put(bubber.icons[icon, +1])
+ total = 0
+ line = []
+ if nbpoints > 0:
+ line.append(RPoints(bubber, nbpoints))
+ bubber.givepoints(nbpoints - total)
+ nbpoints -= 10000
+ line.append(pic)
+ line.append(RNumber(str(int(n*100.00001/maximum)) + '%'))
+ lines.append(line)
+ return lines
+
+
+def just_wait():
+ while 1:
+ yield 2
+
+def screen_scores():
+ results = {}
+ for p in BubPlayer.PlayerList:
+ if p.points:
+ results[p] = p.points
+ lines = ranking_picture(results, None, 0)
+ lines.insert(0, [RText(" THE END")])
+ return lines
+
+def screen_monster():
+ pairs = []
+ for p in BubPlayer.PlayerList:
+ catch = p.stats.get('monster', {})
+ for p2, count in catch.items():
+ if count:
+ pairs.append((count, p, p2))
+ random.shuffle(pairs)
+ pairs.sort()
+ pairs.reverse()
+ del pairs[5:]
+ lines = []
+ if pairs:
+ lines.append([RText('Best Monster Bubblers')])
+ for count, p, p2 in pairs:
+ pic = RPicture()
+ pic.put(p.icons[4,+1], 0, 6)
+ pic.put(images.sprget(GreenAndBlue.new_bubbles[p.pn][1]), 31, 6)
+ pic.put(images.sprget(GreenAndBlue.new_bubbles[p.pn][3]), 69, 6)
+ pic.put(images.sprget(GreenAndBlue.normal_bubbles[p.pn][0]), 101)
+ pic.put(images.sprget(p2), 101)
+ lines.append([pic, RNumber(str(count))])
+ return lines
+
+def screen_catch():
+ pairs = []
+ for p in BubPlayer.PlayerList:
+ catch = p.stats.get('catch', {})
+ for p2, count in catch.items():
+ if count:
+ pairs.append((count, p, p2))
+ random.shuffle(pairs)
+ pairs.sort()
+ pairs.reverse()
+ del pairs[5:]
+ lines = []
+ if pairs:
+ lines.append([RText('Best Dragon Bubblers')])
+ for count, p, p2 in pairs:
+ pic = RPicture()
+ pic.put(p.icons[4,+1], 0, 6)
+ pic.put(images.sprget(GreenAndBlue.new_bubbles[p.pn][1]), 31, 6)
+ pic.put(images.sprget(GreenAndBlue.new_bubbles[p.pn][3]), 69, 6)
+ pic.put(images.sprget(GreenAndBlue.normal_bubbles[p2.pn][0]), 101)
+ pic.put(images.sprget(('eyes', 0,0)), 101)
+ lines.append([pic, RNumber(str(count))])
+ return lines
+
+def screen_bonus():
+ pairs = []
+ for p in BubPlayer.PlayerList:
+ catch = p.stats.get('bonus', {})
+ for p2, count in catch.items():
+ if count > 1:
+ pairs.append((count, p, p2))
+ random.shuffle(pairs)
+ pairs.sort()
+ pairs.reverse()
+ seen = {}
+ npairs = []
+ for count, p, p2 in pairs:
+ if p2 not in seen:
+ npairs.append((count, p, p2))
+ seen[p2] = 1
+ pairs = npairs
+ del pairs[5:]
+ lines = []
+ if pairs:
+ lines.append([RText('Best Bonus Catchers')])
+ for count, p, p2 in pairs:
+ pic = RPicture()
+ pic.put(p.icons[1,+1], 0)
+ pic.put(images.sprget(p2), 44)
+ lines.append([pic, RNumber(str(count))])
+ return lines
+
+def screen_bubble():
+ pairs = []
+ for p in BubPlayer.PlayerList:
+ count = p.stats['bubble']
+ if count:
+ pairs.append((count, p))
+ random.shuffle(pairs)
+ pairs.sort()
+ pairs.reverse()
+ del pairs[5:]
+ lines = []
+ if pairs:
+ lines.append([RText('Best Bubble Exploders')])
+ for count, p in pairs:
+ pic = RPicture()
+ pic.put(p.icons[1,+1], 0)
+ pic.put(images.sprget(Bubble.exploding_bubbles[1]), 27)
+ lines.append([pic, RNumber(str(count))])
+ return lines
+
+def screen_die():
+ pairs = []
+ for p in BubPlayer.PlayerList:
+ count = p.stats['die']
+ if count:
+ pairs.append((count, p))
+ random.shuffle(pairs)
+ pairs.sort()
+ pairs.reverse()
+ del pairs[5:]
+ lines = []
+ if pairs:
+ lines.append([RText('Top Deaths')])
+ n = 0
+ for count, p in pairs:
+ pic = RPicture()
+ pic.put(p.icons[6+(n%3),+1], 0)
+ lines.append([pic, RNumber(str(count))])
+ n += 1
+ return lines
+
+def screen_authors():
+ return [
+ [RText('programming', 6)],
+ [RText(' Armin & Odie')],
+ [RText('art', 6)],
+ [RText(' David Gowers, based on McSebi')],
+ [RText('levels', 6)],
+ [RText(' Gio & Odie & MS & Armin')],
+ [RText('special thanks', 6)],
+ [RText(' Odie & Brachamutanda')],
+ [RText('beta-testers', 6)],
+ [RText(' IMA Connection')],
+ ]
+
+def game_over():
+ while 1:
+ for screen in [screen_scores, screen_monster, screen_catch,
+ screen_bonus, screen_bubble, screen_die,
+ screen_authors]:
+ lines = screen()
+ if lines:
+ for t in display(lines, 300, just_wait(), 1):
+ yield t
diff --git a/bubbob/save_rnglevel.py b/bubbob/save_rnglevel.py
new file mode 100644
index 0000000..8b09571
--- /dev/null
+++ b/bubbob/save_rnglevel.py
@@ -0,0 +1,125 @@
+#
+# This script outputs the random levels in a format you can
+# save into a file in the levels-directory and bub'n'bros
+# will be able to use it.
+#
+# this accepts the following parameters:
+# -seed N use random seed N for the generation
+#
+
+import sys
+import random
+import string
+sys.path.append('..')
+sys.path.append('../common')
+
+n_lvls = 1
+
+idx = 0
+while idx < len(sys.argv):
+ arg = sys.argv[idx]
+ idx += 1
+ if arg == '-seed':
+ arg = sys.argv[idx]
+ idx += 1
+ print "# Using seed: " + arg + "\n"
+ random.seed(arg)
+
+def printlvl(level):
+ mons = {}
+ monconv = {}
+ tmpmons = {}
+
+ # Populate monster tables
+ for y in range(0,level.HEIGHT):
+ for x in range(0,level.WIDTH):
+ wm = level.wmap[y][x]
+ if wm >= 'a':
+ m = getattr(level, wm)
+ if m.dir == 1:
+ dir = 'L'
+ else:
+ dir = 'R'
+ s = dir + m.cls.__name__
+ if tmpmons.has_key(s):
+ tmpmons[s].append(wm)
+ else:
+ tmpmons[s] = [wm]
+ # Build monster character conversion tables
+ lettr = 'a'
+ for m in tmpmons:
+ for n in tmpmons[m]:
+ monconv[n] = lettr
+ mons[lettr] = m
+ lettr = chr(ord(lettr) + 1)
+
+ # Build walls, replacing monsters from mons[]
+ walls = ""
+
+ for y in range(0,level.HEIGHT-1):
+ walls += "##"
+ for x in range(0,level.WIDTH):
+ wm = level.wmap[y][x]
+ if wm >= 'a':
+ if monconv.has_key(wm):
+ walls += monconv[wm]
+ else:
+ walls += '?'
+ else:
+ walls += wm
+ walls += "##\n"
+ walls += "##"
+ for x in range(0,level.WIDTH):
+ if level.wmap[0][x] == '#' or level.wmap[level.HEIGHT-1][x] == '#':
+ walls += "#"
+ else:
+ walls += " "
+ walls += "##\n"
+
+ # Build winds
+ winds = ""
+ for y in range(0,level.HEIGHT):
+ for x in range(0,level.WIDTH+4):
+ winds += level.winds[y][x]
+ winds += "\n"
+
+ for m in mons:
+ print " " + m + " = " + mons[m]
+
+ if level.letter:
+ print " letter = 1"
+ if level.fire:
+ print " fire = 1"
+ if level.lightning:
+ print " lightning = 1"
+ if level.water:
+ print " water = 1"
+ if level.top:
+ print " top = 1"
+
+ print " walls = \"\"\"\n" + walls + "\"\"\""
+ print " winds = \"\"\"\n" + winds + "\"\"\""
+
+
+for i in range(n_lvls):
+ print """
+import boarddef, mnstrmap, random
+from boarddef import LNasty, LMonky, LGhosty, LFlappy
+from boarddef import LSpringy, LOrcy, LGramy, LBlitzy
+from boarddef import RNasty, RMonky, RGhosty, RFlappy
+from boarddef import RSpringy, ROrcy, RGramy, RBlitzy
+"""
+
+ d = {'__name__': 'RandomLevels'}
+ execfile('levels/RandomLevels.py', d)
+
+ for i, Lvl in enumerate(d['GenerateLevels']()):
+ level = Lvl(i)
+
+ if level.monsters:
+ print "\n\nclass level%02d(boarddef.Level):" % (i+1)
+ else:
+ print "\n\nclass levelFinal(boarddef.Level):"
+
+ printlvl(level)
+ print
diff --git a/bubbob/setup.py b/bubbob/setup.py
new file mode 100755
index 0000000..577d3c5
--- /dev/null
+++ b/bubbob/setup.py
@@ -0,0 +1,22 @@
+#! /usr/bin/env python
+
+from distutils.core import setup
+from distutils.extension import Extension
+
+##setup ( name="gencopy",
+## version="0.1",
+## description="generator and iterator state",
+## author="Armin",
+## author_email="arigo@tunes.org",
+## ext_modules=[Extension(name = 'gencopy',
+## sources = ['gencopy.c'])]
+## )
+
+setup ( name="statesaver",
+ version="0.1",
+ description="object duplicator working on generators and iterators",
+ author="Armin",
+ author_email="arigo@tunes.org",
+ ext_modules=[Extension(name = 'statesaver',
+ sources = ['statesaver.c'])]
+ )
diff --git a/bubbob/sounds/die.wav b/bubbob/sounds/die.wav
new file mode 100644
index 0000000..bea33d9
--- /dev/null
+++ b/bubbob/sounds/die.wav
Binary files differ
diff --git a/bubbob/sounds/extra.wav b/bubbob/sounds/extra.wav
new file mode 100644
index 0000000..a95c01b
--- /dev/null
+++ b/bubbob/sounds/extra.wav
Binary files differ
diff --git a/bubbob/sounds/extralife.wav b/bubbob/sounds/extralife.wav
new file mode 100644
index 0000000..9afd8b2
--- /dev/null
+++ b/bubbob/sounds/extralife.wav
Binary files differ
diff --git a/bubbob/sounds/fruit.wav b/bubbob/sounds/fruit.wav
new file mode 100644
index 0000000..19eafd3
--- /dev/null
+++ b/bubbob/sounds/fruit.wav
Binary files differ
diff --git a/bubbob/sounds/hell.wav b/bubbob/sounds/hell.wav
new file mode 100644
index 0000000..aad531b
--- /dev/null
+++ b/bubbob/sounds/hell.wav
Binary files differ
diff --git a/bubbob/sounds/hurry.wav b/bubbob/sounds/hurry.wav
new file mode 100644
index 0000000..c566e54
--- /dev/null
+++ b/bubbob/sounds/hurry.wav
Binary files differ
diff --git a/bubbob/sounds/jump.wav b/bubbob/sounds/jump.wav
new file mode 100644
index 0000000..8d7c10d
--- /dev/null
+++ b/bubbob/sounds/jump.wav
Binary files differ
diff --git a/bubbob/sounds/letsgo.wav b/bubbob/sounds/letsgo.wav
new file mode 100644
index 0000000..3216cdf
--- /dev/null
+++ b/bubbob/sounds/letsgo.wav
Binary files differ
diff --git a/bubbob/sounds/pop.wav b/bubbob/sounds/pop.wav
new file mode 100644
index 0000000..efddf3d
--- /dev/null
+++ b/bubbob/sounds/pop.wav
Binary files differ
diff --git a/bubbob/sounds/shh.wav b/bubbob/sounds/shh.wav
new file mode 100644
index 0000000..8ea1ca9
--- /dev/null
+++ b/bubbob/sounds/shh.wav
Binary files differ
diff --git a/bubbob/sounds/yippee.wav b/bubbob/sounds/yippee.wav
new file mode 100644
index 0000000..1598140
--- /dev/null
+++ b/bubbob/sounds/yippee.wav
Binary files differ
diff --git a/bubbob/sprmap.py b/bubbob/sprmap.py
new file mode 100644
index 0000000..ce34264
--- /dev/null
+++ b/bubbob/sprmap.py
@@ -0,0 +1,533 @@
+sprmap = {
+ 10:('ice_cyan_big.ppm', (0, 0, 90, 90)),
+ 11:('ice_violet_big.ppm', (0, 0, 90, 90)),
+ 12:('peach_big.ppm', (0, 0, 90, 90)),
+ 13:('pastec_big.ppm', (0, 0, 90, 90)),
+ 14:('cream_pie_big.ppm', (0, 0, 90, 90)),
+ 15:('sugar_pie_big.ppm', (0, 0, 90, 90)),
+ 16:('diamond_big_purple.ppm', (0, 0, 90, 90)),
+ 17:('diamond_big_blue.ppm', (0, 0, 90, 90)),
+ 18:('diamond_big_red.ppm', (0, 0, 90, 90)),
+ 19:('diamond_big_yellow.ppm', (0, 0, 90, 90)),
+ 30:('lightning_large.ppm', (0, 0, 90, 66)),
+ 31:('yellow_Hurry_up.ppm', (0, 0, 136, 24)),
+ 32:('red_Hurry_up.ppm', (0, 0, 136, 24)),
+ 128:('extend.ppm', (0, 0, 32, 32)),
+ 129:('extend.ppm', (0, 32, 32, 32)),
+ 130:('extend.ppm', (0, 64, 32, 32)),
+ 131:('bubble.ppm', (0, 0, 32, 32)),
+ 132:('bubble.ppm', (0, 32, 32, 32)),
+ 133:('bubble.ppm', (0, 64, 32, 32)),
+ 134:('bubble.ppm', (0, 96, 32, 32)),
+ 135:('bubble.ppm', (0, 128, 32, 32)),
+ 136:('extend.ppm', (0, 96, 32, 32)),
+ 137:('extend.ppm', (0, 128, 32, 32)),
+ 138:('extend.ppm', (0, 160, 32, 32)),
+ 139:('door.ppm', (0, 0, 32, 32)),
+ 140:('water_surface.ppm', (0, 0, 16, 16)),
+ 141:('water_surface.ppm', (0, 16, 16, 16)),
+ 142:('water_surface.ppm', (0, 32, 16, 16)),
+ 143:('water_surface.ppm', (0, 48, 16, 16)),
+ 144:('extend.ppm', (0, 192, 32, 32)),
+ 145:('extend.ppm', (0, 224, 32, 32)),
+ 146:('extend.ppm', (0, 256, 32, 32)),
+ 152:('extend.ppm', (0, 288, 32, 32)),
+ 153:('extend.ppm', (0, 320, 32, 32)),
+ 154:('extend.ppm', (0, 352, 32, 32)),
+ 155:('bubble.ppm', (0, 352, 32, 32)),
+ 156:('bubble.ppm', (0, 384, 32, 32)),
+ 157:('bubble.ppm', (0, 416, 32, 32)),
+ 160:('extend.ppm', (0, 384, 32, 32)),
+ 161:('extend.ppm', (0, 416, 32, 32)),
+ 162:('extend.ppm', (0, 448, 32, 32)),
+ 163:('bubble.ppm', (0, 160, 32, 32)),
+ 164:('bubble.ppm', (0, 192, 32, 32)),
+ 165:('bubble.ppm', (0, 224, 32, 32)),
+ 168:('extend.ppm', (0, 480, 32, 32)),
+ 169:('extend.ppm', (0, 512, 32, 32)),
+ 170:('extend.ppm', (0, 544, 32, 32)),
+ 171:('bubble.ppm', (0, 256, 32, 32)),
+ 172:('bubble.ppm', (0, 288, 32, 32)),
+ 173:('bubble.ppm', (0, 320, 32, 32)),
+ 239:('nasty.ppm', (0, 0, 32, 32)),
+ 240:('nasty.ppm', (0, 32, 32, 32)),
+ 241:('nasty.ppm', (0, 64, 32, 32)),
+ 242:('nasty.ppm', (0, 96, 32, 32)),
+ 243:('nasty.ppm', (0, 128, 32, 32)),
+ 244:('nasty.ppm', (0, 160, 32, 32)),
+ 245:('nasty.ppm', (0, 192, 32, 32)),
+ 246:('nasty.ppm', (0, 224, 32, 32)),
+ 247:('nasty.ppm', (0, 256, 32, 32)),
+ 248:('nasty.ppm', (0, 288, 32, 32)),
+ 249:('nasty.ppm', (0, 320, 32, 32)),
+ 253:('nasty.ppm', (0, 352, 32, 32)),
+ 254:('nasty.ppm', (0, 384, 32, 32)),
+ 255:('nasty.ppm', (0, 416, 32, 32)),
+ 256:('nasty.ppm', (0, 448, 32, 32)),
+ 265:('monky.ppm', (0, 0, 32, 32)),
+ 266:('monky.ppm', (0, 32, 32, 32)),
+ 267:('monky.ppm', (0, 64, 32, 32)),
+ 268:('monky.ppm', (0, 96, 32, 32)),
+ 269:('monky.ppm', (0, 128, 32, 32)),
+ 270:('monky.ppm', (0, 160, 32, 32)),
+ 271:('monky.ppm', (0, 192, 32, 32)),
+ 272:('monky.ppm', (0, 224, 32, 32)),
+ 273:('monky.ppm', (0, 256, 32, 32)),
+ 274:('monky.ppm', (0, 288, 32, 32)),
+ 275:('monky.ppm', (0, 320, 32, 32)),
+ 279:('monky.ppm', (0, 352, 32, 32)),
+ 280:('monky.ppm', (0, 384, 32, 32)),
+ 281:('monky.ppm', (0, 416, 32, 32)),
+ 282:('monky.ppm', (0, 448, 32, 32)),
+ 291:('ghosty.ppm', (0, 0, 32, 32)),
+ 292:('ghosty.ppm', (0, 32, 32, 32)),
+ 293:('ghosty.ppm', (0, 64, 32, 32)),
+ 294:('ghosty.ppm', (0, 96, 32, 32)),
+ 295:('ghosty.ppm', (0, 128, 32, 32)),
+ 296:('ghosty.ppm', (0, 160, 32, 32)),
+ 297:('ghosty.ppm', (0, 192, 32, 32)),
+ 298:('ghosty.ppm', (0, 224, 32, 32)),
+ 299:('ghosty.ppm', (0, 256, 32, 32)),
+ 300:('ghosty.ppm', (0, 288, 32, 32)),
+ 301:('ghosty.ppm', (0, 320, 32, 32)),
+ 305:('ghosty.ppm', (0, 352, 32, 32)),
+ 306:('ghosty.ppm', (0, 384, 32, 32)),
+ 307:('ghosty.ppm', (0, 416, 32, 32)),
+ 308:('ghosty.ppm', (0, 448, 32, 32)),
+ 317:('flappy.ppm', (0, 0, 32, 32)),
+ 318:('flappy.ppm', (0, 32, 32, 32)),
+ 319:('flappy.ppm', (0, 64, 32, 32)),
+ 320:('flappy.ppm', (0, 96, 32, 32)),
+ 321:('flappy.ppm', (0, 128, 32, 32)),
+ 322:('flappy.ppm', (0, 160, 32, 32)),
+ 323:('flappy.ppm', (0, 192, 32, 32)),
+ 324:('flappy.ppm', (0, 224, 32, 32)),
+ 325:('flappy.ppm', (0, 256, 32, 32)),
+ 326:('flappy.ppm', (0, 288, 32, 32)),
+ 327:('flappy.ppm', (0, 320, 32, 32)),
+ 331:('flappy.ppm', (0, 352, 32, 32)),
+ 332:('flappy.ppm', (0, 384, 32, 32)),
+ 333:('flappy.ppm', (0, 416, 32, 32)),
+ 334:('flappy.ppm', (0, 448, 32, 32)),
+ 343:('springy.ppm', (0, 0, 32, 32)),
+ 344:('springy.ppm', (0, 32, 32, 32)),
+ 345:('springy.ppm', (0, 64, 32, 32)),
+ 346:('springy.ppm', (0, 96, 32, 32)),
+ 347:('springy.ppm', (0, 128, 32, 32)),
+ 348:('springy.ppm', (0, 160, 32, 32)),
+ 349:('springy.ppm', (0, 192, 32, 32)),
+ 350:('springy.ppm', (0, 224, 32, 32)),
+ 351:('springy.ppm', (0, 256, 32, 32)),
+ 352:('springy.ppm', (0, 288, 32, 32)),
+ 353:('springy.ppm', (0, 320, 32, 32)),
+ 357:('springy.ppm', (0, 352, 32, 32)),
+ 358:('springy.ppm', (0, 384, 32, 32)),
+ 359:('springy.ppm', (0, 416, 32, 32)),
+ 360:('springy.ppm', (0, 448, 32, 32)),
+ 369:('springy.ppm', (0, 480, 32, 32)),
+ 370:('springy.ppm', (0, 512, 32, 32)),
+ 371:('springy.ppm', (0, 544, 32, 32)),
+ 372:('springy.ppm', (0, 576, 32, 32)),
+ 373:('orcy.ppm', (0, 0, 32, 32)),
+ 374:('orcy.ppm', (0, 32, 32, 32)),
+ 375:('orcy.ppm', (0, 64, 32, 32)),
+ 376:('orcy.ppm', (0, 96, 32, 32)),
+ 377:('orcy.ppm', (0, 128, 32, 32)),
+ 378:('orcy.ppm', (0, 160, 32, 32)),
+ 379:('orcy.ppm', (0, 192, 32, 32)),
+ 380:('orcy.ppm', (0, 224, 32, 32)),
+ 381:('orcy.ppm', (0, 256, 32, 32)),
+ 382:('orcy.ppm', (0, 288, 32, 32)),
+ 383:('orcy.ppm', (0, 320, 32, 32)),
+ 387:('orcy.ppm', (0, 352, 32, 32)),
+ 388:('orcy.ppm', (0, 384, 32, 32)),
+ 389:('orcy.ppm', (0, 416, 32, 32)),
+ 390:('orcy.ppm', (0, 448, 32, 32)),
+ 399:('gramy.ppm', (0, 0, 32, 32)),
+ 400:('gramy.ppm', (0, 32, 32, 32)),
+ 401:('gramy.ppm', (0, 64, 32, 32)),
+ 402:('gramy.ppm', (0, 96, 32, 32)),
+ 403:('gramy.ppm', (0, 128, 32, 32)),
+ 404:('gramy.ppm', (0, 160, 32, 32)),
+ 405:('gramy.ppm', (0, 192, 32, 32)),
+ 406:('gramy.ppm', (0, 224, 32, 32)),
+ 407:('gramy.ppm', (0, 256, 32, 32)),
+ 408:('gramy.ppm', (0, 288, 32, 32)),
+ 409:('gramy.ppm', (0, 320, 32, 32)),
+ 413:('gramy.ppm', (0, 352, 32, 32)),
+ 414:('gramy.ppm', (0, 384, 32, 32)),
+ 415:('gramy.ppm', (0, 416, 32, 32)),
+ 416:('gramy.ppm', (0, 448, 32, 32)),
+ 425:('blitzy.ppm', (0, 0, 32, 32)),
+ 426:('blitzy.ppm', (0, 32, 32, 32)),
+ 427:('blitzy.ppm', (0, 64, 32, 32)),
+ 428:('blitzy.ppm', (0, 96, 32, 32)),
+ 429:('blitzy.ppm', (0, 128, 32, 32)),
+ 430:('blitzy.ppm', (0, 160, 32, 32)),
+ 431:('blitzy.ppm', (0, 192, 32, 32)),
+ 435:('blitzy.ppm', (0, 224, 32, 32)),
+ 436:('blitzy.ppm', (0, 256, 32, 32)),
+ 437:('blitzy.ppm', (0, 288, 32, 32)),
+ 438:('blitzy.ppm', (0, 320, 32, 32)),
+ 443:('ghost.ppm', (0, 0, 32, 32)),
+ 444:('ghost.ppm', (0, 32, 32, 32)),
+ 445:('ghost.ppm', (0, 64, 32, 32)),
+ 446:('ghost.ppm', (0, 96, 32, 32)),
+ 447:('ghost.ppm', (0, 128, 32, 32)),
+ 448:('ghost.ppm', (0, 160, 32, 32)),
+ 449:('ghost.ppm', (0, 192, 32, 32)),
+ 450:('ghost.ppm', (0, 224, 32, 32)),
+ 451:('monky.ppm', (0, 480, 32, 32)),
+ 452:('monky.ppm', (0, 512, 32, 32)),
+ 453:('monky.ppm', (0, 544, 32, 32)),
+ 454:('monky.ppm', (0, 576, 32, 32)),
+ 455:('monky.ppm', (0, 608, 32, 32)),
+ 456:('orcy.ppm', (0, 480, 32, 32)),
+ 457:('orcy.ppm', (0, 512, 32, 32)),
+ 458:('orcy.ppm', (0, 544, 32, 32)),
+ 459:('orcy.ppm', (0, 576, 32, 32)),
+ 460:('orcy.ppm', (0, 608, 32, 32)),
+ 461:('orcy.ppm', (0, 640, 32, 32)),
+ 462:('orcy.ppm', (0, 672, 32, 32)),
+ 463:('orcy.ppm', (0, 704, 32, 32)),
+ 464:('shot.ppm', (0, 0, 32, 32)),
+ 465:('shot.ppm', (0, 32, 32, 32)),
+ 466:('shot.ppm', (0, 64, 32, 32)),
+ 467:('shot.ppm', (0, 96, 32, 32)),
+ 468:('shot.ppm', (0, 128, 32, 32)),
+ 469:('shot.ppm', (0, 160, 32, 32)),
+ 470:('shot.ppm', (0, 192, 32, 32)),
+ 471:('shot.ppm', (0, 224, 32, 32)),
+ 472:('gramy.ppm', (0, 480, 32, 32)),
+ 473:('gramy.ppm', (0, 512, 32, 32)),
+ 474:('gramy.ppm', (0, 544, 32, 32)),
+ 475:('gramy.ppm', (0, 576, 32, 32)),
+ 476:('blitzy_shot.ppm', (0, 0, 16, 32)),
+ 477:('bonus_0.ppm', (0, 0, 32, 32)),
+ 478:('bonus_0.ppm', (0, 32, 32, 32)),
+ 479:('bonus_0.ppm', (0, 64, 32, 32)),
+ 480:('bonus_0.ppm', (0, 96, 32, 32)),
+ 481:('bonus_0.ppm', (0, 128, 32, 32)),
+ 482:('spinning_drop.ppm', (0, 0, 16, 16)),
+ 483:('spinning_drop.ppm', (0, 16, 16, 16)),
+ 484:('spinning_drop.ppm', (0, 32, 16, 16)),
+ 485:('spinning_drop.ppm', (0, 48, 16, 16)),
+ 486:('spinning_drop.ppm', (0, 64, 16, 16)),
+ 487:('spinning_drop.ppm', (0, 80, 16, 16)),
+ 488:('lightning_small.ppm', (0, 0, 24, 24)),
+ 489:('fire_drop.ppm', (0, 0, 9, 16)),
+ 490:('fire_surface.ppm', (0, 0, 16, 16)),
+ 491:('fire_surface.ppm', (0, 16, 16, 16)),
+ 492:('fire_surface.ppm', (0, 32, 16, 16)),
+ 493:('fire_surface.ppm', (0, 48, 16, 16)),
+ 495:('water_still.ppm', (0, 0, 16, 16)),
+ 496:('bonus_0.ppm', (0, 160, 32, 20)),
+ 519:('level_digits.ppm', (0, 0, 14, 20)),
+ 520:('level_digits.ppm', (0, 20, 14, 20)),
+ 521:('level_digits.ppm', (0, 40, 14, 20)),
+ 522:('level_digits.ppm', (0, 60, 14, 20)),
+ 523:('level_digits.ppm', (0, 80, 14, 20)),
+ 524:('level_digits.ppm', (0, 100, 14, 20)),
+ 525:('level_digits.ppm', (0, 120, 14, 20)),
+ 526:('level_digits.ppm', (0, 140, 14, 20)),
+ 527:('level_digits.ppm', (0, 160, 14, 20)),
+ 528:('level_digits.ppm', (0, 180, 14, 20)),
+ 593:('bonus_1.ppm', (0, 0, 32, 32)),
+ 594:('bonus_1.ppm', (0, 32, 32, 32)),
+ 595:('bonus_1.ppm', (0, 64, 32, 32)),
+ 596:('bonus_1.ppm', (0, 96, 32, 32)),
+ 597:('bonus_1.ppm', (0, 128, 32, 32)),
+ 598:('bonus_1.ppm', (0, 160, 32, 32)),
+ 599:('bonus_1.ppm', (0, 192, 32, 32)),
+ 600:('bonus_1.ppm', (0, 224, 32, 32)),
+ 601:('bonus_2.ppm', (0, 0, 32, 32)),
+ 602:('bonus_2.ppm', (0, 32, 32, 32)),
+ 603:('bonus_2.ppm', (0, 64, 32, 32)),
+ 604:('bonus_2.ppm', (0, 96, 32, 32)),
+ 605:('bonus_2.ppm', (0, 128, 32, 32)),
+ 606:('bonus_2.ppm', (0, 160, 32, 32)),
+ 607:('bonus_2.ppm', (0, 192, 32, 32)),
+ 608:('bonus_2.ppm', (0, 224, 32, 32)),
+ 609:('bonus_3.ppm', (0, 0, 32, 32)),
+ 610:('bonus_3.ppm', (0, 32, 32, 32)),
+ 611:('bonus_3.ppm', (0, 64, 32, 32)),
+ 612:('bonus_3.ppm', (0, 96, 32, 32)),
+ 613:('bonus_3.ppm', (0, 128, 32, 32)),
+ 614:('bonus_3.ppm', (0, 160, 32, 32)),
+ 615:('bonus_3.ppm', (0, 192, 32, 32)),
+ 616:('bonus_3.ppm', (0, 224, 32, 32)),
+ 617:('bonus_4.ppm', (0, 0, 32, 32)),
+ 618:('bonus_4.ppm', (0, 32, 32, 32)),
+ 619:('bonus_4.ppm', (0, 64, 32, 32)),
+ 620:('bonus_4.ppm', (0, 96, 32, 32)),
+ 621:('bonus_4.ppm', (0, 128, 32, 32)),
+ 622:('bonus_4.ppm', (0, 160, 32, 32)),
+ 623:('bonus_4.ppm', (0, 192, 32, 32)),
+ 624:('bonus_4.ppm', (0, 224, 32, 32)),
+ 625:('bonus_5.ppm', (0, 0, 32, 32)),
+ 626:('bonus_5.ppm', (0, 32, 32, 32)),
+ 627:('bonus_5.ppm', (0, 64, 32, 32)),
+ 628:('bonus_5.ppm', (0, 96, 32, 32)),
+ 629:('bonus_5.ppm', (0, 128, 32, 32)),
+ 630:('bonus_5.ppm', (0, 160, 32, 32)),
+ 631:('bonus_5.ppm', (0, 192, 32, 32)),
+ 632:('bonus_5.ppm', (0, 224, 32, 32)),
+ 633:('bonus_6.ppm', (0, 0, 32, 32)),
+ 634:('bonus_6.ppm', (0, 32, 32, 32)),
+ 635:('bonus_6.ppm', (0, 64, 32, 32)),
+ 636:('bonus_6.ppm', (0, 96, 32, 32)),
+ 637:('bonus_6.ppm', (0, 128, 32, 32)),
+ 638:('bonus_6.ppm', (0, 160, 32, 32)),
+ 639:('bonus_6.ppm', (0, 192, 32, 32)),
+ 640:('bonus_6.ppm', (0, 224, 32, 32)),
+ 641:('bonus_7.ppm', (0, 0, 32, 32)),
+ 642:('bonus_7.ppm', (0, 32, 32, 32)),
+ 643:('bonus_7.ppm', (0, 64, 32, 32)),
+ 644:('bonus_7.ppm', (0, 96, 32, 32)),
+ 645:('bonus_7.ppm', (0, 128, 32, 32)),
+ 646:('bonus_7.ppm', (0, 160, 32, 32)),
+ 647:('bonus_7.ppm', (0, 192, 32, 32)),
+ 648:('bonus_7.ppm', (0, 224, 32, 32)),
+ 649:('bonus_8.ppm', (0, 0, 32, 32)),
+ 650:('bonus_8.ppm', (0, 32, 32, 32)),
+ 651:('bonus_8.ppm', (0, 64, 32, 32)),
+ 652:('bonus_8.ppm', (0, 96, 32, 32)),
+ 653:('bonus_8.ppm', (0, 128, 32, 32)),
+ 654:('bonus_8.ppm', (0, 160, 32, 32)),
+ 655:('bonus_8.ppm', (0, 192, 32, 32)),
+ 656:('bonus_8.ppm', (0, 224, 32, 32)),
+ 657:('bonus_9.ppm', (0, 0, 32, 32)),
+ 658:('bonus_9.ppm', (0, 32, 32, 32)),
+ 659:('bonus_9.ppm', (0, 64, 32, 32)),
+ 660:('bonus_9.ppm', (0, 96, 32, 32)),
+ 661:('bonus_9.ppm', (0, 128, 32, 32)),
+ 662:('bonus_9.ppm', (0, 160, 32, 32)),
+ 663:('bonus_9.ppm', (0, 192, 32, 32)),
+ 664:('bonus_9.ppm', (0, 224, 32, 32)),
+ 665:('bonus_10.ppm', (0, 0, 32, 32)),
+ 666:('bonus_10.ppm', (0, 32, 32, 32)),
+ 667:('bonus_10.ppm', (0, 64, 32, 32)),
+ 668:('bonus_10.ppm', (0, 96, 32, 32)),
+ 669:('bonus_10.ppm', (0, 128, 32, 32)),
+ 670:('bonus_10.ppm', (0, 160, 32, 32)),
+ 671:('bonus_10.ppm', (0, 192, 32, 32)),
+ 672:('bonus_10.ppm', (0, 224, 32, 32)),
+ 673:('bonus_11.ppm', (0, 0, 32, 32)),
+ 674:('bonus_11.ppm', (0, 32, 32, 32)),
+ 675:('bonus_11.ppm', (0, 64, 32, 32)),
+ 676:('bonus_11.ppm', (0, 96, 32, 32)),
+ 677:('bonus_11.ppm', (0, 128, 32, 32)),
+ 678:('bonus_11.ppm', (0, 160, 32, 32)),
+ 679:('bonus_11.ppm', (0, 192, 32, 32)),
+ 680:('bonus_11.ppm', (0, 224, 32, 32)),
+ 681:('bonus_12.ppm', (0, 0, 32, 32)),
+ 682:('bonus_12.ppm', (0, 32, 32, 32)),
+ 691:('bonus_12.ppm', (0, 64, 32, 32)),
+ 692:('bonus_12.ppm', (0, 96, 32, 32)),
+ 800:('nasty_angry.ppm', (0, 0, 32, 32)),
+ 801:('nasty_angry.ppm', (0, 32, 32, 32)),
+ 802:('nasty_angry.ppm', (0, 64, 32, 32)),
+ 803:('nasty_angry.ppm', (0, 96, 32, 32)),
+ 804:('nasty_angry.ppm', (0, 128, 32, 32)),
+ 805:('nasty_angry.ppm', (0, 160, 32, 32)),
+ 806:('nasty_angry.ppm', (0, 192, 32, 32)),
+ 807:('nasty_angry.ppm', (0, 224, 32, 32)),
+ 808:('monky_angry.ppm', (0, 0, 32, 32)),
+ 809:('monky_angry.ppm', (0, 32, 32, 32)),
+ 810:('monky_angry.ppm', (0, 64, 32, 32)),
+ 811:('monky_angry.ppm', (0, 96, 32, 32)),
+ 812:('monky_angry.ppm', (0, 128, 32, 32)),
+ 813:('monky_angry.ppm', (0, 160, 32, 32)),
+ 814:('monky_angry.ppm', (0, 192, 32, 32)),
+ 815:('monky_angry.ppm', (0, 224, 32, 32)),
+ 816:('ghosty_angry.ppm', (0, 0, 32, 32)),
+ 817:('ghosty_angry.ppm', (0, 32, 32, 32)),
+ 818:('ghosty_angry.ppm', (0, 64, 32, 32)),
+ 819:('ghosty_angry.ppm', (0, 96, 32, 32)),
+ 820:('ghosty_angry.ppm', (0, 128, 32, 32)),
+ 821:('ghosty_angry.ppm', (0, 160, 32, 32)),
+ 822:('ghosty_angry.ppm', (0, 192, 32, 32)),
+ 823:('ghosty_angry.ppm', (0, 224, 32, 32)),
+ 824:('flapy_angry.ppm', (0, 0, 32, 32)),
+ 825:('flapy_angry.ppm', (0, 32, 32, 32)),
+ 826:('flapy_angry.ppm', (0, 64, 32, 32)),
+ 827:('flapy_angry.ppm', (0, 96, 32, 32)),
+ 828:('flapy_angry.ppm', (0, 128, 32, 32)),
+ 829:('flapy_angry.ppm', (0, 160, 32, 32)),
+ 830:('flapy_angry.ppm', (0, 192, 32, 32)),
+ 831:('flapy_angry.ppm', (0, 224, 32, 32)),
+ 832:('springy_angry.ppm', (0, 0, 32, 32)),
+ 833:('springy_angry.ppm', (0, 32, 32, 32)),
+ 834:('springy_angry.ppm', (0, 64, 32, 32)),
+ 835:('springy_angry.ppm', (0, 96, 32, 32)),
+ 836:('springy_angry.ppm', (0, 128, 32, 32)),
+ 837:('springy_angry.ppm', (0, 160, 32, 32)),
+ 838:('springy_angry.ppm', (0, 192, 32, 32)),
+ 839:('springy_angry.ppm', (0, 224, 32, 32)),
+ 840:('springy_angry.ppm', (0, 256, 32, 32)),
+ 841:('springy_angry.ppm', (0, 288, 32, 32)),
+ 842:('springy_angry.ppm', (0, 320, 32, 32)),
+ 843:('springy_angry.ppm', (0, 352, 32, 32)),
+ 844:('orcy_angry.ppm', (0, 0, 32, 32)),
+ 845:('orcy_angry.ppm', (0, 32, 32, 32)),
+ 846:('orcy_angry.ppm', (0, 64, 32, 32)),
+ 847:('orcy_angry.ppm', (0, 96, 32, 32)),
+ 848:('orcy_angry.ppm', (0, 128, 32, 32)),
+ 849:('orcy_angry.ppm', (0, 160, 32, 32)),
+ 850:('orcy_angry.ppm', (0, 192, 32, 32)),
+ 851:('orcy_angry.ppm', (0, 224, 32, 32)),
+ 852:('gramy_angry.ppm', (0, 0, 32, 32)),
+ 853:('gramy_angry.ppm', (0, 32, 32, 32)),
+ 854:('gramy_angry.ppm', (0, 64, 32, 32)),
+ 855:('gramy_angry.ppm', (0, 96, 32, 32)),
+ 856:('gramy_angry.ppm', (0, 128, 32, 32)),
+ 857:('gramy_angry.ppm', (0, 160, 32, 32)),
+ 858:('gramy_angry.ppm', (0, 192, 32, 32)),
+ 859:('gramy_angry.ppm', (0, 224, 32, 32)),
+ 860:('blitzy_angry.ppm', (0, 0, 32, 32)),
+ 861:('blitzy_angry.ppm', (0, 32, 32, 32)),
+ 862:('blitzy_angry.ppm', (0, 64, 32, 32)),
+ 863:('blitzy_angry.ppm', (0, 96, 32, 32)),
+ 900:('water_flow.ppm', (0, 0, 16, 16)),
+ 901:('water_flow.ppm', (0, 16, 16, 16)),
+ 902:('water_flow.ppm', (0, 32, 16, 16)),
+ 903:('water_flow.ppm', (0, 48, 16, 16)),
+ 904:('water_flow.ppm', (0, 64, 16, 16)),
+ 905:('water_flow.ppm', (0, 80, 16, 16)),
+ 906:('water_flow.ppm', (0, 96, 16, 16)),
+ 907:('water_flow.ppm', (0, 112, 16, 16)),
+ 908:('water_flow.ppm', (0, 128, 16, 16)),
+ 909:('water_flow.ppm', (0, 144, 16, 16)),
+ 910:('big_bubble.ppm', (0, 0, 64, 64)),
+ 911:('big_bubble.ppm', (0, 64, 64, 64)),
+ 912:('big_bubble.ppm', (0, 128, 64, 64)),
+ 913:('big_bubble.ppm', (0, 192, 64, 64)),
+ 914:('big_bubble.ppm', (0, 256, 64, 64)),
+ 915:('big_bubble.ppm', (0, 320, 64, 64)),
+ 920:('level_digits.ppm', (0, 200, 14, 20)),
+ 921:('level_digits.ppm', (0, 220, 14, 20)),
+ 922:('level_digits.ppm', (0, 240, 14, 20)),
+ 923:('level_digits.ppm', (0, 260, 14, 20)),
+ 924:('level_digits.ppm', (0, 280, 14, 20)),
+ 925:('level_digits.ppm', (0, 300, 14, 20)),
+ 926:('level_digits.ppm', (0, 320, 14, 20)),
+ 927:('level_digits.ppm', (0, 340, 14, 20)),
+ 928:('level_digits.ppm', (0, 360, 14, 20)),
+ 929:('level_digits.ppm', (0, 380, 14, 20)),
+ 930:('level_digits.ppm', (0, 400, 14, 20)),
+ 931:('level_digits.ppm', (0, 420, 14, 20)),
+ 932:('level_digits.ppm', (0, 440, 14, 20)),
+ 933:('level_digits.ppm', (0, 460, 14, 20)),
+ 934:('level_digits.ppm', (0, 480, 14, 20)),
+ 935:('level_digits.ppm', (0, 500, 14, 20)),
+ 936:('level_digits.ppm', (0, 520, 14, 20)),
+ 937:('level_digits.ppm', (0, 540, 14, 20)),
+ 938:('level_digits.ppm', (0, 560, 14, 20)),
+ 939:('level_digits.ppm', (0, 580, 14, 20)),
+ 940:('star_large.ppm', (0, 0, 32, 32)),
+ 941:('star_large.ppm', (0, 32, 32, 32)),
+ 942:('star_large.ppm', (0, 64, 32, 32)),
+ 943:('star_large.ppm', (0, 96, 32, 32)),
+ 944:('star_large.ppm', (0, 128, 32, 32)),
+ 945:('star_large.ppm', (0, 160, 32, 32)),
+ 946:('star_large.ppm', (0, 192, 32, 32)),
+ 947:('star_large.ppm', (0, 224, 32, 32)),
+ 948:('star_large.ppm', (0, 256, 32, 32)),
+ 949:('star_large.ppm', (0, 288, 32, 32)),
+ 950:('star_large.ppm', (0, 320, 32, 32)),
+ 951:('star_large.ppm', (0, 352, 32, 32)),
+ 952:('big_bubble_2.ppm', (0, 0, 64, 64)),
+ 953:('big_bubble_2.ppm', (0, 64, 64, 64)),
+ 954:('big_bubble_2.ppm', (0, 128, 64, 64)),
+ 955:('big_bubble_2.ppm', (0, 192, 64, 64)),
+ 956:('big_bubble_2.ppm', (0, 256, 64, 64)),
+
+ 20:('10000_%d.ppm', (0, 0, 90, 40)),
+ 21:('20000_%d.ppm', (0, 0, 98, 45)),
+ 22:('30000_%d.ppm', (0, 0, 98, 45)),
+ 23:('40000_%d.ppm', (0, 0, 98, 45)),
+ 24:('50000_%d.ppm', (0, 0, 95, 45)),
+ 25:('60000_%d.ppm', (0, 0, 96, 45)),
+ 26:('70000_%d.ppm', (0, 0, 96, 45)),
+ 176:('dragon_bubble_%d.ppm', (0, 0, 32, 32)),
+ 177:('dragon_bubble_%d.ppm', (0, 32, 32, 32)),
+ 178:('dragon_bubble_%d.ppm', (0, 64, 32, 32)),
+ 179:('dragon_bubble_%d.ppm', (0, 96, 32, 32)),
+ 180:('dragon_bubble_%d.ppm', (0, 128, 32, 32)),
+ 181:('dragon_bubble_%d.ppm', (0, 160, 32, 32)),
+ 182:('dragon_bubble_%d.ppm', (0, 192, 32, 32)),
+ 183:('dragon_bubble_%d.ppm', (0, 224, 32, 32)),
+ 184:('dragon_bubble_%d.ppm', (0, 256, 32, 32)),
+ 188:('dragon_bubble_%d.ppm', (0, 288, 32, 32)),
+ 189:('dragon_bubble_%d.ppm', (0, 320, 32, 32)),
+ 190:('dragon_bubble_%d.ppm', (0, 352, 32, 32)),
+ 191:('dragon_bubble_%d.ppm', (0, 384, 32, 32)),
+ 192:('dragon_bubble_%d.ppm', (0, 416, 32, 32)),
+ 193:('dragon_bubble_%d.ppm', (0, 448, 32, 32)),
+ 194:('dragon_bubble_%d.ppm', (0, 480, 32, 32)),
+ 210:('dragon_%d.ppm', (0, 0, 32, 32)),
+ 211:('dragon_%d.ppm', (0, 32, 32, 32)),
+ 212:('dragon_%d.ppm', (0, 64, 32, 32)),
+ 213:('dragon_%d.ppm', (0, 96, 32, 32)),
+ 214:('dragon_%d.ppm', (0, 128, 32, 32)),
+ 215:('dragon_%d.ppm', (0, 160, 32, 32)),
+ 216:('dragon_%d.ppm', (0, 192, 32, 32)),
+ 217:('dragon_%d.ppm', (0, 224, 32, 32)),
+ 218:('dragon_%d.ppm', (0, 256, 32, 32)),
+ 219:('dragon_%d.ppm', (0, 288, 32, 32)),
+ 220:('dragon_%d.ppm', (0, 320, 32, 32)),
+ 221:('dragon_%d.ppm', (0, 352, 32, 32)),
+ 222:('dragon_%d.ppm', (0, 384, 32, 32)),
+ 497:('game_over_%d.ppm', (0, 0, 64, 32)),
+ 499:('digits_%d.ppm', (0, 0, 14, 17)),
+ 500:('digits_%d.ppm', (0, 17, 14, 17)),
+ 501:('digits_%d.ppm', (0, 34, 14, 17)),
+ 502:('digits_%d.ppm', (0, 51, 14, 17)),
+ 503:('digits_%d.ppm', (0, 68, 14, 17)),
+ 504:('digits_%d.ppm', (0, 85, 14, 17)),
+ 505:('digits_%d.ppm', (0, 102, 14, 17)),
+ 506:('digits_%d.ppm', (0, 119, 14, 17)),
+ 507:('digits_%d.ppm', (0, 136, 14, 17)),
+ 508:('digits_%d.ppm', (0, 153, 14, 17)),
+ 529:('point_%d.ppm', (0, 0, 48, 24)),
+ 530:('point_%d.ppm', (0, 24, 48, 24)),
+ 531:('point_%d.ppm', (0, 48, 48, 24)),
+ 532:('point_%d.ppm', (0, 72, 48, 24)),
+ 533:('point_%d.ppm', (0, 96, 48, 24)),
+ 534:('point_%d.ppm', (0, 120, 48, 24)),
+ 535:('point_%d.ppm', (0, 144, 48, 24)),
+ 536:('point_%d.ppm', (0, 168, 48, 24)),
+ 537:('point_%d.ppm', (0, 192, 48, 24)),
+ 538:('point_%d.ppm', (0, 216, 48, 24)),
+ 539:('point_%d.ppm', (0, 240, 48, 24)),
+ 540:('point_%d.ppm', (0, 264, 48, 24)),
+ 541:('point_%d.ppm', (0, 288, 48, 24)),
+ 542:('point_%d.ppm', (0, 312, 48, 24)),
+ 543:('point_%d.ppm', (0, 336, 48, 24)),
+ 544:('point_%d.ppm', (0, 360, 48, 24)),
+ 545:('point_%d.ppm', (0, 384, 48, 24)),
+ 546:('point_%d.ppm', (0, 408, 48, 24)),
+ 547:('point_%d.ppm', (0, 432, 48, 24)),
+ 548:('point_%d.ppm', (0, 456, 48, 24)),
+ 549:('point_%d.ppm', (0, 480, 48, 24)),
+ 550:('point_%d.ppm', (0, 504, 48, 24)),
+ 551:('point_%d.ppm', (0, 528, 48, 24)),
+ 552:('point_%d.ppm', (0, 552, 48, 24)),
+ 553:('point_%d.ppm', (0, 576, 48, 24)),
+ 683:('dragon_%d.ppm', (0, 416, 32, 32)),
+ 684:('dragon_%d.ppm', (0, 448, 32, 32)),
+ 685:('dragon_%d.ppm', (0, 480, 32, 32)),
+ 686:('dragon_%d.ppm', (0, 512, 32, 32)),
+ 693:('dragon_%d.ppm', (0, 544, 32, 32)),
+ 694:('dragon_%d.ppm', (0, 576, 32, 32)),
+ 695:('dragon_%d.ppm', (0, 608, 32, 32)),
+
+ # custom images
+ 700:('fish_%d.ppm', (0, 0, 32, 32)),
+ 701:('fish_%d.ppm', (0, 32, 32, 32)),
+ 702:('fish_%d.ppm', (0, 64, 32, 32)),
+ 703:('fish_%d.ppm', (0, 96, 32, 32)),
+ 704:('fish_%d.ppm', (0, 128, 32, 32)),
+ 705:('fish_%d.ppm', (0, 160, 32, 32)),
+ 706:('fish_%d.ppm', (0, 192, 32, 32)),
+ }
diff --git a/bubbob/statesaver.c b/bubbob/statesaver.c
new file mode 100644
index 0000000..5baf033
--- /dev/null
+++ b/bubbob/statesaver.c
@@ -0,0 +1,610 @@
+/** High-performance deep copy.
+
+ This one can copy running generators and their frames!
+
+ statesaver.copy(x) -> recursive copy of x
+
+ You have precise control over what is copied and what should be shared.
+ By default, *only* common built-in types are copied. Unrecognized
+ object types are shared. The copied built-in types are:
+
+ - tuple
+ - list
+ - dict
+ - functions, for possibly mutable func_defaults (func_globals is shared)
+ - methods, for im_self and im_func (im_class is shared)
+ - running or stopped generators (yeah!)
+ - sequence iterators
+
+ Old-style class instances are only copied if they have an
+ inst_build() method, which is called with no argument and must
+ return a new instance whose __dict__ is not filled (it will be
+ filled by the copying mecanisms). Suggested implementation:
+
+ def inst_build(self):
+ return new.instance(self.__class__)
+
+ New-style class instances are not supported (i.e. always shared).
+**/
+
+#include <Python.h>
+#include <compile.h>
+#include <frameobject.h>
+#include <eval.h>
+
+
+static PyObject* copyrec(PyObject* o); /* forward */
+
+static PyObject* empty_iterator;
+
+
+static PyObject* genbuild(PyObject* g)
+{
+ PyObject* x;
+ PyFrameObject* f;
+ PyCodeObject* co;
+ PyObject** dummy;
+ int i, res, ncells, nfrees;
+
+ x = PyObject_GetAttrString(g, "gi_running");
+ if (x == NULL)
+ return NULL;
+ res = PyObject_IsTrue(x);
+ Py_DECREF(x);
+ if (res < 0)
+ return NULL;
+ if (res) {
+ PyErr_SetString(PyExc_ValueError, "generator is running");
+ return NULL;
+ }
+
+ x = PyObject_GetAttrString(g, "gi_frame");
+ if (x == NULL)
+ return NULL;
+ if (!PyFrame_Check(x)) {
+ if (x == Py_None) {
+ /* Python 2.5 only: exhausted generators have g.gi_frame == None */
+ Py_DECREF(x);
+ Py_INCREF(empty_iterator);
+ return empty_iterator;
+ }
+ PyErr_SetString(PyExc_TypeError, "g.gi_frame must be a frame object");
+ goto error;
+ }
+ f = (PyFrameObject*) x;
+ co = f->f_code;
+
+ if (!(co->co_flags & CO_GENERATOR)) {
+ PyErr_SetString(PyExc_ValueError, "the frame is not from a generator");
+ goto error;
+ }
+ if (f->f_stacktop == NULL) {
+ Py_DECREF(f);
+ Py_INCREF(g); /* exhausted -- can return 'g' itself */
+ return g;
+ }
+ ncells = PyTuple_GET_SIZE(co->co_cellvars);
+ nfrees = PyTuple_GET_SIZE(co->co_freevars);
+ if (nfrees || ncells) {
+ PyErr_SetString(PyExc_ValueError, "generator has cell or free vars");
+ goto error;
+ }
+
+ if (co->co_argcount == 0)
+ dummy = NULL;
+ else
+ {
+ dummy = (PyObject**) malloc(co->co_argcount * sizeof(PyObject*));
+ if (dummy == NULL)
+ {
+ PyErr_NoMemory();
+ goto error;
+ }
+ for (i=0; i<co->co_argcount; i++)
+ dummy[i] = Py_None;
+ }
+ x = PyEval_EvalCodeEx(co, f->f_globals, f->f_locals,
+ dummy, co->co_argcount, NULL, 0,
+ NULL, 0, NULL);
+ if (dummy)
+ free(dummy);
+ Py_DECREF(f);
+ return x;
+
+ error:
+ Py_DECREF(x);
+ return NULL;
+}
+
+static int gencopy(PyObject* g2, PyObject* g)
+{
+ PyObject* x;
+ PyFrameObject* f = NULL;
+ PyFrameObject* f2 = NULL;
+ PyCodeObject* co;
+ int i, res;
+
+ if (g != g2)
+ {
+ if (g2->ob_type != g->ob_type)
+ {
+ if (g2 == empty_iterator)
+ return 0;
+ PyErr_SetString(PyExc_TypeError, "type mismatch");
+ return -1;
+ }
+
+ x = PyObject_GetAttrString(g, "gi_frame");
+ if (x == NULL)
+ return -1;
+ if (!PyFrame_Check(x)) {
+ PyErr_SetString(PyExc_TypeError, "g.gi_frame must be a frame object");
+ Py_DECREF(x);
+ goto error;
+ }
+ f = (PyFrameObject*) x;
+ co = f->f_code;
+
+ x = PyObject_GetAttrString(g2, "gi_frame");
+ if (x == NULL)
+ return -1;
+ if (!PyFrame_Check(x)) {
+ PyErr_SetString(PyExc_TypeError, "returned gi_frame");
+ Py_DECREF(x);
+ goto error;
+ }
+ f2 = (PyFrameObject*) x;
+
+ if (f2->f_code != co) {
+ PyErr_SetString(PyExc_TypeError, "generator code mismatch");
+ goto error;
+ }
+
+ if (f2->f_stacktop != NULL)
+ while (f2->f_stacktop != f2->f_localsplus)
+ {
+ f2->f_stacktop--;
+ Py_XDECREF(*f2->f_stacktop);
+ }
+
+ res = f->f_stacktop - f->f_localsplus;
+ f2->f_lasti = f->f_lasti;
+ f2->f_iblock = f->f_iblock;
+ memcpy(f2->f_blockstack, f->f_blockstack, sizeof(PyTryBlock)*f->f_iblock);
+ f2->f_stacktop = f2->f_localsplus;
+ for (i=0; i<res; i++)
+ {
+ x = f->f_localsplus[i];
+ if (x != NULL)
+ x = copyrec(x);
+ *f2->f_stacktop++ = x;
+ }
+ }
+ return 0;
+
+ error:
+ Py_XDECREF(f);
+ Py_XDECREF(f2);
+ return -1;
+}
+
+
+typedef struct {
+ PyObject_HEAD
+ long it_index;
+ PyObject *it_seq; /* Set to NULL when iterator is exhausted */
+} seqiterobject;
+static PyObject* seqiterbuild(PyObject* o)
+{
+ seqiterobject* iter = (seqiterobject*) o;
+ if (iter->it_seq == NULL)
+ {
+ Py_INCREF(iter); /* exhausted */
+ return (PyObject*) iter;
+ }
+ else
+ return PySeqIter_New(iter->it_seq);
+}
+static int seqitercopy(PyObject* o2, PyObject* o)
+{
+ PyObject* x;
+ seqiterobject* iter = (seqiterobject*) o;
+ seqiterobject* iter2 = (seqiterobject*) o2;
+
+ iter2->it_index = iter->it_index;
+ if (iter->it_seq != NULL)
+ {
+ x = copyrec(iter->it_seq);
+ Py_XDECREF(iter2->it_seq);
+ iter2->it_seq = x;
+ }
+ return 0;
+}
+
+#if PY_VERSION_HEX >= 0x02030000 /* 2.3 */
+/* pff */
+typedef struct {
+ PyObject_HEAD
+ long it_index;
+ PyListObject *it_seq; /* Set to NULL when iterator is exhausted */
+} listiterobject;
+static PyTypeObject* PyListIter_TypePtr;
+static PyObject* listiterbuild(PyObject* o)
+{
+ listiterobject* iter = (listiterobject*) o;
+ if (iter->it_seq == NULL)
+ {
+ Py_INCREF(iter); /* exhausted */
+ return (PyObject*) iter;
+ }
+ else
+ return PyList_Type.tp_iter((PyObject*) iter->it_seq);
+}
+static int listitercopy(PyObject* o2, PyObject* o)
+{
+ PyObject* x;
+ listiterobject* iter = (listiterobject*) o;
+ listiterobject* iter2 = (listiterobject*) o2;
+
+ iter2->it_index = iter->it_index;
+ if (iter->it_seq != NULL)
+ {
+ x = copyrec((PyObject*) iter->it_seq);
+ Py_XDECREF(iter2->it_seq);
+ iter2->it_seq = (PyListObject*) x;
+ }
+ return 0;
+}
+
+typedef struct {
+ PyObject_HEAD
+ long it_index;
+ PyTupleObject *it_seq; /* Set to NULL when iterator is exhausted */
+} tupleiterobject;
+static PyTypeObject* PyTupleIter_TypePtr;
+static PyObject* tupleiterbuild(PyObject* o)
+{
+ tupleiterobject* iter = (tupleiterobject*) o;
+ if (iter->it_seq == NULL)
+ {
+ Py_INCREF(iter); /* exhausted */
+ return (PyObject*) iter;
+ }
+ else
+ return PyTuple_Type.tp_iter((PyObject*) iter->it_seq);
+}
+static int tupleitercopy(PyObject* o2, PyObject* o)
+{
+ PyObject* x;
+ tupleiterobject* iter = (tupleiterobject*) o;
+ tupleiterobject* iter2 = (tupleiterobject*) o2;
+
+ iter2->it_index = iter->it_index;
+ if (iter->it_seq != NULL)
+ {
+ x = copyrec((PyObject*) iter->it_seq);
+ Py_XDECREF(iter2->it_seq);
+ iter2->it_seq = (PyTupleObject*) x;
+ }
+ return 0;
+}
+#endif /* PY_VERSION_HEX >= 0x02030000 */
+
+
+/* HACKS HACKS HACKS */
+
+typedef struct {
+ PyObject_HEAD
+ PyObject* o;
+} KeyObject;
+
+#define KEYS_BY_BLOCK 1024
+
+struct key_block {
+ KeyObject keys[KEYS_BY_BLOCK];
+ struct key_block* next;
+};
+
+static long key_hash(KeyObject* k)
+{
+ return (long)(k->o);
+}
+
+static PyObject* key_richcmp(KeyObject* k1, KeyObject* k2, int op)
+{
+ PyObject* r;
+ assert(op == 2 /*PyCmp_EQ*/ );
+ r = k1->o == k2->o ? Py_True : Py_False;
+ Py_INCREF(r);
+ return r;
+}
+
+static PyTypeObject keytype = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "key",
+ sizeof(KeyObject),
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ (hashfunc)key_hash, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ (richcmpfunc)key_richcmp, /* tp_richcompare */
+};
+
+
+/* global state */
+static PyObject* ss_memo;
+static struct key_block* ss_block;
+static int ss_next_in_block;
+static PyObject *ss_error, *ss_errinst, *ss_errtb;
+
+static PyObject* str_inst_build;
+static PyTypeObject* GeneratorType;
+
+/* never returns NULL, and never returns with a Python exception set! */
+static PyObject* copyrec(PyObject* o)
+{
+ PyTypeObject* t;
+ PyObject* n;
+ PyObject* key;
+ KeyObject* fkey;
+
+ if (o == Py_None || o->ob_type == &PyInt_Type ||
+ o->ob_type == &PyString_Type || o->ob_type == &PyFloat_Type ||
+ o == empty_iterator)
+ {
+ Py_INCREF(o);
+ return o;
+ }
+ if (ss_next_in_block < 0)
+ {
+ struct key_block* b = (struct key_block*) malloc(sizeof(struct key_block));
+ if (!b) { PyErr_NoMemory(); goto fail1; }
+ b->next = ss_block;
+ ss_block = b;
+ ss_next_in_block = KEYS_BY_BLOCK - 1;
+ }
+ fkey = ss_block->keys + ss_next_in_block;
+ fkey->ob_refcnt = 1;
+ fkey->ob_type = &keytype;
+ fkey->o = o;
+ key = (PyObject*) fkey;
+ n = PyDict_GetItem(ss_memo, key);
+ if (n)
+ {
+ Py_INCREF(n);
+ return n;
+ }
+ ss_next_in_block--;
+ Py_INCREF(o); /* reference stored in 'fkey->o' */
+ t = o->ob_type;
+ if (t == &PyTuple_Type)
+ {
+ int i, count = PyTuple_GET_SIZE(o);
+ n = PyTuple_New(count);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ for (i=0; i<count; i++)
+ PyTuple_SET_ITEM(n, i, copyrec(PyTuple_GET_ITEM(o, i)));
+ return n;
+ }
+ if (t == &PyList_Type)
+ {
+ int i, count = PyList_GET_SIZE(o);
+ n = PyList_New(count);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ for (i=0; i<count; i++)
+ PyList_SET_ITEM(n, i, copyrec(PyList_GET_ITEM(o, i)));
+ return n;
+ }
+ if (t == &PyDict_Type)
+ {
+ int i = 0;
+ PyObject* dictkey;
+ PyObject* dictvalue;
+ n = PyDict_New();
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ while (PyDict_Next(o, &i, &dictkey, &dictvalue))
+ if (PyDict_SetItem(n, copyrec(dictkey), copyrec(dictvalue)))
+ goto fail;
+ return n;
+ }
+ if (t == &PyInstance_Type)
+ {
+ int i = 0;
+ PyObject* dictkey;
+ PyObject* dictvalue;
+ PyObject* dsrc;
+ PyObject* ddest;
+ PyObject* inst_build = PyObject_GetAttr(o, str_inst_build);
+ if (inst_build == NULL)
+ {
+ PyErr_Clear();
+ goto unmodified;
+ }
+ n = PyObject_CallObject(inst_build, NULL);
+ Py_DECREF(inst_build);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ dsrc = ((PyInstanceObject*) o)->in_dict;
+ ddest = ((PyInstanceObject*) n)->in_dict;
+ while (PyDict_Next(dsrc, &i, &dictkey, &dictvalue))
+ if (PyDict_SetItem(ddest, copyrec(dictkey), copyrec(dictvalue)))
+ goto fail;
+ return n;
+ }
+ if (t == &PyFunction_Type)
+ {
+ int i, count;
+ PyObject* tsrc = PyFunction_GET_DEFAULTS(o);
+ PyObject* tdest;
+ if (!tsrc) goto unmodified;
+ count = PyTuple_GET_SIZE(tsrc);
+ if (count == 0) goto unmodified;
+ n = PyFunction_New(PyFunction_GET_CODE(o), PyFunction_GET_GLOBALS(o));
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ tdest = PyTuple_New(count);
+ if (!tdest) goto fail;
+ for (i=0; i<count; i++)
+ PyTuple_SET_ITEM(tdest, i, copyrec(PyTuple_GET_ITEM(tsrc, i)));
+ i = PyFunction_SetDefaults(n, tdest);
+ Py_DECREF(tdest);
+ if (i) goto fail;
+ return n;
+ }
+ if (t == &PyMethod_Type)
+ {
+ PyObject* x;
+ n = PyMethod_New(PyMethod_GET_FUNCTION(o),
+ PyMethod_GET_SELF(o),
+ PyMethod_GET_CLASS(o));
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ x = copyrec(PyMethod_GET_FUNCTION(n));
+ Py_DECREF(PyMethod_GET_FUNCTION(n));
+ PyMethod_GET_FUNCTION(n) = x;
+ x = copyrec(PyMethod_GET_SELF(n));
+ Py_DECREF(PyMethod_GET_SELF(n));
+ PyMethod_GET_SELF(n) = x;
+ return n;
+ }
+ if (t == GeneratorType)
+ {
+ n = genbuild(o);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ if (gencopy(n, o)) goto fail;
+ return n;
+ }
+ if (t == &PySeqIter_Type)
+ {
+ n = seqiterbuild(o);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ if (seqitercopy(n, o)) goto fail;
+ return n;
+ }
+ #if PY_VERSION_HEX >= 0x02030000 /* 2.3 */
+ if (t == PyListIter_TypePtr)
+ {
+ n = listiterbuild(o);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ if (listitercopy(n, o)) goto fail;
+ return n;
+ }
+ if (t == PyTupleIter_TypePtr)
+ {
+ n = tupleiterbuild(o);
+ if (!n || PyDict_SetItem(ss_memo, key, n)) goto fail;
+ if (tupleitercopy(n, o)) goto fail;
+ return n;
+ }
+ #endif
+
+ ss_next_in_block++;
+ return o; /* reference no longer stored in 'fkey->o' */
+
+ unmodified:
+ PyDict_SetItem(ss_memo, key, o);
+ Py_INCREF(o);
+ return o;
+
+ fail1:
+ n = NULL;
+ fail:
+ Py_INCREF(o);
+ Py_XDECREF(n);
+ if (ss_error == NULL)
+ PyErr_Fetch(&ss_error, &ss_errinst, &ss_errtb);
+ else
+ PyErr_Clear();
+ return o;
+}
+
+static PyObject* sscopy(PyObject* self, PyObject* o)
+{
+ PyObject* n;
+ ss_memo = PyDict_New();
+ if (!ss_memo)
+ return NULL;
+
+ ss_block = NULL;
+ ss_next_in_block = -1;
+ ss_error = NULL;
+ ss_errinst = NULL;
+ ss_errtb = NULL;
+ n = copyrec(o);
+ Py_DECREF(ss_memo);
+ while (ss_block)
+ {
+ int i;
+ struct key_block* b = ss_block;
+ ss_block = b->next;
+ for (i=ss_next_in_block+1; i<KEYS_BY_BLOCK; i++)
+ Py_DECREF(b->keys[i].o);
+ free(b);
+ ss_next_in_block = -1;
+ }
+ if (ss_error && !PyErr_Occurred())
+ PyErr_Restore(ss_error, ss_errinst, ss_errtb);
+ else
+ {
+ Py_XDECREF(ss_error);
+ Py_XDECREF(ss_errinst);
+ Py_XDECREF(ss_errtb);
+ }
+ if (PyErr_Occurred())
+ {
+ Py_DECREF(n);
+ n = NULL;
+ }
+ return n;
+}
+
+
+static PyMethodDef StateSaverMethods[] = {
+ {"copy", sscopy, METH_O},
+ {NULL, NULL} /* Sentinel */
+};
+
+void initstatesaver(void)
+{
+ PyObject* m;
+ PyObject* x, *y;
+ m = Py_InitModule("statesaver", StateSaverMethods);
+ if (m == NULL)
+ return;
+ keytype.ob_type = &PyType_Type;
+ str_inst_build = PyString_InternFromString("inst_build");
+
+ m = PyImport_ImportModule("types");
+ if (!m) return;
+ GeneratorType = (PyTypeObject*) PyObject_GetAttrString(m, "GeneratorType");
+ if (!GeneratorType) return;
+
+ x = PyTuple_New(0);
+ if (!x) return;
+ empty_iterator = PyObject_GetIter(x);
+ Py_DECREF(x);
+ if (!empty_iterator) return;
+ PyTupleIter_TypePtr = empty_iterator->ob_type;
+
+ x = PyList_New(0);
+ if (!x) return;
+ y = PyList_Type.tp_iter(x);
+ if (y) PyListIter_TypePtr = y->ob_type;
+ Py_XDECREF(y);
+ Py_DECREF(x);
+ if (!y) return;
+}
diff --git a/bubbob/statesaver.py b/bubbob/statesaver.py
new file mode 100644
index 0000000..85d3425
--- /dev/null
+++ b/bubbob/statesaver.py
@@ -0,0 +1,135 @@
+"""
+A pure Python implementation of statesaver.c that runs on top of PyPy.
+See description in statesaver.c.
+Difference: this supports new-style instances too.
+Use statesaver.standard_build() as the inst_build() if you want.
+"""
+
+from _pickle_support import generator_new
+import types
+
+def standard_build(self):
+ if type(self) is types.InstanceType:
+ # old-style instance
+ return types.InstanceType(self.__class__)
+ else:
+ # new-style instance
+ return type(self).__new__(type(self))
+
+# ____________________________________________________________
+
+def not_copied(x, memo):
+ return x
+
+def copy_custom_instance(x, memo):
+ try:
+ return memo[id(x)]
+ except KeyError:
+ y = x.inst_build()
+ memo[id(x)] = y
+ for key, value in x.__dict__.items():
+ y.__dict__[key] = copyrec(value, memo)
+ return y
+
+def copy_tuple(x, memo):
+ return tuple([copyrec(item, memo) for item in x])
+
+def copy_list(x, memo):
+ try:
+ return memo[id(x)]
+ except KeyError:
+ y = []
+ memo[id(x)] = y
+ for item in x:
+ y.append(copyrec(item, memo))
+ return y
+
+def copy_dict(x, memo):
+ try:
+ return memo[id(x)]
+ except KeyError:
+ y = {}
+ memo[id(x)] = y
+ for key, value in x.items():
+ y[copyrec(key, memo)] = copyrec(value, memo)
+ return y
+
+def copy_function(x, memo):
+ if not x.func_defaults:
+ return x # not copied
+ try:
+ return memo[id(x)]
+ except KeyError:
+ y = types.FunctionType(x.func_code, x.func_globals, x.func_name)
+ memo[id(x)] = y
+ y.func_defaults = copyrec(x.func_defaults, memo)
+ return y
+
+def copy_method(x, memo):
+ return types.MethodType(copyrec(x.im_func, memo),
+ copyrec(x.im_self, memo),
+ x.im_class)
+
+def copy_generator(x, memo):
+ try:
+ return memo[id(x)]
+ except KeyError:
+ y = generator_new(copyrec(x.gi_frame, memo), x.gi_running)
+ memo[id(x)] = y
+ return y
+
+def copy_frame(x, memo):
+ try:
+ return memo[id(x)]
+ except KeyError:
+ frame_new, args, state = x.__reduce__()
+ y = frame_new(*args)
+ memo[id(x)] = y
+ newstate = []
+ for item in state:
+ if not (item is x.f_globals or item is x.f_builtins):
+ item = copyrec(item, memo)
+ newstate.append(item)
+ y.__setstate__(newstate)
+ return y
+
+def copy_seqiter(x, memo):
+ try:
+ return memo[id(x)]
+ except KeyError:
+ # XXX self-recursion is not correctly handled here
+ seqiter_new, args = x.__reduce__()
+ args = [copyrec(item, memo) for item in args]
+ y = seqiter_new(*args)
+ memo[id(x)] = y
+ return y
+
+# ____________________________________________________________
+
+type_handlers = {tuple: copy_tuple,
+ list: copy_list,
+ dict: copy_dict,
+ types.FunctionType: copy_function,
+ types.MethodType: copy_method,
+ types.GeneratorType: copy_generator,
+ types.FrameType: copy_frame,
+ type(iter([])): copy_seqiter,
+ }
+
+def no_handler_found(x, memo):
+ if hasattr(x, '__dict__') and hasattr(x.__class__, 'inst_build'):
+ handler = copy_custom_instance
+ else:
+ handler = not_copied
+ type_handlers[x.__class__] = handler
+ return handler(x, memo)
+
+def copyrec(x, memo):
+ try:
+ cls = x.__class__
+ except AttributeError:
+ return x # 'cls' is likely an old-style class object
+ return type_handlers.get(cls, no_handler_found)(x, memo)
+
+def copy(x):
+ return copyrec(x, {})
diff --git a/bubbob/statesaver.so b/bubbob/statesaver.so
new file mode 100755
index 0000000..bf805e5
--- /dev/null
+++ b/bubbob/statesaver.so
Binary files differ
diff --git a/bubbob/test_rnglevel.py b/bubbob/test_rnglevel.py
new file mode 100644
index 0000000..6b65514
--- /dev/null
+++ b/bubbob/test_rnglevel.py
@@ -0,0 +1,64 @@
+#
+# This test generates 100 times 25 random levels and checks
+# that it doesn't crash, and that it gives levels that are
+# possible (in the limited sense of not having any full-
+# column walls)
+#
+# this test accepts the following parameters:
+# -wall show the level layout
+# -wind show the level wind pattern
+# -seed N use random seed N for the generation
+#
+
+import sys
+import random
+sys.path.append('..')
+sys.path.append('../common')
+
+n_lvls = 100
+show_lvl = 0
+
+idx = 0
+while idx < len(sys.argv):
+ arg = sys.argv[idx]
+ idx += 1
+ if arg == '-wall':
+ show_lvl |= 1
+ n_lvls = 2
+ if arg == '-wind':
+ show_lvl |= 2
+ n_lvls = 2
+ if arg == '-seed':
+ arg = sys.argv[idx]
+ idx += 1
+ print "Using seed: " + arg + "\n"
+ random.seed(arg)
+
+def printlvl(level):
+ if show_lvl:
+ print "\n\n"
+ for y in range(level.HEIGHT):
+ str = ""
+ if show_lvl & 1:
+ str = level.walls[y]
+ if show_lvl & 2:
+ if str:
+ str += " | "
+ str += level.winds[y]
+ print str
+
+for i in range(n_lvls):
+ print '%4d:' % i,
+ d = {'__name__': 'RandomLevels'}
+ execfile('levels/RandomLevels.py', d)
+ for i, Lvl in enumerate(d['GenerateLevels']()):
+ level = Lvl(i)
+ printlvl(level)
+ for x in range(2, level.width-2):
+ for y in range(0, level.height):
+ if level.walls[y][x] == ' ':
+ break
+ else:
+ for line in level.walls:
+ print line
+ raise AssertionError("full height wall in column %d" % x)
diff --git a/bubbob/test_statesaver.py b/bubbob/test_statesaver.py
new file mode 100644
index 0000000..3fadcd7
--- /dev/null
+++ b/bubbob/test_statesaver.py
@@ -0,0 +1,127 @@
+import py
+import statesaver
+import new
+
+def test_list():
+ lst = [None, 12, "hello", 3.4, ("foo", (), [])]
+ lst1 = statesaver.copy(lst)
+ assert lst1 == lst
+ assert lst1 is not lst
+ assert lst1[-1][-1] is not lst[-1][-1]
+
+def test_dict():
+ dct = {1: "hi", 2: {}}
+ dct1 = statesaver.copy(dct)
+ assert dct1 == dct
+ assert dct1 is not dct
+ assert dct1[2] is not dct[2]
+
+def test_instance():
+ class Foo:
+ def inst_build(self):
+ return Bar()
+ class Bar:
+ pass
+ x = Foo()
+ x.attr = [1, 2, 3]
+ y = statesaver.copy(x)
+ assert y.__class__ is Bar
+ assert y.attr == [1, 2, 3]
+ assert y.attr is not x.attr
+
+glob = 2
+def test_function():
+ # XXX closures not supported
+ def func(x, y=[]):
+ assert glob == 2
+ y.append(x)
+ return y
+ l = func(5)
+ l = func(6)
+ assert l == [5, 6]
+ func1 = statesaver.copy(func)
+ l = func(7)
+ l = func(8)
+ assert l == [5, 6, 7, 8]
+ l = func1(9)
+ l = func1(10)
+ assert l == [5, 6, 9, 10]
+
+def test_method():
+ def func(x, y=[]):
+ assert glob == 2
+ y.append(x)
+ return y
+ m = new.instancemethod(func, {})
+ assert m() == [{}]
+ m1 = statesaver.copy(m)
+ assert m() == [{}, {}]
+ assert m() == [{}, {}, {}]
+ assert m1() == [{}, {}]
+ assert m1() == [{}, {}, {}]
+ l = m1()
+ assert l[0] is l[1] is l[2] is l[3]
+
+def test_generator():
+ def gfunc():
+ lst = [5, 6]
+ yield lst.pop()
+ yield lst.pop()
+ g = gfunc()
+ assert g.next() == 6
+ g1 = statesaver.copy(g)
+ assert g.next() == 5
+ py.test.raises(StopIteration, g.next)
+ assert g1.next() == 5
+ py.test.raises(StopIteration, g1.next)
+
+def test_exhausted_gen():
+ def gfunc():
+ yield 5
+ g = gfunc()
+ for i in g:
+ print i
+ g1 = statesaver.copy(g)
+ assert iter(g1) is g1
+ py.test.raises(StopIteration, g1.next)
+ g2 = statesaver.copy(g1)
+ assert iter(g2) is g2
+ py.test.raises(StopIteration, g2.next)
+
+def test_seqiter():
+ from UserList import UserList
+ seq = UserList([2, 4, 6, 8])
+ it = iter(seq)
+ assert it.next() == 2
+ assert it.next() == 4
+ it1 = statesaver.copy(it)
+ assert list(it) == [6, 8]
+ assert list(it1) == [6, 8]
+
+def test_tupleiter():
+ tup = (2, 4, 6, 8)
+ it = iter(tup)
+ assert it.next() == 2
+ assert it.next() == 4
+ it1 = statesaver.copy(it)
+ assert list(it) == [6, 8]
+ assert list(it1) == [6, 8]
+
+def test_listiter():
+ lst = [2, 4, 6, 8]
+ it = iter(lst)
+ assert it.next() == 2
+ assert it.next() == 4
+ it1 = statesaver.copy(it)
+ lst.append(10)
+ assert list(it) == [6, 8, 10]
+ assert list(it1) == [6, 8]
+
+def test_stringiter():
+ s = "hello"
+ it = iter(s)
+ assert it.next() == 'h'
+ assert it.next() == 'e'
+ it1 = statesaver.copy(it)
+ assert list(it) == ['l', 'l', 'o']
+ assert list(it1) == ['l', 'l', 'o']
diff --git a/bubbob/tmp/pat00.ppm b/bubbob/tmp/pat00.ppm
new file mode 100644
index 0000000..9b8ce4d
--- /dev/null
+++ b/bubbob/tmp/pat00.ppm
Binary files differ
diff --git a/bubbob/tmp/pat01.ppm b/bubbob/tmp/pat01.ppm
new file mode 100644
index 0000000..236b6ea
--- /dev/null
+++ b/bubbob/tmp/pat01.ppm
Binary files differ
diff --git a/bubbob/tmp/pat02.ppm b/bubbob/tmp/pat02.ppm
new file mode 100644
index 0000000..341ecac
--- /dev/null
+++ b/bubbob/tmp/pat02.ppm
Binary files differ
diff --git a/bubbob/tmp/pat03.ppm b/bubbob/tmp/pat03.ppm
new file mode 100644
index 0000000..ad98a6f
--- /dev/null
+++ b/bubbob/tmp/pat03.ppm
Binary files differ
diff --git a/bubbob/tmp/pat04.ppm b/bubbob/tmp/pat04.ppm
new file mode 100644
index 0000000..c35f525
--- /dev/null
+++ b/bubbob/tmp/pat04.ppm
Binary files differ
diff --git a/bubbob/tmp/pat05.ppm b/bubbob/tmp/pat05.ppm
new file mode 100644
index 0000000..a7745ca
--- /dev/null
+++ b/bubbob/tmp/pat05.ppm
Binary files differ
diff --git a/bubbob/tmp/pat06.ppm b/bubbob/tmp/pat06.ppm
new file mode 100644
index 0000000..ed4e844
--- /dev/null
+++ b/bubbob/tmp/pat06.ppm
Binary files differ
diff --git a/bubbob/tmp/pat07.ppm b/bubbob/tmp/pat07.ppm
new file mode 100644
index 0000000..fbe1e7f
--- /dev/null
+++ b/bubbob/tmp/pat07.ppm
Binary files differ
diff --git a/bubbob/tmp/pat08.ppm b/bubbob/tmp/pat08.ppm
new file mode 100644
index 0000000..40c750a
--- /dev/null
+++ b/bubbob/tmp/pat08.ppm
Binary files differ
diff --git a/bubbob/tmp/pat09.ppm b/bubbob/tmp/pat09.ppm
new file mode 100644
index 0000000..5821f76
--- /dev/null
+++ b/bubbob/tmp/pat09.ppm
Binary files differ
diff --git a/bubbob/tmp/pat10.ppm b/bubbob/tmp/pat10.ppm
new file mode 100644
index 0000000..651c51b
--- /dev/null
+++ b/bubbob/tmp/pat10.ppm
Binary files differ
diff --git a/bubbob/tmp/pat11.ppm b/bubbob/tmp/pat11.ppm
new file mode 100644
index 0000000..4854c3e
--- /dev/null
+++ b/bubbob/tmp/pat11.ppm
Binary files differ
diff --git a/bubbob/tmp/pat12.ppm b/bubbob/tmp/pat12.ppm
new file mode 100644
index 0000000..7482e32
--- /dev/null
+++ b/bubbob/tmp/pat12.ppm
Binary files differ
diff --git a/bubbob/tmp/pat13.ppm b/bubbob/tmp/pat13.ppm
new file mode 100644
index 0000000..8fbe9a4
--- /dev/null
+++ b/bubbob/tmp/pat13.ppm
Binary files differ
diff --git a/bubbob/tmp/pat14.ppm b/bubbob/tmp/pat14.ppm
new file mode 100644
index 0000000..54625b2
--- /dev/null
+++ b/bubbob/tmp/pat14.ppm
Binary files differ
diff --git a/bubbob/tmp/pat15.ppm b/bubbob/tmp/pat15.ppm
new file mode 100644
index 0000000..d50dd6f
--- /dev/null
+++ b/bubbob/tmp/pat15.ppm
Binary files differ
diff --git a/bubbob/tmp/pat16.ppm b/bubbob/tmp/pat16.ppm
new file mode 100644
index 0000000..f3a1881
--- /dev/null
+++ b/bubbob/tmp/pat16.ppm
@@ -0,0 +1,4 @@
+P6
+40 256
+255
+ªªªªªªªªªªªªªªªªªªÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆ»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwwwˆˆˆªªªˆˆˆªªªˆˆˆªªª•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªªªªªªªªÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwwwˆˆˆªªªˆˆˆªªªˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªªªªªÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝ݈ˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwwwˆˆˆªªªˆˆˆªªª•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªªÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»ªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwwwˆˆˆªªªˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ªªªªªªÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwwwˆˆˆªªª•••‚‚‚ooo]]]JJJ777%%%ªªªÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝ݈ˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwwwˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ÝÝÝ»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»ˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUUwww•••‚‚‚ooo]]]JJJ777%%%»»»wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆUUU•••‚‚‚ooo]]]JJJ777%%%wwwwwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ•••‚‚‚ooo]]]JJJ777%%%wwwwwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆˆˆˆ•••‚‚‚ooo]]]JJJ777%%%wwwUUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªÝÝ݈ˆˆwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆˆˆˆ•••‚‚‚ooo]]]JJJ777%%%UUUwwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªÝÝÝ»»»ˆˆˆˆˆˆwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆˆˆˆ•••‚‚‚ooo]]]JJJ777%%%wwwUUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªÝÝÝ»»»»»»ˆˆˆˆˆˆˆˆˆwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwwwˆˆˆ•••‚‚‚ooo]]]JJJ777%%%UUUDDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªÝÝÝ»»»ÝÝÝ»»»ˆˆˆˆˆˆˆˆˆˆˆˆwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUUwww•••‚‚‚ooo]]]JJJ777%%%DDDˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªÝÝÝ»»»ÝÝÝ»»»ÝÝ݈ˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªª»»»UUU•••‚‚‚ooo]]]JJJ777%%%ˆˆˆîîîÝÝÝ»»»»»»ÝÝÝ»»»»»»ˆˆˆªªªÝÝÝ»»»ÝÝÝ»»»ÝÝÝîîªˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwUUUˆˆˆˆˆˆˆˆˆˆˆˆªªªˆˆˆªªªˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ˆˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆªªªˆˆˆªªªˆˆˆªªªˆˆˆDDDwwwUUUwwwUUUwwwUUUDDDwwwwwwUUUwwwUUUUUUDDDªªª•••‚‚‚ooo]]]JJJ777%%%ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆªªªˆˆˆªªªˆˆˆªªªwwwUUUwwwUUUwwwUUUDDDwwwwwwUUUwwwUUUUUUDDDªªªîîî•••‚‚‚ooo]]]JJJ777%%%»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆªªªˆˆˆªªªˆˆˆUUUwwwUUUwwwUUUDDDwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ•••‚‚‚ooo]]]JJJ777%%%»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆªªªˆˆˆªªªwwwUUUwwwUUUDDDwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»•••‚‚‚ooo]]]JJJ777%%%ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆªªªˆˆˆUUUwwwUUUDDDwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»•••‚‚‚ooo]]]JJJ777%%%»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆªªªwwwUUUDDDwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»•••‚‚‚ooo]]]JJJ777%%%ªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwˆˆˆUUUDDDwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ªªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwDDDwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»•••‚‚‚ooo]]]JJJ777%%%ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªªªªwwwwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»www•••‚‚‚ooo]]]JJJ777%%%»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwªªªwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUU•••‚‚‚ooo]]]JJJ777%%%ªªª»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwUUUwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUUªªª•••‚‚‚ooo]]]JJJ777%%%ªªªªªª»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªˆˆˆwwwUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUUªªªˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªª»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwªªªUUUUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUUªªªˆˆˆªªª•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªªªªª»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUwwwUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUUªªªˆˆˆªªªˆˆˆ•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªªªªªªªª»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆUUUDDDªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUUªªªˆˆˆªªªˆˆˆªªª•••‚‚‚ooo]]]JJJ777%%%ªªªªªªªªªªªªªªªªªª»»»ÝÝݪªªªªª»»»ªªª»»»»»»ÝÝ݈ˆˆªªªîîîÝÝÝ»»»»»»»»»ˆˆˆ»»»wwwUUUªªªˆˆˆªªªˆˆˆªªªwww•••‚‚‚ooo]]]JJJ777%%%ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff£££ŽŽŽzzzeeeQQQ===(((ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª™™™ªªªªªªªªª»»»»»»»»»ÝÝÝ»»»ÝÝÝÝÝÝÝÝÝîîîîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™ªªªªªªªªª»»»ªªª»»»»»»»»»ÝÝÝÝÝÝÝÝÝîîîÝÝÝîîîîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª™™™ªªªªªªªªª»»»™™™™™™™™™™™™wwwwwwÝÝÝîîîîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™ªªªªªª™™™ªªªªªªªªªªªªªªªªªªªªªªªªªªªwwwwwwîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª™™™™™™ªªª»»»»»»»»»»»»»»»»»»»»»»»»ªªªªªª™™™wwwÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™™™™ªªª»»»ÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ»»»»»»ªªªªªª™™™wwwÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™ªªª»»»ÝÝÝÝÝÝîîîîîîîîîÝÝÝÝÝÝÝÝÝÝÝÝ»»»»»»ªªªªªª™™™wwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª»»»ÝÝÝÝÝÝîîîîîîÿÿÿîîîîîîÝÝÝÝÝÝÝÝÝ»»»»»»»»»ªªªªªª™™™wwwÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª»»»ÝÝÝÝÝÝÝÝÝîîîîîîîîîÝÝÝÝÝÝÝÝÝÝÝÝ»»»»»»»»»ªªªªªª™™™™™™wwwÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™ªªªªªª»»»ÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ»»»»»»»»»»»»ªªªªªª™™™™™™wwwÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™ªªªªªª»»»ÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ»»»»»»»»»»»»»»»ªªªªªª™™™™™™wwwÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™ˆˆˆ™™™ªªªªªª»»»»»»ÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ»»»»»»»»»»»»»»»ªªªªªªªªª™™™™™™ˆˆˆwwwÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™ˆˆˆ™™™ªªªªªªªªª»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»ªªªªªª™™™™™™™™™ˆˆˆwwwÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™ˆˆˆ™™™™™™ªªªªªªªªª»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»ªªªªªªªªª™™™™™™™™™ˆˆˆwwwÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™ˆˆˆ™™™™™™ªªªªªªªªªªªª»»»»»»»»»»»»»»»»»»»»»ªªªªªªªªªªªª™™™™™™™™™™™™ˆˆˆwwwÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™wwwˆˆˆ™™™™™™ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª™™™™™™™™™™™™ˆˆˆˆˆˆwwwÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™wwwˆˆˆ™™™™™™™™™™™™ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª™™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆwwwÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™ˆˆˆˆˆˆ™™™™™™™™™™™™™™™ªªªªªªªªªªªªªªª™™™™™™™™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆwwwÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™wwwˆˆˆˆˆˆ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆwwwÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™wwwˆˆˆˆˆˆˆˆˆ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™wwwˆˆˆˆˆˆˆˆˆˆˆˆ™™™™™™™™™™™™™™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™wwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªªwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwîîîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™ªªªwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª™™™ªªªwwwwwwwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwwwwwwwîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™ªªªªªªªªª»»»ªªªwwwwwwwwwwwwwwwwwwîîîÝÝÝîîîîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™ªªª™™™ªªªªªªªªª»»»»»»»»»ÝÝÝ»»»ÝÝÝÝÝÝÝÝÝîîîîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffff™™™™™™™™™™™™™™™™™™ªªªªªªªªª»»»ªªª»»»»»»»»»ÝÝÝÝÝÝÝÝÝîîîÝÝÝîîîîîîîîîÿÿÿîîîÿÿÿÿÿÿÿÿÿÿÿÿffffff£££ŽŽŽzzzeeeQQQ===(((ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff£££ŽŽŽzzzeeeQQQ===(((ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff£££ŽŽŽzzzeeeQQQ===(((ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌfffÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww™™Ì™™ÌfffÌÌÌwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ»»»ÝÝÝ™™Ì™™Ì™™ÌfffÌÌÌwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ»»»ÝÝÝ™™Ì™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ì™™ÌfffÌÌÌwwwˆˆˆªªªªªªªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ì™™Ì™™ÌfffÌÌÌwwwˆˆˆªªªªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ì™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ì™™Ì™™Ì™™ÌfffÌÌÌwwwˆˆˆªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ì™™Ì™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™ÌfffÌÌÌ™™Ì™™ÌfffÌÌÌwwwˆˆˆªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™ÌfffÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìffff™ÌÌÌÌ™™Ì™™ÌfffÌÌÌwwwˆˆˆªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™ÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªª»»»ÝÝÝ™™Ì™™Ì™™Ìffff™Ìf™ÌÌÌÌ™™Ì™™ÌfffÌÌÌwwwˆˆˆªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆ»»»ÝÝÝ™™Ì™™Ì™™Ìffff™Ìf™Ìf™ÌÌÌÌ™™Ì™™ÌfffÌÌÌwwwˆˆˆ»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwww»»»ÝÝÝ™™Ì™™Ì™™Ìffff™Ìf™Ìf™Ìf™ÌÌÌÌ™™Ì™™ÌfffÌÌÌwww»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwÝÝÝ™™Ì™™Ì™™Ìffff™Ìf™Ìf™Ìf™Ìf™ÌÌÌÌ™™Ì™™ÌfffÌÌÌwwwÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwww™™Ì™™Ì™™Ì™™ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ™™Ì™™ÌfffÌÌÌwww™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌ™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™ÌfffÌÌÌ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌ™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ìfff™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-fffffffffffffffffffffffffffffffffffffffffffff™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªªªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆªªª»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwˆˆˆ»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwww»»»ÝÝÝ™™Ì™™Ì™™Ìfffff™f™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ìf™Ì™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwwwÝÝÝ™™Ì™™Ì™™Ìfffff™™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌ™ÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌwww™™Ì™™Ì™™Ì™™ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌ™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-ÌÌÌ™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ì™™Ìfff‘œ´ˆžlu‡ZaqHNZ6:C$'-fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffwww‘œ´ˆžlu‡ZaqHNZ6:C$'-
diff --git a/bubbob/tmp/pat17.ppm b/bubbob/tmp/pat17.ppm
new file mode 100644
index 0000000..dcc159e
--- /dev/null
+++ b/bubbob/tmp/pat17.ppm
Binary files differ
diff --git a/bubbob/tmp/pat18.ppm b/bubbob/tmp/pat18.ppm
new file mode 100644
index 0000000..f36ff3d
--- /dev/null
+++ b/bubbob/tmp/pat18.ppm
Binary files differ
diff --git a/bubbob/tmp/pat19.ppm b/bubbob/tmp/pat19.ppm
new file mode 100644
index 0000000..e44e5c7
--- /dev/null
+++ b/bubbob/tmp/pat19.ppm
Binary files differ
diff --git a/bubbob/tmp/pat20.ppm b/bubbob/tmp/pat20.ppm
new file mode 100644
index 0000000..eba7c3b
--- /dev/null
+++ b/bubbob/tmp/pat20.ppm
Binary files differ