From 1d9925c287b318ec21343e2682b51ab6a36ae8db Mon Sep 17 00:00:00 2001 From: Diego Roversi Date: Sun, 8 Sep 2019 18:12:27 +0200 Subject: initial commit from cvs 1.6.2 --- .cvsignore | 1 + .gitignore | 3 + BubBob.icns | Bin 0 -> 42521 bytes BubBob.py | 164 +++ INSTALL.txt | 63 + LICENSE.txt | 33 + Makefile | 63 + artistic.txt | 73 + bubbob/.cvsignore | 4 + bubbob/Makefile | 2 + bubbob/bb.py | 345 +++++ bubbob/binboards.py | 122 ++ bubbob/boarddef.py | 69 + bubbob/boards.py | 1470 ++++++++++++++++++++ bubbob/bonuses.py | 2504 ++++++++++++++++++++++++++++++++++ bubbob/bubbles.py | 1284 +++++++++++++++++ bubbob/command.py | 10 + bubbob/doc/.cvsignore | 1 + bubbob/doc/bonus-doc.py | 170 +++ bubbob/doc/images/.cvsignore | 1 + bubbob/ext1/.cvsignore | 2 + bubbob/ext1/__init__.py | 403 ++++++ bubbob/ext1/brick.wav | Bin 0 -> 29150 bytes bubbob/ext1/image1-0.ppm | Bin 0 -> 7732 bytes bubbob/ext1/music.wav | Bin 0 -> 748826 bytes bubbob/ext1/wall.wav | Bin 0 -> 13274 bytes bubbob/ext2/.cvsignore | 1 + bubbob/ext2/__init__.py | 578 ++++++++ bubbob/ext2/image1.ppm | Bin 0 -> 24635 bytes bubbob/ext2/image2.ppm | Bin 0 -> 3898 bytes bubbob/ext2/music.wav | Bin 0 -> 353000 bytes bubbob/ext3/.cvsignore | 2 + bubbob/ext3/__init__.py | 367 +++++ bubbob/ext3/image1-0.ppm | Bin 0 -> 3124 bytes bubbob/ext3/music.wav | Bin 0 -> 284134 bytes bubbob/ext3/shoot.wav | Bin 0 -> 13274 bytes bubbob/ext4/.cvsignore | 2 + bubbob/ext4/__init__.py | 440 ++++++ bubbob/ext4/image1-0.ppm | Bin 0 -> 1588 bytes bubbob/ext4/music.wav | Bin 0 -> 566532 bytes bubbob/ext5/.cvsignore | 1 + bubbob/ext5/__init__.py | 289 ++++ bubbob/ext5/image1.ppm | 5 + bubbob/ext5/image2.ppm | 5 + bubbob/ext5/image3.ppm | 5 + bubbob/ext5/image4.ppm | Bin 0 -> 9274 bytes bubbob/ext5/music.wav | Bin 0 -> 346458 bytes bubbob/ext5/ouch.wav | Bin 0 -> 2631 bytes bubbob/ext6/.cvsignore | 2 + bubbob/ext6/__init__.py | 268 ++++ bubbob/ext6/crash.wav | Bin 0 -> 13123 bytes bubbob/ext6/image1-0.ppm | Bin 0 -> 1971 bytes bubbob/ext6/music.wav | Bin 0 -> 431045 bytes bubbob/ext7/.cvsignore | 2 + bubbob/ext7/__init__.py | 399 ++++++ bubbob/ext7/fire.wav | Bin 0 -> 3352 bytes bubbob/ext7/hit.wav | Bin 0 -> 3352 bytes bubbob/ext7/image1-0.ppm | Bin 0 -> 93365 bytes bubbob/ext7/music.wav | Bin 0 -> 379900 bytes bubbob/images.py | 542 ++++++++ bubbob/images/.cvsignore | 7 + bubbob/images/10000_0.ppm | Bin 0 -> 10852 bytes bubbob/images/20000_0.ppm | Bin 0 -> 13282 bytes bubbob/images/30000_0.ppm | Bin 0 -> 13282 bytes bubbob/images/40000_0.ppm | Bin 0 -> 13282 bytes bubbob/images/50000_0.ppm | Bin 0 -> 12877 bytes bubbob/images/60000_0.ppm | Bin 0 -> 13012 bytes bubbob/images/70000_0.ppm | Bin 0 -> 13012 bytes bubbob/images/big_bubble.ppm | 4 + bubbob/images/big_bubble_2.ppm | 4 + bubbob/images/black.ppm | 5 + bubbob/images/blitzy.ppm | Bin 0 -> 33845 bytes bubbob/images/blitzy_angry.ppm | Bin 0 -> 12341 bytes bubbob/images/blitzy_shot.ppm | Bin 0 -> 1588 bytes bubbob/images/bonus_0.ppm | Bin 0 -> 17339 bytes bubbob/images/bonus_1.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_10.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_11.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_12.ppm | Bin 0 -> 12341 bytes bubbob/images/bonus_2.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_3.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_4.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_5.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_6.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_7.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_8.ppm | Bin 0 -> 24629 bytes bubbob/images/bonus_9.ppm | Bin 0 -> 24629 bytes bubbob/images/bubble.ppm | 5 + bubbob/images/buildcolors.py | 324 +++++ bubbob/images/butterfly.ppm | Bin 0 -> 27701 bytes bubbob/images/cream_pie_big.ppm | Bin 0 -> 24352 bytes bubbob/images/diamond_big_blue.ppm | Bin 0 -> 24352 bytes bubbob/images/diamond_big_purple.ppm | 5 + bubbob/images/diamond_big_red.ppm | Bin 0 -> 24352 bytes bubbob/images/diamond_big_yellow.ppm | Bin 0 -> 24352 bytes bubbob/images/digits_0.ppm | Bin 0 -> 7193 bytes bubbob/images/door.ppm | Bin 0 -> 3130 bytes bubbob/images/dragon_0.ppm | Bin 0 -> 61493 bytes bubbob/images/dragon_bubble_0.ppm | Bin 0 -> 49205 bytes bubbob/images/extend.ppm | Bin 0 -> 55355 bytes bubbob/images/extra1.ppm | Bin 0 -> 24629 bytes bubbob/images/extra2.ppm | Bin 0 -> 6203 bytes bubbob/images/extra3.ppm | Bin 0 -> 21518 bytes bubbob/images/extra4.ppm | Bin 0 -> 9229 bytes bubbob/images/extra5.ppm | 5 + bubbob/images/extra6.ppm | Bin 0 -> 11374 bytes bubbob/images/extra7.ppm | 13 + bubbob/images/extra8.ppm | Bin 0 -> 34259 bytes bubbob/images/fire_drop.ppm | Bin 0 -> 444 bytes bubbob/images/fire_surface.ppm | Bin 0 -> 3085 bytes bubbob/images/fish_0.ppm | Bin 0 -> 21557 bytes bubbob/images/flappy.ppm | Bin 0 -> 46133 bytes bubbob/images/flapy_angry.ppm | Bin 0 -> 24629 bytes bubbob/images/game_over_0.ppm | Bin 0 -> 6196 bytes bubbob/images/ghost.ppm | 6 + bubbob/images/ghosty.ppm | Bin 0 -> 46133 bytes bubbob/images/ghosty_angry.ppm | Bin 0 -> 24629 bytes bubbob/images/glue.ppm | 4 + bubbob/images/gramy.ppm | Bin 0 -> 58421 bytes bubbob/images/gramy_angry.ppm | Bin 0 -> 24629 bytes bubbob/images/hat1.ppm | 5 + bubbob/images/hat2.ppm | 5 + bubbob/images/hat5.ppm | 5 + bubbob/images/ice_cyan_big.ppm | Bin 0 -> 24358 bytes bubbob/images/ice_violet_big.ppm | Bin 0 -> 24358 bytes bubbob/images/keys.ppm | Bin 0 -> 49167 bytes bubbob/images/level_digits.ppm | Bin 0 -> 25253 bytes bubbob/images/lightning_large.ppm | Bin 0 -> 17833 bytes bubbob/images/lightning_small.ppm | Bin 0 -> 1741 bytes bubbob/images/monky.ppm | Bin 0 -> 61493 bytes bubbob/images/monky_angry.ppm | Bin 0 -> 24629 bytes bubbob/images/nasty.ppm | Bin 0 -> 46133 bytes bubbob/images/nasty_angry.ppm | Bin 0 -> 24629 bytes bubbob/images/orcy.ppm | Bin 0 -> 70709 bytes bubbob/images/orcy_angry.ppm | Bin 0 -> 24629 bytes bubbob/images/palettes.dat | Bin 0 -> 28800 bytes bubbob/images/pastec_big.ppm | 44 + bubbob/images/peach_big.ppm | Bin 0 -> 24352 bytes bubbob/images/point_0.ppm | Bin 0 -> 86453 bytes bubbob/images/red_Hurry_up.ppm | Bin 0 -> 9851 bytes bubbob/images/sheep.ppm | Bin 0 -> 27707 bytes bubbob/images/shot.ppm | Bin 0 -> 24629 bytes bubbob/images/spinning_drop.ppm | Bin 0 -> 4660 bytes bubbob/images/springy.ppm | Bin 0 -> 58421 bytes bubbob/images/springy_angry.ppm | Bin 0 -> 36917 bytes bubbob/images/star_large.ppm | Bin 0 -> 36923 bytes bubbob/images/sugar_pie_big.ppm | Bin 0 -> 24358 bytes bubbob/images/water_flow.ppm | 4 + bubbob/images/water_still.ppm | 4 + bubbob/images/water_surface.ppm | 4 + bubbob/images/yellow_Hurry_up.ppm | Bin 0 -> 9851 bytes bubbob/levels/Arena.bin | Bin 0 -> 164736 bytes bubbob/levels/CompactLevels.py | 1902 ++++++++++++++++++++++++++ bubbob/levels/HouseOfFun.bin | Bin 0 -> 196096 bytes bubbob/levels/Levels.bin | Bin 0 -> 178432 bytes bubbob/levels/LostLevels.bin | Bin 0 -> 178432 bytes bubbob/levels/README.txt | 23 + bubbob/levels/RandomLevels.py | 362 +++++ bubbob/levels/rnglevel | 1276 +++++++++++++++++ bubbob/levels/scratch.py | 2301 +++++++++++++++++++++++++++++++ bubbob/macbinary.py | 260 ++++ bubbob/mnstrmap.py | 397 ++++++ bubbob/monsters.py | 937 +++++++++++++ bubbob/music/Snd1-8.wav | Bin 0 -> 532268 bytes bubbob/music/Snd2-8.wav | Bin 0 -> 10778156 bytes bubbob/music/Snd3-8.wav | Bin 0 -> 1890476 bytes bubbob/music/Snd4-8.wav | Bin 0 -> 339884 bytes bubbob/music/Snd5-8.wav | Bin 0 -> 503468 bytes bubbob/music/Snd6-8.wav | Bin 0 -> 9895724 bytes bubbob/patmap.py | 177 +++ bubbob/player.py | 1213 ++++++++++++++++ bubbob/ranking.py | 391 ++++++ bubbob/save_rnglevel.py | 125 ++ bubbob/setup.py | 22 + bubbob/sounds/die.wav | Bin 0 -> 29758 bytes bubbob/sounds/extra.wav | Bin 0 -> 3084 bytes bubbob/sounds/extralife.wav | Bin 0 -> 20624 bytes bubbob/sounds/fruit.wav | Bin 0 -> 3300 bytes bubbob/sounds/hell.wav | Bin 0 -> 41036 bytes bubbob/sounds/hurry.wav | Bin 0 -> 19536 bytes bubbob/sounds/jump.wav | Bin 0 -> 5516 bytes bubbob/sounds/letsgo.wav | Bin 0 -> 19007 bytes bubbob/sounds/pop.wav | Bin 0 -> 2837 bytes bubbob/sounds/shh.wav | Bin 0 -> 18272 bytes bubbob/sounds/yippee.wav | Bin 0 -> 6305 bytes bubbob/sprmap.py | 533 ++++++++ bubbob/statesaver.c | 610 +++++++++ bubbob/statesaver.py | 135 ++ bubbob/statesaver.so | Bin 0 -> 62672 bytes bubbob/test_rnglevel.py | 64 + bubbob/test_statesaver.py | 127 ++ bubbob/tmp/pat00.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat01.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat02.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat03.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat04.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat05.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat06.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat07.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat08.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat09.ppm | Bin 0 -> 17294 bytes bubbob/tmp/pat10.ppm | Bin 0 -> 6925 bytes bubbob/tmp/pat11.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat12.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat13.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat14.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat15.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat16.ppm | 4 + bubbob/tmp/pat17.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat18.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat19.ppm | Bin 0 -> 30734 bytes bubbob/tmp/pat20.ppm | Bin 0 -> 7693 bytes common/.cvsignore | 1 + common/__init__.py | 1 + common/gamesrv.py | 1378 +++++++++++++++++++ common/hostchooser.py | 192 +++ common/httpserver.py | 192 +++ common/javaserver.py | 157 +++ common/msgstruct.py | 75 + common/pixmap.py | 163 +++ common/stdlog.py | 106 ++ common/udpovertcp.py | 46 + display/.cvsignore | 4 + display/Client.py | 170 +++ display/Makefile | 2 + display/__init__.py | 1 + display/caching.py | 261 ++++ display/dpy_gtk.py | 204 +++ display/dpy_pygame.py | 245 ++++ display/dpy_windows.py | 23 + display/dpy_x.py | 40 + display/modes.py | 196 +++ display/music1.py | 33 + display/pclient.py | 860 ++++++++++++ display/playback.py | 203 +++ display/puremixer.py | 123 ++ display/pythonxlibintf.py | 202 +++ display/setup.py | 16 + display/snd_linux.py | 148 ++ display/snd_off.py | 5 + display/snd_pygame.py | 82 ++ display/snd_windows.py | 93 ++ display/windows/.cvsignore | 3 + display/windows/wingame.c | 867 ++++++++++++ display/windows/wingame.def | 2 + display/windows/wingame.dsp | 111 ++ display/windows/wingame.dsw | 29 + display/xshm.c | 1202 ++++++++++++++++ display/xshm.so | Bin 0 -> 112912 bytes doc/BubBob.py.1 | 32 + doc/Client.py.1 | 200 +++ doc/Makefile | 30 + doc/bb.py.1 | 129 ++ http2/.cvsignore | 2 + http2/config.txt | 1 + http2/data/bab.png | Bin 0 -> 1902 bytes http2/data/baub.png | Bin 0 -> 1736 bytes http2/data/beab.png | Bin 0 -> 1677 bytes http2/data/beb.png | Bin 0 -> 1587 bytes http2/data/biab.png | Bin 0 -> 1679 bytes http2/data/bib.png | Bin 0 -> 1655 bytes http2/data/biob.png | Bin 0 -> 1531 bytes http2/data/bob.png | Bin 0 -> 1682 bytes http2/data/boob.png | Bin 0 -> 1617 bytes http2/data/bub.png | Bin 0 -> 1490 bytes http2/data/byb.png | Bin 0 -> 2592 bytes http2/data/checked.png | Bin 0 -> 1032 bytes http2/data/close.png | Bin 0 -> 478 bytes http2/data/confirm.html | 34 + http2/data/disabled.png | Bin 0 -> 814 bytes http2/data/hat1.png | Bin 0 -> 202 bytes http2/data/hat2.png | Bin 0 -> 310 bytes http2/data/header.png | Bin 0 -> 11619 bytes http2/data/index.html | 292 ++++ http2/data/lbab.png | Bin 0 -> 2592 bytes http2/data/lbeb.png | Bin 0 -> 1583 bytes http2/data/lbib.png | Bin 0 -> 1663 bytes http2/data/lbiob.png | Bin 0 -> 1520 bytes http2/data/name.html | 189 +++ http2/data/new.html | 271 ++++ http2/data/options.html | 285 ++++ http2/data/sfbob.png | Bin 0 -> 1785 bytes http2/data/sfbub.png | Bin 0 -> 1664 bytes http2/data/stop.html | 101 ++ http2/data/unchecked.png | Bin 0 -> 327 bytes http2/data/wave1.png | Bin 0 -> 307 bytes http2/data/wave2.png | Bin 0 -> 327 bytes http2/data/wave3.png | Bin 0 -> 313 bytes http2/header.png | Bin 0 -> 11619 bytes http2/httppages.py | 705 ++++++++++ http2/sf/bb12.py | 274 ++++ http2/sf/sfbub.png | Bin 0 -> 1664 bytes http2/sf/started.html | 44 + java/.cvsignore | 1 + java/Makefile | 2 + java/pclient.java | 1196 ++++++++++++++++ metaserver/.cvsignore | 4 + metaserver/__init__.py | 1 + metaserver/home.png | Bin 0 -> 1283 bytes metaserver/index.html | 51 + metaserver/mbub.png | Bin 0 -> 1138 bytes metaserver/metaclient.py | 596 ++++++++ metaserver/metaserver.py | 365 +++++ metaserver/metastruct.py | 75 + metaserver/pipelayer.py | 337 +++++ metaserver/socketoverudp.py | 174 +++ 306 files changed, 33834 insertions(+) create mode 100644 .cvsignore create mode 100644 .gitignore create mode 100644 BubBob.icns create mode 100755 BubBob.py create mode 100644 INSTALL.txt create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 artistic.txt create mode 100644 bubbob/.cvsignore create mode 100644 bubbob/Makefile create mode 100755 bubbob/bb.py create mode 100644 bubbob/binboards.py create mode 100644 bubbob/boarddef.py create mode 100644 bubbob/boards.py create mode 100644 bubbob/bonuses.py create mode 100644 bubbob/bubbles.py create mode 100644 bubbob/command.py create mode 100644 bubbob/doc/.cvsignore create mode 100755 bubbob/doc/bonus-doc.py create mode 100644 bubbob/doc/images/.cvsignore create mode 100644 bubbob/ext1/.cvsignore create mode 100644 bubbob/ext1/__init__.py create mode 100644 bubbob/ext1/brick.wav create mode 100644 bubbob/ext1/image1-0.ppm create mode 100644 bubbob/ext1/music.wav create mode 100644 bubbob/ext1/wall.wav create mode 100644 bubbob/ext2/.cvsignore create mode 100644 bubbob/ext2/__init__.py create mode 100644 bubbob/ext2/image1.ppm create mode 100644 bubbob/ext2/image2.ppm create mode 100644 bubbob/ext2/music.wav create mode 100644 bubbob/ext3/.cvsignore create mode 100644 bubbob/ext3/__init__.py create mode 100644 bubbob/ext3/image1-0.ppm create mode 100644 bubbob/ext3/music.wav create mode 100644 bubbob/ext3/shoot.wav create mode 100644 bubbob/ext4/.cvsignore create mode 100644 bubbob/ext4/__init__.py create mode 100644 bubbob/ext4/image1-0.ppm create mode 100644 bubbob/ext4/music.wav create mode 100644 bubbob/ext5/.cvsignore create mode 100644 bubbob/ext5/__init__.py create mode 100644 bubbob/ext5/image1.ppm create mode 100644 bubbob/ext5/image2.ppm create mode 100644 bubbob/ext5/image3.ppm create mode 100644 bubbob/ext5/image4.ppm create mode 100644 bubbob/ext5/music.wav create mode 100644 bubbob/ext5/ouch.wav create mode 100644 bubbob/ext6/.cvsignore create mode 100644 bubbob/ext6/__init__.py create mode 100644 bubbob/ext6/crash.wav create mode 100644 bubbob/ext6/image1-0.ppm create mode 100644 bubbob/ext6/music.wav create mode 100644 bubbob/ext7/.cvsignore create mode 100644 bubbob/ext7/__init__.py create mode 100644 bubbob/ext7/fire.wav create mode 100644 bubbob/ext7/hit.wav create mode 100644 bubbob/ext7/image1-0.ppm create mode 100644 bubbob/ext7/music.wav create mode 100644 bubbob/images.py create mode 100644 bubbob/images/.cvsignore create mode 100644 bubbob/images/10000_0.ppm create mode 100644 bubbob/images/20000_0.ppm create mode 100644 bubbob/images/30000_0.ppm create mode 100644 bubbob/images/40000_0.ppm create mode 100644 bubbob/images/50000_0.ppm create mode 100644 bubbob/images/60000_0.ppm create mode 100644 bubbob/images/70000_0.ppm create mode 100644 bubbob/images/big_bubble.ppm create mode 100644 bubbob/images/big_bubble_2.ppm create mode 100644 bubbob/images/black.ppm create mode 100644 bubbob/images/blitzy.ppm create mode 100644 bubbob/images/blitzy_angry.ppm create mode 100644 bubbob/images/blitzy_shot.ppm create mode 100644 bubbob/images/bonus_0.ppm create mode 100644 bubbob/images/bonus_1.ppm create mode 100644 bubbob/images/bonus_10.ppm create mode 100644 bubbob/images/bonus_11.ppm create mode 100644 bubbob/images/bonus_12.ppm create mode 100644 bubbob/images/bonus_2.ppm create mode 100644 bubbob/images/bonus_3.ppm create mode 100644 bubbob/images/bonus_4.ppm create mode 100644 bubbob/images/bonus_5.ppm create mode 100644 bubbob/images/bonus_6.ppm create mode 100644 bubbob/images/bonus_7.ppm create mode 100644 bubbob/images/bonus_8.ppm create mode 100644 bubbob/images/bonus_9.ppm create mode 100644 bubbob/images/bubble.ppm create mode 100644 bubbob/images/buildcolors.py create mode 100644 bubbob/images/butterfly.ppm create mode 100644 bubbob/images/cream_pie_big.ppm create mode 100644 bubbob/images/diamond_big_blue.ppm create mode 100644 bubbob/images/diamond_big_purple.ppm create mode 100644 bubbob/images/diamond_big_red.ppm create mode 100644 bubbob/images/diamond_big_yellow.ppm create mode 100644 bubbob/images/digits_0.ppm create mode 100644 bubbob/images/door.ppm create mode 100644 bubbob/images/dragon_0.ppm create mode 100644 bubbob/images/dragon_bubble_0.ppm create mode 100644 bubbob/images/extend.ppm create mode 100644 bubbob/images/extra1.ppm create mode 100644 bubbob/images/extra2.ppm create mode 100644 bubbob/images/extra3.ppm create mode 100644 bubbob/images/extra4.ppm create mode 100644 bubbob/images/extra5.ppm create mode 100644 bubbob/images/extra6.ppm create mode 100644 bubbob/images/extra7.ppm create mode 100644 bubbob/images/extra8.ppm create mode 100644 bubbob/images/fire_drop.ppm create mode 100644 bubbob/images/fire_surface.ppm create mode 100644 bubbob/images/fish_0.ppm create mode 100644 bubbob/images/flappy.ppm create mode 100644 bubbob/images/flapy_angry.ppm create mode 100644 bubbob/images/game_over_0.ppm create mode 100644 bubbob/images/ghost.ppm create mode 100644 bubbob/images/ghosty.ppm create mode 100644 bubbob/images/ghosty_angry.ppm create mode 100644 bubbob/images/glue.ppm create mode 100644 bubbob/images/gramy.ppm create mode 100644 bubbob/images/gramy_angry.ppm create mode 100644 bubbob/images/hat1.ppm create mode 100644 bubbob/images/hat2.ppm create mode 100644 bubbob/images/hat5.ppm create mode 100644 bubbob/images/ice_cyan_big.ppm create mode 100644 bubbob/images/ice_violet_big.ppm create mode 100644 bubbob/images/keys.ppm create mode 100644 bubbob/images/level_digits.ppm create mode 100644 bubbob/images/lightning_large.ppm create mode 100644 bubbob/images/lightning_small.ppm create mode 100644 bubbob/images/monky.ppm create mode 100644 bubbob/images/monky_angry.ppm create mode 100644 bubbob/images/nasty.ppm create mode 100644 bubbob/images/nasty_angry.ppm create mode 100644 bubbob/images/orcy.ppm create mode 100644 bubbob/images/orcy_angry.ppm create mode 100644 bubbob/images/palettes.dat create mode 100644 bubbob/images/pastec_big.ppm create mode 100644 bubbob/images/peach_big.ppm create mode 100644 bubbob/images/point_0.ppm create mode 100644 bubbob/images/red_Hurry_up.ppm create mode 100644 bubbob/images/sheep.ppm create mode 100644 bubbob/images/shot.ppm create mode 100644 bubbob/images/spinning_drop.ppm create mode 100644 bubbob/images/springy.ppm create mode 100644 bubbob/images/springy_angry.ppm create mode 100644 bubbob/images/star_large.ppm create mode 100644 bubbob/images/sugar_pie_big.ppm create mode 100644 bubbob/images/water_flow.ppm create mode 100644 bubbob/images/water_still.ppm create mode 100644 bubbob/images/water_surface.ppm create mode 100644 bubbob/images/yellow_Hurry_up.ppm create mode 100644 bubbob/levels/Arena.bin create mode 100644 bubbob/levels/CompactLevels.py create mode 100644 bubbob/levels/HouseOfFun.bin create mode 100644 bubbob/levels/Levels.bin create mode 100644 bubbob/levels/LostLevels.bin create mode 100644 bubbob/levels/README.txt create mode 100644 bubbob/levels/RandomLevels.py create mode 100644 bubbob/levels/rnglevel create mode 100644 bubbob/levels/scratch.py create mode 100644 bubbob/macbinary.py create mode 100644 bubbob/mnstrmap.py create mode 100644 bubbob/monsters.py create mode 100644 bubbob/music/Snd1-8.wav create mode 100644 bubbob/music/Snd2-8.wav create mode 100644 bubbob/music/Snd3-8.wav create mode 100644 bubbob/music/Snd4-8.wav create mode 100644 bubbob/music/Snd5-8.wav create mode 100644 bubbob/music/Snd6-8.wav create mode 100644 bubbob/patmap.py create mode 100644 bubbob/player.py create mode 100644 bubbob/ranking.py create mode 100644 bubbob/save_rnglevel.py create mode 100755 bubbob/setup.py create mode 100644 bubbob/sounds/die.wav create mode 100644 bubbob/sounds/extra.wav create mode 100644 bubbob/sounds/extralife.wav create mode 100644 bubbob/sounds/fruit.wav create mode 100644 bubbob/sounds/hell.wav create mode 100644 bubbob/sounds/hurry.wav create mode 100644 bubbob/sounds/jump.wav create mode 100644 bubbob/sounds/letsgo.wav create mode 100644 bubbob/sounds/pop.wav create mode 100644 bubbob/sounds/shh.wav create mode 100644 bubbob/sounds/yippee.wav create mode 100644 bubbob/sprmap.py create mode 100644 bubbob/statesaver.c create mode 100644 bubbob/statesaver.py create mode 100755 bubbob/statesaver.so create mode 100644 bubbob/test_rnglevel.py create mode 100644 bubbob/test_statesaver.py create mode 100644 bubbob/tmp/pat00.ppm create mode 100644 bubbob/tmp/pat01.ppm create mode 100644 bubbob/tmp/pat02.ppm create mode 100644 bubbob/tmp/pat03.ppm create mode 100644 bubbob/tmp/pat04.ppm create mode 100644 bubbob/tmp/pat05.ppm create mode 100644 bubbob/tmp/pat06.ppm create mode 100644 bubbob/tmp/pat07.ppm create mode 100644 bubbob/tmp/pat08.ppm create mode 100644 bubbob/tmp/pat09.ppm create mode 100644 bubbob/tmp/pat10.ppm create mode 100644 bubbob/tmp/pat11.ppm create mode 100644 bubbob/tmp/pat12.ppm create mode 100644 bubbob/tmp/pat13.ppm create mode 100644 bubbob/tmp/pat14.ppm create mode 100644 bubbob/tmp/pat15.ppm create mode 100644 bubbob/tmp/pat16.ppm create mode 100644 bubbob/tmp/pat17.ppm create mode 100644 bubbob/tmp/pat18.ppm create mode 100644 bubbob/tmp/pat19.ppm create mode 100644 bubbob/tmp/pat20.ppm create mode 100644 common/.cvsignore create mode 100644 common/__init__.py create mode 100644 common/gamesrv.py create mode 100644 common/hostchooser.py create mode 100644 common/httpserver.py create mode 100644 common/javaserver.py create mode 100644 common/msgstruct.py create mode 100644 common/pixmap.py create mode 100644 common/stdlog.py create mode 100644 common/udpovertcp.py create mode 100644 display/.cvsignore create mode 100644 display/Client.py create mode 100644 display/Makefile create mode 100644 display/__init__.py create mode 100644 display/caching.py create mode 100644 display/dpy_gtk.py create mode 100644 display/dpy_pygame.py create mode 100755 display/dpy_windows.py create mode 100644 display/dpy_x.py create mode 100644 display/modes.py create mode 100644 display/music1.py create mode 100644 display/pclient.py create mode 100644 display/playback.py create mode 100644 display/puremixer.py create mode 100644 display/pythonxlibintf.py create mode 100755 display/setup.py create mode 100644 display/snd_linux.py create mode 100644 display/snd_off.py create mode 100644 display/snd_pygame.py create mode 100644 display/snd_windows.py create mode 100644 display/windows/.cvsignore create mode 100755 display/windows/wingame.c create mode 100755 display/windows/wingame.def create mode 100755 display/windows/wingame.dsp create mode 100755 display/windows/wingame.dsw create mode 100644 display/xshm.c create mode 100755 display/xshm.so create mode 100644 doc/BubBob.py.1 create mode 100644 doc/Client.py.1 create mode 100644 doc/Makefile create mode 100644 doc/bb.py.1 create mode 100644 http2/.cvsignore create mode 100644 http2/config.txt create mode 100644 http2/data/bab.png create mode 100644 http2/data/baub.png create mode 100644 http2/data/beab.png create mode 100644 http2/data/beb.png create mode 100644 http2/data/biab.png create mode 100644 http2/data/bib.png create mode 100644 http2/data/biob.png create mode 100644 http2/data/bob.png create mode 100644 http2/data/boob.png create mode 100644 http2/data/bub.png create mode 100644 http2/data/byb.png create mode 100644 http2/data/checked.png create mode 100644 http2/data/close.png create mode 100644 http2/data/confirm.html create mode 100644 http2/data/disabled.png create mode 100644 http2/data/hat1.png create mode 100644 http2/data/hat2.png create mode 100644 http2/data/header.png create mode 100644 http2/data/index.html create mode 100644 http2/data/lbab.png create mode 100644 http2/data/lbeb.png create mode 100644 http2/data/lbib.png create mode 100644 http2/data/lbiob.png create mode 100644 http2/data/name.html create mode 100644 http2/data/new.html create mode 100644 http2/data/options.html create mode 100644 http2/data/sfbob.png create mode 100644 http2/data/sfbub.png create mode 100644 http2/data/stop.html create mode 100644 http2/data/unchecked.png create mode 100644 http2/data/wave1.png create mode 100644 http2/data/wave2.png create mode 100644 http2/data/wave3.png create mode 100644 http2/header.png create mode 100644 http2/httppages.py create mode 100755 http2/sf/bb12.py create mode 100644 http2/sf/sfbub.png create mode 100644 http2/sf/started.html create mode 100644 java/.cvsignore create mode 100644 java/Makefile create mode 100644 java/pclient.java create mode 100644 metaserver/.cvsignore create mode 100644 metaserver/__init__.py create mode 100644 metaserver/home.png create mode 100644 metaserver/index.html create mode 100644 metaserver/mbub.png create mode 100644 metaserver/metaclient.py create mode 100644 metaserver/metaserver.py create mode 100644 metaserver/metastruct.py create mode 100644 metaserver/pipelayer.py create mode 100644 metaserver/socketoverudp.py diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..ccabf62 --- /dev/null +++ b/.cvsignore @@ -0,0 +1 @@ +BubBob.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cbc609 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +CVS/ +*~ +*.py[co] diff --git a/BubBob.icns b/BubBob.icns new file mode 100644 index 0000000..4f34b4b Binary files /dev/null and b/BubBob.icns differ diff --git a/BubBob.py b/BubBob.py new file mode 100755 index 0000000..52820c3 --- /dev/null +++ b/BubBob.py @@ -0,0 +1,164 @@ +#! /usr/bin/env python + +# +# This script is used to start the server. +# For command-line usage please run +# +# python bubbob/bb.py --help +# + +# __________ +import os, sys +print 'Running on Python', sys.version +if __name__ == '__main__': + LOCALDIR = sys.argv[0] +else: + LOCALDIR = __file__ +try: + LOCALDIR = os.readlink(LOCALDIR) +except: + pass +sys.argv[0] = os.path.abspath(LOCALDIR) +LOCALDIR = os.path.dirname(sys.argv[0]) +# ---------- + +import socket, tempfile + +sys.path.insert(0, LOCALDIR) +os.chdir(LOCALDIR) + +try: + username = '-'+os.getlogin() +except: + try: + import pwd + username = '-'+pwd.getpwuid(os.getuid())[0] + except: + username = '' +TAGFILENAME = 'BubBob-%s%s.url' % (socket.gethostname(), username) +TAGFILENAME = os.path.join(tempfile.gettempdir(), TAGFILENAME) + + +def load_url_file(): + try: + url = open(TAGFILENAME, 'r').readline().strip() + except (OSError, IOError): + return None, None + if not url.startswith('http://127.0.0.1:'): + return None, None + url1 = url[len('http://127.0.0.1:'):] + try: + port = int(url1[:url1.index('/')]) + except ValueError: + return None, None + return url, port + +def look_for_local_server(): + # Look for a running local web server + url, port = load_url_file() + if port is None: + return None + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(('127.0.0.1', port)) + except socket.error, e: + return None + try: + s.shutdown(2) + s.close() + except Exception, e: + pass + url2, port2 = load_url_file() + if port2 != port: + return None + return url2 + +def start_local_server(): + MAINSCRIPT = os.path.join('bubbob', 'bb.py') + has_server = os.path.exists(MAINSCRIPT) + if hasattr(os, 'fork') and hasattr(os, 'dup2'): + if os.fork() == 0: + # in the child process + if has_server: + sys.path.insert(0, os.path.join(LOCALDIR, 'bubbob')) + import bb + bb.BubBobGame.Quiet = 1 + else: + sys.path.insert(0, os.path.join(LOCALDIR, 'http2')) + import httppages + import gamesrv, stdlog + logfile = stdlog.LogFile() + if has_server: + bb.start_metaserver(TAGFILENAME, 0) + else: + httppages.main(None, TAGFILENAME, 0) + if logfile: + print >> logfile + if logfile: + print "Logging to", logfile.filename + fd = logfile.f.fileno() + try: + # detach from parent + os.dup2(fd, 1) + os.dup2(fd, 2) + os.dup2(fd, 0) + except OSError: + pass + logfile.close() + gamesrv.mainloop() + sys.exit(0) + else: + if not has_server: + MAINSCRIPT = os.path.join('http2', 'httppages.py') + args = [sys.executable, MAINSCRIPT, '--quiet', + '--saveurlto=%s' % TAGFILENAME] + # (quoting sucks on Windows) ** 42 + if sys.platform == 'win32': + args[0] = '"%s"' % (args[0],) + os.spawnv(os.P_NOWAITO, sys.executable, args) + + +# main +url = look_for_local_server() +if not url: + start_local_server() + # wait for up to 5 seconds for the server to start + for i in range(10): + import time + time.sleep(0.5) + url = look_for_local_server() + if url: + break + else: + print >> sys.stderr, 'The local server is not starting, giving up.' + sys.exit(1) + +try: + import webbrowser + browser = webbrowser.get() + name = getattr(browser, 'name', browser.__class__.__name__) + print "Trying to open '%s' with '%s'..." % (url, name) + browser.open(url) +except: + exc, val, tb = sys.exc_info() + print '-'*60 + print >> sys.stderr, "Failed to launch the web browser:" + print >> sys.stderr, " %s: %s" % (exc.__name__, val) + print + print "Sorry, I guess you have to go to the following URL manually:" +else: + print "Done running '%s'." % name + if look_for_local_server() != url: + # assume that browser.open() waited for the browser to finish + # and that the server has been closed from the browser. + raise SystemExit + print + print '-'*60 + print "If the browser fails to open the page automatically," + print "you will have to manually go to the following URL:" +print ' ', url +print '-'*60 +print "Note that the server runs in the background. You have to use" +print "the 'Stop this program' link to cleanly stop it." +print "Normally, however, running this script multiple times should" +print "not create multiple servers in the background." diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..2ffd320 --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,63 @@ + ======================================================= +==== The Bub's Brothers ==== + ======================================================= + + +Home page: + http://bub-n-bros.sourceforge.net/ + +Installation instructions: + http://bub-n-bros.sourceforge.net/download.html + +Basic instructions on how to play: + http://bub-n-bros.sourceforge.net/howto.html + +If you unpacked this from an official release, you will find a copy of +some pages in the doc/ subdirectory. + +Installation on *NIX +-------------------- + +NOTE!! THIS IS STILL EXPERIMENTAL. The game does not have to be + installed to play. The currently directory layout was not + designed with Unix-style installation in mind. For a + QUICK START see in doc/Introduction.html. + +The game (and the man pages) can be installed on *NIX systems into a +standard location like this: + +1) Check locations and permissions in the top-level Makefile. The game + is installed into /usr/local by default. + +2) Optionally run "make" to build extensions that enable cool powerups + and xshm operation. Recommended + +3) Run "make install" as root or other user with sufficient rights. + +NOTE!! The game logic is still quite directory-layout dependant ATM so + installing is implemented by copying the whole directory into + /usr/local/lib/ (the location can be changed in the Makefile) + and making symlinks to appropriate directories. This procedure + will probably be cleaned up in future releases. + +Copyright +--------- + +Almost all sprite images, sounds, background musics and levels are +directly taken from the MacOS version of Bub & Bob 1 by McSebi, and +redistributed with his gracious permission. Most graphics have been +improved or remade by David Gowers. + + http://www.mcsebi.de + +See LICENSE.txt for more information. + + +Authors +------- + +Programming: Armin Rigo +Art: David Gowers, based on graphics from McSebi +Levels: Gio & Armin +Special thanks: Odie & Brachamutanda +Beta-testers: IMA Connection diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..60378c1 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,33 @@ +License +======= + +The source code is copyrighted by Armin Rigo , and +distributed under the MIT License, a copy of which is included below. + +In the current version most graphics have been made by David +Gowers, based on graphics from McSebi. + +Almost all sounds, background musics and levels are directly +taken from the MacOS version of Bub & Bob 1 by McSebi. Thanks +to him for great artwork and his permission to redistribute +it! He released them under the following terms: + + """ + I release the graphics and sound data of my + program Bub & Bob 1 under the revised Artistic License. + Please read artistic.txt for details. + Sebastian Wegner + Copyright (C) 2005 Sebastian Wegner + """ + + +MIT License +=========== + +Source Code Copyright (c) 2003-2005 Armin Rigo + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..39ed683 --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +OWNER=root +GROUP=games +LIBDIR=/usr/local/lib +BINDIR=/usr/local/games + +MANOWNER=root +MANGROUP=root +MANDIR=/usr/local/man + +INSTALL=install +PYTHON=python + +export # we export all variales to sub-makes + +all: + if [ -e bubbob ]; then make -C bubbob; fi + make -C display + @echo ------------------------------------------------------------- + @echo \'make\' successful. + @echo ' ' + @echo ' Start the game interactively with: python BubBob.py' + @if [ -e bubbob ]; then echo ' Server only (pure command-line): python bubbob/bb.py --help'; else echo ' Only the client is installed here.'; fi + @echo ' ' + @echo ------------------------------------------------------------- + +clean: + -rm -f `find -name "*~"` + -rm -f `find -name "*.py[co]"` + -rm -fr `find -name "build"` + make -C doc clean + cd bubbob/images && python buildcolors.py -c + rm -fr cache + +sync: magma-sync codespeak-sync + +magma-sync: + rsync --delete -avz -e ssh ~/games/* magma:games/x/ + +codespeak-sync: + rsync --delete -avz -e ssh ${HOME}/games/metaserver ${HOME}/games/common codespeak.net:games/ + +meta: + ssh codespeak.net python games/metaserver/metaserver.py -f + +docs: + make -C doc + +install-docs: + make -C doc install + +# crude install +install: install-docs +# install fanciness not yet implemented :) +# make -C bubbob install +# make -C display install + $(INSTALL) -d $(LIBDIR)/bub-n-bros + cp -R . $(LIBDIR)/bub-n-bros + chown -R $(OWNER):$(GROUP) $(LIBDIR)/bub-n-bros + ln -s $(LIBDIR)/bub-n-bros/display/Client.py $(BINDIR)/bubnbros + ln -s $(LIBDIR)/bub-n-bros/bubbob/bb.py $(BINDIR)/bubnbros-server + chmod +x $(BINDIR)/bubnbros + chmod +x $(BINDIR)/bubnbros-server + $(PYTHON) $(LIBDIR)/bub-n-bros/bubbob/images/buildcolors.py diff --git a/artistic.txt b/artistic.txt new file mode 100644 index 0000000..afcdcea --- /dev/null +++ b/artistic.txt @@ -0,0 +1,73 @@ +Artistic License 2.0 +Copyright (c) 2000-2006, The Perl Foundation. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Preamble +This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. + +Definitions +"Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. + +"Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. + +"You" and "your" means any person who would like to copy, distribute, or modify the Package. + +"Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. + +"Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. + +"Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. + +"Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. + +"Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. + +"Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. + +"Source" form means the source code, documentation source, and configuration files for the Package. + +"Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. + +Permission for Use and Modification Without Distribution +(1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. + +Permissions for Redistribution of the Standard Version +(2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. + +Distribution of Modified Versions of the Package as Source +(4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: + +(a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. +(b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. +(c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under +(i) the Original License or +(ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. +Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source +(5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. + +Aggregating or Linking the Package +(7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. + +Items That are Not Considered Part of a Modified Version +(9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. + +General Provisions +(10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. + +(11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. + +(12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. + +(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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)= 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 ydy] + if lst: south[dy] = min(lst) + lst = [x for x, y in self.bubbles_pos if xdx 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< 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 '%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, """ + + + + + The Bub's Brothers Bonuses + + + + + + + + + + +""" +#" 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 += '
' + 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, '', + print >> dfile, '' % (bgcolor, doc), + print >> dfile, '' % bgcolor, + print >> dfile, '' % (bgcolor, bigdoc) + +print >> dfile, """
+ + + regular bonus + + + big bonus +
', + print >> dfile, images, + print >> dfile, '%s%s
+ + +""" + +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 Binary files /dev/null and b/bubbob/ext1/brick.wav differ diff --git a/bubbob/ext1/image1-0.ppm b/bubbob/ext1/image1-0.ppm new file mode 100644 index 0000000..03d6b3a Binary files /dev/null and b/bubbob/ext1/image1-0.ppm differ diff --git a/bubbob/ext1/music.wav b/bubbob/ext1/music.wav new file mode 100644 index 0000000..261b9cf Binary files /dev/null and b/bubbob/ext1/music.wav differ diff --git a/bubbob/ext1/wall.wav b/bubbob/ext1/wall.wav new file mode 100644 index 0000000..f93df15 Binary files /dev/null and b/bubbob/ext1/wall.wav 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 Binary files /dev/null and b/bubbob/ext2/image1.ppm differ diff --git a/bubbob/ext2/image2.ppm b/bubbob/ext2/image2.ppm new file mode 100644 index 0000000..4ed7060 Binary files /dev/null and b/bubbob/ext2/image2.ppm differ diff --git a/bubbob/ext2/music.wav b/bubbob/ext2/music.wav new file mode 100644 index 0000000..921322e Binary files /dev/null and b/bubbob/ext2/music.wav 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 Binary files /dev/null and b/bubbob/ext3/image1-0.ppm differ diff --git a/bubbob/ext3/music.wav b/bubbob/ext3/music.wav new file mode 100644 index 0000000..4684177 Binary files /dev/null and b/bubbob/ext3/music.wav differ diff --git a/bubbob/ext3/shoot.wav b/bubbob/ext3/shoot.wav new file mode 100644 index 0000000..fb2b099 Binary files /dev/null and b/bubbob/ext3/shoot.wav 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 xxmax: 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 Binary files /dev/null and b/bubbob/ext4/image1-0.ppm differ diff --git a/bubbob/ext4/music.wav b/bubbob/ext4/music.wav new file mode 100644 index 0000000..b4b01a4 Binary files /dev/null and b/bubbob/ext4/music.wav 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 +@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file 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 +@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file 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 +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿ@ÿ@ÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ÿ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/bubbob/ext5/image4.ppm b/bubbob/ext5/image4.ppm new file mode 100644 index 0000000..8f22db8 Binary files /dev/null and b/bubbob/ext5/image4.ppm differ diff --git a/bubbob/ext5/music.wav b/bubbob/ext5/music.wav new file mode 100644 index 0000000..de1efb4 Binary files /dev/null and b/bubbob/ext5/music.wav differ diff --git a/bubbob/ext5/ouch.wav b/bubbob/ext5/ouch.wav new file mode 100644 index 0000000..5d3dd8b Binary files /dev/null and b/bubbob/ext5/ouch.wav 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 Binary files /dev/null and b/bubbob/ext6/crash.wav differ diff --git a/bubbob/ext6/image1-0.ppm b/bubbob/ext6/image1-0.ppm new file mode 100644 index 0000000..bb54ce2 Binary files /dev/null and b/bubbob/ext6/image1-0.ppm differ diff --git a/bubbob/ext6/music.wav b/bubbob/ext6/music.wav new file mode 100644 index 0000000..8c55b34 Binary files /dev/null and b/bubbob/ext6/music.wav 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 Binary files /dev/null and b/bubbob/ext7/fire.wav differ diff --git a/bubbob/ext7/hit.wav b/bubbob/ext7/hit.wav new file mode 100644 index 0000000..36d497a Binary files /dev/null and b/bubbob/ext7/hit.wav differ diff --git a/bubbob/ext7/image1-0.ppm b/bubbob/ext7/image1-0.ppm new file mode 100644 index 0000000..2df7b9b Binary files /dev/null and b/bubbob/ext7/image1-0.ppm differ diff --git a/bubbob/ext7/music.wav b/bubbob/ext7/music.wav new file mode 100644 index 0000000..b45f018 Binary files /dev/null and b/bubbob/ext7/music.wav 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 Binary files /dev/null and b/bubbob/images/10000_0.ppm differ diff --git a/bubbob/images/20000_0.ppm b/bubbob/images/20000_0.ppm new file mode 100644 index 0000000..d1abe4b Binary files /dev/null and b/bubbob/images/20000_0.ppm differ diff --git a/bubbob/images/30000_0.ppm b/bubbob/images/30000_0.ppm new file mode 100644 index 0000000..9869ceb Binary files /dev/null and b/bubbob/images/30000_0.ppm differ diff --git a/bubbob/images/40000_0.ppm b/bubbob/images/40000_0.ppm new file mode 100644 index 0000000..56b3fca Binary files /dev/null and b/bubbob/images/40000_0.ppm differ diff --git a/bubbob/images/50000_0.ppm b/bubbob/images/50000_0.ppm new file mode 100644 index 0000000..765ab0f Binary files /dev/null and b/bubbob/images/50000_0.ppm differ diff --git a/bubbob/images/60000_0.ppm b/bubbob/images/60000_0.ppm new file mode 100644 index 0000000..ed220b8 Binary files /dev/null and b/bubbob/images/60000_0.ppm differ diff --git a/bubbob/images/70000_0.ppm b/bubbob/images/70000_0.ppm new file mode 100644 index 0000000..575cd03 Binary files /dev/null and b/bubbob/images/70000_0.ppm 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 Binary files /dev/null and b/bubbob/images/blitzy.ppm differ diff --git a/bubbob/images/blitzy_angry.ppm b/bubbob/images/blitzy_angry.ppm new file mode 100644 index 0000000..464ce2b Binary files /dev/null and b/bubbob/images/blitzy_angry.ppm differ diff --git a/bubbob/images/blitzy_shot.ppm b/bubbob/images/blitzy_shot.ppm new file mode 100644 index 0000000..378c956 Binary files /dev/null and b/bubbob/images/blitzy_shot.ppm differ diff --git a/bubbob/images/bonus_0.ppm b/bubbob/images/bonus_0.ppm new file mode 100644 index 0000000..4fa6e4c Binary files /dev/null and b/bubbob/images/bonus_0.ppm differ diff --git a/bubbob/images/bonus_1.ppm b/bubbob/images/bonus_1.ppm new file mode 100644 index 0000000..82ddd25 Binary files /dev/null and b/bubbob/images/bonus_1.ppm differ diff --git a/bubbob/images/bonus_10.ppm b/bubbob/images/bonus_10.ppm new file mode 100644 index 0000000..9ffd660 Binary files /dev/null and b/bubbob/images/bonus_10.ppm differ diff --git a/bubbob/images/bonus_11.ppm b/bubbob/images/bonus_11.ppm new file mode 100644 index 0000000..1a5c509 Binary files /dev/null and b/bubbob/images/bonus_11.ppm differ diff --git a/bubbob/images/bonus_12.ppm b/bubbob/images/bonus_12.ppm new file mode 100644 index 0000000..354a1fe Binary files /dev/null and b/bubbob/images/bonus_12.ppm differ diff --git a/bubbob/images/bonus_2.ppm b/bubbob/images/bonus_2.ppm new file mode 100644 index 0000000..61588b0 Binary files /dev/null and b/bubbob/images/bonus_2.ppm differ diff --git a/bubbob/images/bonus_3.ppm b/bubbob/images/bonus_3.ppm new file mode 100644 index 0000000..dd1ff6d Binary files /dev/null and b/bubbob/images/bonus_3.ppm differ diff --git a/bubbob/images/bonus_4.ppm b/bubbob/images/bonus_4.ppm new file mode 100644 index 0000000..c85e926 Binary files /dev/null and b/bubbob/images/bonus_4.ppm differ diff --git a/bubbob/images/bonus_5.ppm b/bubbob/images/bonus_5.ppm new file mode 100644 index 0000000..57bd455 Binary files /dev/null and b/bubbob/images/bonus_5.ppm differ diff --git a/bubbob/images/bonus_6.ppm b/bubbob/images/bonus_6.ppm new file mode 100644 index 0000000..2ac4a64 Binary files /dev/null and b/bubbob/images/bonus_6.ppm differ diff --git a/bubbob/images/bonus_7.ppm b/bubbob/images/bonus_7.ppm new file mode 100644 index 0000000..5de3eaa Binary files /dev/null and b/bubbob/images/bonus_7.ppm differ diff --git a/bubbob/images/bonus_8.ppm b/bubbob/images/bonus_8.ppm new file mode 100644 index 0000000..b1c6c2a Binary files /dev/null and b/bubbob/images/bonus_8.ppm differ diff --git a/bubbob/images/bonus_9.ppm b/bubbob/images/bonus_9.ppm new file mode 100644 index 0000000..8d51608 Binary files /dev/null and b/bubbob/images/bonus_9.ppm 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<>^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 Binary files /dev/null and b/bubbob/images/butterfly.ppm differ diff --git a/bubbob/images/cream_pie_big.ppm b/bubbob/images/cream_pie_big.ppm new file mode 100644 index 0000000..827a2e0 Binary files /dev/null and b/bubbob/images/cream_pie_big.ppm differ diff --git a/bubbob/images/diamond_big_blue.ppm b/bubbob/images/diamond_big_blue.ppm new file mode 100644 index 0000000..78f7a8c Binary files /dev/null and b/bubbob/images/diamond_big_blue.ppm 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 Binary files /dev/null and b/bubbob/images/diamond_big_red.ppm differ diff --git a/bubbob/images/diamond_big_yellow.ppm b/bubbob/images/diamond_big_yellow.ppm new file mode 100644 index 0000000..7ace2ba Binary files /dev/null and b/bubbob/images/diamond_big_yellow.ppm differ diff --git a/bubbob/images/digits_0.ppm b/bubbob/images/digits_0.ppm new file mode 100644 index 0000000..54cc4e0 Binary files /dev/null and b/bubbob/images/digits_0.ppm differ diff --git a/bubbob/images/door.ppm b/bubbob/images/door.ppm new file mode 100644 index 0000000..dbc8b69 Binary files /dev/null and b/bubbob/images/door.ppm differ diff --git a/bubbob/images/dragon_0.ppm b/bubbob/images/dragon_0.ppm new file mode 100644 index 0000000..14272ec Binary files /dev/null and b/bubbob/images/dragon_0.ppm differ diff --git a/bubbob/images/dragon_bubble_0.ppm b/bubbob/images/dragon_bubble_0.ppm new file mode 100644 index 0000000..7457ffe Binary files /dev/null and b/bubbob/images/dragon_bubble_0.ppm differ diff --git a/bubbob/images/extend.ppm b/bubbob/images/extend.ppm new file mode 100644 index 0000000..bed1ae8 Binary files /dev/null and b/bubbob/images/extend.ppm differ diff --git a/bubbob/images/extra1.ppm b/bubbob/images/extra1.ppm new file mode 100644 index 0000000..bf57813 Binary files /dev/null and b/bubbob/images/extra1.ppm differ diff --git a/bubbob/images/extra2.ppm b/bubbob/images/extra2.ppm new file mode 100644 index 0000000..0aa664d Binary files /dev/null and b/bubbob/images/extra2.ppm differ diff --git a/bubbob/images/extra3.ppm b/bubbob/images/extra3.ppm new file mode 100644 index 0000000..42d3030 Binary files /dev/null and b/bubbob/images/extra3.ppm differ diff --git a/bubbob/images/extra4.ppm b/bubbob/images/extra4.ppm new file mode 100644 index 0000000..c2b1066 Binary files /dev/null and b/bubbob/images/extra4.ppm 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[~5\9a“[~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©HKhYJNRRRRRRrrr–––¥¥¥ªªª®®®¹||À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<‡€>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<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<[”><<:‹: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:mjqn;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{{{ƒƒƒrrriiiRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRqn?spF|x–ŸŸ¬¬¬§§§———zzz\\\]]]wwwŠŠŠb„€CxtCyuE{wE{wDzvCyuGyvEwt?sp?ro?roAvrG}yM‡‚t—”¢¢¢”””zzz]]]ZZZrrr~€€?pm???? .-.A@AGEFÓÓÓÅÅÅ›š›qopéééÓÓÓ¤¤¤423KIK···”””"!"‘‘‘ŒŒŒqpqHFG]]]535/// +  +MKLcacA?AàààÖÖÖ¤££…„…ÚÚÚÒÒÒ¨¨¨767Ž®®®ªªª‰ˆ‰pppyyy#"# + +CCCJII%#$ECDÐÎÏÔÔÔ_]^/./²±²ÞÞÞÚÚÚ¬¬¬  poo›››µµµ´´´•”• *()WWWuuu‡†‡trt&%&=;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 Binary files /dev/null and b/bubbob/images/ghosty.ppm differ diff --git a/bubbob/images/ghosty_angry.ppm b/bubbob/images/ghosty_angry.ppm new file mode 100644 index 0000000..95f533e Binary files /dev/null and b/bubbob/images/ghosty_angry.ppm 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 Binary files /dev/null and b/bubbob/images/gramy.ppm differ diff --git a/bubbob/images/gramy_angry.ppm b/bubbob/images/gramy_angry.ppm new file mode 100644 index 0000000..c1672ee Binary files /dev/null and b/bubbob/images/gramy_angry.ppm 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 Binary files /dev/null and b/bubbob/images/ice_cyan_big.ppm differ diff --git a/bubbob/images/ice_violet_big.ppm b/bubbob/images/ice_violet_big.ppm new file mode 100644 index 0000000..d1d967e Binary files /dev/null and b/bubbob/images/ice_violet_big.ppm differ diff --git a/bubbob/images/keys.ppm b/bubbob/images/keys.ppm new file mode 100644 index 0000000..4d20147 Binary files /dev/null and b/bubbob/images/keys.ppm differ diff --git a/bubbob/images/level_digits.ppm b/bubbob/images/level_digits.ppm new file mode 100644 index 0000000..1110f8b Binary files /dev/null and b/bubbob/images/level_digits.ppm differ diff --git a/bubbob/images/lightning_large.ppm b/bubbob/images/lightning_large.ppm new file mode 100644 index 0000000..67ac930 Binary files /dev/null and b/bubbob/images/lightning_large.ppm differ diff --git a/bubbob/images/lightning_small.ppm b/bubbob/images/lightning_small.ppm new file mode 100644 index 0000000..cd3cf22 Binary files /dev/null and b/bubbob/images/lightning_small.ppm differ diff --git a/bubbob/images/monky.ppm b/bubbob/images/monky.ppm new file mode 100644 index 0000000..01afbae Binary files /dev/null and b/bubbob/images/monky.ppm differ diff --git a/bubbob/images/monky_angry.ppm b/bubbob/images/monky_angry.ppm new file mode 100644 index 0000000..786d39a Binary files /dev/null and b/bubbob/images/monky_angry.ppm differ diff --git a/bubbob/images/nasty.ppm b/bubbob/images/nasty.ppm new file mode 100644 index 0000000..a006bcd Binary files /dev/null and b/bubbob/images/nasty.ppm differ diff --git a/bubbob/images/nasty_angry.ppm b/bubbob/images/nasty_angry.ppm new file mode 100644 index 0000000..58375e1 Binary files /dev/null and b/bubbob/images/nasty_angry.ppm differ diff --git a/bubbob/images/orcy.ppm b/bubbob/images/orcy.ppm new file mode 100644 index 0000000..1ddb609 Binary files /dev/null and b/bubbob/images/orcy.ppm differ diff --git a/bubbob/images/orcy_angry.ppm b/bubbob/images/orcy_angry.ppm new file mode 100644 index 0000000..59b51c3 Binary files /dev/null and b/bubbob/images/orcy_angry.ppm differ diff --git a/bubbob/images/palettes.dat b/bubbob/images/palettes.dat new file mode 100644 index 0000000..5070cbb Binary files /dev/null and b/bubbob/images/palettes.dat 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ûÿþøêSCÚ¬‰Ïê»ÌÿÌÌÿÌÌÿÌÎÿÎÌÿÌÌÿÌÌÿÌÌÿÌÐî¿Ý¼šîveüHEÿ^^ü[[þFFþ]]ÿzzÿNNÿRRÿSSÿ__ÿttýuuýooÿÿÿyyÿ††ÿ‹‹ÿÿÿ””ÿŽŽÿ““ÿ˜˜ÿ™™ÿ™™ÿ˜˜ÿ““ÿŽŽÿ‹‹ÿÿ‰‰ÿˆˆÿŠŠÿ‡‡ÿƒƒÿ||ÿÿvvÿ{{ÿwwÿ||ÿ‡‡ÿÿffÿccÿQQÿWWÿnnÿxxÿrrþggÿ\\ÿ~~ÿjjÿffþqqÿkkÿssÿ[[ÿ,,ÿ þ&&û ü%%ý))úIIþkküccúLLÿ88ÿBBÿ('ü õèSCج‰Ï껬ú¬ÌÿÌÌÿÌÎÿÎÌÿÌÌÿÌÌÿÌÌÿÌÐî¿Ý»™îsbû@<þ::ýKKþNNþ^^ÿooÿTTÿjjÿeeþPPüNNøVVúppûqqýttþttÿnnÿ‡‡ÿŒŒÿ‘‘ÿÿŽŽÿ‹‹ÿ’’ÿ––ÿ••ÿ’’ÿ‹‹ÿ€€ÿŽŽÿ~~ÿƒƒÿ{{ÿwwÿvvÿ{{ÿssÿqqÿ{{ÿrrÿwwÿ††ÿ~~ÿppÿffÿaaÿ]]ÿWWÿqqÿyyÿwwÿwwÿ{{ÿmmÿvvÿhhÿYYÿeeÿSSþ55üùøùû**øþ^^üRRýVVÿ66ý""þ))ýû.*ó"åSC׬‰®æœ©ù©©ù©ÌÿÌÎÿÎÌÿÌÌÿÌÌÿÌÍÿÌÑí¾Ý·•îjZû40ÿ22þEEþUUÿ[[ÿXXÿccþVVþXXú++ñ**ç22äAAíXXô``úXXþppÿvvÿ„„ÿŽŽÿ’’ÿ‹‹ÿ€€ÿŠŠÿ„„ÿ‹‹ÿ‡‡ÿ~~ÿˆˆÿ||ÿqqÿuuÿmmÿ||ÿ||ÿppÿccÿ``ÿnnÿwwÿwwÿkkÿggÿhhÿ__ÿ]]ÿ[[ÿjjÿ__ÿccÿ~~ÿuuÿppÿooÿmmÿppÿffÿTTþIIú$$óçâçñü66þ??ÿ``ÿ**ýþù''ù#"öîâSCÖ¬‰«åš¨ù¨¨ù¨ÌÿÌÎÿÎÌÿÌÌÿÌÍÿÌÎÿÌÒí¾Ü³î^Nú$ ÿÿDDþ==ÿXXþMMýIIÿHHý@@øNNßÈÁÈ$$à::òJJüXXÿppÿ}}ÿ‡‡ÿ‹‹ÿƒƒÿ‹‹ÿÿggÿ~~ÿyyÿ||ÿkkÿqqÿqqÿggÿyyÿrrÿƒƒÿggÿ__ÿZZÿYYÿiiÿeeÿaaÿ]]ÿ]]ÿYYÿYYÿYYÿXXÿZZÿ__ÿ__ÿeeÿhhÿ^^ÿhhÿaaÿbbÿEEü??ô**áÎÁÈÝôý<<ÿ$$ÿ$$ÿùû&&ù22óêßSCÔ¬‰À鯣ø£ÌÿÌÌÿÌÎÿÎÀü¼ÃýÂÄþÃÌÿ̽ê«Æ·„ìVEùÿ22ÿ00ÿ<<ÿ66ýNNþBBþ44üPPô>>ãGG¯¥µÝLLðXXúMMÿ``ÿ~~ÿppÿÿ‹‹ÿ‚‚ÿjjÿÿbbÿ||ÿjjÿvvÿhhÿddÿmmÿxxÿ}}ÿ]]ÿooÿYYÿYYÿddÿZZÿ\\ÿYYÿWWÿWWÿWWÿRRÿRRÿIIÿJJþJJþ__ÿZZÿeeÿccÿeeÿSSÿ))ÿ..úìÎ ¯¥¯Èèúÿÿÿüû**õïèÞSCÆ®€›çŽ°ú°®ú®ÌÿÌÎÿιü¹­ø«»üº±ù°ºé©Á±yÐb<øþ,,ÿBBÿVVþQQþDDýHHþHHû<<ôOOÑ$$Å44›Á11ÙEEíQQúRRÿxxÿkkÿÿiiÿttÿƒƒÿuuÿ^^ÿwwÿiiÿllÿddÿffÿiiÿvvÿttÿ__ÿttÿXXÿZZÿYYÿZZÿTTÿ^^ÿZZÿGGÿVVÿNNÿGGÿXXÿ[[ÿOOÿBBÿHHÿ\\ÿooÿggÿPPÿ==ÿ''ÿ55û@@âÁ¥›¥Áãø + +ÿÿýýEE÷ñíçÝSC£¸k±è¢ñ€¥÷¤¯ú¯Îÿήù®£ö¢³ú³³ú²¨å—µ¯oãR=÷#þBBÿNNÿXXýIIýHHþBBþ@@ùLLî++ä::·¥°Û99öhhühhÿ``ÿzzÿhhÿqqÿhhÿggÿnnÿeeÿjjÿffÿbbÿ``ÿccÿ__ÿbbÿooÿuuÿQQÿYYÿUUÿQQÿLLÿUUÿ[[þWWþRRþ..ÿ??ÿNNÿSSÿeeÿ``ÿkkÿ\\ÿccÿZZÿbbÿeeÿ;;ÿ55ÿGGüDDõDDȯ¥¯Éé û$$þ33ý66ýZZ÷û00øìå×R=Ǫ~䀖õ• ø ¥ø¥ÎÿÎôŒŠôŠ…ó…‰ô‰c½@pŸ/…tö5/üþFFÿ^^ÿOOýKKýEEýUUüZZôâÛÎ ÎñIIùVVýddÿppÿkkÿttÿYYÿccÿ]]ÿ^^ÿddÿ[[ÿ[[ÿZZÿPPÿTTÿSSÿUUÿppÿjjÿ[[ÿXXÿIIÿPPþMMÿXXþ^^ÿbbý66þ11ÿ&&ÿ44ÿPPÿggÿjjÿTTÿeeÿqqÿTTÿ[[ý88ü;;ÿ55ÿ55þTTøBBåÈÁÈÝòüü==üDDø##û66ú""ôñ à ÆP-¦¤]Œà{…ó…vñv•õ•ÉþÉWëWNêNdíd"â""É-» ­LØü**þÿEEÿ[[ÿTTÿQQþeeýAAúõðïî òüOOþ__ÿ@@ÿxxÿrrÿggÿPPÿXXÿYYÿZZÿSSÿQQÿ>>ÿ;;ÿLLÿUUÿYYÿTTÿ\\ÿaaÿUUÿNNÿ[[ÿXXýGGÿWWÿUUÿ..ÿÿÿJJÿEEÿKKÿ\\ÿQQÿzzÿPPÿggý99þRRú77ü""ÿ33þBBü66ôçâçñúüû''û@@÷**óõõ!!ï Ø­LX¡3Ï#<æã<'Ô-ÀhvÉó" ò22ý((ù77ü;;õAAüJJø//ü..ûHHüYYùNNÿbbþkkþLLýnnÿLLÿ@@ÿUUÿZZÿPPÿGGÿZZþPPÿ;;þ11ü//û<<û==üOOýQQþCCÿMMÿ!!ÿLLþQQÿIIþ22ÿPPþ33ÿCCþ""üùú++úýþÿ ÿKKÿ99ÿOOþHHþ--þ::ú((ÿbbÿQQú$$ýÿþýûøýô÷ú""õñåáÉhv#»#Î Þ ÛÚ4™ Ü)Û!à +Ý Ê È C“¾ó52ñ55ýEEøUUýQQõUUýFF÷&&ý22ýYYýSSþ^^ÿKKÿZZþQQÿ22ÿ11ÿSSÿ99ÿZZÿCCÿ33ÿ==ÿSSÿ88ý##ö##é""ã""é""ù33ýKKþ??þTTÿTTþ==û,,ýKKýNNý99û üôéãéôüÿ##ÿ$$ÿGGÿ%%þüûüÿú&&ý ú!!ùýúøóòöðöøçâÛ¾C“,ÂË +ßÙÓ4™ÛRßG×ÝÚ 4È4¢²,ð(%ú??ôddñFFõVVÿ\\ÿ]]÷GGö,,ùEEüNNÿQQÿVVÿ__ÿ[[ÿPPÿ''ÿ99ÿFFÿ??ÿ--ÿ88ÿEEþ>>ü((öéÎÅÏåü>>þJJÿTTþOOþOOþRRþVVüOOüOOþ÷åÏÅÎëöý22þ$$ÿJJÿ ýý + +ú ýõ ûú,,úüüôøõôóõööäàÔ ²,4¢! ÛÞØÏ4™NßEØHà@Ý!ÛÄJ¨­8ê ö""ù22ûII÷=<÷==öIIøJJÿHHÿö<<ü@@ÿFFÿ``ÿLLÿ==ÿ!!ÿ ÿ%%ÿ!!ÿ!!ÿÿ""üôéÔµ¬¸ÕôþIIÿXXþRRþVVþQQþVVÿWWÿ**üïÕ¸¬µÒ îöýIIÿ88ÿ33þÿ!!øñ÷ òþ""ý00û33ø óööõ÷÷öõçáÒ ­8%²ÁÚÝÖ!Ì4™ÅIÞAÙÝÙ+Ç.«‹TØó÷77ïB>ð20öAAøFFù;;ü++û31û??ø::ÿNNÿNNÿWWÿ ÿÿÿÿ + +ÿÿÿúïÛº¦Ÿ¬Éò((ý<<þHHÿKKÿAAÿOOÿOOÿ==ÿBBü..èɬŸ¦ºØî + +ü77ÿ55þ88ÿý--óæîòùû--ùü==õ ÷ úùõ÷øóïêØ‹T$³ÂÙÛÒ%Æ4™¸ÈØÝÙÂ)®_yÎ èó77÷43ù,+ö**î43ó66ö;;ú99ø,,ûEEÿBBÿBBÿ@@ÿ//ÿÿÿÿÿÿÿüïÓº¦Ÿ¬Éêý33ÿ55ÿ,,ÿ++þBBÿ<<ÿJJÿ))ý66ñ))ɬŸ¦ºÓëû''þEEþþýþ::óîù00ú77ôû66õ÷õõùøöïðãæÎ _y#³ÂÙØÊ,¼4™³ÅÖÝÙÂ$´MŠÍå æû>>÷((ð//ð=<õ=<ôBBõ22û76ÿÿ''ÿ99þ$$ýûûýþÿÿÿ üôã̵¬¸Õù00ü""ÿ88ýÿEEÿHHÿDDüÿ::ý**ïÕ¸¬µÌãø%%ý))ÿ))ÿ==ûüûö÷66÷77÷22ðó÷ôôö÷öû ôïàÛÍMŠ"´ÃÖÒÀ3³4™#®¿ÒÛÚÏ ·8žÀ#Ùæ îó00îKKïFFô@?û??ô66þ,,ÿ''þü÷ïìíöùýþ ÿ þý ÷ãÎÅÏî""ú''ý;;ý ÿKKþ;;ý@@ý;;þ22ú&&þ÷åÏÅÎãôü þ&&þ&&ûûôëãßî//éðòöòôøùúøù ëÝÕÀ#8ž¸ÅÕ Ì·9«4™)¦· ÌÙÛ Ó½'®jjÉÞàìCAí>>øCCøDDò,,ò%%ü%%ý&&üóåÕÉÏëðöüÿ ÿÿü öéãëøý ý,,þ44þEEü<<þLLú00ù''ý--þ##üôéãéôüþü + +û ÷îàÐÄÃËÙñìö÷îñùùù ñ äØÉjj'®½ÔÖÈ!±<§4™. "°ÆÖÛ×Â)®C“ÆÛõ99é**ò98î99èí22ôù""úôåÍ·¬¬ºØðûÿÿÿþýùùúý""þ00ÿÿEEþ<<ýAAû::ýBBü00ÿ""ÿþüùøùüýûøõïàÈ´©©³ÆëíøöñîúõúæãÚÆC“$±ÁØ×Ç"¯4™4™0&ª¿ ÒÛÙÊ"³/¦¶Úèô==ð%#íð,,èîîðêÖ¼¨ŸŸ«Åäøÿÿÿÿÿ ÿü&&üýÿÿüÿ==þ??ÿIIÿÿÿ11ÿ ÿÿþþþýúöóñê×¾©žž¨½åñøõúøùï÷ëèÚ¶/¦"³Ë×ÖÅ#®4™4™4™*¥¶Ë×٠θ(­ej»÷11íêçìï ñ ñ ê åÛű¦¨³Êäöýþþþ þ,,ü--üú""û÷ü((ÿ þ55þ33ÿõ))û&&þþ00ýúëêùøöòðîéÛƲ¨¨²Ðëïõ÷ö÷ïôôíÜ»ej(­· ÍÖÒ¿&ª4™4™,¤$¯ÅÎ×ÒÁ#²C“s\Õ æéí êéêç +æåæÙȾÁËÛêõùùùúûû++ú##ù%%óóôû//þ--þ&&õ!!÷##ô ù$$ù÷æâäæóòðîîìåÙËÁÁÏêìì÷ûöôòõãçÕ »C“$±ÀÈÔʵ4™4™4™4™'­!¿ Î××ʸ3¥bmÁìàæâ ææèßçìèÞØÛáèîòóôôô÷øû,,ø''òðóøú//ú##õïõ!!ôôñåãâéððîêíîìèáßãçðëõûø÷ø÷êáåÁbm(­º Î Ñ ÐÀ%¬4™4™4™4™+¦¶Ê×Õ Î»(­C“©*ÊÞ +àé ãàäèåäãäéêìîîïïîñòôõû11÷%%ô((êóù ø!!õòñö''õòïíåçïûCCð êèèíîîììðñðòòöõøóïÝæÊ©*C“#²Ç×Õʵ+¤4™4™4™4™4  ²ÆÖ×ÒÀ%°3£bm·ÜÝ +æ ßãåëãëíëæîîîíìëôïóòõôñ$$éêëòò ëêîðúCCø99ö++éèèø..ð + +øEEêèèíîðòòôöõôòõø÷ôðëÜ·bm&°½×ÙÒÀ%¬4™4™4™4™:¢ &ª¿ Ñ×ÕÈ%±-¨C“—<½Ù æÝááèáêçåãéíìéêõîõðîëìíéêíëëîëîíñ÷--õ$$ìåäèø::ïîêíëðòñôõñúõôóùõñÛѽ—<C“¸ÌÙÙ Ì·+¤4™4™4™4™+¤µÆÔÓ Î¼(®2£\t«*ÅßäàááâáÞäãèéèèèëôîìåÛÚáíð!!ï íìïìéì êõ òåÞØÛåéíîëëîíñõ÷úúõøõòæëÕÅ«*\t$²ÀÔÛÖÆ"°. 4™4™4™4™0Ÿ%­½ ÐÑ ÎÈ#³+ªC“Eµ"éçæãé áâàåæåæéååçìéå׎Ðó**ð÷::îíì ìçßäãèÙƽÅÚãæìéçæãðóòöùø÷ôììãÐ +µ"EC“» ÎØÛÒ¿&ª4™4™4™4™4™6,¢!²Æ +ÐÑÇÃ"´.£O“@ºçáæäåäëæãäèçææèæåÖÄ°¨²Ó + +ô11êèêéçááàåß˲¨°ÏÚäïîñìíìõòöòöõøîèÔº“@C“¹ÁÔÙØʵ+¤4™4™4™4™1)¦·É ÑËɾ%°.£S}š9µÝäÞèíæâàåçåààäãÚƳ¤ž¨ÔåæééåÜÚàäàÕÀ¨ž¤³Õæìêðõøòóðõ÷ùõïåÌ µš9S} ¶ÀÒØÙÒÀ%¬4™4™4™4™4™7œ/ž%¬¿ +Ñ +ÐÅǺ&¯.£Wx›7·éãåãàØäæçâÝáâßÒ°£ÂÒãççæáÚãçêÞѽ­£©±Ìçèãî÷÷ìôøøøøîäÏ +·›7Wx ¶½ +ÐØÛ×ʵ+¤4™4™4™4™3›+¤µ ÍÒ Ì Îǹ&¯.£Vxš5¹äçéÞÙæëâëìéääØË»¬¦®Õææèã + +ßâØÖÚæÔÊ´¬²ÂÙæéêéìóù÷õð÷ðèѹš5Vx#²»ÊÖÛÚ +п%«4™4™4™4™4™7œ/ž%«¿ ÑÓÉ ÎŽ'®.£YtRÀäèÝÞìîåîÝÞÜèáÕÑý½ÓÛâë á å äâÕÒßèÔ¼µ¹Îîêéò÷øùöûøïâæÞÀRYt$°ºÀÒÙÙ ÒÃ!±,¢4™4™4™4™3›,¢!±à ÒÑ Ì Ïɹ'®.£I†RËÝ ìððñïåÜÙÚàäÞÚÕÒÒÔãåêâàÝ×ÒÑßíÕËÌÐæøñæéõøüùö÷å×Ý ËRM‰&¯¹Ç Ð×Ø ÒÄ ²*¤14™4™4™4™7œ1œ*¤ ³ÅÓÒ ÎÈȼ$±0¥:˜gq¥+ÎëðæìàçÝÜÞíçÚÔØÚèäîãêá××ØÔÚìÙäåëãôìðìöò÷éèïîÎ¥+gq6 &¯¸ÉÒ×Ù ÑÄ ²*¤1œ4™4™4™4™4š1œ)¦·ËÕÔ Í +ÏǼ#³.§;›NˆwcÚÜßæßàîÞçóéâØÝáìîéÜâìâáàâáåçêïïêïðõôõëñïåâ ÚwcC“(­"³¹ÉÔÙÙ ÑÄ ²*¤1œ4™4™4™4™4™8›3™/ž%«¿ +ÐÖÔ Ð +ÐÊ¿!µ+«5 >—Nˆy`ÒÙâÞìéáëáìåççèäáàâðéðôòâêíñêîíìëíööçÝØÒy`C“/¨$±¸ÂÊÓÙÚ ÒÄ ²*¤1œ3™4™4™4™4™4™2›,¢!±à Ò×Ö Ð ÒÊÁ¹%±/¦;;C“t[ÃÜ îÞÕÜäâòèïëÝàäîôöëååðòôñêìðòíØÝäÝ Ãt[C“/«(°!´¾Ê ÐÔØÙ ÒÄ ²*¤1œ3™4™4™4™4™8›3™1œ*¤ ²Ä ÒÙ× Ò ÑÌÆ¿#´(­.ª;;C“v^ÉËÎÖÝæðêîðçàèðçíèäå÷ðâçòíåçÔÐËÉv^C“2§)° ·¾Å ÎÕØÚØ ÑÄ ²*¤1œ3™4™4™4™4™4™4™3™1œ*¤ ²à +ÐØØÓÓ ÑËÇ¿¸-¯:¡;;C“vVÁ Å ÌÞçíÕÜàíçíêÜáãæñàÝãðèÞÌÅ Á vVC“%°)®#´»Ç +ÏÔ×ÚÜÚ ÒÄ ²*¤1œ3™4™4™4™4™4™4™3™1œ*¤!±¿ËÕÙØØÔ +Ð Ï Ìƾ%°(¬(¬2¦4 P€uYÇ¿ Ñ Ö ÔÑêâèã××çíëÞÔÖ Ñ ¿ ÇuYP€4 '¯$±#²¸ÂÉÒ×ÚÚÙÖ +Ðà ²*¤1œ3™4™4™4™4™4™4™4™3™1œ,¢%«·Æ ÒÚÝÛØÖÔÒ ÏÊÂ"´-©)¯.­2©2¢<™EV€nk…V€SÉ ÐØâÝÐÉ €S…VnkV€E<™2¢&°#³!¶··ÀË ÐÔØÚØÒ Ìƾ!±*¤1œ3™4™4™4™4™4™4™4™3™2›/ž)¦ ³à +ÐØÛÛÛÙØ×ÖÒÌÇÁ¹!¶$³'°'®+ª2¥5¢7¡5 5 5 6¡7¡5 5 7¡5¢2¥+ª'®$±"³¸»ÀÂ Í ÑÖØÚÙÓËÀ·"°'©,¢1œ4™4™4™4™4™4™4™8›4™3™1œ*¤!±¿ÊÒÖÙÛÜÜÛÚØÖÓ ÏËǽ¹ µ#²%¯&®&®&®&®&®'¯'°'°'¯&°#²#²»ÄÉ Í ÑÖØÙÚÛÛØ +ÐÅ·%¬+¤. 02›4™4™4™4™4™4™8›8›8›3™1œ,¢%«µ¿Æ ÌÒØÛÝÝÜÛÚÙ×Ò Ëƾ½¿¿¿¿¿¿¿¿¿¿¿ÀÅ ËÒ×ÙÚÛÜÛÚÖÒÊ¿!±)¦/ž2›3™3™4™4™4™4™4™4™4™8›8›3™2›/ž+¤&ª"°·ÀÊÒÖÙÛÝÝÝÛÖÔÑ Ñ Ñ Ñ Ñ Ñ Ñ Ñ Ñ Ñ Ñ Ñ Ñ Ñ ÑÑÔØÛÛÚÙÖÒ ÌÆ¿µ%«,¢1œ3™3™4™4™4™4™4™4™4™8›8›3™3™2›0. +¤%¬µ¿Æ ÌÒØÛÝÝÛÙØØØØØØØØØØØÙÙÙØ×××ÖÒ ÌÈÅ¿·"°&ª+¤/ž2›3™3™4™4™4™4™4™4™4™8›8›4™3™3™3™2›/ž+¤&ª"°·ÀÊÒÖØØØØØØØØØØØØØØØØØØÖÒ ÌÆ¿·!±#®&ª+¤. 02›3™3™4™4™4™4™4™4™4™4™8›4™3™3™3™2›0. +¤%¬µ¿ÅÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÅ¿·"°&ª+¤. /ž02›3™3™3™4™4™4™4™4™4™4™4™4™4™8›4™3™3™3™2›/ž+¤&ª#®"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯"¯#®&ª+¤. 02›3™3™3™3™4™4™4™4™4™4™4™4™4™4™4™8›4™3™3™3™2›0/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž/ž02›3™3™3™3™3™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™4™ \ No newline at end of file diff --git a/bubbob/images/peach_big.ppm b/bubbob/images/peach_big.ppm new file mode 100644 index 0000000..3f2b6ca Binary files /dev/null and b/bubbob/images/peach_big.ppm differ diff --git a/bubbob/images/point_0.ppm b/bubbob/images/point_0.ppm new file mode 100644 index 0000000..c6ef319 Binary files /dev/null and b/bubbob/images/point_0.ppm differ diff --git a/bubbob/images/red_Hurry_up.ppm b/bubbob/images/red_Hurry_up.ppm new file mode 100644 index 0000000..6f957ac Binary files /dev/null and b/bubbob/images/red_Hurry_up.ppm differ diff --git a/bubbob/images/sheep.ppm b/bubbob/images/sheep.ppm new file mode 100644 index 0000000..66fe04a Binary files /dev/null and b/bubbob/images/sheep.ppm differ diff --git a/bubbob/images/shot.ppm b/bubbob/images/shot.ppm new file mode 100644 index 0000000..74606ff Binary files /dev/null and b/bubbob/images/shot.ppm differ diff --git a/bubbob/images/spinning_drop.ppm b/bubbob/images/spinning_drop.ppm new file mode 100644 index 0000000..6bb22f9 Binary files /dev/null and b/bubbob/images/spinning_drop.ppm differ diff --git a/bubbob/images/springy.ppm b/bubbob/images/springy.ppm new file mode 100644 index 0000000..16277ba Binary files /dev/null and b/bubbob/images/springy.ppm differ diff --git a/bubbob/images/springy_angry.ppm b/bubbob/images/springy_angry.ppm new file mode 100644 index 0000000..07bb7f1 Binary files /dev/null and b/bubbob/images/springy_angry.ppm differ diff --git a/bubbob/images/star_large.ppm b/bubbob/images/star_large.ppm new file mode 100644 index 0000000..513c538 Binary files /dev/null and b/bubbob/images/star_large.ppm differ diff --git a/bubbob/images/sugar_pie_big.ppm b/bubbob/images/sugar_pie_big.ppm new file mode 100644 index 0000000..e63512b Binary files /dev/null and b/bubbob/images/sugar_pie_big.ppm 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 Binary files /dev/null and b/bubbob/images/yellow_Hurry_up.ppm differ diff --git a/bubbob/levels/Arena.bin b/bubbob/levels/Arena.bin new file mode 100644 index 0000000..73db756 Binary files /dev/null and b/bubbob/levels/Arena.bin 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>>>>>>>>vvvvvvv<<<<<<<>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<< +>>> 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<<<<>>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 Binary files /dev/null and b/bubbob/levels/HouseOfFun.bin differ diff --git a/bubbob/levels/Levels.bin b/bubbob/levels/Levels.bin new file mode 100644 index 0000000..fd176d1 Binary files /dev/null and b/bubbob/levels/Levels.bin differ diff --git a/bubbob/levels/LostLevels.bin b/bubbob/levels/LostLevels.bin new file mode 100644 index 0000000..a09e365 Binary files /dev/null and b/bubbob/levels/LostLevels.bin 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<<<>^ >>> ^ ^<< +>>^ ^ <<< ^<< +>>^ ^<< +>>^ >> << ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>>^ ^<< +>> << +""" + + +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 vvxvv>v v<< +>>v vv v<< +>>v vv v<< +>>v v<>v>vv v<< +>>v v<< v x <>>v v<< +>>v vv v<< +>>v vv v<< +>>v vv v<< +>>v vv v<< +>>v v<>v v<< +>>v vv v<< +>>v vv v<< +>>v vv v<< +>>v vv v<< +>>v vv v<< +>>v v<>>vv v<< +>>v v<< v x x v>>v v<< +>>v v>vvvvv>v v<< +>>v vv v<< +>>v vv v<< +>>v v<>v>vv v<< +>>v v<>v v<< +>>v vv v<< +>>v vv v<< +>>v vv v<< +>>v v<>v>vv 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<<<>>>>>x>>>>v>>>>v<>v<<<>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<v +>>^ ^<v +>>^ ^<v +>>^ ^<v +>>^ ^<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 = """ +vv +vv +vv +vv +>> << +>> << +>> << +>> << +>> << +>> << +>> << +>> << +>>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<<<>>>>>x>>>>v>>>>v<>v<<<v +vv +""" + + +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>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +v>>>vvvvvvvvvvvvvvvvvvvv<<<v +vvvvvvvvvvvvvvvvvvvvv<>vvvv>v +v>vvvvvvvvvvvvvvvvvvvv<v +vvvvvvvvvvvvvvvvvvvvvvvvvvv>vv>v +vvvvvvvvvvvvvvvvvvvvvvvvvvv>vv>v +v>vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>>>>>>>>>>>>>>>>^^^^<<<<<<<<<<<<<<<< +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +>> ^^^^ << +""" 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<', 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 Binary files /dev/null and b/bubbob/music/Snd1-8.wav differ diff --git a/bubbob/music/Snd2-8.wav b/bubbob/music/Snd2-8.wav new file mode 100644 index 0000000..c59f1e8 Binary files /dev/null and b/bubbob/music/Snd2-8.wav differ diff --git a/bubbob/music/Snd3-8.wav b/bubbob/music/Snd3-8.wav new file mode 100644 index 0000000..dda70f0 Binary files /dev/null and b/bubbob/music/Snd3-8.wav differ diff --git a/bubbob/music/Snd4-8.wav b/bubbob/music/Snd4-8.wav new file mode 100644 index 0000000..509afad Binary files /dev/null and b/bubbob/music/Snd4-8.wav differ diff --git a/bubbob/music/Snd5-8.wav b/bubbob/music/Snd5-8.wav new file mode 100644 index 0000000..b4bfabc Binary files /dev/null and b/bubbob/music/Snd5-8.wav differ diff --git a/bubbob/music/Snd6-8.wav b/bubbob/music/Snd6-8.wav new file mode 100644 index 0000000..0f2bad5 Binary files /dev/null and b/bubbob/music/Snd6-8.wav 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 Binary files /dev/null and b/bubbob/sounds/die.wav differ diff --git a/bubbob/sounds/extra.wav b/bubbob/sounds/extra.wav new file mode 100644 index 0000000..a95c01b Binary files /dev/null and b/bubbob/sounds/extra.wav differ diff --git a/bubbob/sounds/extralife.wav b/bubbob/sounds/extralife.wav new file mode 100644 index 0000000..9afd8b2 Binary files /dev/null and b/bubbob/sounds/extralife.wav differ diff --git a/bubbob/sounds/fruit.wav b/bubbob/sounds/fruit.wav new file mode 100644 index 0000000..19eafd3 Binary files /dev/null and b/bubbob/sounds/fruit.wav differ diff --git a/bubbob/sounds/hell.wav b/bubbob/sounds/hell.wav new file mode 100644 index 0000000..aad531b Binary files /dev/null and b/bubbob/sounds/hell.wav differ diff --git a/bubbob/sounds/hurry.wav b/bubbob/sounds/hurry.wav new file mode 100644 index 0000000..c566e54 Binary files /dev/null and b/bubbob/sounds/hurry.wav differ diff --git a/bubbob/sounds/jump.wav b/bubbob/sounds/jump.wav new file mode 100644 index 0000000..8d7c10d Binary files /dev/null and b/bubbob/sounds/jump.wav differ diff --git a/bubbob/sounds/letsgo.wav b/bubbob/sounds/letsgo.wav new file mode 100644 index 0000000..3216cdf Binary files /dev/null and b/bubbob/sounds/letsgo.wav differ diff --git a/bubbob/sounds/pop.wav b/bubbob/sounds/pop.wav new file mode 100644 index 0000000..efddf3d Binary files /dev/null and b/bubbob/sounds/pop.wav differ diff --git a/bubbob/sounds/shh.wav b/bubbob/sounds/shh.wav new file mode 100644 index 0000000..8ea1ca9 Binary files /dev/null and b/bubbob/sounds/shh.wav differ diff --git a/bubbob/sounds/yippee.wav b/bubbob/sounds/yippee.wav new file mode 100644 index 0000000..1598140 Binary files /dev/null and b/bubbob/sounds/yippee.wav 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 +#include +#include +#include + + +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; ico_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; if_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; iin_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= 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; ikeys[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 Binary files /dev/null and b/bubbob/statesaver.so 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 Binary files /dev/null and b/bubbob/tmp/pat00.ppm differ diff --git a/bubbob/tmp/pat01.ppm b/bubbob/tmp/pat01.ppm new file mode 100644 index 0000000..236b6ea Binary files /dev/null and b/bubbob/tmp/pat01.ppm differ diff --git a/bubbob/tmp/pat02.ppm b/bubbob/tmp/pat02.ppm new file mode 100644 index 0000000..341ecac Binary files /dev/null and b/bubbob/tmp/pat02.ppm differ diff --git a/bubbob/tmp/pat03.ppm b/bubbob/tmp/pat03.ppm new file mode 100644 index 0000000..ad98a6f Binary files /dev/null and b/bubbob/tmp/pat03.ppm differ diff --git a/bubbob/tmp/pat04.ppm b/bubbob/tmp/pat04.ppm new file mode 100644 index 0000000..c35f525 Binary files /dev/null and b/bubbob/tmp/pat04.ppm differ diff --git a/bubbob/tmp/pat05.ppm b/bubbob/tmp/pat05.ppm new file mode 100644 index 0000000..a7745ca Binary files /dev/null and b/bubbob/tmp/pat05.ppm differ diff --git a/bubbob/tmp/pat06.ppm b/bubbob/tmp/pat06.ppm new file mode 100644 index 0000000..ed4e844 Binary files /dev/null and b/bubbob/tmp/pat06.ppm differ diff --git a/bubbob/tmp/pat07.ppm b/bubbob/tmp/pat07.ppm new file mode 100644 index 0000000..fbe1e7f Binary files /dev/null and b/bubbob/tmp/pat07.ppm differ diff --git a/bubbob/tmp/pat08.ppm b/bubbob/tmp/pat08.ppm new file mode 100644 index 0000000..40c750a Binary files /dev/null and b/bubbob/tmp/pat08.ppm differ diff --git a/bubbob/tmp/pat09.ppm b/bubbob/tmp/pat09.ppm new file mode 100644 index 0000000..5821f76 Binary files /dev/null and b/bubbob/tmp/pat09.ppm differ diff --git a/bubbob/tmp/pat10.ppm b/bubbob/tmp/pat10.ppm new file mode 100644 index 0000000..651c51b Binary files /dev/null and b/bubbob/tmp/pat10.ppm differ diff --git a/bubbob/tmp/pat11.ppm b/bubbob/tmp/pat11.ppm new file mode 100644 index 0000000..4854c3e Binary files /dev/null and b/bubbob/tmp/pat11.ppm differ diff --git a/bubbob/tmp/pat12.ppm b/bubbob/tmp/pat12.ppm new file mode 100644 index 0000000..7482e32 Binary files /dev/null and b/bubbob/tmp/pat12.ppm differ diff --git a/bubbob/tmp/pat13.ppm b/bubbob/tmp/pat13.ppm new file mode 100644 index 0000000..8fbe9a4 Binary files /dev/null and b/bubbob/tmp/pat13.ppm differ diff --git a/bubbob/tmp/pat14.ppm b/bubbob/tmp/pat14.ppm new file mode 100644 index 0000000..54625b2 Binary files /dev/null and b/bubbob/tmp/pat14.ppm differ diff --git a/bubbob/tmp/pat15.ppm b/bubbob/tmp/pat15.ppm new file mode 100644 index 0000000..d50dd6f Binary files /dev/null and b/bubbob/tmp/pat15.ppm 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$'-ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆŒ{iXF4#ˆ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̈Œ{iXF4#ˆ3Ì»»»»»»»»»»»»»»»»»»»»»»»»»»»»"ˆŒ{iXF4#ˆ3Ì»»ªªªªªªªªªªªªªªªªªªªªªªªªˆU"ˆŒ{iXF4#ˆ3Ì»ª»ª»ª»ª»ª»ª»ª»ª»ª»ª»ª»ª»ˆwU"ˆŒ{iXF4#ˆ3Ì»»ª»ª»ª»ª»™»™»™™™™™™™™™™ˆwˆU"ˆŒ{iXF4#ˆ3Ì»ª»ª»™™™™™™™™™™™™™™™™™™ˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª»ª™™™™™™™™™™™™™™™™™ˆˆˆwˆU"ˆŒ{iXF4#ˆ3Ì»ª»ª™ª™™™™™™™™™™™™™™™ˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª™ª™™™™™™™™™™™™™™™ˆˆˆˆˆwˆU"ˆŒ{iXF4#ˆ3Ì»ª»ª™™™™™™™™™™™™™™™ˆˆˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª™ª™™™™™™™™™™™™™ˆˆˆˆˆˆˆwˆU"ˆŒ{iXF4#ˆ3Ì»ª»ª™™™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª™ª™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆwˆU"ˆŒ{iXF4#ˆ3Ì»ª»ª™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª™™™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆwwU"ˆŒ{iXF4#ˆ3Ì»ª™ª™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª™™™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆwwU"ˆŒ{iXF4#ˆ3Ì»ª™ª™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»»ª™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwU"ˆŒ{iXF4#ˆ3Ì»ª™™™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwU"ˆŒ{iXF4#ˆ3Ì»™ª™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwU"ˆŒ{iXF4#ˆ3Ì»ª™™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwU"ˆŒ{iXF4#ˆ3Ì»™ª™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwU"ˆŒ{iXF4#ˆ3Ì»ª™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwU"ˆŒ{iXF4#ˆ3Ì»™ª™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwwwwU"ˆŒ{iXF4#ˆ3Ì»ª™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆwˆwwwwUU"ˆŒ{iXF4#ˆ3Ì»™ˆwˆwˆwˆwˆwˆwˆwˆwˆwwwwwwUwU"ˆŒ{iXF4#ˆ3Ì»ˆwˆwˆwˆwˆwˆwwwwwwwwwwwwUwUU"ˆŒ{iXF4#ˆ3ÌUUUUUUUUUUUUUUUUUUUUUUUUUUUU"ˆŒ{iXF4#ˆ""""""""""""""""""""""""""""""ˆŒ{iXF4#ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ"Œ{iXF4#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Ìÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿÌÿfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿf™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=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™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=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™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿ3fÿ3fÿ3™ÿ™™ÿ™™ÿ3™ÿ3fÿ3™ÿ3™ÿ3fÿ3™ÿ3Ìÿ3™ÿ3fÿ3fÿ™™ÿ3fÿ3Ìÿ3fÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ3Ìÿ3fÿ™™ÿ3™ÿ3Ìÿ3™ÿ3fÿ3™ÿfÌ™™™ÿ3fÿ3ÌÿfÌ™3fÿ3™ÿ3™ÿ™™ÿ™™ÿ3™ÿ™™ÿ3™™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ™™ÿ3™ÿ3fÿ™™ÿ3fÿ3™ÿ3™ÿ3Ìÿ3Ìÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3Ìÿ3™ÿ3™ÿ3™ÿ3fÿ3™™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ™™ÿ3™ÿfÌ™3™ÿ3™ÿ3™ÿ3™ÿ3™ÿ™™ÿ3™ÿ3fÿ™™ÿ3™ÿ3fÿ™™ÿ3fÿ3™ÿ™™ÿ3Ìÿ3™ÿ3™™ffÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿ3™ÿ3™ÿ™™ÿ™™ÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿfÌ™3™ÿ3™ÿ3™ÿ™™ÿ3™ÿ3fÿffÿf3Ìffÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ™™ÿ3™ÿ3Ìÿ3fÿ3fÿ3™ÿ3™ÿ3fÿ3Ìÿ3™ÿ3fÿ3™ÿ3fÿ3™ÿ3™ÿfÌ™3fÿ3™™3™ÿ3fÿ3™ÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿfÌ™3™ÿ3fÿ3™ÿ3™ÿ3fÿ™™ÿ3Ìÿ3™ÿ3™ÿ™™ÿ™™ÿ3fÿ3™ÿ3Ìÿ3™ÿ3™ÿ3™ÿf3Ì3fÿî3™™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿ3fÿfÌ™3™ÿ3™ÿ3™ÿ3™ÿ3fÿ3™ÿ™™ÿ3™ÿfÌ™3Ìÿ3fÿ3™ÿffÿ3fÿ3™ÿ3™™3fÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3Ìÿ3fÿ3ÌÿfÌ™3Ìÿ3™ÿ3fÿ™™ÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿ3Ìÿ3™ÿf3Ì3™™3™ÿ3™™3™ÿ3™ÿ3f™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ™™ÿ3™ÿ™™ÿ3™ÿ™™ÿ3™ÿ3™ÿ3™ÿ3™ÿfÌ™3™ÿ3™ÿ3fÿ3™ÿ3™ÿffÿ3f™3™ÿffÿîf3Ì3™ÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3Ìÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿ3fÿfÌ™3fÿ3Ìÿ3™ÿfÌ™3™ÿffÿ3fÿ3™ÿ3™ÿ3fÿ3™ÿ3™™ffÿffÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ™™ÿ™™ÿ3™ÿ™™ÿ3™ÿ3fÿ™™ÿ™™ÿ3™ÿ3™ÿ3fÿ3™ÿ3fÿ3fÿîffÿ3™™f3Ì3™ÿ3™ÿ3fÿ3™ÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ3Ìÿ™™ÿ™™ÿfÌ™3™ÿ3™ÿ™™ÿ3Ìÿ3™ÿ3™ÿffÿ3f™ffÿ3™ÿ3™ÿffÿffÿ3fÿffÿ3™ÿ3f™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3fÿ3™ÿ3™ÿ™™ÿ3™ÿ3fÿ3™ÿ3fÿffÿ3™ÿ3™ÿ3™™f3Ì3fÿ3™ÿî3™ÿffÿîf3Ì3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3fÿ™™ÿ3™ÿ3™ÿ3fÿ3fÿ3Ìÿ3™ÿffÿ3™ÿ3™ÿffÿffÿî3™ÿ3™™3™ÿ3™ÿ3™™3™ÿ3™™3™™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ3™ÿ3Ìÿ3fÿ3Ìÿ™™ÿ3™ÿ3™ÿf3Ì3f™3™™3™™ffÿ3™ÿ3fÿ3™ÿîffÿ3™ÿ3fÿffÿ3™ÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿfÌ™3™ÿ3™ÿfÌ™™™ÿ3™ÿffÿffÿ3™ÿ3fÿ3™ÿ3™ÿ3™™3™ÿ3f™ffÿ3™ÿ3™ÿf3Ì3™™ffÿî3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3fÿ™™ÿ3™ÿ™™ÿ3™ÿf3Ì3™ÿ3fÿ3™ÿ3™ÿf3Ì3fÿ3™ÿffÿ3™™3™ÿ3™ÿffÿ3fÿ3™ÿ3™ÿ3™ÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ™™ÿ3™ÿ3fÿ3™ÿî3fÿ3™ÿ3f™ffÿffÿ3™™3™ÿ3™ÿffÿffÿ3™ÿ3™ÿ3™ÿ3fÿffÿ3f™3™™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3™ÿ3fÿ3fÿ3™ÿffÿ3™ÿ3™™3™ÿ3™ÿffÿ3™ÿ3™ÿ3™™3™ÿf3Ìî3™™3™™3™ÿ3™ÿf3Ì3™™3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3Ìÿ3Ìÿ3™ÿ3fÿffÿ3™ÿ3™ÿffÿf3Ì3™ÿ3™ÿî3™ÿffÿî3fÿffÿ3™™îffÿ3™ÿ3™ÿ3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿf™ÿ3™ÿ3™ÿ3fÿ3™ÿffÿ3™™3™™î3fÿ3™ÿffÿ3™™3fÿ3™ÿf3Ì3™ÿ3™™3™ÿf3Ì3fÿffÿ3™ÿ3™™î3™ÿ3™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=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™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=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™ÿffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3ÌÿÌÿffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌffÌfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3Ìÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿfÿ3fÿ9Žö2|×+j¸$XšG{5\#=3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ3fÿ9Žö2|×+j¸$XšG{5\#=™™™™™™™™™UUU™™™UUU™™™UUU™™™UUU™™™UUU™™™UUUUUU"""UUUDDDUUU"""UUUDDDUUU"""UUU"""DDD""""""""""""#B®:˜2ƒ)m!W A+™™™™™™UUU™™™™™™™™™UUU™™™UUU™™™UUUUUUUUUUUUUUUUUUUUUUUU™™™UUU™™™UUUUUUUUUDDDUUU"""UUU"""UUU"""#B®:˜2ƒ)m!W A+™™™UUU™™™UUU™™™UUU™™™UUU™™™UUUUUUUUUÿÿª3ÿªÿªUUUUUU"""UUU"""UUU"""UUU"""DDD""""""#B®:˜2ƒ)m!W A+UUU™™™™™™™™™UUU™™™UUUUUUUUUÿ3ÿÿ3ÿÿÿÿ3ÿªÿªÿª™™™UUU™™™UUUDDDUUU"""UUU"""#B®:˜2ƒ)m!W A+™™™UUU™™™UUU™™™UUUUUUUUU3ÿ3ÿ3ÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿÿªÿªDDDUUU"""UUU"""UUU"""DDD#B®:˜2ƒ)m!W A+™™™™™™UUU™™™UUUUUUÿ3ÿ3ÿfÿfÿfÿfÿfÿfÿfÿ3ÿÿ3ÿª3ÿª3ÿª3ÿUUU™™™UUUDDDUUU"""#B®:˜2ƒ)m!W A+™™™UUU™™™UUUUUUÿ3ÿfÿfÿfÿ™ÿ™ÿ™ÿ™ÿfÿfÿfÿ3ÿÿ3ÿÿÿª3ÿª3ÿUUU"""UUU"""UUU#B®:˜2ƒ)m!W A+UUU™™™UUUUUUUUU3ÿfÿfÿ™ÿ™ÿ™ÿ™ÿ™ÿ™ÿ™ÿfÿfÿ3ÿ3ÿÿ3ÿª3ÿª3ÿª™™™UUU™™™UUUDDD#B®:˜2ƒ)m!W A+™™™UUU™™™UUU3ÿfÿfÿ™ÿ™ÿ™ÿÿÿÿÿÿÿÿÿÿ™ÿ™ÿfÿfÿ3ÿ3ÿ3ÿÿÿÿÿªÿªDDDUUU"""UUU#B®:˜2ƒ)m!W A+UUU™™™UUUÿ3ÿfÿfÿ™ÿ™ÿÿÿÿÿÿÿÿÿÿÿÿÿ™ÿ™ÿfÿfÿ3ÿ3ÿÿ3ÿª3ÿª3ÿÿfÿÿ™™™UUUUUU#B®:˜2ƒ)m!W A+™™™UUUUUU3ÿ3ÿfÿfÿ™ÿ™ÿÿÿÿÿÿÿÿÿÿ™ÿ™ÿ™ÿfÿfÿ3ÿ3ÿ3ÿÿ3ÿÿÿÿ3ÿfÿÿUUUDDDUUU#B®:˜2ƒ)m!W A+UUUUUUUUU3ÿ3ÿfÿfÿ™ÿ™ÿ™ÿ™ÿ™ÿ™ÿ™ÿfÿfÿ3ÿ3ÿ3ÿÿ3ÿÿ3ÿª3ÿÿ3ÿfÿ™™™UUUUUU#B®:˜2ƒ)m!W A+™™™UUUÿ3ÿ3ÿfÿfÿfÿ™ÿ™ÿ™ÿ™ÿfÿfÿfÿfÿ3ÿ3ÿ3ÿ3ÿÿÿÿ3ÿÿ3ÿfÿ3ÿ3ÿ"""UUU#B®:˜2ƒ)m!W A+UUUUUU3ÿ3ÿ3ÿ3ÿfÿfÿfÿfÿfÿfÿfÿfÿfÿ3ÿ3ÿ3ÿ3ÿÿ3ÿª3ÿª3ÿ3ÿ3ÿfÿ3ÿUUUDDD#B®:˜2ƒ)m!W A+UUUUUU3ÿ3ÿ3ÿ3ÿ3ÿfÿfÿfÿfÿfÿfÿ3ÿ3ÿ3ÿ3ÿ3ÿÿÿÿ3ÿÿ3ÿ3ÿ3ÿfÿ3ÿ3ÿ™™™UUU#B®:˜2ƒ)m!W A+DDDUUU3ÿÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿÿ3ÿÿ3ÿª3ÿ3ÿ3ÿfÿ3ÿfÿ3ÿUUU#B®:˜2ƒ)m!W A+UUU™™™3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿÿÿÿ3ÿÿ3ÿ3ÿ3ÿ3ÿ3ÿfÿ3ÿ3ÿ™™™UUU#B®:˜2ƒ)m!W A+"""UUUfÿ3ÿ3ÿÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿÿ3ÿª3ÿª3ÿÿ3ÿ3ÿfÿfÿ3ÿ3ÿ3ÿUUUDDD#B®:˜2ƒ)m!W A+UUUDDDfÿ3ÿ3ÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿÿÿ3ÿÿ3ÿÿ3ÿ3ÿ3ÿfÿ3ÿfÿ3ÿ3ÿ"""UUU#B®:˜2ƒ)m!W A+UUUUUU™™™3ÿ3ÿÿ3ÿÿ3ÿÿ3ÿª3ÿÿ3ÿª3ÿª3ÿÿ3ÿ3ÿ3ÿfÿ3ÿfÿ3ÿ3ÿ™™™UUUUUU#B®:˜2ƒ)m!W A+UUU"""UUUfÿ3ÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿ3ÿ3ÿ3ÿfÿ3ÿfÿfÿ3ÿ3ÿUUUDDDUUU#B®:˜2ƒ)m!W A+UUUUUU™™™fÿ3ÿ3ÿ3ÿ3ÿ3ÿÿ3ÿÿ3ÿÿ3ÿÿ3ÿ3ÿ3ÿ3ÿ3ÿfÿ3ÿfÿ3ÿ3ÿ3ÿ3ÿ™™™UUU"""#B®:˜2ƒ)m!W A+UUU"""UUUDDDfÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿfÿ3ÿfÿfÿ3ÿ3ÿ3ÿDDDUUU"""UUU#B®:˜2ƒ)m!W A+DDDUUUUUUUUU™™™3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿfÿfÿ3ÿfÿ3ÿ3ÿ3ÿ3ÿ™™™UUUUUUUUUDDD#B®:˜2ƒ)m!W A+UUU"""UUU"""UUUfÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿfÿfÿ3ÿfÿ3ÿfÿ3ÿ3ÿ3ÿ3ÿ3ÿUUU"""UUU"""UUU#B®:˜2ƒ)m!W A+"""UUUDDDUUU™™™UUUfÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿUUU™™™UUUDDDUUU"""#B®:˜2ƒ)m!W A+DDD"""UUU"""UUU"""UUUDDDfÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿDDDUUU"""UUU"""UUU"""DDD#B®:˜2ƒ)m!W A+"""UUU"""UUUDDDUUU™™™UUU™™™fÿfÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿ3ÿfÿ™™™UUU™™™UUUDDDUUU"""UUU"""#B®:˜2ƒ)m!W A+""""""DDD"""UUU"""UUU"""UUU"""UUU™™™fÿfÿfÿfÿfÿfÿfÿ™™™UUU"""UUU"""UUU"""UUU"""DDD""""""#B®:˜2ƒ)m!W A+"""UUU"""UUU"""UUUDDDUUUUUUUUUUUUUUU™™™UUU™™™UUU™™™UUU™™™UUUUUUUUUUUUUUUDDDUUU"""UUU"""UUU"""#B®:˜2ƒ)m!W A+""""""""""""DDD"""UUU"""UUUDDDUUU"""UUUDDDUUU"""UUUDDDUUU"""UUUDDDUUU"""UUU"""DDD""""""""""""#B®:˜2ƒ)m!W A+fff#B®:˜2ƒ)m!W A+f33DUUUUUUUUUDDDDDDîîîÝÝ݈ˆˆwwwDUUUUUUUUUDDDDDDîîîÝÝ݈ˆˆwwwDUUUUUUUUUDDDDDDîîîÝÝ݈ˆˆwwwD]<= 2 + try: + msgdef = self.sendmsgdef + except AttributeError: + fileid = len(filereaders) + filereaders[fileid] = self.read + data = self.read() + msgdef = self.sendmsgdef = (self.getmd5def(fileid, data) + + self.getmsgdef(fileid)) + client.msgl.append(msgdef) + + def getmd5def(self, fileid, data, offset=0): + checksum = md5.new(data).digest() + return message(MSG_MD5_FILE, fileid, protofilepath(self.filename), + offset, len(data), checksum) + + +class Bitmap(DataChunk): + + def __init__(self, code, filename, colorkey=None): + self.code = code + self.filename = filename + self.icons = {} + self.colorkey = colorkey + DataChunk.__init__(self) + + def geticon(self, x,y,w,h, alpha=255): + rect = (x,y,w,h) + try: + return self.icons[rect] + except: + ico = Icon(self, Icon.count, x,y,w,h, alpha) + Icon.count += 1 + self.icons[rect] = ico + return ico + + def geticonlist(self, w, h, count): + return map(lambda i, fn=self.geticon, w=w, h=h: fn(i*w, 0, w, h), range(count)) + + def getmsgdef(self, data): + if self.colorkey is not None: + return message(MSG_DEF_BITMAP, self.code, data, self.colorkey) + else: + return message(MSG_DEF_BITMAP, self.code, data) + + def defall(self, client): + DataChunk.defall(self, client) + for i in self.icons.values(): + client.msgl.append(i.msgdef) + + +class MemoryBitmap(Bitmap): + + def __init__(self, code, data, colorkey=None): + self.data = data + Bitmap.__init__(self, code, None, colorkey) + + def read(self, slice=None): + data = self.data + if slice: + start, length = slice + data = data[start:start+length] + return data + + +class Sample(DataChunk): + + def __init__(self, code, filename, freqfactor=1): + self.code = code + self.filename = filename + self.freqfactor = freqfactor + DataChunk.__init__(self) + + def defall(self, client): + if client.has_sound > 0: + DataChunk.defall(self, client) + + def getmsgdef(self, data): + return message(MSG_DEF_SAMPLE, self.code, data) + + def read(self, slice=None): + f = open(self.filename, "rb") + data = f.read() + f.close() + if self.freqfactor != 1: + freq, = unpack(" 1.0: + lvolume = 1.0 + if rvolume < 0.0: + rvolume = 0.0 + elif rvolume > 1.0: + rvolume = 1.0 + message = pack("!hBBh", self.code, int(lvolume*255.0), + int(rvolume*255.0), -1) + if singleclient is None: + clist = clients[:] + else: + clist = [singleclient] + for c in clist: + if c.has_sound: + c.sounds.setdefault(message, 4) + + +class Music(DataChunk): + + def __init__(self, filename, filerate=44100): + self.filename = filename + self.filerate = filerate + self.f = open(filename, 'rb') + self.f.seek(0, 2) + filesize = self.f.tell() + self.endpos = max(self.filerate, filesize - self.filerate) + self.fileid = len(filereaders) + filereaders[self.fileid] = self.read + self.md5msgs = {} + DataChunk.__init__(self) + + def read(self, (start, length)): + self.f.seek(start) + return self.f.read(length) + + def msgblock(self, position, limited=1): + blocksize = self.filerate + if limited and position+blocksize > self.endpos: + blocksize = self.endpos-position + if blocksize <= 0: + return '' + #self.f.seek(position) + #return message(MSG_DEF_MUSIC, self.code, position, self.f.read(blocksize)) + try: + msg = self.md5msgs[position] + except KeyError: + data = self.read((position, blocksize)) + checksum = md5.new(data).digest() + msg = message(MSG_MD5_FILE, self.fileid, protofilepath(self.filename), + position, blocksize, checksum) + self.md5msgs[position] = msg + return msg + + def clientsend(self, clientpos): + msg = self.msgblock(clientpos) + #print 'clientsend:', self.code, len(msg), clientpos + if msg: + return [msg], clientpos + self.filerate + else: + return [], None + + def initialsend(self, c): + return [self.msgblock(0), self.msgblock(self.endpos, 0)], self.filerate + + def defall(self, client): + pass + + +def clearsprites(): + sprites_by_n.clear() + sprites[:] = [''] + +def compactsprites(insert_new=None, insert_before=None): + global sprites, sprites_by_n + if insert_before is not None: + if insert_new.alive: + insert_before = insert_before.alive + else: + insert_before = None + newsprites = [''] + newd = {} + l = sprites_by_n.items() + l.sort() + for n, s in l: + if n == insert_before: + prevn = insert_new.alive + newn = insert_new.alive = len(newsprites) + newsprites.append(sprites[prevn]) + newd[newn] = insert_new + l.remove((prevn, insert_new)) + newn = s.alive = len(newsprites) + newsprites.append(sprites[n]) + newd[newn] = s + sprites = newsprites + sprites_by_n = newd + + +class Sprite: + +## try: +## import psyco.classes +## except ImportError: +## pass +## else: +## __slots__ = ['x', 'y', 'ico', 'alive'] +## __metaclass__ = psyco.classes.psymetaclass + + def __init__(self, ico, x,y): + self.x = x + self.y = y + self.ico = ico + self.alive = len(sprites) + if (-ico.w < x < game.width and + -ico.h < y < game.height): + sprites.append(pack("!hhh", x, y, ico.code)) + else: + sprites.append('') # starts off-screen + sprites_by_n[self.alive] = self + + def move(self, x,y, ico=None): + self.x = x + self.y = y + if ico is not None: + self.ico = ico + sprites[self.alive] = pack("!hhh", x, y, self.ico.code) + + def setdisplaypos(self, x, y): + # special use only (self.x,y are not updated) + s = sprites[self.alive] + if len(s) == 6: + sprites[self.alive] = pack("!hh", x, y) + s[4:] + + def setdisplayicon(self, ico): + # special use only (self.ico is not updated) + s = sprites[self.alive] + if len(s) == 6: + sprites[self.alive] = s[:4] + pack("!h", ico.code) + + #sizeof_displaypos = struct.calcsize("!hh") + def getdisplaypos(self): + # special use only (normally, read self.x,y,ico directly) + s = sprites[self.alive] + if self.alive and len(s) == 6: + return unpack("!hh", s[:4]) + else: + return None, None + + def step(self, dx,dy): + x = self.x = self.x + dx + y = self.y = self.y + dy + sprites[self.alive] = pack("!hhh", x, y, self.ico.code) + + def seticon(self, ico): + self.ico = ico + sprites[self.alive] = pack("!hhh", self.x, self.y, ico.code) + + def hide(self): + sprites[self.alive] = '' + + def kill(self): + if self.alive: + del sprites_by_n[self.alive] + sprites[self.alive] = '' + self.alive = 0 + + def prefix(self, n, m=0): + pass #sprites[self.alive] = pack("!hhh", n, m, 32767) + sprites[self.alive] + + def to_front(self): + if self.alive and self.alive < len(sprites)-1: + self._force_to_front() + + def _force_to_front(self): + info = sprites[self.alive] + sprites[self.alive] = '' + del sprites_by_n[self.alive] + self.alive = len(sprites) + sprites_by_n[self.alive] = self + sprites.append(info) + + def to_back(self, limit=None): + assert self is not limit + if limit: + n1 = limit.alive + 1 + else: + n1 = 1 + if self.alive > n1: + if n1 in sprites_by_n: + keys = sprites_by_n.keys() + keys.remove(self.alive) + keys.sort() + keys = keys[keys.index(n1):] + reinsert = [sprites_by_n[n] for n in keys] + for s1 in reinsert: + s1._force_to_front() + assert n1 not in sprites_by_n + info = sprites[self.alive] + sprites[self.alive] = '' + del sprites_by_n[self.alive] + self.alive = n1 + sprites_by_n[n1] = self + sprites[n1] = info + + def __repr__(self): + if self.alive: + return "" % (self.alive, self.x, self.y) + else: + return "" + + +class Player: + standardplayericon = None + + def playerjoin(self): + pass + + def playerleaves(self): + pass + + def _playerleaves(self): + if self.isplaying(): + self._client.killplayer(self) + del self._client + self.playerleaves() + + def isplaying(self): + return hasattr(self, "_client") + + +class Client: + SEND_BOUND_PER_FRAME = 0x6000 # bytes + KEEP_ALIVE = 2.2 # seconds + + def __init__(self, socket, addr): + socket.setblocking(0) + self.socket = socket + self.addr = addr + self.udpsocket = None + self.udpsockcounter = 0 + self.initialdata = MSG_WELCOME + self.initialized = 0 + self.msgl = [message(MSG_PING)] + self.buf = "" + self.players = { } + self.sounds = None + self.has_sound = 0 + self.has_music = 0 + self.musicpos = { } + self.proto = 1 + self.dyncompress = None + addsocket('CLIENT', self.socket, self.input_handler) + clients.append(self) + self.log('connected') + self.send_buffer(self.initialdata) + + def opengame(self, game): + if self.initialized == 0: + self.initialdata += game.FnDesc + '\n' + self.initialized = 1 + if self.initialized == 1: + if game.broadcast_port: + self.initialdata += message(MSG_BROADCAST_PORT, game.broadcast_port) + game.trigger_broadcast() + self.initialdata += game.deffieldmsg() + else: + self.msgl.append(game.deffieldmsg()) + self.activity = self.last_ping = time() + self.force_ping_delay = 0.6 + for c in clients: + for id in c.players.keys(): + self.msgl.append(message(MSG_PLAYER_JOIN, id, c is self)) + + def emit(self, udpdata, broadcast_extras): + if self.initialdata: + self.send_buffer(self.initialdata) + elif self.initialized == 2: + buffer = ''.join(self.msgl) + if buffer: + self.send_buffer(buffer) + if self.udpsocket is not None: + if self.sounds: + if broadcast_extras is None or self not in broadcast_clients: + udpdata = ''.join(self.sounds.keys() + [udpdata]) + else: + broadcast_extras.update(self.sounds) + for key, value in self.sounds.items(): + if value: + self.sounds[key] = value-1 + else: + del self.sounds[key] + if broadcast_extras is None or self not in broadcast_clients: + if self.dyncompress is not None: + udpdatas = self.dynamic_compress(udpdata) + else: + udpdatas = [udpdata] + for udpdata in udpdatas: + try: + self.udpsockcounter += self.udpsocket.send(udpdata) + except error, e: + print >> sys.stderr, 'ignored:', str(e) + pass # ignore UDP send errors (buffer full, etc.) + if self.has_music > 1 and NOW >= self.musicstreamer: + self.musicstreamer += 0.99 + self.sendmusicdata() + if not self.msgl: + if abs(NOW - self.activity) <= self.KEEP_ALIVE: + if abs(NOW - self.last_ping) <= self.force_ping_delay: + return + if self.udpsockcounter < 1024: + return + self.force_ping_delay += 0.2 + self.msgl.append(message(MSG_PING, self.udpsockcounter>>10)) + self.last_ping = NOW + + def setup_dyncompress(self): + def dyncompress(): + # See comments in pclient.Playfield.dynamic_decompress(). + threads = [] + for t in range(3): + co = zlib.compressobj(6) + threads.append((chr(0x88 + t) + chr(t), co)) + frame = 0 + globalsync = 0 + + while 1: + # write three normal packets, one on each thread + for t in range(3): + head, co = threads.pop(0) + yield head + chr(frame), co + threads.append((chr(ord(head[0]) & 0x87) + chr(frame), co)) + yield None, None + frame = (frame + 1) & 0xFF + + # sync frame, write two packets (on two threads) + # and restart compression at the current frame for these threads + head, co = threads.pop(0) + yield head + chr(frame), co + co1 = zlib.compressobj(6) + co2 = zlib.compressobj(6) + globalsync += 1 + if globalsync == 4: + # next on this thread will be a global sync packet + nextframe = (frame + 2) & 0xFF + globalsync = 0 + else: + # next of this thread will be a local sync packet + yield None, co1 + nextframe = frame + threads.append((chr(ord(head[0]) | 8) + chr(nextframe), co1)) + + # 2nd packet of the current frame + head, co = threads.pop(0) + yield head + chr(frame), co + yield None, co2 + threads.append((chr(ord(head[0]) | 8) + chr(frame), co2)) + + yield None, None + frame = (frame + 1) & 0xFF + + self.dyncompress = dyncompress() + + def dynamic_compress(self, framedata): + result = [] + for head, co in self.dyncompress: + if not co: + return result + data = [head, co.compress(framedata), co.flush(zlib.Z_SYNC_FLUSH)] + if head: + result.append(''.join(data)) + + def send_can_mix(self): + return not self.msgl and self.socket is not None + + def send_buffer(self, buffer): + try: + count = self.socket.send(buffer[:self.SEND_BOUND_PER_FRAME]) + except error, e: + if e.args[0] != EWOULDBLOCK: + self.msgl = [] + self.initialdata = "" + self.disconnect(e, 'emit') + return + else: + #g = open('log', 'ab'); g.write(buffer[:count]); g.close() + buffer = buffer[count:] + self.activity = NOW + if self.initialdata: + self.initialdata = buffer + elif buffer: + self.msgl = [buffer] + else: + self.msgl = [] + + def receive(self, data): + #print "receive:", `data` + try: + data = self.buf + data + while data: + values, data = decodemessage(data) + if not values: + break # incomplete message + fn = self.MESSAGES.get(values[0]) + if fn: + fn(self, *values[1:]) + else: + print "unknown message from", self.addr, ":", values + self.buf = data + except struct.error: + import traceback + traceback.print_exc() + self.socket.send('\n\n

Protocol Error

\n') + hs = findsocket('HTTP') + if hs is not None: + url = 'http://%s:%s' % (HOSTNAME, displaysockport(hs)) + self.socket.send(''' +If you meant to point your web browser to this server, +then use the following address: + +%s +''' % (url, url)) + self.disconnect('protocol error', 'receive') + + def input_handler(self): + try: + data = self.socket.recv(2048) + except error, e: + self.disconnect(e, "socket.recv") + else: + if data: + self.activity = NOW + self.receive(data) + elif not hasattr(self.socket, 'RECV_CAN_RETURN_EMPTY'): + # safecheck that this means disconnected + iwtd, owtd, ewtd = select([self.socket], [], [], 0.0) + if self.socket in iwtd: + self.disconnect('end of data', 'socket.recv') + + def disconnect(self, err=None, infn=None): + removesocket('CLIENT', self.socket) + if err: + extra = ": " + str(err) + else: + extra = "" + if infn: + extra += " in " + infn + print 'Disconnected by', self.addr, extra + self.log('disconnected' + extra) + for p in self.players.values(): + p._playerleaves() + try: + del broadcast_clients[self] + except KeyError: + pass + clients.remove(self) + try: + self.socket.close() + except: + pass + self.socket = None + if not clients and game is not None: + game.FnDisconnected() + + def killplayer(self, player): + for id, p in self.players.items(): + if p is player: + framemsgappend(message(MSG_PLAYER_KILL, id)) + del self.players[id] + if game: + game.updateplayers() + + def joinplayer(self, id, *rest): + if self.players.has_key(id): + print "Note: player %s is already playing" % (self.addr+(id,),) + return + if game is None: + return # refusing new player before the game starts + p = game.FnPlayers()[id] + if p is None: + print "Too many players. New player %s refused." % (self.addr+(id,),) + self.msgl.append(message(MSG_PLAYER_KILL, id)) + elif p.isplaying(): + print "Note: player %s is already played by another client" % (self.addr+(id,),) + else: + print "New player %s" % (self.addr+(id,),) + p._client = self + p.playerjoin() + p.setplayername('') + self.players[id] = p + game.updateplayers() + for c in clients: + c.msgl.append(message(MSG_PLAYER_JOIN, id, c is self)) + + def remove_player(self, id, *rest): + try: + p = self.players[id] + except KeyError: + print "Note: player %s is not playing" % (self.addr+(id,),) + else: + p._playerleaves() + + def set_player_name(self, id, name, *rest): + p = game.FnPlayers()[id] + p.setplayername(name) + + def set_udp_port(self, port, addr=None, *rest): + if port == MSG_BROADCAST_PORT: + self.log('set_udp_port: broadcast') + broadcast_clients[self] = 1 + #print "++++ Broadcasting ++++ to", self.addr + else: + try: + del broadcast_clients[self] + except KeyError: + pass + if port == MSG_INLINE_FRAME or port == 0: + # client requests data in-line on the TCP stream + self.dyncompress = None + import udpovertcp + self.udpsocket = udpovertcp.SocketMarshaller(self.socket, self) + s = self.udpsocket.tcpsock + self.log('set_udp_port: udp-over-tcp') + else: + try: + if hasattr(self.socket, 'udp_over_udp_mixer'): + # for SocketOverUdp + self.udpsocket = self.socket.udp_over_udp_mixer() + else: + self.udpsocket = socket(AF_INET, SOCK_DGRAM) + self.udpsocket.setblocking(0) + addr = addr or self.addr[0] + self.udpsocket.connect((addr, port)) + except error, e: + print >> sys.stderr, "Cannot set UDP socket to", addr, str(e) + self.udpsocket = None + self.udpsockcounter = sys.maxint + else: + if self.proto >= 3: + self.setup_dyncompress() + s = self.udpsocket + self.log('set_udp_port: %s:%d' % (addr, port)) + if s: + try: + s.setsockopt(SOL_IP, IP_TOS, 0x10) # IPTOS_LOWDELAY + except error, e: + print >> sys.stderr, "Cannot set IPTOS_LOWDELAY:", str(e) + + def enable_sound(self, sound_mode=1, *rest): + if sound_mode != self.has_sound: + self.sounds = {} + self.has_sound = sound_mode + if self.has_sound > 0: + for snd in samples.values(): + snd.defall(self) + #self.log('enable_sound %s' % sound_mode) + + def enable_music(self, mode, *rest): + if mode != self.has_music: + self.has_music = mode + self.startmusic() + #self.log('enable_music') + + def startmusic(self): + if self.has_music: + self.musicstreamer = time() + for cde in currentmusics[1:]: + if cde not in self.musicpos: + msgl, self.musicpos[cde] = music_by_id[cde].initialsend(self) + self.msgl += msgl + if self.has_music > 1: + self.sendmusicdata() + self.msgl.append(message(MSG_PLAY_MUSIC, *currentmusics)) + + def sendmusicdata(self): + for cde in currentmusics[1:]: + if self.musicpos[cde] is not None: + msgl, self.musicpos[cde] = music_by_id[cde].clientsend(self.musicpos[cde]) + self.msgl += msgl + return + + def ping(self, *rest): + if self.initialized < 2: + # send all current bitmap data + self.initialized = 2 + for b in bitmaps.values(): + b.defall(self) + self.finishinit(game) + for id, p in game.FnPlayers().items(): + if p.standardplayericon is not None: + self.msgl.append(message(MSG_PLAYER_ICON, id, p.standardplayericon.code)) + self.msgl.append(message(MSG_PONG, *rest)) + + def finishinit(self, game): + pass + + def pong(self, *rest): + pass + + def log(self, message): + print self.addr, message + + def protocol_version(self, version, *rest): + self.proto = version + + def md5_data_request(self, fileid, position, size, *rest): + data = filereaders[fileid]((position, size)) + data = zlib.compress(data) + self.msgl.append(message(MSG_ZPATCH_FILE, fileid, position, data)) + +## def def_file(self, filename, md5sum): +## fnp = [] +## while filename: +## filename, tail = os.path.split(filename) +## fnp.insert(0, tail) +## if fnp[:len(FnBasePath)] == FnBasePath: +## filename = os.path.join(*fnp[len(FnBasePath):]) +## self.known_files[filename] = md5sum + + MESSAGES = { + CMSG_PROTO_VERSION: protocol_version, + CMSG_ADD_PLAYER : joinplayer, + CMSG_REMOVE_PLAYER: remove_player, + CMSG_UDP_PORT : set_udp_port, + CMSG_ENABLE_SOUND : enable_sound, + CMSG_ENABLE_MUSIC : enable_music, + CMSG_PING : ping, + CMSG_PONG : pong, + CMSG_DATA_REQUEST : md5_data_request, + CMSG_PLAYER_NAME : set_player_name, +## CMSG_DEF_FILE : def_file, + } + + +class SimpleClient(Client): + + def finishinit(self, game): + num = 0 + for keyname, icolist, fn in game.FnKeys: + self.msgl.append(message(MSG_DEF_KEY, keyname, num, + *[ico.code for ico in icolist])) + num += 1 + + def cmsg_key(self, pid, keynum): + if game is not None: + try: + player = self.players[pid] + fn = game.FnKeys[keynum][2] + except (KeyError, IndexError): + game.FnUnknown() + else: + getattr(player, fn) () + + MESSAGES = Client.MESSAGES.copy() + MESSAGES.update({ + CMSG_KEY: cmsg_key, + }) + + +MAX_CLIENTS = 32 + +clients = [] +FnClient = SimpleClient +broadcast_clients = {} +filereaders = {} +bitmaps = {} +samples = {} +music_by_id = {} +currentmusics = [0] +sprites = [''] +sprites_by_n = {} +recording = None +game = None +serversockets = {} +socketsbyrole = {} +socketports = {} + +def framemsgappend(msg): + for c in clients: + c.msgl.append(msg) + if recording: + recording.write(msg) + +##def sndframemsgappend(msg): +## for c in clients: +## if c.has_sound: +## c.msgl.append(msg) + +def set_udp_port(port): + hostchooser.UDP_PORT = port + +def has_loop_music(): + return currentmusics[0] < len(currentmusics)-1 + +def finalsegment(music1, music2): + intro1 = music1[1:1+music1[0]] + intro2 = music2[1:1+music2[0]] + loop1 = music1[1+music1[0]:] + loop2 = music2[1+music2[0]:] + return loop1 == loop2 and intro1 == intro2[len(intro2)-len(intro1):] + +def set_musics(musics_intro, musics_loop, reset=1): + mlist = [] + loop_from = len(musics_intro) + mlist.append(loop_from) + for m in musics_intro + musics_loop: + mlist.append(m.fileid) + reset = reset or not finalsegment(mlist, currentmusics) + currentmusics[:] = mlist + if reset: + for c in clients: + c.startmusic() + +def fadeout(time=1.0): + msg = message(MSG_FADEOUT, int(time*1000)) + for c in clients: + if c.has_music > 1: + c.msgl.append(msg) + currentmusics[:] = [0] + + +def getbitmap(filename, colorkey=None): + try: + return bitmaps[filename] + except: + bmp = Bitmap(len(bitmaps), filename, colorkey) + bitmaps[filename] = bmp + return bmp + +def getsample(filename, freqfactor=1): + try: + return samples[filename, freqfactor] + except: + snd = Sample(len(samples), filename, freqfactor) + samples[filename, freqfactor] = snd + return snd + +def getmusic(filename, filerate=44100): + try: + return samples[filename] + except: + mus = Music(filename, filerate) + samples[filename] = mus + music_by_id[mus.fileid] = mus + return mus + +def newbitmap(data, colorkey=None): + bmp = MemoryBitmap(len(bitmaps), data, colorkey) + bitmaps[bmp] = bmp + return bmp + + +def addsocket(role, socket, handler=None, port=None): + if port is None: + host, port = socket.getsockname() + if handler is not None: + serversockets[socket] = handler + socketsbyrole.setdefault(role, []).append(socket) + socketports[socket] = port + +def findsockets(role): + return socketsbyrole.get(role, []) + +def findsocket(role): + l = findsockets(role) + if l: + return l[-1] + else: + return None + +def removesocket(role, socket=None): + if socket is None: + for socket in socketsbyrole.get(role, [])[:]: + removesocket(role, socket) + return + try: + del serversockets[socket] + except KeyError: + pass + try: + socketsbyrole.get(role, []).remove(socket) + except ValueError: + pass + try: + del socketports[socket] + except KeyError: + pass + +def opentcpsocket(port=None): + port = port or PORTS.get('LISTEN', INADDR_ANY) + s = findsocket('LISTEN') + if s is None: + s = socket(AF_INET, SOCK_STREAM) + try: + s.bind(('', port)) + s.listen(1) + except error: + if port == INADDR_ANY: + for i in range(10): + port = random.choice(xrange(8000, 12000)) + try: + s.bind(('', port)) + s.listen(1) + except error: + pass + else: + break + else: + raise error, "server cannot find a free TCP socket port" + else: + raise + + def tcpsocket_handler(s=s): + conn, addr = s.accept() + game.newclient(conn, addr) + + addsocket('LISTEN', s, tcpsocket_handler) + return s + +def openpingsocket(only_port=None): + only_port = only_port or PORTS.get('PING', None) + s = findsocket('PING') + if s is None: + import hostchooser + s = hostchooser.serverside_ping(only_port) + if s is None: + return None + def pingsocket_handler(s=s): + global game + import hostchooser + if game is not None: + args = game.FnDesc, ('', game.address[1]), game.FnExtraDesc() + else: + ts = findsocket('LISTEN') + if ts: + address = '', displaysockport(ts) + else: + address = '', '' + args = 'Not playing', address, '' + hs = findsocket('HTTP') + args = args + (displaysockport(hs),) + hostchooser.answer_ping(s, *args) + addsocket('PING', s, pingsocket_handler) + return s + +def openhttpsocket(ServerClass=None, HandlerClass=None, + port=None): + port = port or PORTS.get('HTTP', None) + s = findsocket('HTTP') + if s is None: + if ServerClass is None: + from BaseHTTPServer import HTTPServer as ServerClass + if HandlerClass is None: + import javaserver + from httpserver import MiniHandler as HandlerClass + server_address = ('', port or 8000) + try: + httpd = ServerClass(server_address, HandlerClass) + except error: + if port is None: + server_address = ('', INADDR_ANY) + try: + httpd = ServerClass(server_address, HandlerClass) + except error, e: + print >> sys.stderr, "cannot start HTTP server", str(e) + return None + else: + raise + s = httpd.socket + addsocket('HTTP', s, httpd.handle_request) + return s + +BROADCAST_PORT_RANGE = xrange(18000, 19000) +#BROADCAST_MESSAGE comes from msgstruct +BROADCAST_DELAY = 0.6180 +BROADCAST_DELAY_INCR = 2.7183 + +def openbroadcastsocket(broadcastport=None): + s = findsocket('BROADCAST') + if s is None: + try: + s = socket(AF_INET, SOCK_DGRAM) + s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + except error, e: + print >> sys.stderr, "Cannot broadcast", str(e) + return None + port = broadcastport or random.choice(BROADCAST_PORT_RANGE) + addsocket('BROADCAST', s, port=port) + return s + +def displaysockport(s): + return socketports.get(s, 'off') + + +class Game: + width = 640 + height = 480 + backcolor = 0x000000 + + FnDesc = "NoName" + FnFrame = lambda self: 1.0 + FnExcHandler=lambda self, k: 0 + FnServerInfo=lambda self, s: None + FnPlayers = lambda self: {} + FnKeys = [] + FnUnknown = lambda self: None + FnDisconnected = lambda self: None + + def __init__(self): + global game + s = opentcpsocket() + self.address = HOSTNAME, socketports[s] + bs = self.broadcast_s = openbroadcastsocket() + self.broadcast_port = socketports.get(bs) + self.broadcast_next = None + self.nextframe = time() + clearsprites() + game = self + if recording: + for b in bitmaps.values(): + b.defall(recording) + self.pendingclients = [] + + def openserver(self): + ps = openpingsocket() + print '%s server at %s:%d, Broadcast %d, UDP %d' % ( + self.FnDesc, self.address[0], self.address[1], + displaysockport(self.broadcast_s), displaysockport(ps)) + + hs = openhttpsocket() + if hs: + print 'HTTP server: http://%s:%d' % ( + self.address[0], displaysockport(hs)) + + try: + from localmsg import autonotify + except ImportError: + pass + else: + autonotify(self.FnDesc, *self.address) + + if clients: + for c in clients: + c.opengame(self) + if recording: + recording.start() + recording.write(self.deffieldmsg()) + + def trigger_broadcast(self): + assert self.broadcast_s is not None + game.broadcast_delay = BROADCAST_DELAY + game.broadcast_next = time() + self.broadcast_delay + + def deffieldmsg(self): + return message(MSG_DEF_PLAYFIELD, + self.width, self.height, self.backcolor, + self.FnDesc) + + def socketerrors(self, ewtd): + for c in clients[:]: + if c.socket in ewtd: + del ewtd[c.socket] + c.disconnect("error", "select") + + def mainstep(self): + global NOW + if self.pendingclients: + self.newclient(*self.pendingclients.pop()) + NOW = time() + delay = self.nextframe - NOW + if delay<=0.0: + self.nextframe += self.FnFrame() + self.sendudpdata() + NOW = time() + delay = self.nextframe - NOW + if delay<0.0: + self.nextframe = NOW + delay = 0.0 + if self.broadcast_next is not None and NOW >= self.broadcast_next: + if not clients: + self.broadcast_next = None + else: + try: + self.broadcast_s.sendto(BROADCAST_MESSAGE, + ('', self.broadcast_port)) + #print "Broadcast ping" + except error: + pass # ignore failed broadcasts + self.broadcast_next = time() + self.broadcast_delay + self.broadcast_delay *= BROADCAST_DELAY_INCR + return delay + + def sendudpdata(self): + sprites[0] = '' + udpdata = ''.join(sprites) + if len(broadcast_clients) >= 2: + broadcast_extras = {} + else: + broadcast_extras = None + for c in clients[:]: + c.emit(udpdata, broadcast_extras) + if recording: + recording.udpdata(udpdata) + if broadcast_extras is not None: + udpdata = ''.join(broadcast_extras.keys() + [udpdata]) + try: + self.broadcast_s.sendto(udpdata, + ('', self.broadcast_port)) + #print "Broadcast UDP data" + except error: + pass # ignore failed broadcasts + + def FnExtraDesc(self): + players = 0 + for c in clients: + players += len(c.players) + if players == 0: + return 'no player' + elif players == 1: + return 'one player' + else: + return '%d players' % players + + def updateplayers(self): + pass + + def updateboard(self): + pass + + def newclient(self, conn, addr): + if len(clients)==MAX_CLIENTS: + print "Too many connections; refusing new connection from", addr + conn.close() + else: + try: + addrname = (gethostbyaddr(addr[0])[0],) + addr[1:] + except: + addrname = addr + print 'Connected by', addrname + try: + c = FnClient(conn, addrname) + except error, e: + print 'Connexion already lost!', e + else: + if game is not None: + c.opengame(game) + + def newclient_threadsafe(self, conn, addr): + self.pendingclients.insert(0, (conn, addr)) + + +def recursiveloop(endtime, extra_sockets): + global game + timediff = 1 + while timediff: + if game is not None: + delay = game.mainstep() + else: + delay = 5.0 + iwtd = extra_sockets + serversockets.keys() + timediff = max(0.0, endtime - time()) + iwtd, owtd, ewtd = select(iwtd, [], iwtd, min(delay, timediff)) + if ewtd: + if game: + game.socketerrors(ewtd) + if ewtd: + print >> sys.stderr, "Unexpected socket error reported" + for s in iwtd: + if s in serversockets: + serversockets[s]() # call handler + elif s in extra_sockets: + return s + if not extra_sockets and timediff: + return 1 + return None + +SERVER_SHUTDOWN = 0.0 + +def mainloop(): + global game, SERVER_SHUTDOWN + servertimeout = None + try: + while serversockets: + try: + if game is not None: + delay = game.mainstep() + else: + delay = SERVER_SHUTDOWN or 5.0 + iwtd = serversockets.keys() + try: + iwtd, owtd, ewtd = select(iwtd, [], iwtd, delay) + except Exception, e: + from select import error as select_error + if not isinstance(e, select_error): + raise + iwtd, owtd, ewtd = [], [], [] + if ewtd: + if game: + game.socketerrors(ewtd) + if ewtd: + print >> sys.stderr, "Unexpected socket error reported" + servertimeout = None + if iwtd: + for s in iwtd: + if s in serversockets: + serversockets[s]() # call handler + servertimeout = None + elif SERVER_SHUTDOWN and not ewtd and not owtd: + SERVER_SHUTDOWN -= delay + if SERVER_SHUTDOWN <= 0.001: + raise SystemExit, "Server shutdown requested." + elif clients or getattr(game, 'autoreset', 0): + servertimeout = None + elif servertimeout is None: + servertimeout = time() + SERVER_TIMEOUT + elif time() > servertimeout: + raise SystemExit, "No more server activity, timing out." + except KeyboardInterrupt: + if game is None or not game.FnExcHandler(1): + raise + except SystemExit: + raise + except: + if game is None or not game.FnExcHandler(0): + raise + finally: + removesocket('LISTEN') + removesocket('PING') + if clients: + print "Server crash -- waiting for clients to terminate..." + while clients: + iwtd = [c.socket for c in clients] + try: + iwtd, owtd, ewtd = select(iwtd, [], iwtd, 120.0) + except KeyboardInterrupt: + break + if not (iwtd or owtd or ewtd): + break # timeout - give up + for c in clients[:]: + if c.socket in ewtd: + c.disconnect("select reported an error") + elif c.socket in iwtd: + try: + data = c.socket.recv(2048) + except error, e: + c.disconnect(e) + else: + if not data and not hasattr(c.socket, 'RECV_CAN_RETURN_EMPTY'): + c.disconnect("end of data") + print "Server closed." + +def closeeverything(): + global SERVER_SHUTDOWN + SERVER_SHUTDOWN = 2.5 + if game is not None: + game.FnServerInfo("Server is stopping!") + +# ____________________________________________________________ + +try: + from localmsg import recordfilename +except ImportError: + pass +else: + + class RecordFile: + proto = 2 + has_sound = 0 + + def __init__(self, filename, sampling=1/7.77): + self.filename = filename + self.f = None + self.sampling = sampling + self.msgl = [] + self.write = self.msgl.append + + def start(self): + if not self.f: + import gzip, atexit + self.f = gzip.open(self.filename, 'wb') + atexit.register(self.f.close) + self.recnext = time() + self.sampling + + def udpdata(self, udpdata): + if self.f: + now = time() + if now >= self.recnext: + while now >= self.recnext: + self.recnext += self.sampling + self.write(message(MSG_RECORDED, udpdata)) + self.f.write(''.join(self.msgl)) + del self.msgl[:] + + recording = RecordFile(recordfilename) + del recordfilename diff --git a/common/hostchooser.py b/common/hostchooser.py new file mode 100644 index 0000000..3243e06 --- /dev/null +++ b/common/hostchooser.py @@ -0,0 +1,192 @@ +from socket import * +import time, select, sys +from errno import ETIMEDOUT + +UDP_PORT = 8056 +PING_MESSAGE = "pclient-game-ping" +PONG_MESSAGE = "server-game-pong" + + +def serverside_ping(only_port=None): + port = only_port or UDP_PORT + s = socket(AF_INET, SOCK_DGRAM) + try: + s.bind(('', port)) + s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + return s + except error, e: + if only_port is None: + s = socket(AF_INET, SOCK_DGRAM) + s.bind(('', INADDR_ANY)) + return s + else: + return None + +def answer_ping(s, descr, addr, extra='', httpport=''): + try: + data, source = s.recvfrom(100) + except error, e: + print >> sys.stderr, 'ping error:', str(e) + return + if data == PING_MESSAGE: + print >> sys.stderr, "ping by", source + answer = '%s:%s:%s:%s:%s:%s' % (PONG_MESSAGE, descr, + addr[0], addr[1], extra, httpport) + s.sendto(answer, source) + else: + print >> sys.stderr, \ + "unexpected data on UDP port %d by" % UDP_PORT, source + + +def pick(hostlist, delay=1): + s = socket(AF_INET, SOCK_DGRAM) + s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + for host in hostlist: + print >> sys.stderr, "* Looking for a server on %s... " % host + try: + s.sendto(PING_MESSAGE, (host, UDP_PORT)) + except error, e: + print >> sys.stderr, 'send:', str(e) + continue + while 1: + iwtd, owtd, ewtd = select.select([s], [], [], delay) + if not iwtd: + break + try: + data, answer_from = s.recvfrom(200) + except error, e: + if e.args[0] != ETIMEDOUT: + print >> sys.stderr, 'recv:', str(e) + continue + break + data = data.split(':') + if len(data) >= 4 and data[0] == PONG_MESSAGE: + hostname = data[2] or answer_from[0] + try: + port = int(data[3]) + except ValueError: + pass + else: + result = (hostname, port) + print >> sys.stderr, "* Picking %r at" % data[1], result + return result + print >> sys.stderr, "got an unexpected answer", data, "from", answer_from + print >> sys.stderr, "no server found." + raise SystemExit + +def find_servers(hostlist=[('127.0.0.1', None), ('', None)], + tries=2, delay=0.5, verbose=1, port_needed=1): + import gamesrv + if verbose: + print >> sys.stderr, 'Looking for servers in the following list:' + for host, udpport in hostlist: + print >> sys.stderr, ' %s, UDP port %s' % ( + host, udpport or ("%s (default)" % UDP_PORT)) + events = {} + replies = [] + s = socket(AF_INET, SOCK_DGRAM) + s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + for trynum in range(tries): + for host, udpport in hostlist: + try: + ipaddr = host + if host != '': + try: + ipaddr = gethostbyname(host) + except error, e: + print >> sys.stderr, 'gethostbyname:', str(e) + s.sendto(PING_MESSAGE, (ipaddr, udpport or UDP_PORT)) + hostsend, hostrecv = events.setdefault(ipaddr, ([], [])) + hostsend.append(time.time()) + except error, e: + print >> sys.stderr, 'send:', str(e) + continue + endtime = time.time() + delay + while gamesrv.recursiveloop(endtime, [s]): + try: + data, answer_from = s.recvfrom(200) + except error, e: + if e.args[0] != ETIMEDOUT: + print >> sys.stderr, 'recv:', str(e) + continue + break + try: + ipaddr = gethostbyname(answer_from[0]) + except error: + ipaddr = answer_from[0] + else: + hostsend, hostrecv = events.setdefault(ipaddr, ([], [])) + hostrecv.append(time.time()) + data = data.split(':') + if len(data) >= 4 and data[0] == PONG_MESSAGE: + try: + port = int(data[3]) + except ValueError: + if port_needed: + continue + port = '' + if data[2]: + hostname = data[2] + realhostname = [hostname] + else: + hostname = answer_from[0] + realhostname = lazy_gethostbyaddr(hostname) + server = ':'.join(data[1:2]+data[4:]) + replies.append((hostname, realhostname, port, server, ipaddr)) + else: + print >> sys.stderr, "got an unexpected answer from", answer_from + servers = {} + aliases = {} + timeout = time.time() + 2.0 # wait for gethostbyaddr() for 2 seconds + while replies: + i = 0 + now = time.time() + while i < len(replies): + hostname, realhostname, port, server, ipaddr = replies[i] + if realhostname: + hostname = realhostname[0] # got an answer + elif now < timeout: + i += 1 # must wait some more time + continue + result = (hostname, port) + servers[result] = server + aliases[hostname] = ipaddr + del replies[i] + if replies: + time.sleep(0.08) # time for gethostbyaddr() to finish + if verbose: + print >> sys.stderr, "%d answer(s):" % len(servers), servers.keys() + for host, port in servers.keys(): + ping = None + ipaddr = aliases[host] + if ipaddr in events: + hostsend, hostrecv = events[ipaddr] + if len(hostsend) == len(hostrecv) == tries: + ping = min([t2-t1 for t1, t2 in zip(hostsend, hostrecv)]) + servers[host, port] = (servers[host, port], ping) + sys.setcheckinterval(4096) + return servers + +# ____________________________________________________________ + +HOSTNAMECACHE = {} + +def _lazygetter(hostname, resultlst): + try: + try: + hostname = gethostbyaddr(hostname)[0] + if hostname == 'localhost': + from msgstruct import HOSTNAME as hostname + except error: + pass + finally: + resultlst.append(hostname) + +def lazy_gethostbyaddr(hostname): + try: + return HOSTNAMECACHE[hostname] + except KeyError: + resultlst = HOSTNAMECACHE[hostname] = [] + import thread + thread.start_new_thread(_lazygetter, (hostname, resultlst)) + return resultlst diff --git a/common/httpserver.py b/common/httpserver.py new file mode 100644 index 0000000..a39f854 --- /dev/null +++ b/common/httpserver.py @@ -0,0 +1,192 @@ +from __future__ import generators +from __future__ import nested_scopes +import BaseHTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +import urlparse, cgi, htmlentitydefs +import sys, os, time +from cStringIO import StringIO + + +class Translator: + """For use with format strings. + + 'formatstring % translator' will evaluate all %(xxx)s expressions + found in the format string in the given globals/locals. + + Multiline expressions are assumed to be one or several complete + statements; they are executed and whatever they print is inserted back + into the format string.""" + + def __init__(self, globals, locals): + self.globals = globals + self.locals = locals + + def __getitem__(self, expr): + if '\n' in expr: + if not expr.endswith('\n'): + expr += '\n' + prevstdout = sys.stdout + try: + sys.stdout = f = StringIO() + exec expr in self.globals, self.locals + finally: + sys.stdout = prevstdout + return f.getvalue() + else: + return eval(expr, self.globals, self.locals) + +class TranslatorIO: + "Lazy version of Translator." + + def __init__(self, fmt, d): + self.gen = self.generate(fmt, d) + + def read(self, ignored=None): + for text in self.gen: + if text: + return text + return '' + + def close(self): + self.gen = () + + def generate(self, fmt, d): + t = Translator(d, d) + for data in fmt.split('\x0c'): + yield data % t + + +# HTML quoting + +text_to_html = {} +for key, value in htmlentitydefs.entitydefs.items(): + text_to_html[value] = '&' + key + ';' + +def htmlquote(s): + return ''.join([text_to_html.get(c, c) for c in s]) + + +# HTTP Request Handler + +pathloaders = {} + +def canonicalpath(url): + if url.startswith('/'): + url = url[1:] + return url.lower() + +def register(url, loader): + pathloaders[canonicalpath(url)] = loader + +def is_registered(url): + return canonicalpath(url) in pathloaders + +def load(filename, mimetype=None, locals=None): + if mimetype and mimetype.startswith('text/'): + mode = 'r' + else: + mode = 'rb' + f = open(filename, mode) + if locals is not None: + data = f.read() + f.close() + #data = data.replace('%"', '%%"') + d = globals().copy() + d.update(locals) + f = TranslatorIO(data, d) + return f, mimetype + +def fileloader(filename, mimetype=None): + def loader(**options): + return load(filename, mimetype) + return loader + +class HTTPRequestError(Exception): + pass + +class MiniHandler(SimpleHTTPRequestHandler): + + def send_head(self, query=''): + addr, host, path, query1, fragment = urlparse.urlsplit(self.path) + path = canonicalpath(path) + if path not in pathloaders: + if path + '/' in pathloaders: + return self.redirect(path + '/') + self.send_error(404) + return None + kwds = {} + for q in [query1, query]: + if q: + kwds.update(cgi.parse_qs(q)) + loader = pathloaders[path] + try: + hdr = self.headers + hdr['remote host'] = self.client_address[0] + f, ctype = loader(headers=hdr, **kwds) + except IOError, e: + self.send_error(404, "I/O error: " + str(e)) + return None + except HTTPRequestError, e: + self.send_error(500, str(e)) + return None + except: + f = StringIO() + import traceback + traceback.print_exc(file=f) + data = htmlquote(f.getvalue()) + data = data.replace('\n', '
\n') + self.send_error(500) + return StringIO('

'+data+'

') + if ctype is None: + ctype = self.guess_type(self.translate_path(self.path)) + elif f is None: + return self.redirect(ctype) + + self.send_response(200) + self.send_header("Content-type", ctype) + self.end_headers() + return f + + def redirect(self, url): + self.send_response(302) + self.send_header("Content-type", 'text/html') + self.send_header("Location", url) + self.end_headers() + return StringIO(''' +Please click here to continue. + +''' % url) + + def do_POST(self): + try: + nbytes = int(self.headers.getheader('content-length')) + except: + nbytes = 0 + query = self.rfile.read(nbytes).strip() + f = self.send_head(query) + if f: + self.copyfile(f, self.wfile) + f.close() + + def parse_request(self): + if self.raw_requestline == '': + return False + else: + return SimpleHTTPRequestHandler.parse_request(self) + + def address_string(self): + """Override to avoid DNS lookups""" + return "%s:%d" % self.client_address + + def finish(self): + SimpleHTTPRequestHandler.finish(self) + self.connection.close() + while actions_when_finished: + actions_when_finished.pop(0)() + +actions_when_finished = [] + +def my_host(): + import gamesrv + port = gamesrv.socketports[gamesrv.openhttpsocket()] + return '127.0.0.1:%d' % port diff --git a/common/javaserver.py b/common/javaserver.py new file mode 100644 index 0000000..15a586b --- /dev/null +++ b/common/javaserver.py @@ -0,0 +1,157 @@ +import sys, os +from cStringIO import StringIO +import httpserver + +PLAYERNAMES = ['Bub', 'Bob', 'Boob', 'Beb', + 'Biob', 'Bab', 'Bib', + 'Baub', 'Beab', 'Biab'] +LOCALDIR = os.path.abspath(os.path.dirname(__file__)) +DATADIR = os.path.join(LOCALDIR, os.pardir, 'http2', 'data') + +EMPTY_PAGE = ''' +No server is running +

No server is running at the moment.

+ + +''' + +INDEX_PAGE = ''' +%(title)s +

%(title)s

+ + + %(names1)s + +
+

Player Names & Teams

+ + +''' + +NAME_LINE1 = '' +NAME_SEP1 = '\n' +NAME_LINE2 = '%s=%s' +NAME_SEP2 = '&' + +def playernames(options): + NUM_PLAYERS = len(PLAYERNAMES) + result = {} + anyname = None + for id in range(NUM_PLAYERS): + keyid = 'player%d' % id + if keyid in options: + value = options[keyid][0] + anyname = anyname or value + teamid = 'team%d' % id + if teamid in options: + team = options[teamid][0] + if len(team) == 1: + value = '%s (%s)' % (value, team) + result[keyid] = value + if 'c' in options: + for id in range(NUM_PLAYERS): + keyid = 'player%d' % id + try: + del result[keyid] + except KeyError: + pass + if 'f' in options: + for id in range(NUM_PLAYERS): + keyid = 'player%d' % id + if not result.get(keyid): + result[keyid] = anyname or PLAYERNAMES[id] + else: + anyname = result[keyid] + return result + +def indexloader(**options): + if 'cheat' in options: + for opt in options.pop('cheat'): + __cheat(opt) + import gamesrv + if gamesrv.game is None: + indexdata = EMPTY_PAGE + else: + names = playernames(options).items() + indexdata = INDEX_PAGE % { + 'title': gamesrv.game.FnDesc, + 'width': gamesrv.game.width, + 'height': gamesrv.game.height, + 'gameport': gamesrv.game.address[1], + 'names1': NAME_SEP1.join([NAME_LINE1 % kv for kv in names]), + 'names2': NAME_SEP2.join([NAME_LINE2 % kv for kv in names]), + } + return StringIO(indexdata), 'text/html' + +def nameloader(**options): + if 's' in options: + return indexloader(**options) + locals = { + 'options': playernames(options), + } + return httpserver.load(os.path.join(DATADIR, 'name.html'), + 'text/html', locals=locals) + + +wave_cache = {} + +def wav2au(data): + # Very limited! Assumes a standard 8-bit mono .wav as input + import audioop, struct + freq, = struct.unpack("4siiiii8s', + '.snd', # header + struct.calcsize('>4siiiii8s'), # header size + len(data), # data size + 1, # encoding + 8000, # sample rate + 1, # channels + 'magic.au') + data + return data + +def sampleloader(code=[], **options): + import gamesrv + try: + data = wave_cache[code[0]] + except KeyError: + for key, snd in gamesrv.samples.items(): + if str(getattr(snd, 'code', '')) == code[0]: + data = wave_cache[code[0]] = wav2au(snd.read()) + break + else: + raise KeyError, code[0] + return StringIO(data), 'audio/wav' + + +def setup(): + dir = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir, + 'java')) + if not os.path.isdir(dir): + return + + # register all '.class' files + for name in os.listdir(dir): + if name.endswith('.class'): + httpserver.register(name, httpserver.fileloader(os.path.join(dir, name))) + + # register a '', an 'index.html', and a 'name.html' file + httpserver.register('', indexloader) + httpserver.register('index.html', indexloader) + httpserver.register('name.html', nameloader) + + # 'name.html' has a few images, list the .png files in DATADIR + for fn in os.listdir(DATADIR): + fn = fn.lower() + if fn.endswith('.png'): + httpserver.register(fn, httpserver.fileloader( + os.path.join(DATADIR, fn))) + + # register the sample loader + httpserver.register('sample.wav', sampleloader) + +setup() diff --git a/common/msgstruct.py b/common/msgstruct.py new file mode 100644 index 0000000..26389f0 --- /dev/null +++ b/common/msgstruct.py @@ -0,0 +1,75 @@ +from struct import pack, unpack, calcsize + +try: + from localmsg import PORTS +except ImportError: + PORTS = {} +try: + from localmsg import HOSTNAME +except ImportError: + from socket import gethostname + HOSTNAME = gethostname() + + +MSG_WELCOME = "Welcome to gamesrv.py(3) !\n" +MSG_BROADCAST_PORT= "*" +MSG_DEF_PLAYFIELD = "p" +MSG_DEF_KEY = "k" +MSG_DEF_ICON = "r" +MSG_DEF_BITMAP = "m" +MSG_DEF_SAMPLE = "w" +MSG_DEF_MUSIC = "z" +MSG_PLAY_MUSIC = "Z" +MSG_FADEOUT = "f" +MSG_PLAYER_JOIN = "+" +MSG_PLAYER_KILL = "-" +MSG_PLAYER_ICON = "i" +MSG_PING = "g" +MSG_PONG = "G" +MSG_INLINE_FRAME = "\\" +MSG_PATCH_FILE = MSG_DEF_MUSIC +MSG_ZPATCH_FILE = "P" +MSG_MD5_FILE = "M" +MSG_RECORDED = "\x00" + +CMSG_PROTO_VERSION= "v" +CMSG_KEY = "k" +CMSG_ADD_PLAYER = "+" +CMSG_REMOVE_PLAYER= "-" +CMSG_UDP_PORT = "<" +CMSG_ENABLE_SOUND = "s" +CMSG_ENABLE_MUSIC = "m" +CMSG_PING = "g" +CMSG_PONG = "G" +CMSG_DATA_REQUEST = "M" +CMSG_PLAYER_NAME = "n" + +BROADCAST_MESSAGE = "game!" # less than 6 bytes + + +def message(tp, *values): + strtype = type('') + typecodes = [''] + for v in values: + if type(v) is strtype: + typecodes.append('%ds' % len(v)) + elif 0 <= v < 256: + typecodes.append('B') + else: + typecodes.append('l') + typecodes = ''.join(typecodes) + assert len(typecodes) < 256 + return pack(("!B%dsc" % len(typecodes)) + typecodes, + len(typecodes), typecodes, tp, *values) + +def decodemessage(data): + if data: + limit = ord(data[0]) + 1 + if len(data) >= limit: + typecodes = "!c" + data[1:limit] + end = limit + calcsize(typecodes) + if len(data) >= end: + return unpack(typecodes, data[limit:end]), data[end:] + elif end > 1000000: + raise OverflowError + return None, data diff --git a/common/pixmap.py b/common/pixmap.py new file mode 100644 index 0000000..13142d5 --- /dev/null +++ b/common/pixmap.py @@ -0,0 +1,163 @@ +from __future__ import generators +import cStringIO + +def decodepixmap(data): + f = cStringIO.StringIO(data) + 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() + f.close() + return w, h, data + +def encodepixmap(w, h, data): + return 'P6\n%d %d\n255\n%s' % (w, h, data) + +def cropimage((w, h, data), (x1, y1, w1, h1)): + assert 0 <= x1 <= x1+w1 <= w + assert 0 <= y1 <= y1+h1 <= h + scanline = w*3 + lines = [data[p:p+w1*3] + for p in range(y1*scanline + x1*3, + (y1+h1)*scanline + x1*3, + scanline)] + return w1, h1, ''.join(lines) + +def vflip(w, h, data): + scanline = w*3 + lines = [data[p:p+scanline] for p in range(0, len(data), scanline)] + lines.reverse() + return ''.join(lines) + +def hflip(w, h, data): + scanline = w*3 + lines = [''.join([data[p:p+3] for p in range(p1+scanline-3, p1-3, -3)]) + for p1 in range(0, len(data), scanline)] + return ''.join(lines) + +def rotate_cw(w, h, data): + scanline = w*3 + lastline = len(data) - scanline + lines = [''.join([data[p:p+3] for p in range(lastline + p1, -1, -scanline)]) + for p1 in range(0, scanline, 3)] + return ''.join(lines) + +def rotate_ccw(w, h, data): + scanline = w*3 + lines = [''.join([data[p:p+3] for p in range(p1, len(data), scanline)]) + for p1 in range(scanline-3, -3, -3)] + return ''.join(lines) + +def rotate_180(w, h, data): + scanline = w*3 + lines = [''.join([data[p:p+3] for p in range(p1+scanline-3, p1-3, -3)]) + for p1 in range(0, len(data), scanline)] + lines.reverse() + return ''.join(lines) + +def makebkgnd(w, h, data): + scanline = 3*w + result = [] + for position in range(0, scanline*h, scanline): + line = [] + for p in range(position, position+scanline, 3): + line.append(2 * (chr(ord(data[p ]) >> 3) + + chr(ord(data[p+1]) >> 3) + + chr(ord(data[p+2]) >> 3))) + line = ''.join(line) + result.append(line) + result.append(line) + return w*2, h*2, ''.join(result) + +translation_darker = ('\x00\x01' + '\x00'*126 + + ''.join([chr(n//4) for n in range(0,128)])) +translation_dragon = translation_darker[:255] + '\xC0' + +def make_dark((w, h, data), translation): + return w, h, data.translate(translation) + +def col((r, g, b)): + r = ord(r) + g = ord(g) + b = ord(b) + return ((g>>2 + r>>3) << 24) | (b << 16) | (g << 8) | r + +def imagezoomer(w, h, data): + "Zoom a cartoon image by a factor of three, progressively." + scale = 3 + scanline = 3*w + rw = (w-1)*scale+1 + rh = (h-1)*scale+1 + pixels = [] + colcache = {} + revcache = {} + for base in range(0, scanline*h, scanline): + line = [] + for x in range(w): + key = data[base + 3*x : base + 3*(x+1)] + try: + c = colcache[key] + except KeyError: + c = colcache[key] = col(key) + revcache[c] = key + line.append(c) + pixels.append(line) + yield None + + Pairs = { + (0, 0): [(0, 0, 0, 0), + (-1,0, 1, 0), + (0,-1, 0, 1)], + (1, 0): [(0, 0, 1, 0), + (0, 1, 1,-1), + (0,-1, 1, 1)], + (2, 0): [(0, 0, 1, 0), + (0, 1, 1,-1), + (0,-1, 1, 1)], + (0, 1): [(0, 0, 0, 1), + (-1,0, 1, 1), + (1, 0,-1, 1)], + (1, 1): [(0, 0, 1, 1), + (0, 1, 1, -1), + (1, 0,-1, 1)], + (2, 1): [(1, 0, 0, 1), + (0,-1, 1, 1), + (0, 0, 2, 1)], + (0, 2): [(0, 0, 0, 1), + (-1,0, 1, 1), + (1, 0,-1, 1)], + (1, 2): [(0, 1, 1, 0), + (-1,0, 1, 1), + (0, 0, 1, 2)], + (2, 2): [(0, 0, 1, 1), + (0, 1, 2, 0), + (1, 0, 0, 2)], + } + result = [] + for y in range(rh): + yield None + for x in range(rw): + # ______________________________ + + i = x//scale + j = y//scale + ps = [] + for dx1, dy1, dx2, dy2 in Pairs[x%scale, y%scale]: + if (0 <= i+dx1 < w and 0 <= i+dx2 < w and + 0 <= j+dy1 < h and 0 <= j+dy2 < h): + p1 = pixels[j+dy1][i+dx1] + p2 = pixels[j+dy2][i+dx2] + ps.append(max(p1, p2)) + p1 = min(ps) + + # ______________________________ + result.append(revcache[p1]) + data = ''.join(result) + yield (rw, rh, data) diff --git a/common/stdlog.py b/common/stdlog.py new file mode 100644 index 0000000..3d8063c --- /dev/null +++ b/common/stdlog.py @@ -0,0 +1,106 @@ +import sys, os +from time import localtime, ctime + + +class LogFile: + + def __init__(self, filename=None, limitsize=32768): + if filename is None: + filename = sys.argv[0] + if filename.endswith('.py'): + filename = filename[:-3] + filename += '.log' + self.limitsize = limitsize + if not self._open(filename): + import tempfile + filename = os.path.join(tempfile.gettempdir(), + os.path.basename(filename)) + if not self._open(filename): + self.f = self.filename = None + self.lasttime = None + + def close(self): + if self.f is not None: + self.f.close() + self.f = None + + def __nonzero__(self): + return self.f is not None + + def _open(self, filename): + try: + self.f = open(filename, 'r+', 1) + self.f.seek(0, 2) + except IOError: + # The open r+ might have failed simply because the file + # does not exist. Try to create it. + try: + self.f = open(filename, 'w+', 1) + except (OSError, IOError): + return 0 + except OSError: + return 0 + self.filename = filename + if self.f.tell() > 0: + print >> self.f + print >> self.f, '='*44 + return 1 + + def _check(self): + if self.f is None: + return 0 + lt = localtime() + if lt[:4] != self.lasttime: + self.lasttime = lt[:4] + if self.f.tell() >= self.limitsize: + self.f.seek(-(self.limitsize>>1), 1) + data = self.f.read() + self.f.seek(0) + self.f.write('(...)' + data) + self.f.truncate() + self.f.write('========= %s =========\n' % ctime()) + return 1 + + def write(self, data): + if self._check(): + self.f.write(data) + + def writelines(self, data): + if self._check(): + self.f.writelines(data) + + def flush(self): + if self._check(): + self.f.flush() + + +class Logger: + stdout_captured = 0 + stderr_captured = 0 + + def __init__(self, f): + self.targets = [f] + + def capture_stdout(self): + if not Logger.stdout_captured: + self.targets.append(sys.stdout) + sys.stdout = self + Logger.stdout_captured = 1 + + def capture_stderr(self): + if not Logger.stderr_captured: + self.targets.append(sys.stderr) + sys.stderr = self + Logger.stderr_captured = 1 + + def write(self, data): + for f in self.targets: + f.write(data) + + def writelines(self, data): + for f in self.targets: + f.writelines(data) + + def flush(self): + for f in self.targets: + f.flush() diff --git a/common/udpovertcp.py b/common/udpovertcp.py new file mode 100644 index 0000000..eae3d6b --- /dev/null +++ b/common/udpovertcp.py @@ -0,0 +1,46 @@ +from socket import * +from msgstruct import * +#from fcntl import ioctl +#from termios import TIOCOUTQ +from zlib import compressobj, Z_SYNC_FLUSH +import struct + +ZeroBuffer = struct.pack("i", 0) + + +class SocketMarshaller: + + def __init__(self, tcpsock, mixer): + self.tcpsock = tcpsock + self.mixer = mixer + self.mixer_can_mix = mixer.send_can_mix + self.mixer_send = mixer.send_buffer + self.tcpsock_fd = tcpsock.fileno() + # try to reduce TCP latency + try: + tcpsock.setsockopt(SOL_IP, IP_TOS, 0x10) # IPTOS_LOWDELAY + except error, e: + print "Cannot set IPTOS_LOWDELAY for client:", str(e) + try: + tcpsock.setsockopt(SOL_TCP, TCP_NODELAY, 1) + except error, e: + print "Cannot set TCP_NODELAY for client:", str(e) + compressor = compressobj(6) + self.compress = compressor.compress + self.compress_flush = compressor.flush + + def send(self, data): + if self.mixer_can_mix(): + # discard all packets if there is still data waiting in tcpsock + # --- mmmh, works much better without this check --- + #try: + # if ioctl(self.tcpsock_fd, TIOCOUTQ, ZeroBuffer) != ZeroBuffer: + # return + #except IOError, e: + # print "ioctl(TIOCOUTQ) failed, disconnecting client" + # self.mixer.disconnect(e) + #else: + data = self.compress(data) + self.compress_flush(Z_SYNC_FLUSH) + self.mixer_send(message(MSG_INLINE_FRAME, data)) + return len(data) + return 0 diff --git a/display/.cvsignore b/display/.cvsignore new file mode 100644 index 0000000..687980a --- /dev/null +++ b/display/.cvsignore @@ -0,0 +1,4 @@ +*.py[co] +build +*.so +*.pyd diff --git a/display/Client.py b/display/Client.py new file mode 100644 index 0000000..3d1330d --- /dev/null +++ b/display/Client.py @@ -0,0 +1,170 @@ +#! /usr/bin/env python + +# __________ +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)) +# ---------- + +sys.path.insert(0, os.path.dirname(LOCALDIR)) +sys.path.insert(0, LOCALDIR) +import common +import pclient +import modes + + +UdpLookForServer = [ + '127.0.0.1', + '', + ] + +def parse_cmdline(argv): + # parse command-line + def usage(): + print >> sys.stderr, 'usage:' + print >> sys.stderr, ' python Client.py [-d#] [-s#] [extra options] [host[:port]]' + print >> sys.stderr + print >> sys.stderr, 'options:' + print >> sys.stderr, ' host search for a game on the given machine' + print >> sys.stderr, ' host:port connect to the given game server' + print >> sys.stderr, ' (default search for any local server)' + print >> sys.stderr, ' -d# --display=# graphic driver (see below)' + print >> sys.stderr, ' -s# --sound=# sound driver (see below)' + print >> sys.stderr, ' --music=no disable background music' + print >> sys.stderr, ' -h --help display this text' + print >> sys.stderr, ' -m --metaserver connect with the help of the metaserver' + print >> sys.stderr, ' (list servers with Client.py -m)' + print >> sys.stderr, ' -t --tcp for slow or proxy connections' + print >> sys.stderr, ' -u --udp for fast direct connections' + print >> sys.stderr, ' (default is to autodetect tcp or udp)' + print >> sys.stderr, ' --port UDP=# or #:# fixed inbound udp port or host:port' + print >> sys.stderr, ' --port TCP=# fixed inbound tcp port (-m only)' + print >> sys.stderr + print >> sys.stderr, 'graphic drivers:' + for info in modes.graphicmodeslist(): + info.printline(sys.stderr) + print >> sys.stderr + print >> sys.stderr, 'sound drivers:' + for info in modes.soundmodeslist(): + info.printline(sys.stderr) + print >> sys.stderr + sys.exit(2) + + shortopts = 'd:s:htum' + longopts = ['display=', 'sound=', 'music=', 'help', 'tcp', 'udp', + 'cfg=', 'metaserver', 'port='] + for info in modes.graphicmodeslist() + modes.soundmodeslist(): + short, long = info.getformaloptions() + shortopts += short + longopts += long + try: + from getopt import gnu_getopt as getopt + except ImportError: + from getopt import getopt + from getopt import error + try: + opts, args = getopt(argv, shortopts, longopts) + except error, e: + print >> sys.stderr, 'Client.py: %s' % str(e) + print >> sys.stderr + usage() + + metaserver = 0 + driver = sound = None + extraopts = {} + for key, value in opts: + if key in ('-d', '--display'): + driver = value + elif key in ('-s', '--sound'): + sound = value + elif key in ('-t', '--tcp'): + extraopts['udp_over_tcp'] = 1 + elif key in ('-u', '--udp'): + extraopts['udp_over_tcp'] = 0 + elif key in ('-m', '--metaserver'): + metaserver = 1 + elif key == '--port': + import common.msgstruct + try: + portname, value = value.split('=') + if portname == 'UDP': + portname = 'CLIENT' + elif portname == 'TCP': + portname = 'BACK' + except ValueError: + portname = 'CLIENT' + if portname == 'CLIENT' and ':' in value: + udphostname, value = value.split(':') + common.msgstruct.PORTS['sendudpto'] = udphostname + common.msgstruct.PORTS[portname] = int(value) + elif key == '--cfg': + extraopts['cfgfile'] = value + elif key in ('-h', '--help'): + usage() + else: + extraopts[key] = value + mode = driver, sound, extraopts + + if metaserver: + if len(args) == 0: + metalist() + sys.exit(0) + elif len(args) != 1 or ':' not in args[0]: + usage() + return metaconnect(args[0]), mode + + if args: + if len(args) > 1: + usage() + hosts = args[0].split(':') + if len(hosts) == 1: + host, = hosts + from common import hostchooser + server = hostchooser.pick([host] * 5) + elif len(hosts) == 2: + host, port = hosts + try: + port = int(port) + except ValueError: + usage() + server = host, port + else: + usage() + return directconnect(server), mode + + from common import hostchooser + server = hostchooser.pick(UdpLookForServer * 3) + return directconnect(server), mode + +def directconnect(sockaddr): + print "connecting to %s:%d..." % sockaddr + from socket import socket, AF_INET, SOCK_STREAM + s = socket(AF_INET, SOCK_STREAM) + s.connect(sockaddr) + return s, sockaddr + +def metaconnect(metaaddr): + from metaserver import metaclient + import common.msgstruct + port = common.msgstruct.PORTS.get('BACK') + s = metaclient.meta_connect(metaaddr, port) + sockaddr = s.getpeername() + return s, sockaddr + +def metalist(): + from metaserver import metaclient + metaclient.print_server_list() + +def main(): + (s, sockaddr), mode = parse_cmdline(sys.argv[1:]) + pclient.run(s, sockaddr, mode) + +if __name__ == '__main__': + main() diff --git a/display/Makefile b/display/Makefile new file mode 100644 index 0000000..214347a --- /dev/null +++ b/display/Makefile @@ -0,0 +1,2 @@ +xshm.so: xshm.c + python setup.py build_ext -i diff --git a/display/__init__.py b/display/__init__.py new file mode 100644 index 0000000..ea30561 --- /dev/null +++ b/display/__init__.py @@ -0,0 +1 @@ +#empty diff --git a/display/caching.py b/display/caching.py new file mode 100644 index 0000000..27d772c --- /dev/null +++ b/display/caching.py @@ -0,0 +1,261 @@ +from __future__ import generators +import os, md5, sys +#import common.debug + + +class FileCache: + MAX_FILES = 8 + + def __init__(self): + self.cache = {} + self.time = 0 + + def access(self, filename, position, writing=0): + if filename in self.cache: + time, mode, f = self.cache[filename] + if writing > mode: + f.close() + del self.cache[filename] + if filename not in self.cache: + if len(self.cache) >= FileCache.MAX_FILES: + (time, mode, f), k = min([(v,k) for (k,v) in self.cache.items()]) + f.close() + del self.cache[k] + try: + f = open(filename, ('rb', 'r+b')[writing]) + except (IOError, OSError): + if not writing: + raise + if not os.path.isdir(os.path.dirname(filename)): + os.mkdir(os.path.dirname(filename)) + f = open(filename, 'w+b') + mode = writing + self.time += 1 + self.cache[filename] = self.time, mode, f + f.seek(position) + return f + + +class MemoryBlock: + def __init__(self, data): + self.data = data + def overwrite(self, newdata): + self.data = newdata + def read(self): + return self.data + +class FileBlock: + def __init__(self, filename, position, length, readonly=1, complete=1): + self.filename = filename + self.position = position + self.length = length + self.readonly = readonly + self.complete = complete + def overwrite(self, newdata): + self.memorydata = newdata + if self.readonly: + print >> sys.stderr, "cannot overwrite file", self.filename + return + try: + f = Data.Cache.access(self.filename, self.position, writing=1) + f.write(newdata) + except (IOError, OSError): + print >> sys.stderr, "cache write error:", self.filename + return + self.complete = 1 + del self.memorydata + def read(self): + if self.complete: + f = Data.Cache.access(self.filename, self.position) + return f.read(self.length) + else: + return self.memorydata + + +class Data: + SafeChars = {} + for c in ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": + SafeChars[c] = c + Translate = ''.join([SafeChars.get(chr(c), '_') for c in range(256)]) + del c, SafeChars + Cache = FileCache() + + def __init__(self): + self.content = {} + self.backupfile = None + self.readonly = 0 + + clear = __init__ + + ### Public interface ### + + def store(self, position, data, filename=None, readonly=1): + """This class assumes that all accesses to block within the data + are done for disjoint intervals: no overlapping writes !""" + if self.content is not None: + try: + self.content[position].overwrite(data) + except KeyError: + if filename is None: + self.content[position] = MemoryBlock(data) + else: + self.content[position] = FileBlock(filename, position, + len(data), readonly) + if self.backupfile and not self.readonly: + try: + f = Data.Cache.access(self.backupfile, position, writing=1) + f.write(data) + f.flush() + except (IOError, OSError): + print >> sys.stderr, "cache write error:", self.backupfile + + def loadfrom(self, filename, position, length, checksum): + """Try to load data from the given filename, with the given + expected MD5 checksum. The filename must be Unix-style, and is + looked up both in the directory SOURCEDIR and with a mangled name + in the cache directory CACHEDIR.""" + directname = os.path.join(self.SOURCEDIR, *filename.split('/')) + mangledname = filename.translate(Data.Translate) + cachename = os.path.join(self.CACHEDIR, mangledname) + for name, readonly in ((directname, 1), (cachename, 0)): + try: + f = Data.Cache.access(name, position) + data = f.read(length) + except (IOError, OSError): + pass + else: + if len(data) == length and md5.new(data).digest() == checksum: + # correct data + self.store(position, data, name, readonly) + return 1 + if self.content is not None and not self.content.has_key(position): + self.content[position] = FileBlock(cachename, position, length, + readonly=0, complete=0) + elif self.readonly: + print >> sys.stderr, "Note: the music data has changed. You can get" + print >> sys.stderr, "the server's version by deleting", directname + return 1 # incorrect data, but ignored + return 0 + + def read(self): + """Return the data as built so far.""" + if self.content is not None: + items = self.content.items() + items.sort() + result = '' + for position, block in items: + if len(result) < position: + result += '\x00' * (position-len(result)) + data = block.read() + result = result[:position] + data + result[position+len(data):] + return result + else: + f = Data.Cache.access(self.backupfile, 0) + return f.read() + + def fopen(self): + if self.content is not None: + from cStringIO import StringIO + return StringIO(self.read()) + else: + return Data.Cache.access(self.backupfile, 0) + + def freezefilename(self, fileexthint='.wav'): + """Return the name of a file from which the data can be read. If all + the current data comes from the same file, it is assumed to be exactly + the file that we want.""" + if not self.backupfile: + files = {} + for position, block in self.content.items(): + if not isinstance(block, FileBlock): + break + if block.complete: + files[block.filename] = block + else: + if len(files) == 1: + self.backupfile, block = files.items()[0] + self.readonly = block.readonly + if not self.backupfile: + self.backupfile = mktemp(fileexthint) + f = Data.Cache.access(self.backupfile, 0, writing=1) + for position, block in self.content.items(): + f.seek(position) + f.write(block.read()) + f.flush() + #print 'freezefilename ->', self.backupfile + #print ' readonly =', self.readonly + self.content = None + return self.backupfile + +# ____________________________________________________________ +# Temporary files management +# from the 'py' lib, mostly written by hpk + +def try_remove_dir(udir): + try: + for name in os.listdir(udir): + try: + os.unlink(os.path.join(udir, name)) + except: + pass + os.rmdir(udir) + except: + pass + +def make_numbered_dir(prefix='tmp-bub-n-bros-', rootdir=None, keep=0, + lock_timeout = 172800): # two days + """ return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + Directories with a number less than (maxnum-keep) will be removed. + """ + import atexit, tempfile + if rootdir is None: + rootdir = tempfile.gettempdir() + + def parse_num(bn): + """ parse the number out of a path (if it matches the prefix) """ + if bn.startswith(prefix): + try: + return int(bn[len(prefix):]) + except ValueError: + pass + + # compute the maximum number currently in use with the + # prefix + maxnum = -1 + for basename in os.listdir(rootdir): + num = parse_num(basename) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + udir = os.path.join(rootdir, prefix + str(maxnum+1)) + os.mkdir(udir) + + # try to remove the directory at process exit + atexit.register(try_remove_dir, udir) + + # prune old directories + for basename in os.listdir(rootdir): + num = parse_num(basename) + if num is not None and num <= (maxnum - keep): + d1 = os.path.join(rootdir, basename) + try: + t1 = os.stat(d1).st_mtime + t2 = os.stat(udir).st_mtime + if abs(t2-t1) < lock_timeout: + continue # skip directories still recently used + except: + pass + try_remove_dir(d1) + return udir + +def enumtempfiles(): + tempdir = make_numbered_dir() + i = 0 + while True: + yield os.path.join(tempdir, 'b%d' % i) + i += 1 + +def mktemp(fileext, gen = enumtempfiles()): + return gen.next() + fileext diff --git a/display/dpy_gtk.py b/display/dpy_gtk.py new file mode 100644 index 0000000..7b73516 --- /dev/null +++ b/display/dpy_gtk.py @@ -0,0 +1,204 @@ + + ################################################ +## GTK-based implementation of xshm ## +################################################ + +import os, sys, math +from modes import KeyPressed, KeyReleased +import caching + +def import_trickery(): + global gtk, gdk + argv = sys.argv[:] + del sys.argv[1:] + import gtk + from gtk import gdk + sys.argv[:] = argv +import_trickery() + + +class Display: + + def __init__(self, width, height, title, zoom="100"): + if zoom.endswith('%'): + zoom = zoom[:-1] + scale = float(zoom) / 100.0 + iscale = int(scale+0.001) + if abs(scale - iscale) < 0.002: + scale = iscale + self.scale = scale + + self.width = int(width * scale) + self.height = int(height * scale) + self.tempppmfile = caching.mktemp('.ppm') + + # create a top level window + w = self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + w.connect("destroy", lambda w: sys.exit()) + w.connect("key-press-event", self.key_press_event) + w.connect("key-release-event", self.key_release_event) + w.connect("motion-notify-event", self.motion_notify_event) + w.connect("button-press-event", self.button_press_event) + w.add_events(gdk.KEY_PRESS_MASK | + gdk.POINTER_MOTION_MASK | + gdk.BUTTON_PRESS_MASK) + w.resize(self.width, self.height) + w.set_title(title) + w.show() + + self.offscreen = gtk.create_pixmap(w.window, self.width, self.height) + self.gc = gdk.gc_new(w.window) + self.gc.set_rgb_fg_color(gdk.color_parse('#000000')) + + self.events_key = [] + self.events_mouse = [] + self.event_motion = None + + pixel = "\x00\x00\x80" + hole = "\x01\x01\x01" + pb = self.pixmap(32, 32, ((pixel+hole)*16 + (hole+pixel)*16) * 16, 0x010101) + self.taskbkgnd = self.renderpixbuf(pb) + + def taskbar(self, (x, y, w, h)): + scale = self.scale + x2 = x+w + y2 = y+h + x, y, x2, y2 = int(x*scale), int(y*scale), int(x2*scale), int(y2*scale) + pixmap, gc, ignored = self.taskbkgnd + for j in range(y, y2, 32): + for i in range(x, x2, 32): + gc.set_clip_origin(i, j) + self.offscreen.draw_drawable(gc, pixmap, 0, 0, + i, j, x2-i, y2-j) + + def pixmap(self, w, h, data, colorkey=-1): + filename = self.tempppmfile + f = open(filename, 'wb') + print >> f, 'P6' + print >> f, w, h + print >> f, 255 + f.write(data) + f.close() + pb = gdk.pixbuf_new_from_file(filename) + if colorkey >= 0: + pb = pb.add_alpha(1, chr(colorkey >> 16), + chr((colorkey >> 8) & 0xFF), + chr(colorkey & 0xFF)) + if self.scale == 1: + return self.renderpixbuf((pb,)) + else: + return (pb,) + + def renderpixbuf(self, input): + if len(input) == 3: + return input + pb, = input + pixmap, mask = pb.render_pixmap_and_mask() + if mask is not None: + gc = gdk.gc_new(self.window.window) + gc.set_clip_mask(mask) + return (pixmap, gc, mask) + else: + return (pixmap, self.gc, None) + + def getopticon(self, input, (x, y, w, h), ignored_alpha=255): + if len(input) == 3: + return None + pb, = input + scale = self.scale + newpb = gdk.Pixbuf("rgb", 1, 8, w, h) + newpb.fill(0) + pb.copy_area(x, y, w, h, newpb, 0, 0) + newpb = newpb.scale_simple(int(w*scale), int(h*scale), + gdk.INTERP_HYPER) + if newpb is None: + return None, None, None + else: + return self.renderpixbuf((newpb,)) + + def getppm(self, (x, y, w, h), int=int, ceil=math.ceil): + scale = self.scale + if isinstance(scale, int): + x *= scale + y *= scale + w *= scale + h *= scale + else: + w = int(ceil((x+w)*scale)) + h = int(ceil((y+h)*scale)) + x = int(x*scale) + y = int(y*scale) + w -= x + h -= y + bkgnd = gtk.create_pixmap(self.window.window, w, h) + bkgnd.draw_drawable(self.gc, self.offscreen, x, y, 0, 0, w, h) + return bkgnd, self.gc, None + + def putppm(self, x, y, (pixmap, gc, ignored), rect=None, int=int): + if pixmap is None: + return + scale = self.scale + if rect is None: + srcx = srcy = 0 + w = h = 4095 + else: + srcx, srcy, w, h = rect + x = int(x*scale) + y = int(y*scale) + if gc is not self.gc: + gc.set_clip_origin(x-srcx, y-srcy) + self.offscreen.draw_drawable(gc, pixmap, srcx, srcy, x, y, w, h) + + def flip(self): + self.window.window.draw_drawable(self.gc, self.offscreen, + 0, 0, 0, 0, self.width, self.height) + gdk.flush() + self.events_poll() + + def close(self): + self.window.destroy() + + def clear(self): + self.offscreen.draw_rectangle(self.gc, 1, + 0, 0, self.width, self.height) + + def events_poll(self): + while gtk.events_pending(): + gtk.main_iteration() + + def key_press_event(self, window, event): + self.events_key.append((event.keyval, KeyPressed)) + + def key_release_event(self, window, event): + self.events_key.append((event.keyval, KeyReleased)) + + def motion_notify_event(self, window, event): + self.event_motion = (int(event.x/self.scale), int(event.y/self.scale)) + + def button_press_event(self, window, event): + self.events_mouse.append((int(event.x/self.scale), int(event.y/self.scale))) + + def keyevents(self): + self.events_poll() + result = self.events_key + self.events_key = [] + return result + + def pointermotion(self): + result = self.event_motion + self.event_motion = None + return result + + def mouseevents(self): + self.events_poll() + result = self.events_mouse + self.events_mouse = [] + return result + + def selectlist(self): + return [] + + +def htmloptionstext(nameval): + return 'Scale image by <%s size=5>%%' % ( + nameval('text', 'zoom', default='100')) diff --git a/display/dpy_pygame.py b/display/dpy_pygame.py new file mode 100644 index 0000000..acfd200 --- /dev/null +++ b/display/dpy_pygame.py @@ -0,0 +1,245 @@ + + ################################################ +## pygame-based implementation of xshm ## +################################################ + +import os +import pygame +from pygame.locals import * +from modes import KeyPressed, KeyReleased + + +class Display: + musthidemouse = 0 + mousevisible = 1 + + def __init__(self, width, height, title, transparency='yes', + fullscreen='no', zoom='100%', smooth='yes', + smoothfast='yes'): + self.use_transparency = not transparency.startswith('n') + self.use_fullscreen = fullscreen.startswith('y') + if zoom.endswith('%'): + zoom = zoom[:-1] + scale = float(zoom) / 100.0 + iscale = int(scale+0.001) + if abs(scale - iscale) < 0.002: + scale = iscale + self.scale = scale + self.smooth = smooth.startswith('y') + self.smoothfast = smoothfast.startswith('y') + + # Initialize pygame + pygame.init() + + # Set the display mode + winstyle = HWSURFACE + if self.use_fullscreen: + winstyle |= FULLSCREEN + bestdepth = pygame.display.mode_ok((int(width * self.scale), + int(height * self.scale)), + winstyle, 32) + self.screen = pygame.display.set_mode((int(width * self.scale), + int(height * self.scale)), + winstyle, bestdepth) + self.offscreen = pygame.Surface((width, height)) + #decorate the game window + pygame.display.set_caption(title) + #pygame.mouse.set_visible(0) + self.tbcache = None, None + self.events_key = [] + self.events_mouse = [] + self.prevposition = None + EVENT_HANDLERS[KEYDOWN] = self.keydown_handler + EVENT_HANDLERS[KEYUP] = self.keyup_handler + EVENT_HANDLERS[MOUSEBUTTONDOWN] = self.mousebuttondown_handler + + def keydown_handler(self, e): + if e.key == K_ESCAPE and self.use_fullscreen: + raise SystemExit # ESC to exit the game if full-screen + self.showmouse(not self.musthidemouse) + self.events_key.append((e.key, KeyPressed)) + del self.events_key[:-16] + + def keyup_handler(self, e): + self.events_key.append((e.key, KeyReleased)) + del self.events_key[:-16] + + def mousebuttondown_handler(self, e): + self.showmouse(1) + self.events_mouse.append(self.fixpos(e.pos)) + del self.events_mouse[:-8] + + def pixmap(self, w, h, data, colorkey=-1): + img = pygame.image.fromstring(data, (w, h), "RGB") + if colorkey >= 0: + r = colorkey & 0xFF + g = (colorkey >> 8) & 0xFF + b = (colorkey >> 16) & 0xFF + img.set_colorkey([r, g, b]) + return img # not optimized -- must use getopticon() + + def getopticon(self, pixmap, rect, alpha=255): + if not self.use_transparency: + alpha = 255 + img = pixmap.subsurface(rect) + colorkey = pixmap.get_colorkey() + if alpha == 255 and not colorkey: + return img.convert(self.offscreen) + else: + if colorkey: + img.set_colorkey(colorkey, RLEACCEL) + if alpha < 255: + img.set_alpha(alpha, RLEACCEL) + img = img.convert_alpha(self.offscreen) + img.set_alpha(255, RLEACCEL) + return img + +## def vflipppm(self, img): +## w, h = img.get_size() +## colorkey = img.get_colorkey() +## data = pygame.image.tostring(img, "RGB", 1) +## flipimg = pygame.image.fromstring(data, (w, h), "RGB") +## flipimg.set_colorkey(colorkey, RLEACCEL) +## return flipimg, h + + def getppm(self, rect): + bkgnd = pygame.Surface(rect[2:]) + bkgnd.blit(self.offscreen, (0, 0), rect) + return bkgnd + + def putppm(self, x, y, bitmap, rect=None): + if rect: + self.offscreen.blit(bitmap, (x, y), rect) + else: + self.offscreen.blit(bitmap, (x, y)) + + def flip(self): + offscreen = self.offscreen + if self.scale != 1: + w, h = offscreen.get_size() + w = int(w * self.scale) + h = int(h * self.scale) + if self.scale == 2 and self.smoothfast: + offscreen = pygame.transform.scale2x(offscreen) + elif self.smooth: + offscreen = pygame.transform.smoothscale(offscreen, (w, h)) + else: + offscreen = pygame.transform.scale(offscreen, (w, h)) + self.screen.blit(offscreen, (0, 0)) + pygame.display.flip() + events_dispatch() + + def close(self): + self.showmouse(1) + pygame.display.quit() + + def clear(self): + self.offscreen.fill([0,0,0,]) + + def fixpos(self, (x, y)): + if self.scale != 1: + x = int(x / self.scale) + y = int(y / self.scale) + return (x, y) + + def events_poll(self): + while 1: + e = pygame.event.poll() + if e.type == NOEVENT: + break + elif e.type == KEYDOWN: + self.events_key.append((e.key, KeyPressed)) + del self.events_key[:-16] + elif e.type == KEYUP: + self.events_key.append((e.key, KeyReleased)) + del self.events_key[:-16] + elif e.type == MOUSEBUTTONDOWN: + self.events_mouse.append(self.fixpos(e.pos)) + del self.events_mouse[:-8] + elif e.type == ENDMUSICEVENT: + self.next_music() + elif e.type == QUIT: + raise SystemExit + + def keyevents(self): + events_dispatch() + events = self.events_key + self.events_key = [] + return events + + def pointermotion(self): + position = pygame.mouse.get_pos() + if position != self.prevposition: + self.showmouse(1) + self.prevposition = position + return self.fixpos(position) + else: + return None + + def mouseevents(self): + events_dispatch() + events = self.events_mouse + self.events_mouse = [] + return events + + def selectlist(self): + return [] + + def taskbar(self, (x, y, w, h)): + tbs, tbh = self.tbcache + if tbh != h: + tbs = pygame.Surface((32, h)).convert_alpha(self.offscreen) + alpha_f = 256.0 / h + for j in range(h): + tbs.fill((128, 128, 255, int(j*alpha_f)), + (0, j, 32, 1)) + self.tbcache = tbs, h + for i in range(x, x+w, 32): + dw = x+w-i + if dw < 32: + self.offscreen.blit(tbs, (i, y), (0, 0, dw, h)) + else: + self.offscreen.blit(tbs, (i, y)) + + def settaskbar(self, tb_visible): + self.showmouse(1) + self.musthidemouse = not tb_visible # and self.use_fullscreen + + def showmouse(self, v): + if v != self.mousevisible: + self.mousevisible = v + pygame.mouse.set_visible(v) + + +def quit_handler(e): + raise SystemExit + +EVENT_HANDLERS = { + QUIT: quit_handler, + } + +def events_dispatch(handlers = EVENT_HANDLERS): + while 1: + e = pygame.event.poll() + if e.type == NOEVENT: + break + elif handlers.has_key(e.type): + handlers[e.type](e) + + +def htmloptionstext(nameval): + return ''' +<%s> Full Screen (Esc key to exit)<%s>
+<%s> Draw slightly transparent bubbles<%s>
+Scale image by <%s size=5>%%
+<%s> Smoothed scaled image<%s>
+<%s> Semi-smoothed scaled image (for 200%% only)<%s>
+''' % (nameval("checkbox", "fullscreen", "yes", default="no"), + nameval("hidden", "fullscreen", "no"), + nameval("checkbox", "transparency", "yes", default="yes"), + nameval("hidden", "transparency", "no"), + nameval("text", "zoom", default="100"), + nameval("checkbox", "smooth", "yes", default="yes"), + nameval("hidden", "smooth", "no"), + nameval("checkbox", "smoothfast", "yes", default="no"), + nameval("hidden", "smoothfast", "no")) diff --git a/display/dpy_windows.py b/display/dpy_windows.py new file mode 100755 index 0000000..b01dfcf --- /dev/null +++ b/display/dpy_windows.py @@ -0,0 +1,23 @@ +import sys +import wingame +from modes import BaseDisplay +from cStringIO import StringIO + + +class Display(BaseDisplay): + + def __init__(self, width, height, title): + self.xdpy = xdpy = wingame.Display(width, height) + xdpy.settitle(title) + self.pixmap = xdpy.pixmap + self.getppm = xdpy.getppm + self.putppm = xdpy.putppm + self.close = xdpy.close + self.clear = xdpy.clear + self.flip = xdpy.flip + self.keyevents = xdpy.keyevents + self.mouseevents = xdpy.mouseevents + self.pointermotion = xdpy.pointermotion + + def selectlist(self): + return [] diff --git a/display/dpy_x.py b/display/dpy_x.py new file mode 100644 index 0000000..ffca90f --- /dev/null +++ b/display/dpy_x.py @@ -0,0 +1,40 @@ +import sys +import xshm +from modes import BaseDisplay +from cStringIO import StringIO + + +class Display(BaseDisplay): + + def __init__(self, width, height, title, shm='yes'): + use_shm = not shm.startswith('n') + self.xdpy = xdpy = xshm.Display(width, height, use_shm) + self.pixmap = xdpy.pixmap + self.getppm = xdpy.getppm + self.putppm = xdpy.putppm + self.overlayppm = xdpy.overlayppm + self.close = xdpy.close + self.clear = xdpy.clear + self.flip = xdpy.flip + self.keyevents = xdpy.keyevents + self.mouseevents = xdpy.mouseevents + self.pointermotion = xdpy.pointermotion + if use_shm and not xdpy.shmmode(): + print >> sys.stderr, \ + "Note: cannot use SHM extension (%dx%d), display will be slow." % \ + (width, height) + + def selectlist(self): + if hasattr(self.xdpy, 'fd'): + from socket import fromfd, AF_INET, SOCK_STREAM + return [fromfd(self.xdpy.fd(), AF_INET, SOCK_STREAM)] + else: + return [] + + +def htmloptionstext(nameval): + return ''' +<%s> Use the shared memory extension<%s>
+Note: Disable it for remote connections or old X servers +''' % (nameval("checkbox", "shm", "yes", default="yes"), + nameval("hidden", "shm", "no")) diff --git a/display/modes.py b/display/modes.py new file mode 100644 index 0000000..ad0c1c0 --- /dev/null +++ b/display/modes.py @@ -0,0 +1,196 @@ +import sys + +KeyPressed = 2 +KeyReleased = 3 + + +class BaseDisplay: + __taskbkgnd = None + + def taskbar(self, (x, y, w, h)): + if self.__taskbkgnd is None: + pixel = "\x00\x00\x80" + hole = "\x01\x01\x01" + self.__taskbkgnd = self.pixmap(32, 32, + ((pixel+hole)*16 + (hole+pixel)*16) * 16, 0x010101) + for j in range(y, y+h, 32): + for i in range(x, x+w, 32): + self.putppm(i, j, self.__taskbkgnd, + (0, 0, x+w-i, y+h-j)) + + +class Mode: + low_priority = 0 + + def __init__(self, name, descr, extraoptsdescr, + options={}, url=None): + self.name = name + self.descr = descr + self.extraoptsdescr = extraoptsdescr + self.options = options.copy() + self.url = url + + def getmodule(self): + return __import__(self.prefix + self.name.lower(), globals(), + locals(), ['available']) + + def imperror(self): + try: + return self.__imperror + except AttributeError: + try: + module = self.getmodule() + except ImportError: + result = 'not installed' + else: + result = hasattr(module, 'imperror') and module.imperror() + self.__imperror = result + return result + + def unique_id(self): + return self.prefix + self.name + + def printline(self, f): + err = self.imperror() + if err: + state = ' [%s]' % err + else: + state = '' + print >> f, ' %-8s %s%s' % (self.name, self.descr, state) + if self.url: + print >> f, ' %s' % self.url + for line in self.extraoptsdescr: + print >> f, ' %s' % line + + def getformaloptions(self): + return '', [c+'=' for c in self.options.keys()] + + def setoptions(self, options): + for key in self.options.keys(): + if options.has_key('--'+key): + self.options[key] = options['--'+key] + + def currentdriver(self): + lst = self.options.items() + lst.sort() + lst = ['--%s=%s' % keyvalue for keyvalue in lst] + return ' '.join([self.name] + lst) + + def htmloptionstext(self, *args): + if self.imperror(): + return None + module = self.getmodule() + return (hasattr(module, 'htmloptionstext') and + module.htmloptionstext(*args)) + + +class GraphicMode(Mode): + prefix = 'dpy_' + + +class SoundMode(Mode): + prefix = 'snd_' + + +def graphicmodeslist(): + return [ + GraphicMode('X', 'XWindow (Linux/Unix)', + ['--shm=yes use the Shared Memory extension (default)', + '--shm=no disable it (for remote connections or old X servers)', + ], + {'shm': 'yes'}), + GraphicMode('windows', 'MS Windows', []), + GraphicMode('pygame', 'PyGame library (all platforms)', + ['--fullscreen=yes go full screen (Esc key to exit)', + '--transparency=yes slightly transparent bubbles (default)', + '--transparency=no disable it (a bit faster)', + '--zoom=xxx% scale image by xxx %', + '--smooth smoothed scaled image', + '--smoothfast semi-smoothed, for 200% scale only'], + {'transparency': 'yes', 'fullscreen': 'no', + 'zoom': '100', 'smooth': 'yes', 'smoothfast': 'no'}, + url='http://www.pygame.org'), + GraphicMode('gtk', 'PyGTK (Gnome)', + ['--zoom=xxx% scale image by xxx %'], + {'zoom': '100'}, + url='http://www.pygtk.org/'), + ] + +def soundmodeslist(): + return [ + SoundMode('pygame', 'PyGame library mixer (all platforms)', + [], url='http://www.pygame.org'), + SoundMode('linux', 'audio mixer for Linux', + ['--freq=# mixer frequency (default 44100)', + '--fmt=# data format (default S16_NE, --fmt=list for a list)'], + {'freq': '44100', 'fmt': 'S16_NE'}), + SoundMode('windows', 'audio mixer for Windows', + ['--freq=# mixer frequency (default 44100)', + '--bits=# bits per sample (8 or default 16)'], + {'freq': '44100', 'bits': '16'}), + SoundMode('off', 'no sounds', []), + ] + +def findmode(name, lst): + if name is None: + # find the first installed mode + last_chance = None + for info in lst: + err = info.imperror() + if err: + continue + if info.low_priority: + if last_chance is None: + last_chance = info + else: + return info + if last_chance is not None: + return last_chance + raise KeyError, 'no driver available!' + else: + # find mode by name + for info in lst: + if info.name.upper() == name.upper(): + err = info.imperror() + if err: + raise KeyError, '%s: %s' % (info.name, err) + return info + raise KeyError, '%s: no such driver' % name + +def findmode_err(*args): + try: + return findmode(*args) + except KeyError, e: + print >> sys.stderr, str(e) + sys.exit(1) + +def open_dpy(mode, width, height, title): + driver, sound, extraopts = mode + ginfo = findmode_err(driver, graphicmodeslist()) + ginfo.setoptions(extraopts) + dpy = ginfo.getmodule().Display(width, height, title, **ginfo.options) + print 'graphics driver:', ginfo.currentdriver() + return dpy + +def open_snd(mode): + driver, sound, extraopts = mode + sinfo = findmode_err(sound, soundmodeslist()) + sinfo.setoptions(extraopts) + snd = sinfo.getmodule().Sound(**sinfo.options) + if snd.has_sound: + sinfo.options['music'] = 'yes' + sinfo.setoptions(extraopts) + if (sinfo.options['music'].startswith('n') or + sinfo.options['music'] == 'off'): + snd.has_music = 0 + print 'sound driver:', sinfo.currentdriver() + return snd + else: + return None + + +def musichtmloptiontext(nameval): + return ''' +<%s> Background music<%s> +''' % (nameval("checkbox", "music", "yes", default="yes", mangling=0), + nameval("hidden", "music", "no", mangling=0)) diff --git a/display/music1.py b/display/music1.py new file mode 100644 index 0000000..b5c0c6d --- /dev/null +++ b/display/music1.py @@ -0,0 +1,33 @@ +class Music: + def __init__(self, filename): + self.filename = filename + self.w = None + self.sampledata = '' + def openchannel(self): + if self.w is not None: + self.w.close() + import wave + self.w = w = wave.open(open(self.filename, 'rb'), 'r') + self.w_params = (w.getnchannels(), + w.getsampwidth(), + w.getframerate()) + chan, width, freq = self.w_params + self.dataleft = w.getnframes() * (chan*width) + self.sampledata = '' + def decode(self, mixer, bytecount): + result = self.sampledata + if not result and self.dataleft > 0: + # decode and convert some more data + chan, width, freq = self.w_params + #framecount = bytecount / (chan*width) + inputdata = self.w.readframes(bytecount) #(framecount) + self.dataleft -= len(inputdata) + result = mixer.resample(inputdata, + freq = freq, + bits = width * 8, + signed = width > 1, + channels = chan, + byteorder = 'little') + #print len(result) + self.sampledata = result[bytecount:] + return result[:bytecount] diff --git a/display/pclient.py b/display/pclient.py new file mode 100644 index 0000000..c6546ce --- /dev/null +++ b/display/pclient.py @@ -0,0 +1,860 @@ +#! /usr/bin/env python + +import sys, os +from socket import * +from select import select +import struct, zlib +import time +from common.msgstruct import * +from common.pixmap import decodepixmap +from common import hostchooser +import modes +from modes import KeyPressed, KeyReleased +import caching + +#import psyco; psyco.full() + +# switch to udp_over_tcp if the udp socket didn't receive at least 60% of +# the packets sent by the server +UDP_EXPECTED_RATIO = 0.60 + + +def loadpixmap(dpy, data, colorkey=None): + w, h, data = decodepixmap(data) + if colorkey is None: + colorkey = -1 + elif colorkey < 0: + r, g, b = struct.unpack("BBB", self.data[:3]) + colorkey = b | (g<<8) | (r<<16) + return dpy.pixmap(w, h, data, colorkey) + +class Icon: + alpha = 255 + def __init__(self, playfield): + self.playfield = playfield + self.size = 0, 0 + def __getattr__(self, attr): + if attr == 'pixmap': + self.pixmap = self.playfield.getpixmap(self.bmpcode) + if hasattr(self.playfield.dpy, 'getopticon'): + ico = self.playfield.dpy.getopticon( + self.pixmap, self.originalrect, self.alpha) + if ico is not None: + self.pixmap = ico + self.rect = None + return self.pixmap + elif attr in ('bmpcode', 'rect'): + raise KeyError, attr + elif attr == 'originalrect': + self.originalrect = self.rect + return self.originalrect + raise AttributeError, attr + def clear(self): + if self.__dict__.has_key('pixmap'): + del self.pixmap + +class DataChunk(caching.Data): + SOURCEDIR = os.path.abspath(os.path.join(os.path.dirname(caching.__file__), + os.pardir)) + CACHEDIR = os.path.join(SOURCEDIR, 'cache') + TOTAL = 0 + + def __init__(self, fileid): + caching.Data.__init__(self) + self.fileid = fileid + self.pending = [] + self.progresshook = None + + def server_md5(self, playfield, filename, position, length, checksum): + if not self.loadfrom(filename, position, length, checksum): + self.pending.append((0, position)) + playfield.s.sendall(message(CMSG_DATA_REQUEST, self.fileid, + position, length)) + + def server_patch(self, position, data, lendata): + #print 'server_patch', self.fileid, position, len(data) + prev = DataChunk.TOTAL >> 10 + DataChunk.TOTAL += lendata + total = DataChunk.TOTAL >> 10 + if total != prev: + print "downloaded %dkb of data from server" % total + self.store(position, data) + try: + self.pending.remove((0, position)) + except ValueError: + pass + else: + while self.pending and self.pending[0][0]: + callback = self.pending[0][1] + del self.pending[0] + callback(self) + + def when_ready(self, callback): + if self.pending: + self.pending.append((1, callback)) + else: + callback(self) + + +class Playfield: + TASKBAR_HEIGHT = 48 + + def __init__(self, s, sockaddr): + self.s = s + self.sockaddr = sockaddr + try: + self.s.setsockopt(SOL_IP, IP_TOS, 0x10) # IPTOS_LOWDELAY + except error, e: + print >> sys.stderr, "Cannot set IPTOS_LOWDELAY:", str(e) + try: + self.s.setsockopt(SOL_TCP, TCP_NODELAY, 1) + except error, e: + print >> sys.stderr, "Cannot set TCP_NODELAY:", str(e) + + initialbuf = "" + while 1: + t = self.s.recv(200) + if not t and not hasattr(self.s, 'RECV_CAN_RETURN_EMPTY'): + raise error, "connexion closed" + initialbuf += t + if len(initialbuf) >= len(MSG_WELCOME): + head = initialbuf[:len(MSG_WELCOME)] + tail = initialbuf[len(MSG_WELCOME):] + if head != MSG_WELCOME: + raise error, "connected to something not a game server" + if '\n' in tail: + break + n = tail.index('\n') + line2 = tail[:n] + self.initialbuf = tail[n+1:] + + self.gameident = line2.strip() +## self.datapath = None +## if self.gameident.endswith(']'): +## i = self.gameident.rfind('[') +## if i >= 0: +## self.gameident, self.datapath = (self.gameident[:i].strip(), +## self.gameident[i+1:-1]) + print "connected to %r." % self.gameident + self.s.sendall(message(CMSG_PROTO_VERSION, 3)) + + def setup(self, mode, udp_over_tcp): + self.playing = {} # 0, 1, or 'l' for local + self.keys = {} + self.keycodes = {} + self.last_key_event = (None, None) + self.dpy = None + self.snd = None + self.pixmaps = {} # {bmpcode: dpy_pixmap} + self.bitmaps = {} # {bmpcode: (fileid_or_data, colorkey)} + self.icons = {} + self.sounds = {} + self.currentmusic = None + self.fileids = {} + self.sprites = [] + self.playingsounds = {} + self.playericons = {} + self.screenmode = mode + self.initlevel = 0 + if mode[-1].has_key('udp_over_tcp'): + udp_over_tcp = mode[-1]['udp_over_tcp'] + self.trackcfgmtime = None + if mode[-1].has_key('cfgfile'): + self.trackcfgfile = mode[-1]['cfgfile'] + else: + self.trackcfgfile = os.path.join(DataChunk.SOURCEDIR, + 'http2', 'config.txt') + self.udpsock = None + self.udpsock_low = None + self.udpsock2 = None + self.accepted_broadcast = 0 + self.tcpbytecounter = 0 + self.udpbytecounter = 0 + if udp_over_tcp == 1: + self.start_udp_over_tcp() + else: + self.pending_udp_data = None + if udp_over_tcp == 'auto': + self.udpsock_low = 0 + self.dyndecompress = [[None, None, None, None] for i in range(8)] + self.dynnorepeat = None + + def run(self, mode, udp_over_tcp='auto'): + self.setup(mode, udp_over_tcp) + try: + self.mainloop() + finally: + if self.dpy: + self.dpy.close() + try: + self.s.close() + except: + pass + + def mainloop(self): + pss = hostchooser.serverside_ping() + self.initial_iwtd = [self.s, pss] + self.iwtd = self.initial_iwtd[:] + self.animdelay = 0.0 + inbuf = self.process_inbuf(self.initialbuf) + self.initialbuf = "" + errors = 0 + while 1: + if self.dpy: + self.processkeys() + iwtd, owtd, ewtd = select(self.iwtd, [], [], self.animdelay) + self.animdelay = 0.5 + if self.dpy: + self.processkeys() + if self.s in iwtd: + inputdata = self.s.recv(0x6000) + self.tcpbytecounter += len(inputdata) + inbuf += inputdata + inbuf = self.process_inbuf(inbuf) + if self.dpy: + if self.udpsock in iwtd: + udpdata1 = None + while self.udpsock in iwtd: + try: + udpdata = self.udpsock.recv(65535) + except error, e: + print >> sys.stderr, e + errors += 1 + if errors > 10: + raise + break + self.udpbytecounter += len(udpdata) + if len(udpdata) > 3 and '\x80' <= udpdata[0] < '\x90': + udpdata = self.dynamic_decompress(udpdata) + if udpdata is not None: + udpdata1 = udpdata + iwtd, owtd, ewtd = select(self.iwtd, [], [], 0) + if udpdata1 is not None: + self.update_sprites(udpdata1) + if self.udpsock2 in iwtd: + while self.udpsock2 in iwtd: + udpdata = self.udpsock2.recv(65535) + self.udpbytecounter += len(udpdata) + if udpdata == BROADCAST_MESSAGE: + if not self.accepted_broadcast: + self.s.sendall(message(CMSG_UDP_PORT, '*')) + self.accepted_broadcast = 1 + #self.udpsock_low = None + udpdata = '' + iwtd, owtd, ewtd = select(self.iwtd, [], [], 0) + if udpdata and self.accepted_broadcast: + self.update_sprites(udpdata) + if self.pending_udp_data: + self.update_sprites(self.pending_udp_data) + self.pending_udp_data = '' + erasetb = self.taskbarmode and self.draw_taskbar() + d = self.dpy.flip() + if d: + self.animdelay = min(self.animdelay, d) + if self.snd: + d = self.snd.flop() + if d: + self.animdelay = min(self.animdelay, d) + if erasetb: + self.erase_taskbar(erasetb) + if pss in iwtd: + hostchooser.answer_ping(pss, self.gameident, self.sockaddr) + + def process_inbuf(self, inbuf): + while inbuf: + values, inbuf = decodemessage(inbuf) + if not values: + break # incomplete message + fn = Playfield.MESSAGES.get(values[0], self.msg_unknown) + fn(self, *values[1:]) + return inbuf + + def dynamic_decompress(self, udpdata): + # Format of a UDP version 3 packet: + # header byte: 0x80 - 0x87 packet from thread 0 - 7 + # or 0x88 - 0x8F reset packet from thread 0 - 7 + # previous frame in same thread (1 byte) + # frame number (1 byte) + thread = self.dyndecompress[ord(udpdata[0]) & 7] + # thread==[decompress, lastframenumber, recompressed, lastframedata] + prevframe = udpdata[1] + thisframe = udpdata[2] + #print '---' + #for t in self.dyndecompress: + # print repr(t)[:120] + #print + #print `udpdata[:3]` + + if udpdata[0] >= '\x88': + # reset + d = zlib.decompressobj().decompress + if prevframe != thisframe: # if not global sync point + # sync point from a previous frame + # find all threads with the same prevframe + threads = [t for t in self.dyndecompress if prevframe == t[1]] + if not threads: + return None # lost + # find a thread with already-recompressed data + for t in threads: + if t[2]: + data = t[3] + break + else: + # recompress and cache the prevframe data + t = threads[0] + data = t[3] + co = zlib.compressobj(6) + data = co.compress(data) + co.flush(zlib.Z_SYNC_FLUSH) + t[2] = 1 + t[3] = data + d(data) # use it to initialize the state of the decompressobj + #print d + thread[0] = d + elif prevframe != thread[1]: + #print 'lost' + return None # lost + else: + d = thread[0] + # go forward in thread + try: + framedata = d(udpdata[3:]) + #print d + thread[1] = thisframe + thread[2] = 0 + thread[3] = framedata + if thisframe == self.dynnorepeat: + return None + self.dynnorepeat = thisframe + return framedata + except zlib.error: + #print 'crash' + return None + + def geticon(self, icocode): + try: + return self.icons[icocode] + except KeyError: + ico = self.icons[icocode] = Icon(self) + return ico + + def getpixmap(self, bmpcode): + try: + return self.pixmaps[bmpcode] + except KeyError: + data, colorkey = self.bitmaps[bmpcode] + if type(data) is type(''): + data = zlib.decompress(data) + else: + if data.pending: + raise KeyError + data = data.read() + pixmap = loadpixmap(self.dpy, data, colorkey) + self.pixmaps[bmpcode] = pixmap + return pixmap + + def update_sprites(self, udpdata): + sprites = self.sprites + unpack = struct.unpack + + currentsounds = {} + base = 0 + while udpdata[base+4:base+6] == '\xFF\xFF': + key, lvol, rvol = struct.unpack("!hBB", udpdata[base:base+4]) + try: + snd = self.sounds[key] + except KeyError: + pass # ignore sounds with bad code (probably not defined yet) + else: + n = self.playingsounds.get(key) + if n: + currentsounds[key] = n-1 + elif self.snd: + self.snd.play(snd, + lvol / 255.0, + rvol / 255.0) + currentsounds[key] = 4 + base += 6 + self.playingsounds = currentsounds + + for j in range(len(sprites)): + if sprites[j][0] != udpdata[base:base+6]: + removes = sprites[j:] + del sprites[j:] + removes.reverse() + eraser = self.dpy.putppm + for reserved, eraseargs in removes: + eraser(*eraseargs) + break + base += 6 + #print "%d sprites redrawn" % (len(udpdata)/6-j) + try: + overlayer = self.dpy.overlayppm + except AttributeError: + getter = self.dpy.getppm + setter = self.dpy.putppm + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + sprites.append((info, (x, y, getter((x, y) + ico.size)))) + setter(x, y, ico.pixmap, ico.rect) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + else: + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + overlay = overlayer(x, y, ico.pixmap, ico.rect, ico.alpha) + sprites.append((info, overlay)) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + + t0, n = self.painttimes + n = n + 1 + if n == 50: + t = time.time() + t, t0 = t-t0, t + if t: + print "%.2f images per second, %.1f kbytes per second" % ( + float(n)/t, + float(self.tcpbytecounter+self.udpbytecounter)/1024/t) + self.tcpbytecounter = -self.udpbytecounter + n = 0 + self.painttimes = t0, n + + def get_taskbar(self): + y0 = self.height - self.TASKBAR_HEIGHT + iconlist = [] + f = 1.5 * time.time() + f = f-int(f) + pi = self.playericons.items() + pi.sort() + xpos = 0 + for id, ico in pi: + if self.playing.get(id) != 'l': + w, h = ico.size + xpos += int(w * 5 / 3) + if not self.playing.get(id): + y = self.height - h + if self.keydefinition and id == self.keydefinition[0]: + num, icons = self.keys[self.nextkeyname()] + ico = icons[int(f*len(icons))-1] + y = y0 + int((self.TASKBAR_HEIGHT-ico.size[1])/2) + self.animdelay = 0.04 + iconlist.append((xpos-w, y, ico, id)) + pi.reverse() + f = f * (1.0-f) * 4.0 + xpos = self.width + for id, ico in pi: + if self.playing.get(id) == 'l': + w, h = ico.size + xpos -= int(w * 5 / 3) + dy = self.TASKBAR_HEIGHT - h - 1 + y = self.height - h - int(dy*f) + iconlist.append((xpos, y, ico, id)) + self.animdelay = 0.04 + return y0, iconlist + + def clic_taskbar(self, (cx,cy)): + y0, icons = self.get_taskbar() + if cy >= y0: + for x, y, ico, id in icons: + if x <= cx < x+ico.size[0]: + return id + return None + + def draw_taskbar(self): + y0, icons = self.get_taskbar() + rect = (0, y0, self.width, self.TASKBAR_HEIGHT) + bkgnd = self.dpy.getppm(rect) + self.dpy.taskbar(rect) + for x, y, ico, id in icons: + try: + self.dpy.putppm(x, y, ico.pixmap, ico.rect) + except KeyError: + pass + return y0, bkgnd + + def erase_taskbar(self, (y0, bkgnd)): + self.dpy.putppm(0, y0, bkgnd) + + def nextkeyname(self): + pid, df = self.keydefinition + undef = [(num, keyname) for keyname, (num, icons) in self.keys.items() + if not df.has_key(keyname) and icons] + if undef: + num, keyname = min(undef) + return keyname + else: + return None + + def startplaying(self): + args = () + if hasattr(self.s, 'udp_over_udp_mixer'): + # for SocketOverUdp: reuse the UDP address + port = self.s.getsockname()[1] + self.udpsock_low = None + self.s.udp_over_udp_decoder = self.udp_over_udp_decoder + self.start_udp_over_tcp() + elif self.pending_udp_data is not None: + port = MSG_INLINE_FRAME + else: + if '*udpsock*' in PORTS: + self.udpsock, (host, port) = PORTS['*udpsock*'] + args = (host,) + else: + self.udpsock = socket(AF_INET, SOCK_DGRAM) + self.udpsock.bind(('', PORTS.get('CLIENT', INADDR_ANY))) + host, port = self.udpsock.getsockname() + # Send a dummy UDP message to the server. Some NATs will + # then let through the UDP messages from the server. + self.udpsock.sendto('.', self.s.getpeername()) + self.iwtd.append(self.udpsock) + self.initial_iwtd.append(self.udpsock) + if 'sendudpto' in PORTS: + args = (PORTS['sendudpto'],) + outbound = [] + outbound.append(message(CMSG_UDP_PORT, port, *args)) + if self.snd and self.snd.has_music: + outbound.append(message(CMSG_ENABLE_MUSIC, 1)) + outbound.append(message(CMSG_PING)) + self.s.sendall(''.join(outbound)) + + def start_udp_over_tcp(self): + self.pending_udp_data = '' + self.udp_over_tcp_decompress = zlib.decompressobj().decompress + self.udpsock_low = None + for name in ('udpsock', 'udpsock2'): + sock = getattr(self, name) + if sock is not None: + try: + self.iwtd.remove(sock) + except ValueError: + pass + try: + self.initial_iwtd.remove(sock) + except ValueError: + pass + sock.close() + setattr(self, name, None) + + def udp_over_udp_decoder(self, udpdata): + if len(udpdata) > 3 and '\x80' <= udpdata[0] < '\x90': + data = self.dynamic_decompress(udpdata) + if data: + self.pending_udp_data = data + + def processkeys(self): + keyevents = self.dpy.keyevents() + if keyevents: + now = time.time() + pending = {} + for keysym, event in keyevents: + pending[keysym] = event + for keysym, event in pending.items(): + code = self.keycodes.get((keysym, event)) + if code and self.playing.get(code[0]) == 'l': + if (code == self.last_key_event[0] and + now - self.last_key_event[1] < 0.77): + continue # don't send too much events for auto-repeat + self.last_key_event = code, now + self.s.sendall(code[1]) + elif self.keydefinition: + self.define_key(keysym) + pointermotion = self.dpy.pointermotion() + if pointermotion: + x, y = pointermotion + self.settaskbar(y >= self.height - 2*self.TASKBAR_HEIGHT) + mouseevents = self.dpy.mouseevents() + if mouseevents: + self.settaskbar(1) + self.keydefinition = None + for clic in mouseevents: + clic_id = self.clic_taskbar(clic) + if clic_id is not None: + if self.playing.get(clic_id) == 'l': + self.s.sendall(message(CMSG_REMOVE_PLAYER, clic_id)) + else: + self.keydefinition = clic_id, {} + if self.taskbartimeout is not None and time.time() > self.taskbartimeout: + self.settaskbar(0) + + def settaskbar(self, nmode): + self.taskbartimeout = None + if self.taskbarfree: + self.taskbarmode = (nmode or + 'l' not in self.playing.values() or + (self.keydefinition is not None)) + if nmode: + self.taskbartimeout = time.time() + 5.0 + if hasattr(self.dpy, 'settaskbar'): + self.dpy.settaskbar(self.taskbarmode) + + def define_key(self, keysym): + clic_id, df = self.keydefinition + if keysym in df.values(): + return + df[self.nextkeyname()] = keysym + if self.nextkeyname() is not None: + return + self.keydefinition = None + self.s.sendall(message(CMSG_ADD_PLAYER, clic_id)) + for keyname, (num, icons) in self.keys.items(): + if keyname[:1] == '-': + event = KeyReleased + keyname = keyname[1:] + else: + event = KeyPressed + if df.has_key(keyname): + keysym = df[keyname] + self.keycodes[keysym, event] = \ + clic_id, message(CMSG_KEY, clic_id, num) + + def msg_unknown(self, *rest): + print >> sys.stderr, "?" + + def msg_player_join(self, id, local, *rest): + if local: + self.playing[id] = 'l' + self.settaskbar(0) + self.checkcfgfile(1) + else: + self.playing[id] = 1 + + def msg_player_kill(self, id, *rest): + self.playing[id] = 0 + for key, (pid, msg) in self.keycodes.items(): + if pid == id: + del self.keycodes[key] + + def msg_broadcast_port(self, port): + if self.pending_udp_data is not None: + return + if self.udpsock2 is not None: + try: + self.iwtd.remove(self.udpsock2) + except ValueError: + pass + try: + self.initial_iwtd.remove(self.udpsock2) + except ValueError: + pass + self.udpsock2.close() + self.udpsock2 = None + self.accepted_broadcast = 0 + try: + self.udpsock2 = socket(AF_INET, SOCK_DGRAM) + self.udpsock2.bind(('', port)) + self.udpsock2.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + except error, e: + print "Cannot listen on the broadcast port %d" % port, str(e) + self.udpsock2 = None + else: + self.iwtd.append(self.udpsock2) + self.initial_iwtd.append(self.udpsock2) + + def msg_def_playfield(self, width, height, backcolor=None, + gameident=None, *rest): + #if self.snd is not None: + # self.snd.close() + if self.dpy is not None: + # clear all pixmaps + for ico in self.icons.values(): + ico.clear() + self.pixmaps.clear() + self.dpy.close() + del self.sprites[:] + self.width = width + self.height = height + if gameident: + self.gameident = gameident + self.dpy = modes.open_dpy(self.screenmode, width, height, self.gameident) + self.snd = self.snd or modes.open_snd(self.screenmode) + if self.snd: + self.s.sendall(message(CMSG_ENABLE_SOUND)) + self.iwtd = self.dpy.selectlist() + self.initial_iwtd + self.dpy.clear() # backcolor is ignored + self.painttimes = (time.time(), 0) + self.s.sendall(message(CMSG_PING)) + self.taskbarmode = 0 + self.taskbarfree = 0 + self.taskbartimeout = None + self.keydefinition = None + + def msg_def_key(self, name, num, *icons): + self.keys[name] = num, [self.geticon(ico) for ico in icons] + + def msg_def_icon(self, bmpcode, icocode, x, y, w, h, alpha=255, *rest): +## if h<0: +## try: +## bitmap, height = self.flippedbitmaps[bmpcode] +## except KeyError: +## bitmap, height = self.dpy.vflipppm(self.bitmaps[bmpcode]) +## self.flippedbitmaps[bmpcode] = bitmap, height +## y = height - y +## h = - h +## else: + ico = self.geticon(icocode) + ico.bmpcode = bmpcode + ico.rect = x, y, w, h + ico.size = w, h + if alpha < 255: + ico.alpha = alpha + + def msg_def_bitmap(self, bmpcode, data, colorkey=None, *rest): + if type(data) is not type(''): + data = self.fileids[data] + self.bitmaps[bmpcode] = data, colorkey + + def msg_def_sample(self, smpcode, data, *rest): + def ready(f, self=self, smpcode=smpcode): + if self.snd: + self.sounds[smpcode] = self.snd.sound(f) + f.clear() + if type(data) is type(''): + data = zlib.decompress(data) + f = DataChunk(None) + f.store(0, data) + ready(f) + else: + f = self.fileids[data] + f.when_ready(ready) + + def msg_patch_file(self, fileid, position, data, lendata=None, *rest): + if self.fileids.has_key(fileid): + f = self.fileids[fileid] + else: + f = self.fileids[fileid] = DataChunk(fileid) + f.server_patch(position, data, lendata or len(data)) + + def msg_zpatch_file(self, fileid, position, data, *rest): + data1 = zlib.decompress(data) + self.msg_patch_file(fileid, position, data1, len(data), *rest) + + def msg_md5_file(self, fileid, filename, position, length, checksum, *rest): + if self.fileids.has_key(fileid): + f = self.fileids[fileid] + else: + f = self.fileids[fileid] = DataChunk(fileid) + f.server_md5(self, filename, position, length, checksum) + + def msg_play_music(self, loop_from, *codes): + codes = [self.fileids[c] for c in codes] + self.currentmusic = loop_from, codes, list(codes) + self.activate_music() + + def activate_music(self, f=None): + loop_from, codes, checkcodes = self.currentmusic + if checkcodes: + checkcodes.pop().when_ready(self.activate_music) + elif self.snd: + self.snd.play_musics(codes, loop_from) + + def msg_fadeout(self, time, *rest): + if self.snd: + self.snd.fadeout(time) + + def msg_player_icon(self, pid, icocode, *rest): + self.playericons[pid] = self.geticon(icocode) + + def checkcfgfile(self, force=0): + if self.trackcfgfile: + try: + st = os.stat(self.trackcfgfile) + except OSError: + pass + else: + if force or (st.st_mtime != self.trackcfgmtime): + self.trackcfgmtime = st.st_mtime + try: + f = open(self.trackcfgfile, 'r') + data = f.read().strip() + f.close() + d = eval(data or '{}', {}, {}) + except: + print >> sys.stderr, 'Invalid config file format' + else: + d = d.get(gethostname(), {}) + namemsg = '' + for id, local in self.playing.items(): + keyid = 'player%d' % id + if local == 'l' and d.has_key(keyid): + namemsg = namemsg + message( + CMSG_PLAYER_NAME, id, d[keyid]) + if namemsg: + self.s.sendall(namemsg) + + def msg_ping(self, *rest): + self.s.sendall(message(CMSG_PONG, *rest)) + self.checkcfgfile() + if rest and self.udpsock_low is not None: + udpkbytes = rest[0] + if not udpkbytes: + return + #inp = self.udpbytecounter / (udpkbytes*1024.0) + #print "(%d%% packet loss)" % int(100*(1.0-inp)) + if (udpkbytes<<10) * UDP_EXPECTED_RATIO > self.udpbytecounter: + # too many packets were dropped (including, maybe, all of them) + self.udpsock_low += 1 + if self.udpsock_low >= 3 and self.initlevel >= 1: + # third time now -- that's too much + print "Note: routing UDP traffic over TCP", + inp = self.udpbytecounter / (udpkbytes*1024.0) + print "(%d%% packet loss)" % int(100*(1.0-inp)) + self.start_udp_over_tcp() + self.s.sendall(message(CMSG_UDP_PORT, MSG_INLINE_FRAME)) + else: + # enough packets received + self.udpsock_low = 0 + + def msg_pong(self, *rest): + if self.initlevel == 0: + self.startplaying() + self.initlevel = 1 + elif self.initlevel == 1: + if self.snd and self.snd.has_music: + self.s.sendall(message(CMSG_ENABLE_MUSIC, 2)) + self.initlevel = 2 + if not self.taskbarfree and not self.taskbarmode: + self.taskbarfree = 1 + self.settaskbar(1) + + def msg_inline_frame(self, data, *rest): + if self.pending_udp_data is not None: + self.pending_udp_data = self.udp_over_tcp_decompress(data) + + MESSAGES = { + MSG_BROADCAST_PORT:msg_broadcast_port, + MSG_DEF_PLAYFIELD: msg_def_playfield, + MSG_DEF_KEY : msg_def_key, + MSG_DEF_ICON : msg_def_icon, + MSG_DEF_BITMAP : msg_def_bitmap, + MSG_DEF_SAMPLE : msg_def_sample, + MSG_PLAY_MUSIC : msg_play_music, + MSG_FADEOUT : msg_fadeout, + MSG_PLAYER_JOIN : msg_player_join, + MSG_PLAYER_KILL : msg_player_kill, + MSG_PLAYER_ICON : msg_player_icon, + MSG_PING : msg_ping, + MSG_PONG : msg_pong, + MSG_INLINE_FRAME : msg_inline_frame, + MSG_PATCH_FILE : msg_patch_file, + MSG_ZPATCH_FILE : msg_zpatch_file, + MSG_MD5_FILE : msg_md5_file, +## MSG_LOAD_PREFIX : msg_load_prefix, + } + + +def run(s, sockaddr, *args, **kw): + try: + import psyco + except ImportError: + pass + else: + psyco.bind(Playfield.update_sprites) + Playfield(s, sockaddr).run(*args, **kw) diff --git a/display/playback.py b/display/playback.py new file mode 100644 index 0000000..f0e698b --- /dev/null +++ b/display/playback.py @@ -0,0 +1,203 @@ +#! /usr/bin/env python + +import sys, os, gzip +from socket import * +from select import select +import cStringIO, struct, zlib +import time +sys.path.insert(0, os.pardir) +from common.msgstruct import * +from common import hostchooser +import modes +from modes import KeyPressed, KeyReleased + +#import psyco; psyco.full() + +SOURCEDIR = os.pardir + + +def loadpixmap(dpy, data, colorkey=None): + f = cStringIO.StringIO(data) + 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() + f.close() + if colorkey is None: + colorkey = -1 + elif colorkey < 0: + r, g, b = struct.unpack("BBB", self.data[:3]) + colorkey = b | (g<<8) | (r<<16) + return dpy.pixmap(w, h, data, colorkey) + +class Icon: + def __init__(self, bitmap, (x, y, w, h), alpha): + self.rect = x, y, w, h + self.size = w, h + self.bitmap = bitmap + self.alpha = alpha + + +class Playback: + gameident = 'Playback' + + def __init__(self, filename, mode=('x', 'off', {})): + f = gzip.open(filename, 'rb') + self.screenmode = mode + self.width = None + self.deffiles = {} + self.defbitmaps = {} + self.deficons = {} + self.icons = {} + self.frames = [] + inbuf = '' + while 1: + values, inbuf = decodemessage(inbuf) + if not values: + # incomplete message + data = f.read(8192) + if not data: + break + inbuf += data + else: + #print values[0], + fn = Playback.MESSAGES.get(values[0], self.msg_unknown) + fn(self, *values[1:]) + print '%d frames in file.' % len(self.frames) + f.close() + assert self.width, "no playfield definition found in file" + + self.dpy = modes.open_dpy(self.screenmode, + self.width, self.height, self.gameident) + self.dpy.clear() # backcolor is ignored + self.sprites = [] + self.buildicons() + self.go(0) + + def buildicons(self): + bitmaps = {} + for bmpcode, (data, colorkey) in self.defbitmaps.items(): + if isinstance(data, str): + data = zlib.decompress(data) + else: + data = self.deffiles[data] + bitmaps[bmpcode] = loadpixmap(self.dpy, data, colorkey) + for icocode, (bmpcode, rect, alpha) in self.deficons.items(): + self.icons[icocode] = Icon(bitmaps[bmpcode], rect, alpha) + + def go(self, n): + self.n = n + self.update_sprites(self.frames[n]) + self.dpy.flip() + + def save(self, filename=None): + "shm only!" + w, h, data, reserved = self.dpy.getppm((0, 0, self.width, self.height)) + f = open(filename or ('frame%d.ppm' % self.n), 'wb') + print >> f, 'P6' + print >> f, w, h + print >> f, 255 + for i in range(0, len(data), 4): + f.write(data[i+2]+data[i+1]+data[i]) + f.close() + + def update_sprites(self, udpdata): + sprites = self.sprites + unpack = struct.unpack + base = 0 + for j in range(len(sprites)): + if sprites[j][0] != udpdata[base:base+6]: + removes = sprites[j:] + del sprites[j:] + removes.reverse() + eraser = self.dpy.putppm + for reserved, eraseargs in removes: + eraser(*eraseargs) + break + base += 6 + try: + overlayer = self.dpy.overlayppm + except AttributeError: + getter = self.dpy.getppm + setter = self.dpy.putppm + #print "%d sprites redrawn" % (len(udpdata)/6-j) + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + sprites.append((info, (x, y, getter((x, y) + ico.size)))) + setter(x, y, ico.bitmap, ico.rect) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + else: + for j in range(base, len(udpdata)-5, 6): + info = udpdata[j:j+6] + x, y, icocode = unpack("!hhh", info[:6]) + try: + ico = self.icons[icocode] + overlay = overlayer(x, y, ico.bitmap, ico.rect, ico.alpha) + sprites.append((info, overlay)) + except KeyError: + #print "bad ico code", icocode + pass # ignore sprites with bad ico (probably not defined yet) + + def msg_unknown(self, *rest): + pass + + def msg_patch_file(self, fileid, position, data, lendata=None, *rest): + try: + s = self.deffiles[fileid] + except KeyError: + s = '' + if len(s) < position: + s += '\x00' * (position-len(s)) + s = s[:position] + data + s[position+len(s):] + self.deffiles[fileid] = s + + def msg_zpatch_file(self, fileid, position, data, *rest): + data1 = zlib.decompress(data) + self.msg_patch_file(fileid, position, data1, len(data), *rest) + + def msg_md5_file(self, fileid, filename, position, length, checksum, *rest): + fn = os.path.join(SOURCEDIR, filename) + f = open(fn, 'rb') + f.seek(position) + data = f.read(length) + f.close() + assert len(data) == length + self.msg_patch_file(fileid, position, data) + + def msg_def_playfield(self, width, height, *rest): + self.width, self.height = width, height + + def msg_def_icon(self, bmpcode, icocode, x, y, w, h, alpha=255, *rest): + self.deficons[icocode] = bmpcode, (x, y, w, h), alpha + + def msg_def_bitmap(self, bmpcode, data, colorkey=None, *rest): + self.defbitmaps[bmpcode] = data, colorkey + + def msg_recorded(self, data): + self.frames.append(data) + + MESSAGES = { + MSG_PATCH_FILE : msg_patch_file, + MSG_ZPATCH_FILE : msg_zpatch_file, + MSG_MD5_FILE : msg_md5_file, + MSG_DEF_PLAYFIELD: msg_def_playfield, + MSG_DEF_ICON : msg_def_icon, + MSG_DEF_BITMAP : msg_def_bitmap, + MSG_RECORDED : msg_recorded, + } + + +if __name__ == '__main__' and len(sys.argv) > 1: + p = Playback(sys.argv[1]) diff --git a/display/puremixer.py b/display/puremixer.py new file mode 100644 index 0000000..d0b567a --- /dev/null +++ b/display/puremixer.py @@ -0,0 +1,123 @@ +import sys, audioop + + +class PureMixer: + # + # An audio mixer in Python based on audioop + # + # Note that opening the audio device itself is outside the scope of + # this module. Anything else could also be done with the mixed data, + # e.g. stored on disk, for all this module knows. + + def __init__(self, freq=44100, bits=8, signed=0, + channels=1, byteorder=None): + """Open the mixer and set its parameters.""" + self.freq = freq + self.bytes = bits/8 + self.signed = signed + self.channels = channels + self.byteorder = byteorder or sys.byteorder + self.parameters = (freq, self.bytes, signed, channels, self.byteorder) + self.bytespersample = channels*self.bytes + self.queue = '\x00' * self.bytes + + def resample(self, data, freq=44100, bits=8, signed=0, + channels=1, byteorder=None): + "Convert a sample to the mixer's own format." + bytes = bits/8 + byteorder = byteorder or sys.byteorder + if (freq, bytes, signed, channels, byteorder) == self.parameters: + return data + # convert to native endianness + if byteorder != sys.byteorder: + data = byteswap(data, bytes) + byteorder = sys.byteorder + # convert unsigned -> signed for the next operations + if not signed: + data = audioop.bias(data, bytes, -(1<<(bytes*8-1))) + signed = 1 + # convert stereo -> mono + while channels > self.channels: + assert channels % 2 == 0 + data = audioop.tomono(data, bytes, 0.5, 0.5) + channels /= 2 + # resample to self.freq + if freq != self.freq: + data, ignored = audioop.ratecv(data, bytes, channels, + freq, self.freq, None) + freq = self.freq + # convert between 8bits and 16bits + if bytes != self.bytes: + data = audioop.lin2lin(data, bytes, self.bytes) + bytes = self.bytes + # convert mono -> stereo + while channels < self.channels: + data = audioop.tostereo(data, bytes, 1.0, 1.0) + channels *= 2 + # convert signed -> unsigned + if not self.signed: + data = audioop.bias(data, bytes, 1<<(bytes*8-1)) + signed = 0 + # convert to mixer endianness + if byteorder != self.byteorder: + data = byteswap(data, bytes) + byteorder = self.byteorder + # done + if (freq, bytes, signed, channels, byteorder) != self.parameters: + raise ValueError, 'sound sample conversion failed' + return data + + def wavesample(self, file): + "Read a sample from a .wav file (or file-like object)." + import wave + w = wave.open(file, 'r') + return self.resample(w.readframes(w.getnframes()), + freq = w.getframerate(), + bits = w.getsampwidth() * 8, + signed = w.getsampwidth() > 1, + channels = w.getnchannels(), + byteorder = 'little') + + def mix(self, mixer_channels, bufsize): + """Mix the next batch buffer. + Each object in the mixer_channels list must be a file-like object + with a 'read(size)' method.""" + data = '' + already_seen = {} + channels = mixer_channels[:] + channels.reverse() + for c in channels: + if already_seen.has_key(c): + data1 = '' + else: + data1 = c.read(bufsize) + already_seen[c] = 1 + if data1: + l = min(len(data), len(data1)) + data = (audioop.add(data[:l], data1[:l], 1) + + (data1[l:] or data[l:])) + else: + try: + mixer_channels.remove(c) + except ValueError: + pass + data += self.queue * ((bufsize - len(data)) / self.bytes) + self.queue = data[-self.bytes:] + return data + + +def byteswap(data, byte): + if byte == 1: + return + if byte == 2: + typecode = 'h' + elif byte == 4: + typecode = 'i' + else: + raise ValueError, 'cannot convert endianness for samples of %d bytes' % byte + import array + a = array.array(typecode, data) + if a.itemsize != byte: + raise ValueError, 'endianness convertion failed' + a.byteswap() + return a.tostring() diff --git a/display/pythonxlibintf.py b/display/pythonxlibintf.py new file mode 100644 index 0000000..5e32715 --- /dev/null +++ b/display/pythonxlibintf.py @@ -0,0 +1,202 @@ + + ################################################ +## pygame-based implementation of xshm ## +################################################ + +import os, sys +from Xlib import X, display + + +# -*-*- SLOOWWW -*-*- +import psyco; psyco.full() + + +class Display: + + def __init__(self, width, height, title): + self.dpy = display.Display() + self.default_scr = self.dpy.screen() + self.root = self.default_scr.root + self.width = width + self.height = height + self.depth = self.default_scr.root_depth + + self.backpixmap = self.root.create_pixmap(width, height, self.depth) + self.win = self.root.create_window( + 0, 0, width, height, 0, self.depth, + override_redirec = 0, + background_pixel = self.default_scr.black_pixel, + backing_store = X.NotUseful, + ) + self.win.map() + + self.gc = self.win.create_gc() + self.gc_and = self.win.create_gc() + self.gc_or = self.win.create_gc() + + self.gc.change(foreground = self.default_scr.black_pixel) + self.gc_and.change(function = X.GXand) + self.gc_or .change(function = X.GXor) + + self.selectinput = 0 + self.keyev = [] + self.mouseev = [] + self.motionev = None + self.dpy.flush() + + pixel = "\x00\x00\x80" + hole = "\x01\x01\x01" + self.taskbkgnd = self.pixmap(32, 32, + ((pixel+hole)*16 + (hole+pixel)*16) * 16, + 0x010101) + + def pixmap(self, w, h, data, colorkey=-1): + print >> sys.stderr, '.', + extent = w*h + depth = self.depth + if depth >= 24: + bitmap_pad = 32 + else: + bitmap_pad = 16 + scanline = ((w+bitmap_pad-1) & ~(bitmap_pad-1)) / 8; + if colorkey >= 0: + key = (chr(colorkey >> 16) + + chr((colorkey>>8) & 0xFF) + + chr(colorkey & 0xFF)) + else: + key = None + if depth == 15: + p_size = 5, 5, 5 + elif depth == 16: + p_size = 5, 6, 5 + elif depth == 24 or depth == 32: + p_size = 8, 8, 8 + else: + raise ValueError, 'unsupported screen depth %d' % depth + + imgdata = [] + maskdata = [] + + for color in range(3): + plane = 128 + while plane >= (1<<(8-p_size[color])): + src = 0 + for y in range(h): + imgline = 0L + maskline = 0L + shifter = 1L + for x in range(w): + if data[src:src+3] == key: + # transparent + maskline |= shifter + elif ord(data[src+color]) & plane: + imgline |= shifter + shifter <<= 1 + src += 3 + imgdata.append(long2string(imgline, scanline)) + maskdata.append(long2string(maskline, scanline)) + plane /= 2 + + imgdata = ''.join(imgdata) + if colorkey >= 0: + maskdata = ''.join(maskdata) + mask = self.win.create_pixmap(w, h, depth) + mask.put_image(self.gc, 0, 0, w, h, X.XYPixmap, depth, 0, maskdata) + else: + mask = None + imgdata = ''.join(imgdata) + image = self.win.create_pixmap(w, h, depth) + image.put_image(self.gc, 0, 0, w, h, X.XYPixmap, depth, 0, imgdata) + image.mask = mask + image.size = w, h + return image + + def getppm(self, (x, y, w, h), bkgnd=None): + if bkgnd is None: + bkgnd = self.win.create_pixmap(w, h, self.depth) + bkgnd.mask = None + bkgnd.size = w, h + bkgnd.copy_area(self.gc, self.backpixmap, x, y, w, h, 0, 0) + return bkgnd + + def putppm(self, x, y, image, rect=None): + if rect: + x1, y1, w1, h1 = rect + else: + x1 = y1 = 0 + w1, h1 = image.size + if image.mask is None: + self.backpixmap.copy_area(self.gc, image, x1, y1, w1, h1, x, y) + else: + self.backpixmap.copy_area(self.gc_and, image.mask, + x1, y1, w1, h1, x, y) + self.backpixmap.copy_area(self.gc_or, image, + x1, y1, w1, h1, x, y) + + def flip(self): + self.win.copy_area(self.gc, self.backpixmap, + 0, 0, self.width, self.height, 0, 0) + self.dpy.flush() + self.readXevents() + + def close(self): + self.dpy.close() + + def clear(self): + self.backpixmap.fill_rectangle(self.gc, 0, 0, self.width, self.height) + + def readXevents(self): + n = self.dpy.pending_events() + if n: + for i in range(n): + event = self.dpy.next_event() + if event.type == X.KeyPress or event.type == X.KeyRelease: + self.keyev.append((event.detail, event.type)) + elif event.type == X.ButtonPress: + self.mouseev.append((event.event_x, event.event_y)) + elif event.type == X.MotionNotify: + self.motionev = event.event_x, event.event_y + elif event.type == X.DestroyNotify: + raise SystemExit + self.readXevents() + + def enable_event(self, mask): + self.selectinput |= mask + self.win.change_attributes(event_mask=self.selectinput) + + def keyevents(self): + if not (self.selectinput & X.KeyReleaseMask): + self.enable_event(X.KeyPressMask | X.KeyReleaseMask) + self.readXevents() + result = self.keyev + self.keyev = [] + return result + + def mouseevents(self): + if not (self.selectinput & X.ButtonPressMask): + self.enable_event(X.ButtonPressMask) + result = self.mouseev + self.mouseev = [] + return result + + def pointermotion(self): + result = self.motionev + self.motionev = None + return result + + def has_sound(self): + return 0 + + def selectlist(self): + from socket import fromfd, AF_INET, SOCK_STREAM + return [fromfd(self.dpy.fileno(), AF_INET, SOCK_STREAM)] + + def taskbar(self, (x, y, w, h)): + for j in range(y, y+h, 32): + for i in range(x, x+w, 32): + self.putppm(i, j, self.taskbkgnd, + (0, 0, x+w-i, y+h-j)) + + +def long2string(bits, strlen): + return ''.join([chr((bits>>n)&0xFF) for n in range(0, 8*strlen, 8)]) diff --git a/display/setup.py b/display/setup.py new file mode 100755 index 0000000..4776e79 --- /dev/null +++ b/display/setup.py @@ -0,0 +1,16 @@ +#! /usr/bin/env python + +from distutils.core import setup +from distutils.extension import Extension + +setup ( name="xshm", + version="0.2", + description="X window system Shared Memory extension", + author="Armin & Odie", + author_email="arigo@tunes.org", + ext_modules=[Extension(name = 'xshm', + sources = ['xshm.c'], + include_dirs = ['/usr/X11R6/include'], + library_dirs = ['/usr/X11R6/lib'], + libraries = ['X11', 'Xext'])] + ) diff --git a/display/snd_linux.py b/display/snd_linux.py new file mode 100644 index 0000000..dae82e0 --- /dev/null +++ b/display/snd_linux.py @@ -0,0 +1,148 @@ +import sys +from cStringIO import StringIO +import puremixer +from music1 import Music + + +class Sound: + # Mono only + has_sound = has_music = 0 # until initialized + + BUFFERTIME = 0.09 + FLOPTIME = 0.07 + + Formats = [ + ('U8', 8, 0, None), + ('S8', 8, 1, None), + ('S16_NE', 16, 1, None), + ('S16_LE', 16, 1, 'little'), + ('S16_BE', 16, 1, 'big'), + ('U16_LE', 16, 0, 'little'), + ('U16_BE', 16, 0, 'big'), + ] + + def __init__(self, freq=44100, fmt='S16_NE'): + self.f = None + self.freq = int(freq) + self.format = fmt.upper() + self.params = p = {} + for name, p['bits'], p['signed'], p['byteorder'] in self.Formats: + if name == self.format: + break + else: + print >> sys.stderr, 'available sound formats:' + for name, bits, signed, byteorder in self.Formats: + print >> sys.stderr, ' %-8s %s' % (name, nicefmttext( + bits, signed, byteorder)) + sys.exit(2) + + import linuxaudiodev + try: + f = linuxaudiodev.open('w') + f.setparameters(self.freq, p['bits'], 1, + getattr(linuxaudiodev, 'AFMT_' + self.format)) + except Exception, e: + print >> sys.stderr, "sound disabled: %s: %s" % ( + e.__class__.__name__, e) + return + self.f = f + self.mixer = mixer = puremixer.PureMixer(**p) + buffertime = self.BUFFERTIME + self.bufsize = int(mixer.bytespersample*mixer.freq*buffertime + + 255.5) & ~ 255 + if self.bufsize > f.bufsize(): + self.bufsize = f.bufsize() + buffertime = self.bufsize / float(freq) + self.buffertime = buffertime + self.mixer_channels = [] + self.mixer_accum = {} + self.has_sound = 1 + self.has_music = 1 + + def close(self): + self.f.close() + self.f = None + + def sound(self, f): + return self.mixer.wavesample(f.fopen()) + + def flop(self): + self.mixer_accum = {} + if self.f is None: + return + for i in range(3): + bufsize = self.bufsize - self.f.obufcount() + if bufsize <= 0: + break + self.f.write(self.mixer.mix(self.mixer_channels, bufsize)) + #cnt = getattr(self, 'CNT', 0) + #import time + #print cnt, time.time() + #self.CNT = cnt+1 + return self.FLOPTIME + + def play(self, sound, lvolume, rvolume): + # volume ignored + if sound not in self.mixer_accum: + self.mixer_channels.append(StringIO(sound)) + self.mixer_accum[sound] = 1 + + def play_musics(self, musics, loop_from): + self.cmusics = musics, loop_from, -1 + if self.mixer_channels[:1] != [self]: + self.mixer_channels.insert(0, self) + + def read(self, size): + "Provide some more data to self.mixer.poll()." + musics, loop_from, c = self.cmusics + if c < 0: + data = '' + else: + data = musics[c].mixed.decode(self.mixer, size) + if not data: + c += 1 + if c >= len(musics): # end + c = loop_from + if c >= len(musics): + return '' + self.cmusics = musics, loop_from, c + try: + mixed = musics[c].mixed + except AttributeError: + mixed = musics[c].mixed = Music(musics[c].freezefilename()) + mixed.openchannel() + data = mixed.decode(self.mixer, size) + if 0 < len(data) < size: + data += self.read(size - len(data)) + return data + + def fadeout(self, millisec): + self.cmusics = [], 0, -1 + + +def imperror(): + try: + import linuxaudiodev + except ImportError: + if sys.platform.startswith('linux'): + return 'linuxaudiodev module not installed' + else: + return 'only available on Linux' + +def nicefmttext(bits, signed, byteorder): + s = '%s %d bits' % (signed and 'signed' or 'unsigned', bits) + if byteorder: + s += ' %s endian' % byteorder + return s + +def htmloptionstext(nameval): + import modes + l = ['Sampling <%s>' % nameval('select', 'fmt')] + for name, bits, signed, byteorder in Sound.Formats: + l.append('<'+nameval('option', 'fmt', name, default='S16_NE')+'>'+ + nicefmttext(bits, signed, byteorder)) + l+= [' rate ', + '<%s size=5>Hz' % nameval('text', 'freq', default='44100'), + '
', + modes.musichtmloptiontext(nameval)] + return '\n'.join(l) diff --git a/display/snd_off.py b/display/snd_off.py new file mode 100644 index 0000000..9d2e640 --- /dev/null +++ b/display/snd_off.py @@ -0,0 +1,5 @@ +class Sound: + has_sound = 0 # no sound + has_music = 0 # no music + def __init__(self): + pass diff --git a/display/snd_pygame.py b/display/snd_pygame.py new file mode 100644 index 0000000..e1a1455 --- /dev/null +++ b/display/snd_pygame.py @@ -0,0 +1,82 @@ +import sys +from modes import musichtmloptiontext as htmloptionstext +from pygame.locals import * +import pygame.mixer + +if pygame.mixer is None: + raise ImportError + + +#ENDMUSICEVENT = USEREVENT + + +class Sound: + has_sound = has_music = 0 # until initialized + + def __init__(self): + try: + pygame.mixer.init() + except pygame.error, e: + print >> sys.stderr, "sound disabled: %s" % str(e) + else: + self.has_sound = 1 + try: + from pygame.mixer import music + except ImportError: + pass + else: + self.has_music = music is not None + self.cmusics = None + + def close(self): + try: + pygame.mixer.stop() + except pygame.error: + pass + if self.has_music: + try: + pygame.mixer.music.stop() + except pygame.error: + pass + + def sound(self, f): + return pygame.mixer.Sound(f.freezefilename()) + + def flop(self): + # the events are not processed if pygame is not also the display, + # so ENDMUSICEVENT will not arrive -- poll for end of music + if self.cmusics and not pygame.mixer.music.get_busy(): + self.next_music() + + def play(self, sound, lvolume, rvolume): + channel = pygame.mixer.find_channel(1) + channel.stop() + try: + channel.set_volume(lvolume, rvolume) + except TypeError: + channel.set_volume(0.5 * (lvolume+rvolume)) + channel.play(sound) + + def play_musics(self, musics, loop_from): + #dpy_pygame.EVENT_HANDLERS[ENDMUSICEVENT] = self.next_music + #pygame.mixer.music.set_endevent(ENDMUSICEVENT) + self.cmusics = musics, loop_from, 0 + self.next_music() + + def next_music(self, e=None): + if self.cmusics: + musics, loop_from, c = self.cmusics + if c >= len(musics): # end + c = loop_from + if c >= len(musics): + pygame.mixer.music.stop() + self.cmusics = None + return + pygame.mixer.music.load(musics[c].freezefilename()) + pygame.mixer.music.play() + self.cmusics = musics, loop_from, c+1 + + def fadeout(self, millisec): + #print "fadeout:", millisec + pygame.mixer.music.fadeout(millisec) + self.cmusics = None diff --git a/display/snd_windows.py b/display/snd_windows.py new file mode 100644 index 0000000..3aea3bd --- /dev/null +++ b/display/snd_windows.py @@ -0,0 +1,93 @@ +import sys +from cStringIO import StringIO +import puremixer +import wingame +from music1 import Music + + +class Sound: + # Mono only + has_sound = has_music = 0 # until initialized + + BUFFERTIME = 0.09 + FLOPTIME = 0.07 + + def __init__(self, freq=44100, bits=16): + self.freq = int(freq) + self.bits = int(bits) + self.bufsize = (int(self.BUFFERTIME*self.freq*self.bits/8) + 64) & ~63 + + try: + self.audio = wingame.Audio(1, self.freq, self.bits, self.bufsize) + except Exception, e: + print >> sys.stderr, "sound disabled: %s: %s" % ( + e.__class__.__name__, e) + return + self.mixer = puremixer.PureMixer(self.freq, self.bits, self.bits==16, + byteorder='little') + self.mixer_channels = [] + self.mixer_accum = {} + self.has_sound = 1 + self.has_music = 1 + + def stop(self): + self.audio.close() + + def sound(self, f): + return self.mixer.wavesample(f.fopen()) + + def flop(self): + self.mixer_accum = {} + while self.audio.ready(): + self.audio.write(self.mixer.mix(self.mixer_channels, self.bufsize)) + return self.FLOPTIME + + def play(self, sound, lvolume, rvolume): + # volume ignored + if sound not in self.mixer_accum: + self.mixer_channels.append(StringIO(sound)) + self.mixer_accum[sound] = 1 + + def play_musics(self, musics, loop_from): + self.cmusics = musics, loop_from, -1 + self.mixer_channels.insert(0, self) + + def read(self, size): + "Provide some more data to self.mixer.poll()." + musics, loop_from, c = self.cmusics + if c < 0: + data = '' + else: + data = musics[c].mixed.decode(self.mixer, size) + if not data: + c += 1 + if c >= len(musics): # end + c = loop_from + if c >= len(musics): + return '' + self.cmusics = musics, loop_from, c + try: + mixed = musics[c].mixed + except AttributeError: + mixed = musics[c].mixed = Music(musics[c].freezefilename()) + mixed.openchannel() + data = mixed.decode(self.mixer, size) + if 0 < len(data) < size: + data += self.read(size - len(data)) + return data + + def fadeout(self, millisec): + self.cmusics = [], 0, -1 + + +def htmloptionstext(nameval): + import modes + l = ['Sampling <%s>' % nameval('select', 'bits')] + for bits in (8, 16): + l.append('<'+nameval('option', 'bits', str(bits), default='16')+'>'+ + '%d bits' % bits) + l+= [' rate ', + '<%s size=5>Hz' % nameval('text', 'freq', default='44100'), + '
', + modes.musichtmloptiontext(nameval)] + return '\n'.join(l) diff --git a/display/windows/.cvsignore b/display/windows/.cvsignore new file mode 100644 index 0000000..0e01180 --- /dev/null +++ b/display/windows/.cvsignore @@ -0,0 +1,3 @@ +*.ncb +*.opt +*.plg diff --git a/display/windows/wingame.c b/display/windows/wingame.c new file mode 100755 index 0000000..644e496 --- /dev/null +++ b/display/windows/wingame.c @@ -0,0 +1,867 @@ +#include +#include +#include + + + /************************** DISPLAY PART ***************************/ + +typedef struct { + BITMAPINFOHEADER bmiHeader; + union { + DWORD bmiMask[3]; + short bmiIndices[255]; + }; +} screenbmp_t; + +typedef struct { + PyObject_HEAD + HWND win; + int width, height, bpp; + HDC dc; + HPALETTE hpal, hprevpal; + screenbmp_t screenbmpinfo; + unsigned char* screenbmpdata; + unsigned char* screenfirstline; + int screenscanline; /* < 0 ! */ + PyObject* keyevents; + PyObject* mouseevents; + PyObject* motionevent; +} DisplayObject; + +#define DisplayObject_Check(v) ((v)->ob_type == &Display_Type) +staticforward PyTypeObject Display_Type; + + +static void flush(DisplayObject* self) +{ + /*GdiFlush();*/ +} + +static void release_window_data(DisplayObject* self) +{ + if (self->hprevpal) + { + SelectPalette(self->dc, self->hprevpal, FALSE); + self->hprevpal = (HPALETTE) NULL; + } + if (self->hpal) + { + DeleteObject(self->hpal); + self->hpal = (HPALETTE) NULL; + } + if (self->dc && self->win) + { + ReleaseDC(self->win, self->dc); + self->dc = (HDC) NULL; + } +} + +static void display_close(DisplayObject* self) +{ + release_window_data(self); + if (self->win) + { + SetWindowLong(self->win, 0, 0); + DestroyWindow(self->win); + self->win = (HWND) NULL; + } +} + +static LRESULT CALLBACK display_proc(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) +{ + DisplayObject* self; + switch (uMsg) { + + case WM_KEYDOWN: + case WM_KEYUP: + self = (DisplayObject*) GetWindowLong(hwnd, 0); + if (self) + { + PyObject* v; + int etype; + if (self->keyevents == NULL) + { + self->keyevents = PyList_New(0); + if (self->keyevents == NULL) + break; + } + etype = (uMsg == WM_KEYDOWN) ? 2 : 3; + v = Py_BuildValue("ii", (int) wParam, etype); + if (v == NULL) + break; + PyList_Append(self->keyevents, v); + Py_DECREF(v); + } + break; + + case WM_LBUTTONDOWN: + self = (DisplayObject*) GetWindowLong(hwnd, 0); + if (self) + { + PyObject* v; + if (self->mouseevents == NULL) + { + self->mouseevents = PyList_New(0); + if (self->mouseevents == NULL) + break; + } + v = Py_BuildValue("ii", LOWORD(lParam), HIWORD(lParam)); + if (v == NULL) + break; + PyList_Append(self->mouseevents, v); + Py_DECREF(v); + } + break; + + case WM_MOUSEMOVE: + self = (DisplayObject*) GetWindowLong(hwnd, 0); + if (self) + { + Py_XDECREF(self->motionevent); + self->motionevent = Py_BuildValue("ii", LOWORD(lParam), HIWORD(lParam)); + } + break; + + case WM_DESTROY: + self = (DisplayObject*) GetWindowLong(hwnd, 0); + if (self) + { + release_window_data(self); + self->win = (HWND) NULL; + } + break; + + default: + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + + return 0; +} + +static PyObject* new_display(PyObject* dummy, PyObject* args) +{ + char* CLASSNAME = "winxshm"; + WNDCLASS wcls; + DisplayObject* self; + int width, height, bytes, bpp, use_shm=0; + if (!PyArg_ParseTuple(args, "ii|i", &width, &height, &use_shm)) + return NULL; + + self = PyObject_New(DisplayObject, &Display_Type); + if (self == NULL) + return NULL; + + memset(&self->win, 0, ((char*)(self+1)) - ((char*)(&self->win))); + self->width = width; + self->height = height; + + /* set window class */ + memset(&wcls, 0, sizeof(wcls)); + wcls.style = CS_BYTEALIGNCLIENT; + wcls.lpfnWndProc = &display_proc; + wcls.cbWndExtra = sizeof(DisplayObject*); + //wcls.hInstance = HINSTANCE; + wcls.hCursor = LoadCursor(0, IDC_ARROW); + wcls.lpszClassName = CLASSNAME; + RegisterClass(&wcls); + + /* Create the window */ + self->win = CreateWindowEx(0, CLASSNAME, NULL, + WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | + WS_MINIMIZEBOX | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + width + 2*GetSystemMetrics(SM_CXFIXEDFRAME), + height + 2*GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION), + (HWND) NULL, (HMENU) NULL, + /*HINSTANCE*/ 0, (LPVOID) NULL); + if (self->win == (HWND) NULL) goto err2; + SetWindowLong(self->win, 0, (long) self); + + /* Create DC */ + self->dc = GetDC(self->win); + if (!self->dc) goto err2; + self->bpp = bpp = GetDeviceCaps(self->dc, BITSPIXEL); + if (bpp == 8) + { + struct { + WORD palVersion; + WORD palNumEntries; + PALETTEENTRY palPalEntry[255]; + } pal; + pal.palNumEntries = GetSystemPaletteEntries(self->dc, 0, 255, pal.palPalEntry); + if (pal.palNumEntries != 0) + { + int i; + pal.palVersion = 0x300; + self->hpal = CreatePalette((LOGPALETTE*)(&pal)); + self->screenbmpinfo.bmiHeader.biClrUsed = pal.palNumEntries; + self->hprevpal = SelectPalette(self->dc, self->hpal, FALSE); + for (i=0; iscreenbmpinfo.bmiIndices[i] = i; + } + } + } + if (bpp != 15 && bpp != 16 && bpp != 24 && bpp != 32 && !self->hpal) + { + bpp = 24; /* default */ + fprintf(stderr, "WARNING: a hi/true color screen mode of 15, 16, 24 or 32 bits per pixels\n"); + fprintf(stderr, " is highly recommended !\n"); + } + + /* Allocate screen bitmaps */ + bytes = (bpp+7)/8; /* per pixel */ + bytes = (bytes*width+3)&~3; /* per scan line */ + self->screenscanline = -bytes; + bytes = bytes*height; /* for the whole screen */ + self->screenbmpdata = PyMem_Malloc(bytes); + self->screenfirstline = self->screenbmpdata + bytes + self->screenscanline; + if (self->screenbmpdata == NULL) + { + PyErr_NoMemory(); + goto err2; + } + self->screenbmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + self->screenbmpinfo.bmiHeader.biWidth = self->width; + self->screenbmpinfo.bmiHeader.biHeight = self->height; + self->screenbmpinfo.bmiHeader.biPlanes = 1; + self->screenbmpinfo.bmiHeader.biBitCount = (bpp+7)&~7; + if (bpp == 16) + { + self->screenbmpinfo.bmiHeader.biCompression = BI_BITFIELDS; + self->screenbmpinfo.bmiMask[0] = 0xF800; + self->screenbmpinfo.bmiMask[1] = 0x07E0; + self->screenbmpinfo.bmiMask[2] = 0x001F; + } + + flush(self); + return (PyObject*) self; + + err2: + display_close(self); + Py_DECREF(self); + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_IOError, "cannot open window"); + return NULL; +} + +static void display_dealloc(DisplayObject* self) +{ + display_close(self); + Py_XDECREF(self->keyevents); + Py_XDECREF(self->mouseevents); + Py_XDECREF(self->motionevent); + PyMem_Free(self->screenbmpdata); + PyObject_Del(self); +} + +static PyObject* display_close1(DisplayObject* self, PyObject* args) +{ + display_close(self); + Py_INCREF(Py_None); + return Py_None; +} + +static int checkopen(DisplayObject* self) +{ + if (self->win) + return 1; + //PyErr_SetString(PyExc_IOError, "the window was closed"); + PyErr_SetString(PyExc_SystemExit, "window closed."); + return 0; +} + +static PyObject* display_clear1(DisplayObject* self, PyObject* args) +{ + if (!checkopen(self)) + return NULL; + memset(self->screenbmpdata, 0, (-self->screenscanline) * self->height); + Py_INCREF(Py_None); + return Py_None; +} + +static void pack_pixel(DisplayObject* self, unsigned char *data, int r, int g, int b, + int depth) +{ + unsigned short pixel; + switch( depth ) + { + case 8: + data[0] = GetNearestPaletteIndex(self->hpal, (b<<16) | (g<<8) | r); + break; + case 15: + pixel = ((r<<7) & 0x7c00) | ((g<<2) & 0x03e0) | ((b>>3) & 0x001f); + data[0] = (pixel) & 0xff; + data[1] = (pixel>>8) & 0xff; + break; + case 16: + /* assumes 5,6,5 model. */ + pixel = ((r<<8) & 0xf800) | ((g<<3) & 0x07e0) | ((b>>3) & 0x001f); + data[0] = (pixel) & 0xff; + data[1] = (pixel>>8) & 0xff; + break; + case 24: + if( 1 ) + { + data[0] = b; + data[1] = g; + data[2] = r; + break; + } + case 32: + *((long *)data) = (r<<16) | (g<<8) | b; + break; + } +} + +static PyObject* display_pixmap1(DisplayObject* self, PyObject* args) +{ + int w,h; + int length; + unsigned char* input = NULL; + long keycol = -1; + + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "ii|s#l", &w, &h, &input, &length, &keycol)) + return NULL; + + if (1) /* SHM */ + { + int x, y; + unsigned char *dst; + int size, bytes_per_pixel; + long packed_keycol = keycol; + PyObject* result; + PyObject* str; + + if (input == NULL ) + { + Py_INCREF(Py_None); + return Py_None; + } + + bytes_per_pixel = self->screenbmpinfo.bmiHeader.biBitCount/8; + size = bytes_per_pixel*w*h; + + if( 3*w*h != length ) + { + PyErr_SetString(PyExc_TypeError, "bad string length"); + return NULL; + } + /* Create a new string and fill it with the correctly packed image */ + str = PyString_FromStringAndSize(NULL, size); + if (!str) + return NULL; + if (keycol >= 0) + switch( self->bpp ) + { + case 8: + packed_keycol = 0xFF; + break; + case 15: + packed_keycol = (1 << 10) | (1 << 5) | 1; + break; + case 16: + packed_keycol = (1 << 11) | (1 << 5) | 1; + break; + default: + packed_keycol = keycol; + break; + } + result = Py_BuildValue("iiOl", w, h, str, packed_keycol); + Py_DECREF(str); /* one ref left in 'result' */ + if (!result) + return NULL; + dst = (unsigned char*) PyString_AS_STRING(str); + memset(dst,0,size); + + for( y=0; ybpp); + } + return result; + } +} + +static PyObject* display_putppm1(DisplayObject* self, PyObject* args) +{ + if (!checkopen(self)) + return NULL; + + if (1) /* SHM */ + { + int x,y,w,h,scanline; + int clipx=0, clipy=0, clipw=65536, cliph=65536; + unsigned char* src; + int length; + long keycol; + int bytes_per_pixel = self->screenbmpinfo.bmiHeader.biBitCount/8; + unsigned char* data = self->screenfirstline; + if (!PyArg_ParseTuple(args, "ii(iis#l)|(iiii)", + &x, &y, &w, &h, &src, &length, &keycol, + &clipx, &clipy, &clipw, &cliph) || !data) + return NULL; + + scanline = bytes_per_pixel*w; + if (scanline*h != length) + { + PyErr_SetString(PyExc_TypeError, "bad string length"); + return NULL; + } + x -= clipx; + y -= clipy; + clipx += x; + clipy += y; + clipw += clipx; + cliph += clipy; + if (clipx<0) clipx=0; + if (clipy<0) clipy=0; + if (clipw>self->width) clipw=self->width; + if (cliph>self->height) cliph=self->height; + if (x clipw) w = clipw-x; + if (y+h > cliph) h = cliph-y; + data += bytes_per_pixel*x+y*self->screenscanline; + while (h>0) + { + int i; + int b; + unsigned char* src0 = src; + unsigned char* data0 = data; + if (keycol < 0) + for (i=0; iscreenscanline; + h--; + } + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* display_getppm1(DisplayObject* self, PyObject* args) +{ + if (!checkopen(self)) + return NULL; + + if (1) /* SHM */ + { + int x,y,w,h,scanline; + int clipx=0, clipy=0, clipw=self->width, cliph=self->height; + unsigned char* dst; + int length; + PyObject* ignored; + PyObject* result; + PyObject* str; + int bytes_per_pixel = self->screenbmpinfo.bmiHeader.biBitCount/8; + unsigned char* data = self->screenfirstline; + if (!PyArg_ParseTuple(args, "(iiii)|O", &x, &y, &w, &h, + &ignored) || !data) + return NULL; + + scanline = bytes_per_pixel*w; + length = scanline*h; + str = PyString_FromStringAndSize(NULL, length); + if (!str) + return NULL; + result = Py_BuildValue("iiOl", w, h, str, -1); + Py_DECREF(str); /* one ref left in 'result' */ + if (!result) + return NULL; + dst = (unsigned char*) PyString_AS_STRING(str); + + if (x clipw) w = clipw-x; + if (y+h > cliph) h = cliph-y; + data += bytes_per_pixel*x+y*self->screenscanline; + while (h>0) + { + int i; + int b; + unsigned char* dst0 = dst; + unsigned char* data0 = data; + for (i=0; iscreenscanline; + h--; + } + return result; + } +} + +static int readXevents(DisplayObject* self) +{ + MSG Msg; + while (PeekMessage(&Msg, (HWND) NULL, 0, 0, PM_REMOVE)) + { + DispatchMessage(&Msg); + if (PyErr_Occurred()) + return 0; + } + return checkopen(self); +} + +#define ENABLE_EVENTS(mask) do { } while (0) /* nothing */ + +static PyObject* display_keyevents1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(KeyPressMask|KeyReleaseMask); + if (!readXevents(self)) + return NULL; + result = self->keyevents; + if (result == NULL) + result = PyList_New(0); + else + self->keyevents = NULL; + return result; +} + +static PyObject* display_mouseevents1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(ButtonPressMask); + result = self->mouseevents; + if (result == NULL) + result = PyList_New(0); + else + self->mouseevents = NULL; + return result; +} + +static PyObject* display_pointermotion1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(PointerMotionMask); + result = self->motionevent; + if (result == NULL) + { + Py_INCREF(Py_None); + result = Py_None; + } + else + self->motionevent = NULL; + return result; +} + +static PyObject* display_flip1(DisplayObject* self, PyObject* args) +{ + int r; + if (!checkopen(self)) + return NULL; + + if (self->hpal) + RealizePalette(self->dc); + + r = SetDIBitsToDevice(self->dc, 0, 0, self->width, self->height, 0, 0, + 0, self->height, self->screenbmpdata, (BITMAPINFO*)(&self->screenbmpinfo), + DIB_PAL_COLORS); + if (!r) + { + PyErr_SetString(PyExc_IOError, "SetDIBitsToDevice failed"); + return NULL; + } + + flush(self); + if (!readXevents(self)) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* display_shmmode(DisplayObject* self, PyObject *args) +{ + return PyInt_FromLong(0); +} + +static PyObject* display_settitle(DisplayObject* self, PyObject* args) +{ + char* title; + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "s", &title)) + return NULL; + SetWindowText(self->win, title); + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef display_methods[] = { + {"close", (PyCFunction)display_close1, METH_VARARGS, NULL}, + {"flip", (PyCFunction)display_flip1, METH_VARARGS, NULL}, + {"clear", (PyCFunction)display_clear1, METH_VARARGS, NULL}, + {"pixmap", (PyCFunction)display_pixmap1, METH_VARARGS, NULL}, + {"putppm", (PyCFunction)display_putppm1, METH_VARARGS, NULL}, + {"getppm", (PyCFunction)display_getppm1, METH_VARARGS, NULL}, + {"keyevents",(PyCFunction)display_keyevents1,METH_VARARGS, NULL}, + {"mouseevents",(PyCFunction)display_mouseevents1,METH_VARARGS,NULL}, + {"pointermotion",(PyCFunction)display_pointermotion1,METH_VARARGS,NULL}, + {"shmmode", (PyCFunction)display_shmmode, METH_VARARGS, NULL}, + {"settitle", (PyCFunction)display_settitle, METH_VARARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject* display_getattr(DisplayObject* self, char* name) +{ + return Py_FindMethod(display_methods, (PyObject*)self, name); +} + + +statichere PyTypeObject Display_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Display", /*tp_name*/ + sizeof(DisplayObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)display_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)display_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ +}; + + + /************************** AUDIO PART ***************************/ + +#define NUM_WAVE_HDR 2 + +typedef struct { + PyObject_HEAD + HWAVEOUT waveOut; + HANDLE doneEvent; + WAVEHDR waveHdr[NUM_WAVE_HDR]; + int waveHdrCount, waveHdrNext; +} AudioObject; + +#define AudioObject_Check(v) ((v)->ob_type == &Audio_Type) +staticforward PyTypeObject Audio_Type; + + +static PyObject* new_audio(PyObject* dummy, PyObject* args) +{ + WAVEFORMATEX wf; + int channels, freq, bits, err, bufsize; + AudioObject* self; + if (!PyArg_ParseTuple(args, "iiii", &channels, &freq, &bits, &bufsize)) + return NULL; + + self = PyObject_New(AudioObject, &Audio_Type); + if (self == NULL) + return NULL; + + self->waveHdrCount = 0; + self->waveHdrNext = 0; + self->waveOut = 0; + self->doneEvent = CreateEvent(NULL, FALSE, TRUE, NULL); + + memset(&wf, 0, sizeof(wf)); + wf.wFormatTag = WAVE_FORMAT_PCM; + wf.nChannels = channels; + wf.nSamplesPerSec = freq; + wf.wBitsPerSample = bits; + wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8; + wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; + err = waveOutOpen(&self->waveOut, WAVE_MAPPER, &wf, (DWORD) self->doneEvent, 0, CALLBACK_EVENT); + if (err != MMSYSERR_NOERROR || self->doneEvent == NULL) { + Py_DECREF(self); + PyErr_SetString(PyExc_IOError, "cannot open audio device"); + return NULL; + } + + while (self->waveHdrCount < NUM_WAVE_HDR) { + WAVEHDR* wh = &self->waveHdr[self->waveHdrCount]; + wh->lpData = PyMem_Malloc(bufsize); + wh->dwBufferLength = bufsize; + wh->dwFlags = 0; + if (wh->lpData == NULL || + waveOutPrepareHeader(self->waveOut, wh, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) { + Py_DECREF(self); + return NULL; + } + wh->dwFlags |= WHDR_DONE; + self->waveHdrCount++; + } + return (PyObject*) self; +} + +static void audio_close(AudioObject* self) +{ + if (self->waveOut != 0) { + waveOutReset(self->waveOut); + while (self->waveHdrCount > 0) { + WAVEHDR* wh = &self->waveHdr[--self->waveHdrCount]; + waveOutUnprepareHeader(self->waveOut, wh, sizeof(WAVEHDR)); + PyMem_Free(wh->lpData); + } + waveOutClose(self->waveOut); + self->waveOut = 0; + } +} + +static void audio_dealloc(AudioObject* self) +{ + audio_close(self); + PyObject_Del(self); +} + +static PyObject* audio_wait1(AudioObject* self, PyObject* args) +{ + float delay = -1.0; + if (!PyArg_ParseTuple(args, "|f", &delay)) + return NULL; + if (self->waveHdrNext >= self->waveHdrCount) { + PyErr_SetString(PyExc_IOError, "audio device not ready"); + return NULL; + } + Py_BEGIN_ALLOW_THREADS + while (!(self->waveHdr[self->waveHdrNext].dwFlags & WHDR_DONE)) { + WaitForSingleObject(self->doneEvent, delay<0.0?INFINITE:(DWORD)(delay*1000.0)); + } + Py_END_ALLOW_THREADS + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* audio_ready1(AudioObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + return PyInt_FromLong(self->waveHdrNext < self->waveHdrCount && + (self->waveHdr[self->waveHdrNext].dwFlags & WHDR_DONE)); +} + +static PyObject* audio_write1(AudioObject* self, PyObject* args) +{ + WAVEHDR* wh; + char* buffer; + int bufsize; + if (!PyArg_ParseTuple(args, "s#", &buffer, &bufsize)) + return NULL; + if (self->waveHdrNext >= self->waveHdrCount) { + PyErr_SetString(PyExc_IOError, "audio device not ready"); + return NULL; + } + wh = &self->waveHdr[self->waveHdrNext]; + if (!(wh->dwFlags & WHDR_DONE)) { + PyErr_SetString(PyExc_IOError, "audio device would block"); + return NULL; + } + if ((DWORD) bufsize != wh->dwBufferLength) { + PyErr_SetString(PyExc_ValueError, "bufsize mismatch"); + return NULL; + } + wh->dwFlags &= ~WHDR_DONE; + memcpy(wh->lpData, buffer, bufsize); + if (waveOutWrite(self->waveOut, wh, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) { + PyErr_SetString(PyExc_IOError, "audio device write error"); + return NULL; + } + self->waveHdrNext++; + if (self->waveHdrNext >= self->waveHdrCount) + self->waveHdrNext = 0; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* audio_close1(AudioObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + audio_close(self); + Py_INCREF(Py_None); + return Py_None; +} + + +static PyMethodDef audio_methods[] = { + {"ready", (PyCFunction)audio_ready1, METH_VARARGS, NULL}, + {"wait", (PyCFunction)audio_wait1, METH_VARARGS, NULL}, + {"write", (PyCFunction)audio_write1, METH_VARARGS, NULL}, + {"close", (PyCFunction)audio_close1, METH_VARARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject* audio_getattr(AudioObject* self, char* name) +{ + return Py_FindMethod(audio_methods, (PyObject*)self, name); +} + + +statichere PyTypeObject Audio_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Audio", /*tp_name*/ + sizeof(AudioObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)audio_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)audio_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ +}; + + +static PyMethodDef WinMethods[] = { + {"Display", new_display, METH_VARARGS}, + {"Audio", new_audio, METH_VARARGS}, + {NULL, NULL} /* Sentinel */ + }; + +void initwingame(void) +{ + Display_Type.ob_type = &PyType_Type; + Audio_Type.ob_type = &PyType_Type; + Py_InitModule("wingame", WinMethods); +} diff --git a/display/windows/wingame.def b/display/windows/wingame.def new file mode 100755 index 0000000..de59dda --- /dev/null +++ b/display/windows/wingame.def @@ -0,0 +1,2 @@ +EXPORTS + initwingame diff --git a/display/windows/wingame.dsp b/display/windows/wingame.dsp new file mode 100755 index 0000000..ce1db26 --- /dev/null +++ b/display/windows/wingame.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="wingame" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** NICHT BEARBEITEN ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=wingame - Win32 Debug +!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE +!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl +!MESSAGE +!MESSAGE NMAKE /f "wingame.mak". +!MESSAGE +!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben +!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel: +!MESSAGE +!MESSAGE NMAKE /f "wingame.mak" CFG="wingame - Win32 Debug" +!MESSAGE +!MESSAGE Für die Konfiguration stehen zur Auswahl: +!MESSAGE +!MESSAGE "wingame - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE "wingame - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "wingame - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "\home\python222\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /U "_DEBUG" /U "DEBUG" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x100c /d "NDEBUG" +# ADD RSC /l 0x100c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /machine:I386 /out:"../wingame.pyd" /libpath:"\home\python23\libs" /libpath:"\home\python222\libs" + +!ELSEIF "$(CFG)" == "wingame - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "\home\python222\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WINGAME_EXPORTS" /U "_DEBUG" /U "DEBUG" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x100c /d "_DEBUG" +# ADD RSC /l 0x100c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /debug /machine:I386 /out:"../wingame.pyd" /pdbtype:sept /libpath:"\home\python222\libs" + +!ENDIF + +# Begin Target + +# Name "wingame - Win32 Release" +# Name "wingame - Win32 Debug" +# Begin Group "Quellcodedateien" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\wingame.c +# End Source File +# Begin Source File + +SOURCE=.\wingame.def +# End Source File +# End Group +# Begin Group "Header-Dateien" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Ressourcendateien" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/display/windows/wingame.dsw b/display/windows/wingame.dsw new file mode 100755 index 0000000..06f07c9 --- /dev/null +++ b/display/windows/wingame.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNUNG: DIESE ARBEITSBEREICHSDATEI DARF NICHT BEARBEITET ODER GELÖSCHT WERDEN! + +############################################################################### + +Project: "wingame"=.\wingame.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/display/xshm.c b/display/xshm.c new file mode 100644 index 0000000..31ea861 --- /dev/null +++ b/display/xshm.c @@ -0,0 +1,1202 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + XImage* m_shm_image; + XShmSegmentInfo m_shminfo; + int m_width, m_height; +} XImage_Shm; + +typedef struct { + PyObject_HEAD + Display* dpy; + int default_scr; + Window root, win; + int width, height; + XVisualInfo visual_info; + GC gc, gc_and, gc_or; + XImage_Shm plane; + Pixmap backpixmap; + int shmmode; + int selectinput; + PyObject* keyevents; + PyObject* mouseevents; + PyObject* motionevent; +} DisplayObject; + +typedef struct { + PyObject_HEAD + DisplayObject* dpy; + int width, height; + Pixmap mask; + Pixmap handle; +} XPixmapObject; + + +#define DisplayObject_Check(v) ((v)->ob_type == &Display_Type) +staticforward PyTypeObject Display_Type; +staticforward PyTypeObject XPixmap_Type; + + +static void pixmap_dealloc(XPixmapObject* pm) +{ + if (pm->dpy->dpy) + { + if (pm->mask != (Pixmap) -1) + XFreePixmap(pm->dpy->dpy, pm->mask); + XFreePixmap(pm->dpy->dpy, pm->handle); + } + Py_DECREF(pm->dpy); + PyObject_Del(pm); +} + +static XPixmapObject* new_pixmap(DisplayObject* self, int w, int h, int withmask) +{ + XPixmapObject* pm = PyObject_New(XPixmapObject, &XPixmap_Type); + if (pm != NULL) + { + Py_INCREF(self); + pm->dpy = self; + pm->width = w; + pm->height = h; + pm->handle = XCreatePixmap(self->dpy, self->win, w, h, + self->visual_info.depth); + if (withmask) + pm->mask = XCreatePixmap(self->dpy, self->win, w, h, + self->visual_info.depth); + else + pm->mask = (Pixmap) -1; + } + return pm; +} + + +static void flush(DisplayObject* self) +{ + XSync(self->dpy, False); +} + +static int create_shm_image(DisplayObject* self, XImage_Shm* img, + int width, int height) +{ + int image_size = 4*width*height; + + if (XShmQueryExtension(self->dpy) == False) + /* does we have the extension at all? */ + return 0; + + img->m_shm_image = XShmCreateImage( + self->dpy, + self->visual_info.visual, + self->visual_info.depth, + ZPixmap, + NULL, + &img->m_shminfo, + width, + height); + if (img->m_shm_image == NULL) + return 0; + img->m_width = width; + img->m_height = height; + + /* Create shared memory segment: */ + img->m_shminfo.shmid = shmget(IPC_PRIVATE, image_size, IPC_CREAT|0777); + if (img->m_shminfo.shmid < 0) + return 0; + + /* Get memory address to segment: */ + img->m_shminfo.shmaddr = (char *) shmat(img->m_shminfo.shmid, 0, 0); + + /* Mark the segment as destroyable (it will be destroyed when this + process terminates) */ + shmctl(img->m_shminfo.shmid, IPC_RMID, NULL); + + /* Tell XServer that it may only read from it and attach to display: */ + img->m_shminfo.readOnly = True; + XShmAttach (self->dpy, &img->m_shminfo); + + /* Fill the XImage struct: */ + img->m_shm_image->data = img->m_shminfo.shmaddr; + return 1; +} + +static PyObject* new_display(PyObject* dummy, PyObject* args) +{ + DisplayObject* self; + XSetWindowAttributes attr; + int width, height, use_shm=1; + if (!PyArg_ParseTuple(args, "ii|i", &width, &height, &use_shm)) + return NULL; + + self = PyObject_New(DisplayObject, &Display_Type); + if (self == NULL) + return NULL; + + self->dpy = XOpenDisplay(NULL); + if (self->dpy == NULL) goto err; + self->default_scr = DefaultScreen(self->dpy); + self->root = RootWindow(self->dpy, self->default_scr); + self->width = width; + self->height = height; + + if (!XMatchVisualInfo(self->dpy, self->default_scr, + DefaultDepth(self->dpy,self->default_scr), TrueColor, + &self->visual_info)) goto err2; + + /* set window attributes */ + memset(&attr, 0, sizeof(attr)); + attr.override_redirect = False; + attr.background_pixel = BlackPixel(self->dpy, self->default_scr); + attr.backing_store = NotUseful; + + /* Create the window */ + self->win = XCreateWindow( + self->dpy, + self->root, + 0, + 0, + width, + height, + 0, + CopyFromParent, + CopyFromParent, + self->visual_info.visual, + CWOverrideRedirect | CWBackPixel | CWBackingStore, + &attr); + if (self->win == (Window) -1) goto err2; + + XMapRaised(self->dpy, self->win); + + self->shmmode = use_shm && + create_shm_image(self, &self->plane, width, height); + + self->gc = XCreateGC(self->dpy, self->win, 0, 0); + if (!self->shmmode) + { + self->backpixmap = XCreatePixmap(self->dpy, self->root, + width, height, self->visual_info.depth); + if (self->backpixmap == (Pixmap) -1) goto err2; + + self->gc_and = XCreateGC(self->dpy, self->win, 0, 0); + self->gc_or = XCreateGC(self->dpy, self->win, 0, 0); + XSetForeground(self->dpy, self->gc, attr.background_pixel); + XSetFunction(self->dpy, self->gc_and, GXand); + XSetFunction(self->dpy, self->gc_or, GXor); + } + + self->selectinput = 0; + self->keyevents = NULL; + self->mouseevents = NULL; + self->motionevent = NULL; + + flush(self); + return (PyObject*) self; + + err2: + XCloseDisplay(self->dpy); + err: + Py_DECREF(self); + PyErr_SetString(PyExc_IOError, "cannot open X11 display"); + return NULL; +} + +static void display_close(DisplayObject* self) +{ + if (self->dpy) + { + XCloseDisplay(self->dpy); + self->dpy = NULL; + } +} + +static void display_dealloc(DisplayObject* self) +{ + display_close(self); + Py_XDECREF(self->keyevents); + Py_XDECREF(self->mouseevents); + Py_XDECREF(self->motionevent); + PyObject_Del(self); +} + +static PyObject* display_close1(DisplayObject* self, PyObject* args) +{ + display_close(self); + Py_INCREF(Py_None); + return Py_None; +} + +static int checkopen(DisplayObject* self) +{ + if (self->dpy) + return 1; + PyErr_SetString(PyExc_IOError, "X11 connexion already closed"); + return 0; +} + +static unsigned char* get_dpy_data(DisplayObject* self) +{ + unsigned char* result; + if (!checkopen(self)) + return NULL; + result = (unsigned char*)(self->plane.m_shminfo.shmaddr); + if (!result) + PyErr_SetString(PyExc_IOError, "X11 SHM failed"); + return result; +} + +static PyObject* display_clear1(DisplayObject* self, PyObject* args) +{ + if (self->shmmode) + { + unsigned char* data = get_dpy_data(self); + if (data == NULL) + return NULL; + memset(data, 0, + ( self->plane.m_shm_image->bits_per_pixel/8 + *self->width*self->height ) ); + } + else + { + if (!checkopen(self)) + return NULL; + XFillRectangle(self->dpy, self->backpixmap, self->gc, + 0, 0, self->width, self->height); + } + Py_INCREF(Py_None); + return Py_None; +} + +inline void pack_pixel(unsigned char *data, int r, int g, int b, + int depth, int bytes_per_pixel) +{ + unsigned short pixel = 0; + switch( depth ) + { + /* No True color below 15 bits per pixel */ + case 15: + pixel = ((r<<7) & 0x7c00) | ((g<<2) & 0x03e0) | ((b>>3) & 0x001f); + data[0] = (pixel) & 0xff; + data[1] = (pixel>>8) & 0xff; + break; + case 16: + /* assumes 5,6,5 model. */ + pixel = ((r<<8) & 0xf800) | ((g<<3) & 0x07e0) | ((b>>3) & 0x001f); + data[0] = (pixel) & 0xff; + data[1] = (pixel>>8) & 0xff; + break; + case 24: + if( bytes_per_pixel == 3 ) + { + data[0] = b; + data[1] = g; + data[2] = r; + break; + } + /* else it's on 32 bits. Drop into depth of 32. */ + case 32: + *((long *)data) = (r<<16) | (g<<8) | b; + break; + } +} + +static PyObject* display_pixmap1(DisplayObject* self, PyObject* args) +{ + int w,h; + unsigned char* input = NULL; + int length; + long keycol = -1; + + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "ii|s#l", &w, &h, &input, &length, &keycol)) + return NULL; + + if (self->shmmode) + { + int x, y; + int bytes_per_pixel = self->plane.m_shm_image->bits_per_pixel/8; + int countblocks, countpixels; + PyObject* result; + PyObject* strblocks; + PyObject* strpixels; + unsigned int* pblocks; + unsigned char* ppixels; + unsigned char* input1; + + if (input == NULL) + { + Py_INCREF(Py_None); + return Py_None; + } + if (3*w*h != length) + { + PyErr_SetString(PyExc_ValueError, "bad string length"); + return NULL; + } + + /* Convert the image to our internal format. + See display_putppm1() for a description of the format. + */ + + countblocks = 0; + countpixels = 0; + input1 = input; + for (y=0; yvisual_info.depth, bytes_per_pixel); + ppixels += bytes_per_pixel; + } + } + *pblocks++ = 0; /* end-of-line marker block */ + } + + result = Py_BuildValue("iiOO", w, h, strblocks, strpixels); + Py_DECREF(strblocks); + Py_DECREF(strpixels); + return result; + } + else + { + XImage* image; + long extent; + unsigned char* data = NULL; + unsigned char* maskdata = NULL; + int scanline, bitmap_pad; + XPixmapObject* pm; + + pm = new_pixmap(self, w, h, keycol>=0); + if (pm == NULL) + return NULL; + + if (input == NULL) + return (PyObject*) pm; /* uninitialized pixmap */ + + extent = w*h; + if (3*extent != length) + { + PyErr_SetString(PyExc_ValueError, "bad string length"); + goto err; + } + + bitmap_pad = self->visual_info.depth >= 24 ? 32 : 16; + scanline = ((w+bitmap_pad-1) & ~(bitmap_pad-1)) / 8; + /*while (scanline&3) scanline++;*/ + data = malloc(self->visual_info.depth*scanline*h); + if (data == NULL) + { + PyErr_NoMemory(); + goto err; + } + memset(data, 0, self->visual_info.depth*scanline*h); + maskdata = malloc(self->visual_info.depth*scanline*h); + if (maskdata == NULL) + { + PyErr_NoMemory(); + goto err; + } + memset(maskdata, 0, self->visual_info.depth*scanline*h); + + { + int key_r = keycol>>16; + unsigned char key_g = keycol>>8; + unsigned char key_b = keycol>>0; + unsigned char* target = data; + unsigned char* masktarget = maskdata; + int plane, color; + + unsigned int p_size[3]; + switch( self->visual_info.depth ) + { + case 15: + p_size[0] = p_size[1] = p_size[2] = 5; + break; + case 16: + p_size[0] = p_size[2] = 5; + p_size[1] = 6; + break; + case 24: + case 32: + p_size[0] = p_size[1] = p_size[2] = 8; + break; + } + + for (color=0; color<3; color++) + for (plane=128; plane>=(1<<(8-p_size[color])); plane/=2) + { + unsigned char* src = input; + int x, y; + for (y=0; ydpy, self->visual_info.visual, + self->visual_info.depth, XYPixmap, 0, + maskdata, w, h, + bitmap_pad, scanline); + if (image == NULL || image == (XImage*) -1) + { + PyErr_SetString(PyExc_IOError, "XCreateImage failed (2)"); + goto err; + } + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + maskdata = NULL; + XPutImage(self->dpy, pm->mask, self->gc, image, 0, 0, 0, 0, w, h); + XDestroyImage(image); + } + + image = XCreateImage(self->dpy, self->visual_info.visual, + self->visual_info.depth, XYPixmap, 0, + data, w, h, + bitmap_pad, scanline); + if (image == NULL || image == (XImage*) -1) + { + PyErr_SetString(PyExc_IOError, "XCreateImage failed"); + goto err; + } + image->byte_order = LSBFirst; + image->bitmap_bit_order = LSBFirst; + data = NULL; + XPutImage(self->dpy, pm->handle, self->gc, image, 0, 0, 0, 0, w, h); + XDestroyImage(image); + + return (PyObject*) pm; + + err: + free(maskdata); + free(data); + Py_DECREF(pm); + return NULL; + } +} + +static PyObject* display_get(DisplayObject* self, int x, int y, int w, int h) +{ + if (self->shmmode) + { + int clipx=0, clipy=0, clipw=self->width, cliph=self->height; + int original_w, original_h; + int firstline=0, firstcol=0; + unsigned int bytes_per_pixel = self->plane.m_shm_image->bits_per_pixel/8; + unsigned char* data = get_dpy_data(self); + if (!data) + return NULL; + + original_w = w; + original_h = h; + if (x clipw) w = clipw-x; + if (y+h > cliph) h = cliph-y; + + { + int countblocks = original_h + ((w>0 && h>0) ? h : 0); + /* end blocks + real blocks */ + int countpixels = (w>0 && h>0) ? w * h : 0; + PyObject* result; + PyObject* strblocks; + PyObject* strpixels; + unsigned int* pblocks; + unsigned char* ppixels; + int wbytes = w * bytes_per_pixel; + int block = (firstcol * bytes_per_pixel) | (wbytes << 16); + int data_scanline = bytes_per_pixel*self->width; + + /* allocate memory */ + strblocks = PyString_FromStringAndSize(NULL, + countblocks*sizeof(int)); + if (strblocks == NULL) + return NULL; + strpixels = PyString_FromStringAndSize(NULL, + countpixels*bytes_per_pixel); + if (strpixels == NULL) + { + Py_DECREF(strblocks); + return NULL; + } + + /* write data */ + pblocks = (unsigned int*) PyString_AS_STRING(strblocks); + ppixels = (unsigned char*) PyString_AS_STRING(strpixels); + data += bytes_per_pixel*(x+y*self->width); + for (y=0; y= firstline && y < firstline+h && w > 0) + { + *pblocks++ = block; + memcpy(ppixels, data, wbytes); + ppixels += wbytes; + data += data_scanline; + } + *pblocks++ = 0; + } + + result = Py_BuildValue("iiOO", original_w, original_h, + strblocks, strpixels); + Py_DECREF(strblocks); + Py_DECREF(strpixels); + return result; + } + } + else + { + XPixmapObject* pm = new_pixmap(self, w, h, 0); + if (pm != NULL) + XCopyArea(self->dpy, self->backpixmap, pm->handle, self->gc, + x, y, w, h, 0, 0); + return (PyObject*) pm; + } +} + +static PyObject* save_background(DisplayObject* self, int x, int y, + int w, int h, int save_bkgnd) +{ + if (save_bkgnd) + { + PyObject* pm = display_get(self, x, y, w, h); + PyObject* result; + if (pm == NULL) + return NULL; + result = Py_BuildValue("iiO", x, y, pm); + Py_DECREF(pm); + return result; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +#define ALPHAFACTOR 2 +#define ALPHABLEND(maximum, x, y) ((maximum-y)*x/(maximum*ALPHAFACTOR) + y) + +static void memcpy_alpha_32(unsigned int* dst, unsigned int* src, int count) +{ + int i; + for (i=0; i> 16; + int xg = x & 0xff00; + int xb = x & 0xff; + + int yr = y >> 16; + int yg = y & 0xff00; + int yb = y & 0xff; + + int zr = ALPHABLEND(0xff, xr, yr); + int zg = ALPHABLEND(0xff00, xg, yg); + int zb = ALPHABLEND(0xff, xb, yb); + + dst[i] = (zr << 16) | (zg & 0xff00) | zb; + } +} + +static void memcpy_alpha_24(unsigned char* dst, unsigned char* src, int count) +{ + int i; + for (i=0; i> 10; + int xg = x & 0x03e0; + int xb = x & 0x001f; + + int yr = y >> 10; + int yg = y & 0x03e0; + int yb = y & 0x001f; + + int zr = ALPHABLEND(31, xr, yr); + int zg = ALPHABLEND(0x3e0, xg, yg); + int zb = ALPHABLEND(31, xb, yb); + + dst[i] = (zr << 10) | (zg & 0x03e0) | zb; + } +} + +static void memcpy_alpha_16(unsigned short* dst, unsigned short* src, int count) +{ + int i; + for (i=0; i> 11; + int xg = x & 0x07e0; + int xb = x & 0x001f; + + int yr = y >> 11; + int yg = y & 0x07e0; + int yb = y & 0x001f; + + int zr = ALPHABLEND(31, xr, yr); + int zg = ALPHABLEND(0x7e0, xg, yg); + int zb = ALPHABLEND(31, xb, yb); + + dst[i] = (zr << 11) | (zg & 0x07e0) | zb; + } +} + +typedef void (*memcpy_alpha_fn) (unsigned char*, unsigned char*, int); + +static PyObject* display_overlay(DisplayObject* self, PyObject* args, + int save_bkgnd) +{ + PyObject* result; + + if (self->shmmode) + { + int x,y,w,h, original_x, original_y, original_w, original_h; + int data_scanline; + int clipx=0, clipy=0, clipw=65536, cliph=65536, alpha=255; + unsigned int* src; + unsigned char* srcdata; + unsigned char* original_srcdata; + int length1, length2, firstline=0, firstcol=0; + unsigned int bytes_per_pixel = self->plane.m_shm_image->bits_per_pixel/8; + memcpy_alpha_fn memcpy_alpha; + unsigned char* data = get_dpy_data(self); + if (!PyArg_ParseTuple(args, "ii(iis#s#)|(iiii)i", + &x, &y, &w, &h, &src, &length1, &srcdata, &length2, + &clipx, &clipy, &clipw, &cliph, &alpha) || !data) + return NULL; + + original_x = x; + original_y = y; + original_w = w; + original_h = h; + original_srcdata = srcdata; + x -= clipx; + y -= clipy; + clipx += x; + clipy += y; + clipw += clipx; + cliph += clipy; + if (clipx<0) clipx=0; + if (clipy<0) clipy=0; + if (clipw>self->width) clipw=self->width; + if (cliph>self->height) cliph=self->height; + if (x clipw) w = clipw-x; + if (y+h > cliph) h = cliph-y; + if (w > 0 && h > 0) + { + int dstoffset, blocksize; + unsigned int block; + data += bytes_per_pixel*(x+y*self->width); + data_scanline = bytes_per_pixel*self->width; + + memcpy_alpha = (memcpy_alpha_fn) memcpy; + if (alpha < 255) + switch (self->visual_info.depth) { + case 15: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_15; break; + case 16: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_16; break; + case 24: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_24; break; + case 32: memcpy_alpha = (memcpy_alpha_fn) memcpy_alpha_32; break; + } + + /* 'structure' points to a sequence of int-sized blocks with the + following meaning: + + n & 0xFFFF -- byte offset within the line + n >> 16 -- number of opaque bytes to copy there + + n == 0 means end of line. + */ + + /* read and ignore 'firstline' complete lines */ + while (firstline--) + { + while ((block = *src++) != 0) + { + blocksize = block >> 16; + srcdata += blocksize; + } + } + + if (w == original_w) + { + if (!save_bkgnd) + { + /* common fast case: copy the whole width of the image */ + do + { + while ((block = *src++) != 0) + { + dstoffset = block & 0xFFFF; + blocksize = block >> 16; + memcpy(data + dstoffset, srcdata, blocksize); + srcdata += blocksize; + } + data += data_scanline; + } + while (--h); + result = Py_None; + Py_INCREF(result); + } + else + { + /* copy and save the background */ + PyObject* cliprect; + PyObject* strblocks; + PyObject* strpixels; + unsigned char* ppixels; + + strpixels = PyString_FromStringAndSize(NULL, length2); + if (strpixels == NULL) + return NULL; + ppixels = (unsigned char*) PyString_AS_STRING(strpixels); + ppixels += srcdata - original_srcdata; + + do + { + while ((block = *src++) != 0) + { + dstoffset = block & 0xFFFF; + blocksize = block >> 16; + memcpy(ppixels, data + dstoffset, blocksize); + ppixels += blocksize; + memcpy_alpha(data + dstoffset, srcdata, blocksize); + srcdata += blocksize; + } + data += data_scanline; + } + while (--h); + + strblocks = PyTuple_GET_ITEM(PyTuple_GET_ITEM(args, 2), 2); + if (PyTuple_GET_SIZE(args) > 3) + { + cliprect = PyTuple_GET_ITEM(args, 3); + result = Py_BuildValue("ii(iiOO)O", + original_x, + original_y, + original_w, + original_h, + strblocks, + strpixels, + cliprect); + } + else + { + result = Py_BuildValue("ii(iiOO)", + original_x, + original_y, + original_w, + original_h, + strblocks, + strpixels); + } + Py_DECREF(strpixels); + } + } + else + { + /* byte offsets within a line */ + unsigned char* blocksrc; + int skip, lastcol; + + result = save_background(self, x, y, w, h, save_bkgnd); + + lastcol = (firstcol + w) * bytes_per_pixel; + firstcol *= bytes_per_pixel; + + /* slow case: only copy a portion of the width of the image */ + data -= firstcol; + do + { + while ((block = *src++) != 0) + { + dstoffset = block & 0xFFFF; + blocksize = block >> 16; + blocksrc = srcdata; + srcdata += blocksize; + skip = firstcol - dstoffset; + if (skip < 0) + skip = 0; + if (blocksize > lastcol - dstoffset) + blocksize = lastcol - dstoffset; + if (blocksize > skip) + memcpy_alpha(data + dstoffset + skip, blocksrc + skip, + blocksize - skip); + } + data += data_scanline; + } + while (--h); + } + } + else + { + result = args; + Py_INCREF(result); + } + } + else + { + int x,y, x1=0,y1=0,w1=-1,h1=-1,alpha; + XPixmapObject* pm; + + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "iiO!|(iiii)i", &x, &y, &XPixmap_Type, &pm, + &x1, &y1, &w1, &h1, &alpha)) + return NULL; + + if (w1 < 0) + w1 = pm->width; + if (h1 < 0) + h1 = pm->height; + + result = save_background(self, x, y, w1, h1, save_bkgnd); + + if (pm->mask == (Pixmap) -1) + { + XCopyArea(self->dpy, pm->handle, self->backpixmap, self->gc, + x1, y1, w1, h1, x, y); + } + else + { + XCopyArea(self->dpy, pm->mask, self->backpixmap, self->gc_and, + x1, y1, w1, h1, x, y); + XCopyArea(self->dpy, pm->handle, self->backpixmap, self->gc_or, + x1, y1, w1, h1, x, y); + } + } + return result; +} + +static PyObject* display_putppm1(DisplayObject* self, PyObject* args) +{ + return display_overlay(self, args, 0); +} + +static PyObject* display_overlayppm1(DisplayObject* self, PyObject* args) +{ + return display_overlay(self, args, 1); +} + +static PyObject* display_getppm1(DisplayObject* self, PyObject* args) +{ + int x, y, w, h; + if (!checkopen(self)) + return NULL; + if (!PyArg_ParseTuple(args, "(iiii)", &x, &y, &w, &h)) + return NULL; + return display_get(self, x, y, w, h); +} + +static int readXevents(DisplayObject* self) +{ + while (XEventsQueued(self->dpy, QueuedAfterReading) > 0) + { + XEvent e; + XNextEvent(self->dpy, &e); + switch (e.type) { + case KeyPress: + case KeyRelease: + { + KeySym sym; + PyObject* v; + int err; + if (self->keyevents == NULL) + { + self->keyevents = PyList_New(0); + if (self->keyevents == NULL) + return 0; + } + sym = XLookupKeysym(&e.xkey,0); + v = Py_BuildValue("ii", sym, e.type); + if (v == NULL) + return 0; + err = PyList_Append(self->keyevents, v); + Py_DECREF(v); + if (err) + return 0; + break; + } + case ButtonPress: + { + PyObject* v; + int err; + if (self->mouseevents == NULL) + { + self->mouseevents = PyList_New(0); + if (self->mouseevents == NULL) + return 0; + } + v = Py_BuildValue("ii", e.xbutton.x, e.xbutton.y); + if (v == NULL) + return 0; + err = PyList_Append(self->mouseevents, v); + Py_DECREF(v); + if (err) + return 0; + break; + } + case MotionNotify: + { + Py_XDECREF(self->motionevent); + self->motionevent = Py_BuildValue("ii", e.xmotion.x, e.xmotion.y); + if (self->motionevent == NULL) + return 0; + break; + } + } + } + return 1; +} + +#define ENABLE_EVENTS(mask) do { \ + if (!(self->selectinput & (mask))) \ + { \ + self->selectinput |= (mask); \ + XSelectInput(self->dpy, self->win, self->selectinput); \ + } \ +} while (0) + +static PyObject* display_keyevents1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(KeyPressMask|KeyReleaseMask); + if (!readXevents(self)) + return NULL; + result = self->keyevents; + if (result == NULL) + result = PyList_New(0); + else + self->keyevents = NULL; + return result; +} + +static PyObject* display_mouseevents1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(ButtonPressMask); + result = self->mouseevents; + if (result == NULL) + result = PyList_New(0); + else + self->mouseevents = NULL; + return result; +} + +static PyObject* display_pointermotion1(DisplayObject* self, PyObject* args) +{ + PyObject* result; + ENABLE_EVENTS(PointerMotionMask); + result = self->motionevent; + if (result == NULL) + { + Py_INCREF(Py_None); + result = Py_None; + } + else + self->motionevent = NULL; + return result; +} + +static PyObject* display_flip1(DisplayObject* self, PyObject* args) +{ + if (!checkopen(self)) + return NULL; + + if (self->shmmode) + { + XShmPutImage(self->dpy, self->win, self->gc, + self->plane.m_shm_image, + 0, 0, 0, 0, + self->plane.m_width, + self->plane.m_height, + False); + } + else + { + XCopyArea(self->dpy, self->backpixmap, self->win, self->gc, + 0, 0, self->width, self->height, 0, 0); + } + flush(self); + if (!readXevents(self)) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* display_fd1(DisplayObject* self, PyObject *args) +{ + return PyInt_FromLong(ConnectionNumber(self->dpy)); +} + +static PyObject* display_shmmode(DisplayObject* self, PyObject *args) +{ + return PyInt_FromLong(self->shmmode); +} + +static PyMethodDef display_methods[] = { + {"close", (PyCFunction)display_close1, METH_VARARGS, NULL}, + {"flip", (PyCFunction)display_flip1, METH_VARARGS, NULL}, + {"clear", (PyCFunction)display_clear1, METH_VARARGS, NULL}, + {"pixmap", (PyCFunction)display_pixmap1, METH_VARARGS, NULL}, + {"putppm", (PyCFunction)display_putppm1, METH_VARARGS, NULL}, + {"getppm", (PyCFunction)display_getppm1, METH_VARARGS, NULL}, + {"overlayppm",(PyCFunction)display_overlayppm1, METH_VARARGS, NULL}, + {"keyevents",(PyCFunction)display_keyevents1,METH_VARARGS, NULL}, + {"mouseevents",(PyCFunction)display_mouseevents1,METH_VARARGS,NULL}, + {"pointermotion",(PyCFunction)display_pointermotion1,METH_VARARGS,NULL}, + {"fd", (PyCFunction)display_fd1, METH_VARARGS, NULL}, + {"shmmode", (PyCFunction)display_shmmode, METH_VARARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject* display_getattr(DisplayObject* self, char* name) +{ + return Py_FindMethod(display_methods, (PyObject*)self, name); +} + + +statichere PyTypeObject Display_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Display", /*tp_name*/ + sizeof(DisplayObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)display_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + (getattrfunc)display_getattr, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ +}; + +statichere PyTypeObject XPixmap_Type = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "Pixmap", /*tp_name*/ + sizeof(XPixmapObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)pixmap_dealloc, /*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*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ +}; + + +static PyMethodDef ShmMethods[] = { + {"Display", new_display, METH_VARARGS}, + {NULL, NULL} /* Sentinel */ + }; + +void initxshm(void) +{ + Display_Type.ob_type = &PyType_Type; + XPixmap_Type.ob_type = &PyType_Type; + Py_InitModule("xshm", ShmMethods); +} diff --git a/display/xshm.so b/display/xshm.so new file mode 100755 index 0000000..2296a67 Binary files /dev/null and b/display/xshm.so differ diff --git a/doc/BubBob.py.1 b/doc/BubBob.py.1 new file mode 100644 index 0000000..0b77548 --- /dev/null +++ b/doc/BubBob.py.1 @@ -0,0 +1,32 @@ +.\" $Id: BubBob.py.1,v 1.2 2005/04/13 18:24:17 opqdonut Exp $ +.\" +.\" Process this file with +.\" groff -man -Tascii BubBob.py.1 +.\" + +.TH BubBob.py 1 "APRIL 2005" Linux "User Manuals" + +.SH NAME +BubBob.py \- Generic startup script for bub-n-bros + +.SH SYNOPSIS +.B BubBob.py +.br +.B python BubBob.py + +.SH DESCRIPTION +.B BubBob.py +runs +.BR bb.py (1) +(the bub-n-bros server) and then tries to open the url of its control +panel in your webbrowser. + +.SH ENVIRONMENT +The environment variable BROWSER affects the choosing of the browser +on Unix systems. + +.SH SEE ALSO +.BR bb.py (1) +.BR Client.py (1) +.BR python (1) + diff --git a/doc/Client.py.1 b/doc/Client.py.1 new file mode 100644 index 0000000..758a8dc --- /dev/null +++ b/doc/Client.py.1 @@ -0,0 +1,200 @@ +.\" $Id: Client.py.1,v 1.3 2005/05/06 21:32:38 arigo Exp $ +.\" +.\" Process this file with +.\" groff -man -Tascii Client.py.1 +.\" + +.TH Client.py 1 "APRIL 2005" Linux "User Manuals" + +.SH NAME +Client.py \- the bub-n-bros client + +.SH SYNOPSIS +.B python Client.py [ +.I options +.BI "] [" host [: port ]] +.PP +Note that this script is in the +.B display/ +subdirectory of the original directory layout. + +.SH DESCRIPTION +This is the bub-n-bros client that connects to bub-n-bros servers +started with +.BR bb.py "(1) or " BubBob.py (1) . +It supports many different audio and video drivers and is very +multi-platform. + +.SH OPTIONS + +.TP +.I host +Search for a game on the given machine, and connect to one if found. +If this is not defined, search for servers on the local network. + +.TP +.IB host : port +Connect to server running on given port on given host. If this is not +defined, search for servers on the local network. + +.TP +.BI --bits= N +This option concerns only the +.B windows +audio driver. Set bits per sample. Valid values are 8 and 16 (default). + +.TP +.BI "-d " DRIVER ", --display=" DRIVER +Use video driver +.IR DRIVER . +Valid drivers are: + +.RS +.TP +.B X +X Window driver for Linux and Unix systems with an X server +running. See the +.B --shm +option. +.TP +.B windows +MS Windows driver. +.TP +.B pygame +PyGame gaming library for python (if installed) has video output. Use +it if this is specified. See +.BR http://www.pygame.org " and the " --transparency " option." +.TP +.B gtk +Use the PyGTK library (if installed). See +.BR http://www.pygtk.org/ " and the " --zoom " option." +.RE + +.TP +.B -h, --help +Display help. + +.TP +.BI --fmt= format +This option concerns only the +.B linux +audio driver. Set the sound data format. Defaults to +.BR S16_NE . +You can get a list of supported formats with +.BR --fmt=list . + +.TP +.BI --freq= N +This option concerns only the +.BR linux " and " windows +audio drivers. Set mixing frequency to +.I N +(in Hz). Defaults to 44100. + +.TP +.B -m, --metaserver +Connect with the help of the Metaserver (see +.BR bb.py (1) +for more info). Run +.I Client.py -m +to print a table of currently running servers, pick the +the exact IP address and port of the server of your choice +from the table, and run again +.I Client.py -m +.BR host:port . + +.TP +.BI "--port TCP=" port +In conjunction with the +.I -m +option, force a specific +.I port +on which to listen for an incoming TCP connexion from the +remote server. This is only used if a direct connexion to +the server fails. A random port number is picked by default. +This option is useful if you are behind a firewall but can +let TCP connexions on specific ports reach you. + +.TP +.BI "--port UDP=" port +Force a specific +.I port +on which to listen for incoming UDP traffic (animation +data). Useful if you are behind a firewall but can let UDP +traffic on a given +.I port +pass through. This is optional: animation data is routed +over the TCP link if it does not appear to reach the client. +A random port number is picked by default. + +.TP +.BI "-s " DRIVER ", --sound=" DRIVER +Use driver +.I DRIVER +for audio. Valid drivers are: + +.RS +.TP +.B pygame +Use the audio capabilities of the PyGame library +.RB ( http://www.pygame.org ). + +.TP +.B linux +Use the linux audio mixer. See the +.BR --freq " and " --fmt " options." + +.TP +.B windows +Use the Windows audio mixer. See the +.BR --freq " and " --bits "options." + +.TP +.B off +No sounds. +.RE + + +.TP +.B --shm=yes, --shm=no +This option concerns only the +.B X +display driver. Disable or enable the +.B shm +(Shared Memory) extension. It is enabled by default and should only be +disabled for remote X connections and old X servers. + +.TP +.B -t +Use the +.B TCP +protocol. The default is to autodetect the protocol. See the +.B -u +option. + +.TP +.B --transparency=yes, --transparency=no +This option concerns only the +.B pygame +display driver. Disable or enable transparent bubbles. Enabled by +default. Disabling makes game a bit faster. + +.TP +.B -u +Use the +.B UDP +protocol. The default is to autodetect the protocol. See the +.B -t +option. + +.TP +.BI --zoom= N % +This option concerns only the +.B gtk +display driver. Scale output by +.IR N %. + +.SH SEE ALSO +.BR bb.py (1) +.BR Client.py (1) +.BR python (1) \ No newline at end of file diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..3c21fcb --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,30 @@ +# 'make compress' to build the docs as gzipped manpages. +# 'make txt' to build the docs as plain text manpages. +# 'make html' to build the docs as html-ized manpages. + +all: txt html compress +txt: bb.py.txt Client.py.txt BubBob.py.txt +html: bb.py.html Client.py.html BubBob.py.html + +compress: bb.py.1.gz Client.py.1.gz BubBob.py.1.gz + +%.html : %.1 + groff -man -Thtml $< > $@ + +%.txt : %.1 + groff -man -Tascii $< > $@ + +%.1.gz : %.1 + gzip -c $< > $@ + +clean: + rm -f bb.py.txt Client.py.txt BubBob.py.txt + rm -f bb.py.html Client.py.html BubBob.py.html + rm -f *.gz + +install: + $(INSTALL) -c -o $(MANOWNER) -g $(MANGROUP) -m 644 bb.py.1.gz $(MANDIR)/man1/bubnbros-server.1.gz + $(INSTALL) -c -o $(MANOWNER) -g $(MANGROUP) -m 644 Client.py.1.gz $(MANDIR)/man1/bubnbros.1.gz + $(INSTALL) -c -o $(MANOWNER) -g $(MANGROUP) -m 644 BubBob.py.1.gz $(MANDIR)/man1/BubBob.1.gz + + ### TODO: install for html and txt files! diff --git a/doc/bb.py.1 b/doc/bb.py.1 new file mode 100644 index 0000000..d7bff4e --- /dev/null +++ b/doc/bb.py.1 @@ -0,0 +1,129 @@ +.\" $Id: bb.py.1,v 1.3 2005/04/17 17:19:32 opqdonut Exp $ +.\" +.\" Process this file with +.\" groff -man -Tascii bb.py.1 +.\" + +.TH bb.py 1 "APRIL 2005" Linux "User Manuals" + +.SH NAME +bb.py \- the bub-n-bros server. + +.SH SYNOPSIS +.B bb.py [ +.I level-file.bin +.BI "] [" options ] +.br +.B python bb.py [ +.I level-file.bin +.BI "] [" options ] +.PP +Note that this script is in the +.B bubbob/ +subdirectory of the original directory layout. + +.SH DESCRIPTION +.B bb.py +starts an http server that acts as a control panel for the server. The +server listens on port +.B 8000 +by default. The url for the control panel +is +.BI http:// server : port / 0xN +where +.I 0xN +is a random hex number (acts as minimal protection). This url is +printed when the server starts. You can start and view games and kill +the server from this panel. The control panel also allows you to type +in the address of a server to connect to, the script will then open a +client to that server. The http server also servers java applet +clients for those players who wish to use one. + +When a game is started the script opens a port for the game +server. This port can then be connected to by a client. Clients +autodetect servers running on the local network with UDP ping on port +.BR 8056 . + +.SS Connection forming + +The client forms a tcp connection to the server. Or, when using the +metaserver, the server forms a connection to the client. If this +fails, the client and server try a simultaneous SYN connect. This +sometimes works if the server and client are behind firewalls. The +server then tries to transmit the data over udp. If it gets no +response from the client it will fall back to the existing tcp +connection. + +.SH OPTIONS + +.TP +.BI "-b " N ", --begin " N ", --start " N +Start at board (level) number +.IR N . +The default is 1. See also the +.B -s +option. + +.TP +.B -h, --help +Display help. + +.TP +.B -i, --infinite +Restart the server at the end of the game. Normally the server quits +after a certain period of inactivity. This is useful when used with the +.B -m +option to make a public server that is available for a long time. + +.TP +.BI "-l " N ", --lives " N +Limit number of lives to +.IR N . +If this option is not specified the number of lives will be infinite. + +.TP +.B -m, --metaserver +Register server with the +.I Metaserver +(currently) at +.BR codespeak.net:8050 . +This makes your server visible to everybody, and also facilitates +joining through a fascistic firewall. + +.TP +.B --port +.IR TYPE = N +Sets default listening ports. If type is +.B LISTEN +, sets the game server port to +.IR N . +The game server port is chosen randomly by default. If the type is +.B HTTP +, sets the http server port to +.IR N . +The http server port defaults to +.BR 8000 . +Another port will be chosen if +none was specified and +.B 8000 +is already in use. The server also listens to +.B UDP +ping on port +.BR 8056 . + +.TP +.BI "-s " N ", --step " N +Increase board number with +.I N +when a board is completed. Defaults to 1. see also the +.B -b +option. + +.SH OUTPUT +The server outputs helpful debug information concerning the http and +game servers. + +.SH SEE ALSO +.BR BubBob.py (1) +.BR Client.py (1) +.BR python (1) diff --git a/http2/.cvsignore b/http2/.cvsignore new file mode 100644 index 0000000..5be8cea --- /dev/null +++ b/http2/.cvsignore @@ -0,0 +1,2 @@ +*.py[co] +config.txt diff --git a/http2/config.txt b/http2/config.txt new file mode 100644 index 0000000..0af71a3 --- /dev/null +++ b/http2/config.txt @@ -0,0 +1 @@ +{'queen': {}, '*': {'finalboard': '100', 'beginboard': '1', 'extralife': '50000', 'autoreset': 'n', 'lvlend': 'y', 'lifegainlimit': '1', 'metapublish': 'y', 'limit': 'n', 'file': 'levels/Levels.bin', 'time': '1567957746.48', 'lives': '3', 'stepboard': '1'}} diff --git a/http2/data/bab.png b/http2/data/bab.png new file mode 100644 index 0000000..b97b8fc Binary files /dev/null and b/http2/data/bab.png differ diff --git a/http2/data/baub.png b/http2/data/baub.png new file mode 100644 index 0000000..8d85660 Binary files /dev/null and b/http2/data/baub.png differ diff --git a/http2/data/beab.png b/http2/data/beab.png new file mode 100644 index 0000000..443d20a Binary files /dev/null and b/http2/data/beab.png differ diff --git a/http2/data/beb.png b/http2/data/beb.png new file mode 100644 index 0000000..8aa3368 Binary files /dev/null and b/http2/data/beb.png differ diff --git a/http2/data/biab.png b/http2/data/biab.png new file mode 100644 index 0000000..9de577d Binary files /dev/null and b/http2/data/biab.png differ diff --git a/http2/data/bib.png b/http2/data/bib.png new file mode 100644 index 0000000..c2ec110 Binary files /dev/null and b/http2/data/bib.png differ diff --git a/http2/data/biob.png b/http2/data/biob.png new file mode 100644 index 0000000..211e023 Binary files /dev/null and b/http2/data/biob.png differ diff --git a/http2/data/bob.png b/http2/data/bob.png new file mode 100644 index 0000000..f01ca02 Binary files /dev/null and b/http2/data/bob.png differ diff --git a/http2/data/boob.png b/http2/data/boob.png new file mode 100644 index 0000000..eef411a Binary files /dev/null and b/http2/data/boob.png differ diff --git a/http2/data/bub.png b/http2/data/bub.png new file mode 100644 index 0000000..22742de Binary files /dev/null and b/http2/data/bub.png differ diff --git a/http2/data/byb.png b/http2/data/byb.png new file mode 100644 index 0000000..716988b Binary files /dev/null and b/http2/data/byb.png differ diff --git a/http2/data/checked.png b/http2/data/checked.png new file mode 100644 index 0000000..df0201c Binary files /dev/null and b/http2/data/checked.png differ diff --git a/http2/data/close.png b/http2/data/close.png new file mode 100644 index 0000000..6d9534b Binary files /dev/null and b/http2/data/close.png differ diff --git a/http2/data/confirm.html b/http2/data/confirm.html new file mode 100644 index 0000000..928764d --- /dev/null +++ b/http2/data/confirm.html @@ -0,0 +1,34 @@ + + + + + +See you + + + +

Confirmation

+ +

%(count > 1 and ('There are %d clients'%count) or 'There is a client')s +still connected to your server.

+ +

Are you sure you want to stop the server now? +           + +

+ +
+ + + +
+ + +
+<<< Cancel +           +Yes, Really Quit >>> +
+
+ + diff --git a/http2/data/disabled.png b/http2/data/disabled.png new file mode 100644 index 0000000..922c394 Binary files /dev/null and b/http2/data/disabled.png differ diff --git a/http2/data/hat1.png b/http2/data/hat1.png new file mode 100644 index 0000000..b3684d1 Binary files /dev/null and b/http2/data/hat1.png differ diff --git a/http2/data/hat2.png b/http2/data/hat2.png new file mode 100644 index 0000000..6494580 Binary files /dev/null and b/http2/data/hat2.png differ diff --git a/http2/data/header.png b/http2/data/header.png new file mode 100644 index 0000000..c9a8566 Binary files /dev/null and b/http2/data/header.png differ diff --git a/http2/data/index.html b/http2/data/index.html new file mode 100644 index 0000000..08dcc2f --- /dev/null +++ b/http2/data/index.html @@ -0,0 +1,292 @@ + + + + + +The Bub's Brothers + + +  +
+ +%( +externaltarget = running and ' target="new"' or '' +)s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   
  Stop this program 
   
 The Bub's Brothers 
   
   
  + Web Home page - + Player Names & Teams - + Configuration       + 
   
  + + + + + +
New game + + + + + +
%( +if running: + print '
' + print '' % tim + print '' + print '' + print '

' + if juststarted: + print 'Server started!' + elif justconnected: + print 'Playing' + elif count == 0: + print 'No client is connected to your server at the moment.' + elif count == 1: + print 'One connected client.' + else: + print '%d connected clients.' % count + print '

' + print '

' % (running[0][0], running[0][1], self.httpport, tim), + print 'Join your own game now at %s:%s

' % ( + running[0]) +##if metapublish: +## import time +## print '

' % time.time() +## if self.globaloptions.metapublish == 'y': +## self.has_been_published = 1 +## metaquery = metaquery or [] +## metaquery.insert(0, 'desc='+fndesc) +## s = 'a=' + metapublish +## if s not in metaquery: +## metaquery.insert(0, s) +## print 'Register again', +## else: +## print 'Register (after all)', +## print 'your server to the SourceForge meta-server
' +## print '' % time.time() +## print 'Unregister your server', +## print '(it is unregistered automatically after some time', +## print 'when other people cannot find it, or', +## print 'if you stop it with the link Stop this program at the top of the page)

' +)s +
+ +%( +if self.Game: + print '' + else: + print 'value="Start a new game">' +else: + print 'You need the complete version to start a new game.
With this version you can only connect to existing servers and only over fast links!' +)s +
+
+
+
 
   
   
 %( +servers = self.getlocalservers()[:] + +found = [] +def show((addr, (info, ping)), found=found): + import socket + infolst = info.split(':') or ['?'] + if len(infolst) >= 3: + httpport = infolst.pop(2) + else: + httpport = 'off' + print '' % ( + addr[0], addr[1], httpport, tim) + host, port = addr + #host = socket.gethostbyaddr(host)[0] + if ping is not None: + infolst.append('ping: %dms' % int(ping*1000)) + if (addr, infolst[0]) == running: + infolst.append('this is your own server') + found.append(1) + infolst = map(htmlquote, infolst) + print '%s:%s playing %s' % ( + host, port, infolst[0]) + if len(infolst) > 1: + print ' (%s)' % ' · · '.join(infolst[1:]) + +if servers is None: + rowspan = 1 +else: + rowspan = (len(servers) or 1)+1 +)s + + + ' + for s in servers[1:]: + print '' + print ' +
+ + Local games + + +%( +if servers is not None: + if servers: + show(servers[0]) + else: + print "(no server found)" + print '
' + show(s) + print '
' +)s + + + +
+%( +if running and not found: + import hostchooser, gamesrv + if gamesrv.displaysockport(gamesrv.openpingsocket()) != hostchooser.UDP_PORT: + print '

Note: your server does not appear in this list' + print 'because the UDP port %d is already in use (is another Bub & Bob server running on this machine?).' % hostchooser.UDP_PORT + print 'Use the full server address %s:%s

' % running[0] +)s +
+ + +
+
+

+ + Or connect to server: + + (host or host:port) + +

+
+
 
   
   
  + + + + + +
Internet games + + + + + +

Go to the Internet servers page

+

Don't forget to give a name + to your dragons before you join a server! +

+
+
 
  
   
+ + + + diff --git a/http2/data/lbab.png b/http2/data/lbab.png new file mode 100644 index 0000000..24fd915 Binary files /dev/null and b/http2/data/lbab.png differ diff --git a/http2/data/lbeb.png b/http2/data/lbeb.png new file mode 100644 index 0000000..a04a9e0 Binary files /dev/null and b/http2/data/lbeb.png differ diff --git a/http2/data/lbib.png b/http2/data/lbib.png new file mode 100644 index 0000000..191142a Binary files /dev/null and b/http2/data/lbib.png differ diff --git a/http2/data/lbiob.png b/http2/data/lbiob.png new file mode 100644 index 0000000..afc809f Binary files /dev/null and b/http2/data/lbiob.png differ diff --git a/http2/data/name.html b/http2/data/name.html new file mode 100644 index 0000000..dab24f9 --- /dev/null +++ b/http2/data/name.html @@ -0,0 +1,189 @@ + + + + + +Name Bub's Brothers + + +  + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%( +buttons = { +0: """ +""", +1: """ +"""} +for id, img, bgcolor in [ + (0, 'bub.png', '#c0ffc0'), + (1, 'bob.png', '#c0c0ff'), + (2,'boob.png', '#ffc0c0'), + (3, 'beb.png', '#ffc000'), + (4,'biob.png', '#ffff00'), + (5, 'bab.png', '#c04040'), + (6, 'bib.png', '#c0c0c0'), + (7,'baub.png', '#fc60ff'), + (8,'beab.png', '#4fa4ff'), + (9,'biab.png', '#0ab237'), + ]: + keyid = "player%d" % id + playername = options.get(keyid) or '' + playername = playername.strip() + for team in [1,2]: + if playername.endswith('(%d)' % team): + playername = playername[:-3].strip() + break + else: + team = 'off' + def nameval(value, team=team, teamid="team%d" % id): + s = 'value="%s"' % (value,) + if team == value: + s += ' selected' + return s + print """ + + + + + + + +""" % (bgcolor, img, bgcolor, keyid, playername, + bgcolor, id, nameval('off'), nameval(1), nameval(2)) +)s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   
   
   
   
 Player Names 
   
   
  + Back to the main page + 
   
    + +  
   
  +    +    + + 
   
   
   
   
   
  
   
+ + +
+
+ + + diff --git a/http2/data/new.html b/http2/data/new.html new file mode 100644 index 0000000..1709c8d --- /dev/null +++ b/http2/data/new.html @@ -0,0 +1,271 @@ + + + + + +New Bub's Brothers Server + + +  + +
+ + +%( +def nameval(name, value, default=0, options=options): + s = getattr(options, name) + if s == value or (default and not s): + s = 'checked ' + else: + s = '' + return s + 'name="%s" value="%s"' % (name, value) +)s + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%( +if running: print ''' + + + + + +''')s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   
   
   
   
 New Server 
   
   
  + Back to the main page + 
   
  + + + + + +
Level file + + + + + +
+
+
+
 
  + + + + + + +
Levels to play + Start at level +and go on ... + + +  to the end of the level file
+  to level
+ skipping levels: + +
+
 
  + + + + + +
Limited lives +  no limited lives --- just run for points!
+  limit to lives (with an extra life for each points)
+  limit lifegain to max. life per level +
+
 
  + + + + + +
Permanent server +  Automatically restart the server after the end is reached, forever
+ Non-permanent servers time out after 2 hours of inactivity +
+
 
  + + + + + +
Internet game + + + + + +

 register the server on the Bub-'n-Bros meta-server, allowing it to appear on everybody's Internet Games list

+
+
 
   
  + + 
  +Note: this will replace the server already running on this machine. + 
   
   
   
   
   
  
   
+
+ + + diff --git a/http2/data/options.html b/http2/data/options.html new file mode 100644 index 0000000..8ded787 --- /dev/null +++ b/http2/data/options.html @@ -0,0 +1,285 @@ + + + + + +Settings - The Bub's Brothers + +  + +%( +def nameval(type, name, value=None, default=None, mangling=1, options=options): + if mangling: + name = mode.unique_id() + '_' + name + s = getattr(options, name) or default + checked = s == value + if type == 'option': + return 'option %svalue="%s"' % (checked and 'selected ' or '', value) + elif type == 'select': + return 'select name="%s"' % name + elif type == 'text': + return 'input type=text name="%s" value="%s"' % ( + name, htmlquote(s)) + else: + return 'input type=%s %sname="%s" value="%s"' % ( + type, checked and 'checked ' or '', name, value) + +def begingroup(text, fgcolor, bgcolor, lightbgcolor, img, nbitems): + global groupinfo + print '' + print '' % (nbitems or 1, bgcolor) + print '%s' % (fgcolor, text) + print '' + groupinfo = [lightbgcolor, bgcolor, fgcolor, 0, 0, img] + if not nbitems: + begingroupitem() + endgroupitem() +def begingroupitem(highlight=0): + if groupinfo[4]: + print '' + groupinfo[4] += 1 + groupinfo[3] = highlight + print '' % groupinfo[highlight] + print '' +def endgroupitem(): + print '
' + if groupinfo[3]: + print '' % groupinfo[-1] + print '
' + print '' +def endgroup(): + pass + +def beginmode(): + highlight = mode in currentmodes + begingroupitem(highlight) + + print '' + err = mode.imperror() + if highlight: + url = None + err = err or "selected" + print 'selected' + elif err: + url = None + print '%s' % err + else: + url = "options.html?%s=%s&savetime=%s" % (mode.prefix, mode.name, + time.time()) + err = "select" + print 'select' % url + print '' + + print '' + if url: print '' % url, + print htmlquote(err), + if url: print '', + print '' + + print '', + print htmlquote(mode.name), + print '' + if mode.url: + print '    -   web site' % mode.url + print '
' + print htmlquote(mode.descr) + return highlight + +def beginmodeoptions(): + print '
' + print '
' + +def endmodeoptions(): + print '' + print '' + print '
' + +def endmode(): + print '' + endgroupitem() + +def modeitems(modelist): + global mode + for mode in modelist: + if beginmode(): + txt = mode.htmloptionstext(nameval) + if txt: + beginmodeoptions() + print txt + endmodeoptions() + endmode() +)s + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   
   
   
   
 Settings 
   
   
  + Technical documentation - + Back to the main page       + 
   
  + +
+ + + + + +%( +graphicmodes = self.graphicmodeslist() +currentmodes = self.localmodes() +begingroup('Display driver', '#800000', '#FFC000', '#C0C0C0', + 'lbeb.png', len(graphicmodes)) +modeitems(graphicmodes) +endgroup() +)s + + + + + +%( +soundmodes = self.soundmodeslist() +java = graphicmodes[0] in currentmodes +begingroup('Sound driver', '#800000', '#FFFF00', '#C0C0C0', + 'lbiob.png', java and 1 or len(soundmodes)) +if java: + begingroupitem(1) + print '' + endgroupitem() +else: + modeitems(soundmodes) +endgroup() +)s + + + +%( +begingroup('Network options', '#004000', '#80FF00', None, 'lbib.png', 1) +begingroupitem(1) +)s + +%( +endgroupitem() +endgroup() +)s + +
  
Java Applet always does sounds, but', + print 'background music is not implemented
  
+ + + + +
+

Network ports are automatically assigned, but you can optionally choose fixed + ones and let them in through your firewall. Clients using the + "Internet games" meta-server can usually connect even through firewalls. + Moreover servers can re-route UDP traffic to clients behind firewalls over TCP. +%( +if java: + print "These settings don't apply to the Java applet." +)s

+ +

TCP game server port: <%(nameval("text", "port_LISTEN", default="", mangling=0))s>
+ HTTP server port: <%(nameval("text", "port_HTTP", default="", mangling=0))s>

+ +

Client incoming UDP port (or host:port if redirected): <%(nameval("text", "port_CLIENT", default="", mangling=0))s>
+ <%(nameval("radio", "datachannel", "ucp", mangling=0))s>always UDP + <%(nameval("radio", "datachannel", "tcp", mangling=0))s>no UDP, only TCP + <%(nameval("radio", "datachannel", "auto", default="auto", mangling=0))s>Auto-detect
+ Client incoming TCP port (metaserver-directed back-connections): <%(nameval("text", "port_BACK", default="", mangling=0))s>

+
+ Help!

+ +
+
+
+ +
 
   
  +
+ + +
+
 
   
+ + + diff --git a/http2/data/sfbob.png b/http2/data/sfbob.png new file mode 100644 index 0000000..388d446 Binary files /dev/null and b/http2/data/sfbob.png differ diff --git a/http2/data/sfbub.png b/http2/data/sfbub.png new file mode 100644 index 0000000..142f3b3 Binary files /dev/null and b/http2/data/sfbub.png differ diff --git a/http2/data/stop.html b/http2/data/stop.html new file mode 100644 index 0000000..464ed36 --- /dev/null +++ b/http2/data/stop.html @@ -0,0 +1,101 @@ + + + + + +See you + + + +
+ + + + + + +
See you !   
+ +
+
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  
   
  +I hope you enjoyed this game! +

+http://bub-n-bros.sourceforge.net +
 
   
  +Almost all sprite images, sounds, background musics and +some of the levels are directly taken from the MacOS version of +Bub & Bob 1 by McSebi, and redistributed with his gracious +permission. +Most graphics have been improved or remade by David Gowers. + +

+ http://www.mcsebi.de +

+ +
 
   
 
+

Authors

+ +
    +
  • Programming: Armin Rigo +
  • Art: David Gowers, based on graphics from McSebi +
  • Levels: Gio & Odie & Michel-Stéphane & Armin +
  • Special thanks: Odie & Brachamutanda +
  • Beta-testers: IMA Connection +
+ +
 
   
+ + + diff --git a/http2/data/unchecked.png b/http2/data/unchecked.png new file mode 100644 index 0000000..0b20e95 Binary files /dev/null and b/http2/data/unchecked.png differ diff --git a/http2/data/wave1.png b/http2/data/wave1.png new file mode 100644 index 0000000..3424216 Binary files /dev/null and b/http2/data/wave1.png differ diff --git a/http2/data/wave2.png b/http2/data/wave2.png new file mode 100644 index 0000000..758f47d Binary files /dev/null and b/http2/data/wave2.png differ diff --git a/http2/data/wave3.png b/http2/data/wave3.png new file mode 100644 index 0000000..93fd6d3 Binary files /dev/null and b/http2/data/wave3.png differ diff --git a/http2/header.png b/http2/header.png new file mode 100644 index 0000000..c9a8566 Binary files /dev/null and b/http2/header.png differ diff --git a/http2/httppages.py b/http2/httppages.py new file mode 100644 index 0000000..3f90093 --- /dev/null +++ b/http2/httppages.py @@ -0,0 +1,705 @@ +import os, sys, random +from cStringIO import StringIO +import socket, time + +PLAYERNAMES = ['Bub', 'Bob', 'Boob', 'Beb', + 'Biob', 'Bab', 'Bib', + 'Baub', 'Beab', 'Biab'] + +try: + FILE = __file__ +except NameError: + FILE = sys.argv[0] +LOCALDIR = os.path.abspath(os.path.dirname(FILE)) + +sys.path.insert(0, os.path.abspath(os.path.join(LOCALDIR, os.pardir))) +sys.path.insert(0, os.path.abspath(os.path.join(LOCALDIR, os.pardir,'common'))) +import gamesrv, httpserver, hostchooser +from metaserver import metaclient +from httpserver import HTTPRequestError + + +class Options: + def __init__(self, dict={}): + self.update(dict) + def dict(self): + return self.__dict__.copy() + def update(self, dict): + self.__dict__.update(dict) + def copy(self): + return Options(self.__dict__) + def clear(self): + self.__dict__.clear() + def __getattr__(self, attr): + if not attr.startswith('_'): + return None + else: + raise AttributeError, attr + + +class PageServer: + CONFIGFILE = 'config.txt' + localservers = None + + def __init__(self, Game): + self.Game = Game + self.seed = hex(random.randrange(0x1000, 0x10000)) + #self.unique_actions = {} + self.localhost = gamesrv.HOSTNAME + self.filename = os.path.join(LOCALDIR, self.CONFIGFILE) + data = self.loadoptionfile() + self.globaloptions = Options(data.get('*', {})) + self.localoptions = Options(data.get(self.localhost, {})) + self.reloadports() + #self.inetserverlist = None + #self.inetservers = {} + #self.has_been_published = 0 + + def registerpages(self): + prefix = '%s/' % self.seed + #httpserver.register('controlcenter.html', self.controlcenterloader) + httpserver.register(prefix, self.indexloader) + httpserver.register(prefix+'index.html', self.indexloader) + #httpserver.register(prefix+'list.html', self.listloader) + httpserver.register(prefix+'new.html', self.newloader) + httpserver.register(prefix+'run.html', self.runloader) + httpserver.register(prefix+'stop.html', self.stoploader) + httpserver.register(prefix+'join.html', self.joinloader) + #httpserver.register(prefix+'register.html',self.registerloader) + httpserver.register(prefix+'options.html', self.optionsloader) + httpserver.register(prefix+'name.html', self.nameloader) + for fn in os.listdir(os.path.join(LOCALDIR, 'data')): + path = prefix + fn + if not httpserver.is_registered(path): + httpserver.register(path, httpserver.fileloader( + os.path.join(LOCALDIR, 'data', fn))) + + def opensocket(self): + hs = gamesrv.openhttpsocket() + if hs is None: + return 0 + self.httpport = port = gamesrv.displaysockport(hs) + self.indexurl = 'http://127.0.0.1:%d/%s/' % (port, self.seed) + if self.Game: + print self.Game.FnDesc, + print 'server is ready at', self.indexurl + return 1 + + def getlocalservers(self): + if self.localservers is None: + self.searchlocalservers() + return self.localservers + + def searchlocalservers(self): + servers = hostchooser.find_servers().items() + servers = filter(self.filterserver, servers) + servers.sort() + self.localservers = servers + +## def parse_inetserv(self, s): +## try: +## host, port, udpport, httpport = s.split(':') +## return host, int(port), int(udpport) +## except (ValueError, IndexError): +## return None, None, None + +## def getinetservers(self): +## if self.inetserverlist is None: +## return None +## result = [] +## for s in self.inetserverlist: +## host, port, udpport = self.parse_inetserv(s) +## addr = host, port +## if addr in self.inetservers: +## result.append((addr, self.inetservers[addr])) +## return result + +## def setinetserverlist(self, lst): +## self.inetserverlist = lst + +## def checkinetserverlist(self): +## ulist = [] +## for s in self.inetserverlist: +## host, port, udpport = self.parse_inetserv(s) +## if host is not None: +## ulist.append((host, udpport)) +## srvs = hostchooser.find_servers(ulist, delay=0.8) +## self.inetservers = {} +## for srv in srvs.items(): +## if not self.filterserver(srv): +## continue +## (host, port), info = srv +## try: +## host = socket.gethostbyaddr(host)[0] +## except socket.error: +## pass +## self.inetservers[host, port] = info +## #print 'hostchooser:', self.inetserverlist, '->', self.inetservers + + def filterserver(self, ((host, port), info)): + for c in host+str(port): + if c not in "-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz": + return 0 + return 1 + +## def statusservers(self): +## result = [], [] +## for s in self.inetserverlist: +## host, port, udpport = self.parse_inetserv(s) +## addr = host, port +## found = addr in self.inetservers +## result[found].append(s) +## return result + + def loadoptionfile(self): + try: + f = open(self.filename, 'r') + data = f.read().strip() + f.close() + except IOError: + data = None + return eval(data or '{}', {}, {}) + + def saveoptions(self): + data = self.loadoptionfile() + data['*'] = self.globaloptions.dict() + data[self.localhost] = self.localoptions.dict() + try: + f = open(self.filename, 'w') + print >> f, `data` + f.close() + except IOError, e: + print >> sys.stderr, "! Cannot save config file: " + str(e) + + def reloadports(self): + import msgstruct + msgstruct.PORTS.clear() + for key, value in self.localoptions.dict().items(): + if key.startswith('port_'): + key = key[5:] + if key == 'CLIENT' and type(value) == str and ':' in value: + udphostname, value = value.split(':') + msgstruct.PORTS['sendudpto'] = udphostname + try: + value = int(value) + except: + continue + msgstruct.PORTS[key] = value + + def startgame(self): + self.reloadports() + options = self.globaloptions + kwds = {} + if options.beginboard is not None: + kwds['beginboard'] = int(options.beginboard) + if options.lvlend is not None and options.lvlend.startswith('n'): + kwds['finalboard'] = int(options.finalboard) + if options.stepboard is not None: + kwds['stepboard'] = int(options.stepboard) + if options.limit is not None and options.limit.startswith('y'): + kwds['limitlives'] = int(options.lives) + if options.limitlifegain is not None: + kwds['lifegainlimit'] = int(options.lifegainlimit) + if options.extralife is not None: + kwds['extralife'] = int(options.extralife) + if options.autoreset is not None: + kwds['autoreset'] = options.autoreset.startswith('y') + if options.metapublish is not None: + kwds['metaserver'] = options.metapublish.startswith('y') + self.Game(options.file, **kwds) + + ### loaders ### + + def metaserverpage(self, headers): + metaserver_url = metaclient.METASERVER_URL + myhost = my_host(headers) + joinurl = quote_plus('%s/%s' % (myhost, self.seed)) + return metaserver_url + '?join=%s&time=%s' % (joinurl, time.time()) + + def mainpage(self, headers, juststarted=0, justconnected=0): + running = my_server() + count = len(gamesrv.clients) + tim = time.time() + #if running: + # metapublish = my_server_meta_address() + # fndesc = quote_plus(gamesrv.game.FnDesc) + #else: + # metapublish = None + return httpserver.load(os.path.join(LOCALDIR, 'data', 'index.html'), + 'text/html', locals=locals()) + + def indexloader(self, headers, cheat=[], **options): + if cheat: + import __builtin__ + for c in cheat: + getattr(__builtin__, '__cheat')(c) + else: + self.localservers = None + return self.mainpage(headers, juststarted=('juststarted' in options)) + + def controlcenterloader(self, headers, **options): + host = headers['remote host'] + host = socket.gethostbyname(host) + if host != '127.0.0.1': + raise HTTPRequestError, "Access denied" + return None, self.indexurl + +## def listloader(self, headers, s=[], **options): +## self.setinetserverlist(s) +## self.checkinetserverlist() +## query = [] +## missing, found = self.statusservers() +## for s in missing: +## query.append('d=' + s) +## for s in found: +## query.append('a=' + s) +## return self.mainpage(headers, query) + + def newloader(self, headers, **options): + if not self.Game: + raise HTTPRequestError, "Complete bub-n-bros installation needed" + locals = { + 'Game': self.Game, + 'options': self.globaloptions, + 'running': gamesrv.game is not None, + } + return httpserver.load(os.path.join(LOCALDIR, 'data', 'new.html'), + 'text/html', locals=locals) + + def runloader(self, headers, **options): + self.globaloptions.metapublish = 'n' + self.globaloptions.autoreset = 'n' + for key, value in options.items(): + if len(value) == 1: + setattr(self.globaloptions, key, value[0]) + self.saveoptions() + self.startgame() + return None, 'index.html?juststarted=%s' % time.time() + + def stoploader(self, headers, really=[], **options): + count = len(gamesrv.clients) + if count == 0 or really: + locals = { + 'self': self, + #'metaserver': METASERVER, + #'metapublish': gamesrv.game and my_server_meta_address(), + #'localdir': LOCALDIR, + } + gamesrv.closeeverything() + return httpserver.load(os.path.join(LOCALDIR, 'data', 'stop.html'), + 'text/html', locals=locals) + else: + locals = { + 'count': count, + } + return httpserver.load(os.path.join(LOCALDIR, 'data', 'confirm.html'), + 'text/html', locals=locals) + +## def registerloader(self, headers, a=[], d=[], **options): +## if a: # the lists 'a' and 'd' contain dummies !! +## self.globaloptions.metapublish = 'y' +## self.has_been_published = 1 +## kwd = 'a' +## else: +## self.globaloptions.metapublish = 'n' +## kwd = 'd' +## url = "%s?cmd=register&%s=%s" % (METASERVER, +## kwd, my_server_meta_address()) +## if a and gamesrv.game: +## url += '&desc=' + quote_plus(gamesrv.game.FnDesc) +## return None, url + + def joinloader(self, headers, host=[], port=[], httpport=[], + m=[], **options): + args = self.buildclientoptions() + assert len(host) == 1 + host = host[0] + if len(port) == 1: + port = port[0] + else: + try: + host, port = host.split(':') + except: + port = None + if args is None: + # redirect to the Java applet + try: + httpport = int(httpport[0]) + except (ValueError, IndexError): + if port: + raise HTTPRequestError, "This server is not running HTTP." + else: + raise HTTPRequestError, "Sorry, I cannot connect the Java applet to a server using this field." + return None, 'http://%s:%s/' % (host, httpport) + + # now is a good time to generate the color files if we can + file = os.path.join(LOCALDIR, os.pardir, 'bubbob', 'images', + 'buildcolors.py') + if os.path.exists(file): + g = {'__name__': '__auto__', '__file__': file} + execfile(file, g) + + if port: + address = '%s:%s' % (host, port) + else: + address = host + nbclients = len(gamesrv.clients) + script = os.path.join(LOCALDIR, os.pardir, 'display', 'Client.py') + script = no_quote_worries(script) + if m: + args.insert(0, '-m') + args = [script] + args + [address] + schedule_launch(args) + if m: + time.sleep(0.5) + s = 'Connecting to %s.' % address + return None, self.metaserverpage(headers) + '&head=' + quote_plus(s) + #elif my_server_address() == address: + # endtime = time.time() + 3.0 + # while gamesrv.recursiveloop(endtime, []): + # if len(gamesrv.clients) > nbclients: + # break + return self.mainpage(headers, justconnected=1) + + def optionsloader(self, headers, reset=[], savetime=[], **options): + if reset: + self.localoptions.clear() + self.globaloptions.clear() + self.saveoptions() + elif savetime: + self.localoptions.port_CLIENT = None + self.localoptions.port_LISTEN = None + self.localoptions.port_HTTP = None + for key, value in options.items(): + setattr(self.localoptions, key, value[0]) + self.saveoptions() + locals = { + 'self' : self, + 'options': self.localoptions, + } + return httpserver.load(os.path.join(LOCALDIR, 'data', 'options.html'), + 'text/html', locals=locals) + + def nameloader(self, headers, **options): + MAX = len(PLAYERNAMES) + if options: + anyname = None + for id in range(MAX): + keyid = 'player%d' % id + if keyid in options: + value = options[keyid][0] + anyname = anyname or value + teamid = 'team%d' % id + if teamid in options: + team = options[teamid][0] + if len(team) == 1: + value = '%s (%s)' % (value, team) + setattr(self.localoptions, keyid, value) + if 'c' in options: + for id in range(MAX): + keyid = 'player%d' % id + try: + delattr(self.localoptions, keyid) + except AttributeError: + pass + if 'f' in options: + for id in range(MAX): + keyid = 'player%d' % id + if not getattr(self.localoptions, keyid): + setattr(self.localoptions, keyid, + anyname or PLAYERNAMES[id]) + else: + anyname = getattr(self.localoptions, keyid) + self.saveoptions() + if 's' in options: + return self.mainpage(headers) + locals = { + 'options': self.localoptions.dict(), + } + return httpserver.load(os.path.join(LOCALDIR, 'data', 'name.html'), + 'text/html', locals=locals) + + def graphicmodeslist(self): + try: + return self.GraphicModesList + except AttributeError: + import display.modes + self.GraphicModesList = display.modes.graphicmodeslist() + javamode = display.modes.GraphicMode( + 'java', 'Java Applet (for Java browsers)', []) + javamode.low_priority = 1 + javamode.getmodule = lambda : None + self.GraphicModesList.insert(0, javamode) + return self.GraphicModesList + + def soundmodeslist(self): + try: + return self.SoundModesList + except AttributeError: + import display.modes + self.SoundModesList = display.modes.soundmodeslist() + return self.SoundModesList + + def localmodes(self): + import display.modes + currentmodes = [] + options = self.localoptions + for name, lst in [(options.dpy_, self.graphicmodeslist()), + (options.snd_, self.soundmodeslist())]: + try: + mode = display.modes.findmode(name, lst) + except KeyError: + try: + mode = display.modes.findmode(None, lst) + except KeyError, e: + print >> sys.stderr, str(e) # no mode! + mode = None + currentmodes.append(mode) + return currentmodes + + def buildclientoptions(self): + dpy, snd = self.localmodes() + if dpy.getmodule() is None: + return None # redirect to the Java applet + if dpy is None or snd is None: + raise HTTPRequestError, "No installed graphics or sounds drivers. See the settings page." + options = self.localoptions + result = ['--cfg='+no_quote_worries(self.filename)] + for key, value in options.dict().items(): + if key.startswith('port_') and value: + result.append('--port') + result.append('%s=%s' % (key[5:], value)) + if options.datachannel == 'tcp': result.append('--tcp') + if options.datachannel == 'udp': result.append('--udp') + if options.music == 'no': result.append('--music=no') + for optname, mode in [('--display', dpy), + ('--sound', snd)]: + result.append(optname + '=' + mode.name) + uid = mode.unique_id() + '_' + for key, value in options.dict().items(): + if key.startswith(uid): + result.append('--%s=%s' % (key[len(uid):], value)) + return result + +def my_host(headers): + return headers.get('host') or httpserver.my_host() + +def my_server(): + if gamesrv.game: + s = gamesrv.opentcpsocket() + return ((gamesrv.HOSTNAME, gamesrv.displaysockport(s)), + gamesrv.game.FnDesc) + else: + return None + +def my_server_address(): + running = my_server() + if running: + (host, port), info = running + return '%s:%d' % (host, port) + else: + return None + +##def my_server_meta_address(): +## s = gamesrv.opentcpsocket() +## ps = gamesrv.openpingsocket() +## hs = gamesrv.openhttpsocket() +## fullname = gamesrv.HOSTNAME +## try: +## fullname = socket.gethostbyaddr(fullname)[0] +## except socket.error: +## pass +## return '%s:%s:%s:%s' % (fullname, +## gamesrv.displaysockport(s), +## gamesrv.displaysockport(ps), +## gamesrv.displaysockport(hs)) + +##def meta_register(): +## # Note: this tries to open a direct HTTP connection to the meta-server +## # which may not work if the proxy is not configured in $http_proxy +## try: +## import urllib +## except ImportError: +## print >> sys.stderr, "cannot register with the meta-server: Python's urllib missing" +## return +## print "registering with the meta-server...", +## sys.stdout.flush() +## addr = my_server_meta_address() +## try: +## f = urllib.urlopen('%s?a=%s&desc=%s' % ( +## METASERVER, addr, quote_plus(gamesrv.game.FnDesc))) +## f.close() +## except Exception, e: +## print +## print >> sys.stderr, "cannot contact the meta-server (check $http_proxy):" +## print >> sys.stderr, "%s: %s" % (e.__class__.__name__, e) +## else: +## print "ok" +## unregister_at_exit(addr) + +##def meta_unregister(addr): +## import urllib +## print "unregistering from the meta-server...", +## sys.stdout.flush() +## try: +## f = urllib.urlopen(METASERVER + '?d=' + addr) +## f.close() +## except Exception, e: +## print "failed" +## else: +## print "ok" + +##def unregister_at_exit(addr, firsttime=[1]): +## if firsttime: +## import atexit +## atexit.register(meta_unregister, addr) +## del firsttime[:] + +QuoteTranslation = {} +for c in ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789' '_.-'): + QuoteTranslation[c] = c +del c +QuoteTranslation[' '] = '+' + +def quote_plus(s): + """Quote the query fragment of a URL; replacing ' ' with '+'""" + getter = QuoteTranslation.get + return ''.join([getter(c, '%%%02X' % ord(c)) for c in s]) + + +def main(Game, save_url_to=None, quiet=0): + #gamesrv.openpingsocket(0) # try to reserve the standard UDP port + srv = PageServer(Game) + srv.registerpages() + if not srv.opensocket(): + print >> sys.stderr, "server aborted." + sys.exit(1) + if quiet: + if Game: + Game.Quiet = 1 + import stdlog + f = stdlog.LogFile() + if f: + print "Logging to", f.filename + sys.stdout = sys.stderr = f + if save_url_to: + data = srv.indexurl + '\n' + def try_to_unlink(fn): + try: + os.unlink(fn) + except: + pass + import atexit + atexit.register(try_to_unlink, save_url_to) + try: + fno = os.open(save_url_to, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, + 0600) + if os.write(fno, data) != len(data): + raise OSError + os.close(fno) + except: + f = open(save_url_to, 'w') + f.write(data) + f.close() + #if webbrowser: + # srv.launchbrowser() + + +# ____________________________________________________________ +# Hack hack hack - workaround for the fact that on Windows +# the socket is inherited by the subprocess, which is quite +# bad because it keeps the browser-server connexion alive +# and the browser gets confused + + +def schedule_launch(args): + httpserver.actions_when_finished.append(lambda args=args: launch(args)) + +def launch(args): + # platform-specific hacks + print 'Running client -> ', ' '.join(args) + if 0: # OLD CODE sys.platform == 'darwin': # must start as a UI process + import tempfile + cmdname = tempfile.mktemp('_BubBob.py') + f = open(cmdname, 'w') + print >> f, 'import sys, os' + print >> f, 'try: os.unlink(%r)' % cmdname + print >> f, 'except OSError: pass' + print >> f, 'sys.argv[:] = %r' % (args,) + print >> f, '__file__ = %r' % cmdname + print >> f, 'execfile(%r)' % args[0] + f.close() + os.system('/usr/bin/open -a PythonLauncher "%s"' % cmdname) + else: + args.insert(0, sys.executable) + # try to close the open fds first + if hasattr(os, 'fork'): + try: + from resource import getrlimit, RLIMIT_NOFILE, error + except ImportError: + pass + else: + try: + soft, hard = getrlimit(RLIMIT_NOFILE) + except error: + pass + else: + if os.fork(): + return # in parent -- done, continue + # in child + for fd in range(3, min(16384, hard)): + try: + os.close(fd) + except OSError: + pass + os.execv(args[0], args) + # this point should never be reached + # fall-back + # (quoting sucks on Windows) ** 42 + if sys.platform == 'win32': + args[0] = '"%s"' % (args[0],) + if hasattr(os, 'P_DETACH'): + mode = os.P_DETACH + elif hasattr(os, 'P_NOWAIT0'): + mode = os.P_NOWAIT0 + else: + mode = os.P_NOWAIT + os.spawnv(mode, sys.executable, args) + +if sys.platform != "win32": + def no_quote_worries(s): + return s +else: + def no_quote_worries(s): # quoting !&?+*:-( + s = os.path.normpath(os.path.abspath(s)) + absroot = os.path.join(LOCALDIR, os.pardir) + absroot = os.path.normpath(os.path.abspath(absroot)) + ROOTDIR = os.curdir + while os.path.normpath(os.path.abspath(ROOTDIR)) != absroot: + if ROOTDIR == os.curdir: + ROOTDIR = os.pardir + else: + ROOTDIR = os.path.join(ROOTDIR, os.pardir) + if len(ROOTDIR) > 200: + # cannot find relative path! try with absolute one anyway + ROOTDIR = absroot + break + assert s.startswith(absroot) + if absroot.endswith(os.sep): # 'C:\' + absroot = absroot[:-1] + assert s[len(absroot)] == os.sep + relpath = s[len(absroot)+1:] + result = os.path.join(ROOTDIR, relpath) + print "no_quote_worries %r => %r" % (s, result) + return result + + +if __name__ == '__main__': + if (len(sys.argv) != 3 or sys.argv[1] != '--quiet' or + not sys.argv[2].startswith('--saveurlto=')): + print >> sys.stderr, "This script should only be launched by BubBob.py." + sys.exit(2) + main(None, sys.argv[2][len('--saveurlto='):], quiet=1) + gamesrv.mainloop() diff --git a/http2/sf/bb12.py b/http2/sf/bb12.py new file mode 100755 index 0000000..7ea6e6e --- /dev/null +++ b/http2/sf/bb12.py @@ -0,0 +1,274 @@ +#! /usr/bin/python + +import cgi, os, string, time +form = cgi.FieldStorage() + +def txtfilter(s): + l = filter(lambda c: c in "!$*,-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}", s) + return string.join(l, '') + +def identity(s): + return s + +def fieldlist(name, filter=txtfilter): + result = [] + if form.has_key(name): + field = form[name] + if type(field) is type([]): + for f in field: + result.append(filter(f.value)) + else: + result.append(filter(field.value)) + return result + +def goodmatch(addr1, addr2): + l1 = string.split(addr1, '.') + if len(l1) >= 4: del l1[-1] + l2 = string.split(addr2, '.') + if len(l2) >= 4: del l2[-1] + return l1 == l2 + + +class Entry: + Notice = '' + FIELDS = [('server', 128), ('desc', 128), ('icon', 64), + ('orig', 64), ('dele1', 64), ('dele2', 64)] + + def __init__(self, f): + self.pos = f.tell() + for fname, flen in self.FIELDS: + s = f.read(flen) + if not s: + self.filled = 0 + break + setattr(self, fname, string.rstrip(s)) + else: + self.filled = 1 + + def __nonzero__(self): + return self.filled + + def write(self, f): + if not getattr(self, 'icon', ''): + try: + import random + lst = os.listdir('../htdocs/images') + except: + pass + else: + lst = filter(lambda x: x[-4:]=='.png' and 'A'<=x[0]<='Z', lst) + if lst: + self.icon = random.choice(lst) + f.seek(self.pos) + for fname, flen in self.FIELDS: + data = getattr(self, fname, '')[:flen] + f.write(data + ' '*(flen-len(data))) + + +def main(): + DATABASE = 'servers' + SIZEMAX = 65536 + Serv = 0 + Orig = 1 + Dele1 = 2 + REMOTE_ADDR = os.environ['REMOTE_ADDR'] + REMOTE_ID = REMOTE_ADDR + time.strftime('@%d%m', time.localtime(time.time())) + OPS = {} + + for srv in fieldlist('d'): + OPS[srv] = 'd' + for srv in fieldlist('a'): + OPS[srv] = 'a' + desc = (fieldlist('desc', identity) or [''])[0] + + f = open(DATABASE, 'r+b') + freelist = [] + published = [] + while 1: + e = Entry(f) + if not e: + break + if e.server: + if OPS.get(e.server) == 'a': + validdesc = desc and goodmatch(REMOTE_ADDR, e.orig) + if e.dele1 or e.dele2 or (validdesc and desc != e.desc): + e.dele1 = e.dele2 = '' # re-enable server + if validdesc: e.desc = desc + e.write(f) + del OPS[e.server] + elif OPS.get(e.server) == 'd': + if goodmatch(REMOTE_ADDR, e.orig) or ( + REMOTE_ID != e.dele1 and REMOTE_ID != e.dele2): + if goodmatch(REMOTE_ADDR, e.orig): + e.server = '' # remove server + elif e.dele1 == '': + e.dele1 = REMOTE_ID + elif e.dele2 == '': + e.dele2 = REMOTE_ID + else: + e.server = '' # remove server + e.write(f) + Entry.Notice = 'd' + if e.server: + published.append((e.pos, e)) + else: + freelist.append(e) + + for srv, action in OPS.items(): + if action == 'a': + if freelist: + e = freelist[-1] + del freelist[-1] + else: + f.seek(0, 2) + e = Entry(f) + if e.pos >= SIZEMAX: + raise Exception("Sorry, server database too big") + hostname = string.split(srv, ':')[0] + if '.' not in hostname: + Entry.Notice = 'Server hostname "%s" incomplete.' % hostname + else: + import socket + try: + result = socket.gethostbyaddr(hostname) + except socket.error, e: + Entry.Notice = ('%s: %s' % (hostname, e)) + else: + if result[0] == 'projects.sourceforge.net': # ???? + Entry.Notice = ('Server hostname "%s" does not exist.' % + hostname) + else: + e.server = srv + e.icon = '' + e.desc = desc + e.orig = REMOTE_ADDR + e.dele1 = e.dele2 = '' + e.write(f) + published.append((e.pos, e)) + Entry.Notice = 'a' + + f.close() + + published.sort() + return map(lambda (pos, e): e, published) + + +def publish_list(serverlist): + url = (fieldlist('url', identity) or ['http://127.0.0.1:8000'])[0] + query = [] + for s in fieldlist('redirected'): + query.append('redirected=' + s) + for s in serverlist: + query.append('s=' + txtfilter(s.server)) + url = url + '?' + string.join(query, '&') + for s in fieldlist('frag'): + url = url + '#' + s + print 'Content-Type: text/html' + print 'Location:', url + print + print '' + print 'Please click here to continue.' % url + print '' + +def publish_default(serverlist): + import htmlentitydefs + text_to_html = {} + for key, value in htmlentitydefs.entitydefs.items(): + text_to_html[value] = '&' + key + ';' + for i in range(32): + text_to_html[chr(i)] = '?' + def htmlquote(s, getter=text_to_html.get): + lst = [] + for c in s: lst.append(getter(c, c)) + return string.join(lst, '') + + REMOTE_ADDR = os.environ['REMOTE_ADDR'] + f = open('started.html', 'r') + header, row, footer = string.split(f.read(), '\\') + f.close() + import sys + print 'Content-Type: text/html' + print + sys.stdout.write(header % "List of registered Internet Servers") + counter = 0 + for s in serverlist: + if s.icon: + s1 = '' % s.icon + else: + s1 = '' + lst = string.split(txtfilter(s.server), ':') + hostname, port, udpport, httpport = (lst+['?','?','?','?'])[:4] + s2 = '%s' % hostname + try: + int(httpport) + except ValueError: + pass + else: + s2 = '%s:%s' % (hostname, httpport, + s2, port) + s2 = '' + s2 + '' + if s.desc: + s2 = s2 + '   playing  %s' % htmlquote(s.desc) + if goodmatch(REMOTE_ADDR, s.orig): + s2 = s2 + '      (if this server is dead, click here to remove it)' % s.server + sys.stdout.write(row % (s1, ('#C0D0D0', '#E0D0A8')[counter&1], s2)) + counter = counter + 1 + if not serverlist: + sys.stdout.write(row % ('', '#FFFFFF', 'There is no registered server at the moment.')) + sys.stdout.write(footer % "If your browser understands Java, you can click on a server to join the game. Note however that you will still need to install the whole Python client to benefit from the background musics, as this feature is missing from the Java client.

This list might contain already-dead servers; such 'zombies' disappear after some time.") + +def publish_raw(serverlist): + print 'Content-Type: text/plain' + print + print 'Raw list produced for', os.environ['REMOTE_ADDR'] + print + for s in serverlist: + print repr((s.server, s.desc, s.icon, s.orig)) + +def publish_img(serverlist): + import sys + print 'Content-Type: image/png' + print + f = open('sfbub.png', 'rb') + sys.stdout.write(f.read()) + f.close() + +def publish_register(serverlist): + if Entry.Notice == 'a': + banner = 'The game server is now registered to SourceForge.' + elif Entry.Notice == 'd': + banner = ('Server unregistered ' + 'from SourceForge.') + elif Entry.Notice == '': + if fieldlist('a'): + banner = "The game server is already registered to SourceForge." + elif fieldlist('d'): + banner = 'The game server was already absent from SourceForge.' + else: + publish_default(serverlist) + return + else: # errors + banner = ('%s

' % Entry.Notice + + 'If you are behind a firewall or NAT device (e.g. ADSL routers) you can still make your server reachable but it requires manual configuration. (Instructions not available yet -- sorry)') + f = open('started.html', 'r') + header, row, footer = string.split(f.read(), '\\') + f.close() + import sys + print 'Content-Type: text/html' + print + sys.stdout.write(header % banner) + sys.stdout.write(footer % 'Press Back to come back to the main page.') + + +try: + slist = main() + cmd = (fieldlist('cmd') or ['?'])[0] + publish = globals().get('publish_'+cmd, publish_default) + publish(slist) +except: + import traceback, sys + print "Content-Type: text/plain" + print + print "ERROR REPORT" + print + traceback.print_exc(file=sys.stdout) diff --git a/http2/sf/sfbub.png b/http2/sf/sfbub.png new file mode 100644 index 0000000..142f3b3 Binary files /dev/null and b/http2/sf/sfbub.png differ diff --git a/http2/sf/started.html b/http2/sf/started.html new file mode 100644 index 0000000..14c24e3 --- /dev/null +++ b/http2/sf/started.html @@ -0,0 +1,44 @@ + +The (old) Bub's Brothers on SourceForge + + + +

The (old) Bub's Brothers on SourceForge

+ +
+ +

This meta-server is now obsolete

+

It has never been too useful because nowadays everybody is behind firewalls and NAT translation devices (e.g. ADSL routers). So:

+

There is a much better meta-server somewhere else that works with the next version of Bub-n-bros. Upgrade to Bub-n-bros 1.3 and enjoy !

+

This page is left here just in case it is useful to someone, but you should really update if you want to enjoy a cool meta-server :-)

+ +




+ + + + + + + + + + + + + + + + + + +
   %s
  
  +\ + + + +\ +
%s       %s
+


+

%s

+

SourceForge.net Logo

+ diff --git a/java/.cvsignore b/java/.cvsignore new file mode 100644 index 0000000..539da74 --- /dev/null +++ b/java/.cvsignore @@ -0,0 +1 @@ +*.py[co] diff --git a/java/Makefile b/java/Makefile new file mode 100644 index 0000000..a24bc68 --- /dev/null +++ b/java/Makefile @@ -0,0 +1,2 @@ +pclient.class: pclient.java + javac -target 1.1 pclient.java diff --git a/java/pclient.java b/java/pclient.java new file mode 100644 index 0000000..7e75536 --- /dev/null +++ b/java/pclient.java @@ -0,0 +1,1196 @@ +import java.applet.*; +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.zip.*; +import java.lang.*; + + +public class pclient extends Applet { + + // Utilities + + public static String[] splitString(String s, char delim) { + // StringTokenizer drops empty tokens :-( + int count = 1; + int length = s.length(); + for (int i=0; i 0) { + int count = st.read(buffer, off, len); + if (count <= 0) + throw new IOException("unexpected end of data"); + off += count; + len -= count; + } + } + + public static Color makeColor(int color) { + return new Color(color & 0xFF, + (color >> 8) & 0xFF, + (color >> 16) & 0xFF); + } + + public static InputStream decompresser(byte[] data, int off, int len) { + return new InflaterInputStream(new ByteArrayInputStream(data, off, len)); + } + + /*class ArraySlice { + public byte[] array; + public int ofs, size; + ArraySlice(byte[] aarray, int aofs, int asize) { + array = aarray; + ofs = aofs; + size = asize; + } + }*/ + + public void debug(Throwable e) { + showStatus(e.toString()); + e.printStackTrace(); + } + + // Bitmaps and icons + + class Bitmap { + + int w, h; + public int[] pixelData; + + Bitmap(pclient client, InputStream st, int keycol) throws IOException { + String line = readLine(st); + if (!"P6".equals(line)) throw new IOException("not a P6 PPM image"); + while ((line = readLine(st)).startsWith("#")) + ; + String[] wh = splitString(line, ' '); + if (wh.length != 2) throw new IOException("invalid PPM image size"); + w = Integer.parseInt(wh[0]); + h = Integer.parseInt(wh[1]); + line = readLine(st); + if (!"255".equals(line)) + throw new IOException("not a 255-levels PPM image"); + + // over-allocate an extra uninitialized line at the bottom of the + // image to work around a bug in the MemoryImageSource constructor + pixelData = new int[w*(h+1)]; + int target = 0; + int w3 = 3*w; + byte[] lineBuffer = new byte[w3]; + for (int y=0; y>>"); + if (data.length >= 4 && pongMessage.equals(data[0])) { + InetAddress result; + if (data[2].length() == 0) { + result = inp.getAddress(); + } + else { + result = InetAddress.getByName(data[2]); + } + port = Integer.parseInt(data[3]); + showStatus("Connecting to "+data[1]+" at "+ + result.toString()+":"+Integer.toString(port)+"..."); + return new Socket(result, port); + } + else + throw new IOException("got an unexpected answer from " + + inp.getAddress().toString()); + } + + // Game state + + class Player { + public int pid; + public boolean playing; + public boolean local; + public Image icon; + public int xmin, xmax; + } + + class KeyName { + public String keyname; + public int keyid; + public Image[] keyicons; + public KeyName next; + public int newkeycode; + } + + public Player[] players = new Player[0]; + public KeyName keys = null; + public Hashtable keycodes = new Hashtable(); + public boolean taskbarfree = false; + public boolean taskbarmode = false; + public KeyName keydefinition_k = null; + public int keydefinition_pid; + public Image[] iconImages = new Image[0]; + public Bitmap[] bitmaps = new Bitmap[0]; + + public Player getPlayer(int id) { + if (id >= players.length) { + Player[] newply = new Player[id+1]; + System.arraycopy(players, 0, newply, 0, players.length); + players = newply; + } + if (players[id] == null) { + players[id] = new Player(); + players[id].pid = id; + players[id].playing = false; + players[id].local = false; + } + return players[id]; + } + + public Player nextPlayer(Player prev) { + int i; + for (i=prev.pid+1; i= iconImages.length) + return null; + else + return iconImages[ico]; + } + + public void setIcon(int ico, Image img) { + if (ico >= iconImages.length) { + Image[] newico = new Image[ico+1]; + System.arraycopy(iconImages, 0, newico, 0, iconImages.length); + iconImages = newico; + } + iconImages[ico] = img; + } + + public void setBitmap(int n, Bitmap bmp) { + if (n >= bitmaps.length) { + Bitmap[] newbmp = new Bitmap[n+1]; + System.arraycopy(bitmaps, 0, newbmp, 0, bitmaps.length); + bitmaps = newbmp; + } + bitmaps[n] = bmp; + } + + // Sprites + + class Sprite { + public int x, y, ico; + public Image bkgnd; + + public final boolean draw(pclient client, Image backBuffer, + Graphics backGC) { + Image iconImage = client.getIcon(ico); + if (iconImage == null) { + ico = -1; + return false; + } + int w = iconImage.getWidth(client); + int h = iconImage.getHeight(client); + + if (bkgnd == null || bkgnd.getWidth(client) != w || + bkgnd.getHeight(client) != h) { + bkgnd = client.createImage(w, h); + } + bkgnd.getGraphics().drawImage(backBuffer, -x, -y, client); + backGC.drawImage(iconImage, x, y, client); + //System.out.println("Draw at "+Integer.toString(x)+", "+ + // Integer.toString(y)); + return true; + } + + public final void erase(pclient client, Graphics backGC) { + if (ico != -1) { + //System.out.println("Erase at "+Integer.toString(x)+", "+ + // Integer.toString(y)); + backGC.drawImage(bkgnd, x, y, client); + } + } + } + + // Playfield + + class Playfield { + public static final int TASKBAR_HEIGHT = 48; + + public pclient client; + public int pfwidth, pfheight; + public Image backBuffer; + public Sprite[] sprites = new Sprite[0]; + public int numSprites = 0; + public byte[] pendingBuffer = new byte[SocketDisplayer.UDP_BUF_SIZE]; + public int pendingBufOfs = 0; + public int pendingBufLen = 0; + public byte[] spriteData = new byte[0]; + public int validDataLen = 0; + public Image tbCache; + public AudioClip[] samples = new AudioClip[0]; + public int[] playingSounds = new int[0]; + + Playfield(pclient aclient, int width, int height, Color bkgnd) { + client = aclient; + pfwidth = width; + pfheight = height; + backBuffer = createImage(width, height); + Graphics backGC = backBuffer.getGraphics(); + backGC.setColor(bkgnd); + backGC.fillRect(0, 0, width, height); + backGC.dispose(); + client.resize(width, height); + client.setBackground(bkgnd); + + int[] pixelData = new int[32*TASKBAR_HEIGHT]; + int target = 0; + for (int y=0; y= samples.length) { + AudioClip[] newclip = new AudioClip[key+5]; + System.arraycopy(samples, 0, newclip, 0, samples.length); + samples = newclip; + } + if (samples[key] == null) { + String filename = "sample.wav?code=" + + Integer.toString(key); + samples[key] = getAudioClip(getCodeBase(), filename); + } + else if (playingSounds.length > key && + playingSounds[key] > 0) { + currentSounds[key] = playingSounds[key] - 1; + } + else { + samples[key].play(); + currentSounds[key] = 4; + } + } + base += 6; + } + playingSounds = currentSounds; + pendingBufOfs = base; + pendingBufLen = buflen - base; + return old; + } + + public synchronized int fetchSprites() { + int valid; + int count = (pendingBufLen < validDataLen) ? pendingBufLen + : validDataLen; + + for (valid=0; valid spriteData.length) { + spriteData = new byte[pendingBufLen+90]; + valid = 0; + } + System.arraycopy(pendingBuffer, pendingBufOfs+valid, + spriteData, valid, pendingBufLen-valid); + return pendingBufLen; + } + + public void paint(Graphics g) { + int buflen = fetchSprites(); + byte[] buffer = spriteData; + int count = validDataLen / 6; + int base = count * 6; + int nspr = buflen / 6; + Image tback; + + if (nspr > sprites.length) { + Sprite[] newspr = new Sprite[nspr+15]; + System.arraycopy(sprites, 0, newspr, 0, sprites.length); + for (int i=sprites.length; i=count; i--) { + sprites[i].erase(client, backGC); + } + + // draw new sprites + validDataLen = pendingBufLen; + while (count < nspr) { + Sprite s = sprites[count++]; + int x = buffer[base+1]; + s.x = (x & 0xFF) | (((int) buffer[base ]) << 8); + int y = buffer[base+3]; + s.y = (y & 0xFF) | (((int) buffer[base+2]) << 8); + int ico = buffer[base+5]; + s.ico = (ico & 0xFF) | (((int) buffer[base+4]) << 8); + if (!s.draw(client, backBuffer, backGC)) { + if (base < validDataLen) + validDataLen = base; + //System.out.println(Integer.toString(s.x)+';'+ + // Integer.toString(s.y)+';'+ + // Integer.toString(s.ico)); + } + base += 6; + } + numSprites = count; + + if (client.taskbarmode) + tback = paintTaskbar(backGC); + else + tback = null; + + g.drawImage(backBuffer, 0, 0, client); + + if (tback != null) + eraseTaskbar(backGC, tback); + backGC.dispose(); + } + + public Image paintTaskbar(Graphics g) { + boolean animated = false; + int y0 = pfheight - TASKBAR_HEIGHT; + Image bkgnd = client.createImage(pfwidth, TASKBAR_HEIGHT); + bkgnd.getGraphics().drawImage(backBuffer, 0, -y0, client); + for (int i=0; i 0) { + int index = (int)(f*icons.length); + ico = icons[index % icons.length]; + animated = true; + } + y = y0 + (TASKBAR_HEIGHT-h)/2; + } + } + p.xmin = x; + p.xmax = x + w; + g.drawImage(ico, x, y, client); + } + + if (animated) + repaint(50); + return bkgnd; + } + + public void eraseTaskbar(Graphics g, Image bkgnd) { + int y0 = pfheight - TASKBAR_HEIGHT; + g.drawImage(bkgnd, 0, y0, client); + } + } + + // Socket listener + + class SocketListener extends Thread { + + public pclient client; + public Socket socket; + public InputStream socketInput; + public OutputStream socketOutput; + + public static final String MSG_WELCOME = "Welcome to gamesrv.py(3) !\n"; + public static final byte MSG_DEF_PLAYFIELD = (byte) 'p'; + public static final byte MSG_DEF_KEY = (byte) 'k'; + public static final byte MSG_DEF_ICON = (byte) 'r'; + public static final byte MSG_DEF_BITMAP = (byte) 'm'; + public static final byte MSG_DEF_SAMPLE = (byte) 'w'; + public static final byte MSG_DEF_MUSIC = (byte) 'z'; + public static final byte MSG_PLAY_MUSIC = (byte) 'Z'; + public static final byte MSG_FADEOUT = (byte) 'f'; + public static final byte MSG_PLAYER_JOIN = (byte) '+'; + public static final byte MSG_PLAYER_KILL = (byte) '-'; + public static final byte MSG_PLAYER_ICON = (byte) 'i'; + public static final byte MSG_PING = (byte) 'g'; + public static final byte MSG_PONG = (byte) 'G'; + public static final byte MSG_INLINE_FRAME = (byte) '\\'; + + public static final byte CMSG_KEY = (byte) 'k'; + public static final byte CMSG_ADD_PLAYER = (byte) '+'; + public static final byte CMSG_REMOVE_PLAYER= (byte) '-'; + public static final byte CMSG_UDP_PORT = (byte) '<'; + public static final byte CMSG_ENABLE_SOUND = (byte) 's'; + public static final byte CMSG_ENABLE_MUSIC = (byte) 'm'; + public static final byte CMSG_PING = (byte) 'g'; + public static final byte CMSG_PONG = (byte) 'G'; + public static final byte CMSG_PLAYER_NAME = (byte) 'n'; + + public void connectionClosed() throws IOException { + throw new IOException("connection closed"); + } + + public void protocolError() throws IOException { + throw new IOException("protocol error"); + } + + SocketListener(pclient aclient, Socket asocket) throws IOException { + setDaemon(true); + byte[] msgWelcome = MSG_WELCOME.getBytes("UTF8"); + + client = aclient; + socket = asocket; + socketInput = socket.getInputStream(); + socketOutput = socket.getOutputStream(); + + for (int i=0; i> 24); + n = value >> 16; + buffer[p++] = (byte)(((n&0x80) == 0) ? n&0x7F : n|0xFFFFFF80); + n = value >> 8; + buffer[p++] = (byte)(((n&0x80) == 0) ? n&0x7F : n|0xFFFFFF80); + n = value; + buffer[p++] = (byte)(((n&0x80) == 0) ? n&0x7F : n|0xFFFFFF80); + } + if (lastarg != null) { + for (int i=0; i= end) return -1; + byte msgcode = buffer[base++]; + int[] args = new int[typecodes]; + int repeatcount = 0; + int nargs = 0; + for (int i=0; i end) + return -1; + args[nargs++] = ((int) buffer[base++]) & 0xFF; + } + else if (c == (byte) 'l') { + if (base+4 > end) + return -1; + int n4 = buffer[base++]; + int n3 = buffer[base++]; n3 &= 0xFF; + int n2 = buffer[base++]; n2 &= 0xFF; + int n1 = buffer[base++]; n1 &= 0xFF; + int value = n1 | (n2<<8) | (n3<<16) | (n4<<24); + //System.out.println(n4); + //System.out.println(n3); + //System.out.println(n2); + //System.out.println(n1); + //System.out.println(value); + //System.out.println(); + args[nargs++] = value; + } + else if ((byte) '0' <= c && c <= (byte) '9') { + repeatcount = repeatcount*10 + (c - (byte) '0'); + } + else if (c == (byte) 's') { + if (base+repeatcount > end) + return -1; + args[nargs++] = base; + args[nargs++] = repeatcount; + base += repeatcount; + repeatcount = 0; + } + else + protocolError(); + } + + //System.out.print("Message "); + //System.out.print((char) msgcode); + //for (int i=0; i 3) ? args[3] : -1; + InputStream st = decompresser(buffer, dataofs, datalen); + client.setBitmap(bmpcode, new Bitmap(client, st, colorkey)); + break; + } + case MSG_PLAYER_ICON: { + int pid = args[0]; + int icocode = args[1]; + Player p = client.getPlayer(pid); + p.icon = client.getIcon(icocode); + break; + } + case MSG_PING: { + buffer[ofs+1+typecodes] = CMSG_PONG; + sendData(buffer, ofs, base-ofs); + if (nargs > 0 && !client.udpovertcp) { + int udpkbytes = args[0]; + /* switch to udp_over_tcp if the udp socket didn't + receive at least 60% of the packets sent by the server, + or if the socketdisplayer thread died */ + if (sockdisplayer != null && !sockdisplayer.isAlive()) { + showStatus("routing UDP traffic over TCP (no UDP socket)"); + client.start_udp_over_tcp(); + } + else if (udpkbytes * 1024.0 * 0.60 > client.udpbytecounter) { + client.udpsock_low += 1; + if (client.udpsock_low >= 4) { + double inp =client.udpbytecounter/(udpkbytes*1024.0); + int loss = (int)(100.0*(1.0-inp)); + showStatus("routing UDP traffic over TCP (" + + Integer.toString(loss) + + "% packet loss)"); + client.start_udp_over_tcp(); + } + } + else + client.udpsock_low = 0; + } + break; + } + case MSG_PONG: { + if (!client.taskbarfree && !client.taskbarmode) { + client.taskbarfree = true; + client.setTaskbar(true); + } + break; + } + case MSG_INLINE_FRAME: { + if (client.uinflater != null) { + int dataofs = args[0]; + int datalen = args[1]; + int len; + byte[] pkt = client.uinflater_buffer; + client.uinflater.setInput(buffer, dataofs, datalen); + try { + len = client.uinflater.inflate(pkt); + } + catch (DataFormatException e) { + len = 0; + } + Playfield pf = client.playfield; + if (len > 0 && pf != null) { + client.uinflater_buffer = pf.setSprites(pkt, len); + client.repaint(); + } + } + break; + } + default: { + System.err.println("Note: unknown message " + + Byte.toString(msgcode)); + break; + } + } + return base; + } + + public void run() { + try { + byte[] buffer = new byte[0xC000]; + int begin = 0; + int end = 0; + try { + while (true) { + if (end + 0x6000 > buffer.length) { + // compact buffer + byte[] newbuf; + end = end-begin; + if (end + 0x8000 > buffer.length) + newbuf = new byte[end + 0x8000]; + else + newbuf = buffer; + System.arraycopy(buffer, begin, newbuf, 0, end); + begin = 0; + buffer = newbuf; + } + int count = socketInput.read(buffer, end, 0x6000); + if (isInterrupted()) + break; + if (count <= 0) + connectionClosed(); + end += count; + while ((count=decodeMessage(buffer, begin, end)) >= 0) { + begin = count; + } + } + } + catch (InterruptedIOException e) { + } + socket.close(); + } + catch (IOException e) { + client.debug(e); + } + } + } + + // UDP Socket messages + + class SocketDisplayer extends Thread { + public static final int UDP_BUF_SIZE = 0x10000; + + public pclient client; + public DatagramSocket socket; + + SocketDisplayer(pclient aclient) { + setDaemon(true); + client = aclient; + } + + public void run() { + /* This thread may die early, typically because of JVM + security restrictions. */ + byte[] buffer = new byte[UDP_BUF_SIZE]; + DatagramPacket pkt = new DatagramPacket(buffer, UDP_BUF_SIZE); + try { + { + socket = new DatagramSocket(); + int[] args = new int[1]; + args[0] = socket.getLocalPort(); + client.socklistener.sendMessage(SocketListener.CMSG_UDP_PORT, + args); + } + try { + while (true) { + socket.receive(pkt); + if (isInterrupted()) + break; + client.udpbytecounter += (double) pkt.getLength(); + Playfield pf = client.playfield; + if (pf != null) { + pkt.setData(pf.setSprites(pkt.getData(), + pkt.getLength())); + pkt.setLength(UDP_BUF_SIZE); + client.repaint(); + } + } + } + catch (InterruptedIOException e) { + } + socket.close(); + } + catch (IOException e) { + client.debug(e); + } + } + } + + // Applet methods + + public SocketListener socklistener = null; + public SocketDisplayer sockdisplayer = null; + public Playfield playfield = null; + + public void init() { + try { + Socket link; + String param; + + String gamesrv = getParameter("gamesrv"); + if (gamesrv == null) { + gamesrv = getDocumentBase().getHost(); + } + param = getParameter("gameport"); + if (param != null) { + // direct TCP connexion to the game server + link = new Socket(gamesrv, Integer.parseInt(param)); + } + else { + // UCP query + param = getParameter("port"); + int port = (param != null) ? Integer.parseInt(param) : defaultPort; + link = pickHost(gamesrv, port); + } + socklistener = new SocketListener(this, link); + socklistener.start(); + } + catch (IOException e) { + debug(e); + } + } + + public void destroy() { + if (socklistener != null) { + socklistener.interrupt(); + socklistener = null; + } + } + + public void start() { + enableEvents(AWTEvent.KEY_EVENT_MASK | + AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK); + if (socklistener != null) { + sockdisplayer = new SocketDisplayer(this); + sockdisplayer.start(); + } + } + + public void stop() { + if (sockdisplayer != null) { + sockdisplayer.interrupt(); + sockdisplayer = null; + } + } + + public void update(Graphics g) { + paint(g); + } + + public void paint(Graphics g) { + Playfield pf = playfield; + if (pf != null) { + pf.paint(g); + } + else { + int appWidth = getSize().width; + int appHeight = getSize().height; + g.clearRect(0, 0, appWidth, appHeight); + } + } + + protected void processKeyEvent(KeyEvent e) { + int num; + byte[] msg; + e.consume(); + switch (e.getID()) { + case KeyEvent.KEY_PRESSED: + num = e.getKeyCode(); + break; + case KeyEvent.KEY_RELEASED: + num = -e.getKeyCode(); + break; + default: + return; + } + msg = (byte[]) keycodes.get(new Integer(num)); + if (msg != null && socklistener != null) { + Player p = getPlayer(msg[0]); + if (p.local) { + try { + socklistener.sendData(msg, 1, msg.length-1); + } + catch (IOException ioe) { + debug(ioe); + } + return; + } + } + if (keydefinition_k != null && e.getID() == KeyEvent.KEY_PRESSED) + defineKey(num); + } + + public void nextKey() { + KeyName k = keydefinition_k; + if (k == null) + k = keys; + else + k = k.next; + while (k != null && k.keyname.charAt(0) == '-') { + k.newkeycode = 0; + k = k.next; + } + keydefinition_k = k; + } + + public void defineKey(int num) { + KeyName k; + for (k=keys; k!=keydefinition_k; k=k.next) + if (k.newkeycode == num) + return; + k.newkeycode = num; + nextKey(); + if (keydefinition_k == null) { + if (socklistener != null) { + try { + byte[] buffer; + int[] args = new int[1]; + args[0] = keydefinition_pid; + socklistener.sendMessage(SocketListener.CMSG_ADD_PLAYER, + args); + String param = "player" + + Integer.toString(keydefinition_pid); + param = getParameter(param); + if (param != null) { + socklistener.sendMessageEx( + SocketListener.CMSG_PLAYER_NAME, + args, + param); + } + args = new int[2]; + args[0] = keydefinition_pid; + for (k=keys; k!=null; k=k.next) { + if (k.keyname.charAt(0) == '-') { + String test = k.keyname.substring(1); + for (KeyName r=keys; r!=null; r=r.next) + if (r.keyname.equals(test)) + k.newkeycode = -r.newkeycode; + } + args[1] = k.keyid; + buffer = socklistener.codeMessage + (1, SocketListener.CMSG_KEY, args, null); + buffer[0] = (byte) keydefinition_pid; + keycodes.put(new Integer(k.newkeycode), buffer); + } + } + catch (IOException ioe) { + debug(ioe); + } + } + } + repaint(); + } + + protected void processMouseMotionEvent(MouseEvent e) { + Playfield pf = playfield; + if (pf != null) + setTaskbar(e.getY() >= pf.pfheight - pf.TASKBAR_HEIGHT); + e.consume(); + } + + protected void processMouseEvent(MouseEvent e) { + if (e.getID() != MouseEvent.MOUSE_PRESSED) { + e.consume(); + return; + } + requestFocus(); + keydefinition_k = null; + Playfield pf = playfield; + if (pf != null && e.getY() >= pf.pfheight - pf.TASKBAR_HEIGHT) { + int x = e.getX(); + for (Player p=firstPlayer(); p!=null; p=nextPlayer(p)) + if (p.xmin <= x && x < p.xmax) { + if (p.local) { + if (socklistener != null) { + try { + int[] args = new int[1]; + args[0] = p.pid; + socklistener.sendMessage + (SocketListener.CMSG_REMOVE_PLAYER, args); + } + catch (IOException ioe) { + debug(ioe); + } + } + } + else { + keydefinition_pid = p.pid; + nextKey(); + } + break; + } + } + e.consume(); + repaint(); + } + + // UDP-over-TCP + public double udpbytecounter = 0.0; + public int udpsock_low = 0; + public boolean udpovertcp = false; + public Inflater uinflater = null; + public byte[] uinflater_buffer = null; + + public void start_udp_over_tcp() + { + udpovertcp = true; + int[] args = new int[1]; + args[0] = 0; + try { + socklistener.sendMessage(SocketListener.CMSG_UDP_PORT, args); + } + catch (IOException e) { + return; + } + uinflater_buffer = new byte[SocketDisplayer.UDP_BUF_SIZE]; + uinflater = new Inflater(); + if (sockdisplayer != null) { + sockdisplayer.interrupt(); + sockdisplayer = null; + } + } + +// // ImageObserver interface + +// public boolean imageUpdate(Image img, +// int infoflags, +// int x, +// int y, +// int width, +// int height) { +// return false; +// } +} diff --git a/metaserver/.cvsignore b/metaserver/.cvsignore new file mode 100644 index 0000000..687980a --- /dev/null +++ b/metaserver/.cvsignore @@ -0,0 +1,4 @@ +*.py[co] +build +*.so +*.pyd diff --git a/metaserver/__init__.py b/metaserver/__init__.py new file mode 100644 index 0000000..1bb8bf6 --- /dev/null +++ b/metaserver/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/metaserver/home.png b/metaserver/home.png new file mode 100644 index 0000000..cc7a46e Binary files /dev/null and b/metaserver/home.png differ diff --git a/metaserver/index.html b/metaserver/index.html new file mode 100644 index 0000000..fdb5583 --- /dev/null +++ b/metaserver/index.html @@ -0,0 +1,51 @@ + +The Bub's Brothers Server List + + + +

The Bub's Brothers Server List

+%s +
+ + +
+ + + + + + + + + + + + + + + +
+    Running servers
  
  +\ + + + + + +\ +
%(stime)s      %(icon)s   %(hosthtml)s +
%(desc)s (%(extradesc)s)
+
+%(tbfiles)s +


+

+%(bottommsg)s +
+Reload this page for an up-to-date version! +

+%(extrafooter)s +

I click on the servers but nothing happens - why?

+

Chat about the Bub's Brothers? Go to our IRC channel #bub-n-bros on irc.freenode.net.

+

The Bub's Brothers Home Page

+

Thanks to CTPUG for hosting the meta-server.

+ diff --git a/metaserver/mbub.png b/metaserver/mbub.png new file mode 100644 index 0000000..d7aaae9 Binary files /dev/null and b/metaserver/mbub.png differ diff --git a/metaserver/metaclient.py b/metaserver/metaclient.py new file mode 100644 index 0000000..aedb99c --- /dev/null +++ b/metaserver/metaclient.py @@ -0,0 +1,596 @@ +import sys, os, time, random +from select import select +from socket import * +from metastruct import * + +_SERVER = 'ctpug.org.za' + +METASERVER = (_SERVER, 8055) +METASERVER_UDP = (_SERVER, 8055) +METASERVER_URL = 'http://%s:8050/bub-n-bros.html' % (_SERVER,) +VERSION_TAG = 1601 + +def connect(failure=[]): + if len(failure) >= 2: + return None + print >> sys.stderr, 'Connecting to the meta-server %s:%d...' % METASERVER + try: + s = socket(AF_INET, SOCK_STREAM) + s.connect(METASERVER) + except error, e: + print >> sys.stderr, '*** cannot contact meta-server:', str(e) + failure.append(e) + return None + else: + print >> sys.stderr, 'connected.' + return s + +sys.setcheckinterval(4096) + + +def float2str(f): + # don't trust locale issues and write a string with a '.' + s = str(long(f*1000000.0)) + return s[:-6] + '.' + s[-6:] + +def str2float(s): + try: + return float(s) + except: + # locale issues may prevent float() from decoding the string + s = s.strip() + try: + i = s.index('.') + except ValueError: + try: + i = s.index(',') + except ValueError: + i = len(s) + frac = s[i+1:] + return float(s[:i] or '0') + float(frac or '0')/(10**len(frac)) + + +# ____________________________________________________________ +# Game Servers + +class MetaClientSrv(MessageSocket): + + def __init__(self, s, game): + MessageSocket.__init__(self, s) + self.game = game + self.lastwakeup = None + self.synsockets = {} + import gamesrv + gamesrv.addsocket('META', s, self.receive) + self.closed = 0 + + def close(self): + if not self.closed: + self.disconnect() + try: + self.s.shutdown(2) + except: + pass + + def disconnect(self): + import gamesrv + gamesrv.removesocket('META', self.s) + self.closed = 1 + print >> sys.stderr, 'disconnected from the meta-server' + + def send_traceback(self): + if not self.closed: + import traceback, cStringIO, sys + f = cStringIO.StringIO() + print >> f, sys.version + print >> f, 'platform: ', sys.platform + print >> f, 'executable: ', sys.executable + print >> f, 'argv: ', sys.argv + print >> f, 'cwd: ', os.getcwd() + print >> f, 'version tag:', VERSION_TAG + print >> f + traceback.print_exc(file = f) + self.s.sendall(message(MMSG_TRACEBACK, f.getvalue())) + + def msg_wakeup(self, origin, *rest): + if self.lastwakeup is None or time.time()-self.lastwakeup > 4.0: + def fastresponses(wakeup): + sys.setcheckinterval(64) + time.sleep(12.01) + if self.lastwakeup == wakeup: + sys.setcheckinterval(4096) + self.synsockets.clear() + import thread + self.lastwakeup = time.time() + thread.start_new_thread(fastresponses, (self.lastwakeup,)) + + def msg_connect(self, origin, port, *rest): + def connect(origin, port): + host, _ = origin.split(':') + addr = host, port + s = socket(AF_INET, SOCK_STREAM) + print >> sys.stderr, 'backconnecting to', addr + try: + s.connect(addr) + except error, e: + print >> sys.stderr, 'backconnecting:', str(e) + else: + self.game.newclient_threadsafe(s, addr) + import thread + thread.start_new_thread(connect, (origin, port)) + + def msg_udp_conn(self, origin, secret, port, *rest): + def connect(origin, secret, port): + host, _ = origin.split(':') + addr = host, port + s = socket(AF_INET, SOCK_DGRAM) + print >> sys.stderr, 'udp connecting to', addr + s.connect(addr) + mysecret = random.randrange(0, 65536) + packet = ('B' + chr( secret & 0xFF) + chr( secret >> 8) + + chr(mysecret & 0xFF) + chr(mysecret >> 8)) + from socketoverudp import SocketOverUdp + from socketoverudp import SOU_RANGE_START, SOU_RANGE_STOP + for i in range(5): + #print 'sending', repr(packet) + s.send(packet) + iwtd, owtd, ewtd = select([s], [], [], 0.25) + if s in iwtd: + #print 'reading' + try: + inbuf = s.recv(SocketOverUdp.PACKETSIZE) + except error: + inbuf = '' + # try again? + iwtd, owtd, ewtd = select([s], [], [], 0.35) + if s in iwtd: + try: + inbuf = s.recv(SocketOverUdp.PACKETSIZE) + except error: + pass + #print 'got', repr(inbuf) + if (inbuf and + SOU_RANGE_START <= ord(inbuf[0]) < SOU_RANGE_STOP): + break + else: + print >> sys.stderr, 'udp connecting: no answer, giving up' + return + sock = SocketOverUdp(s, (mysecret, secret)) + data = sock._decode(inbuf) + #print 'decoded as', repr(data) + expected = '[bnb c->s]' + packet[3:5] + while len(data) < len(expected) + 2: + #print 'waiting for more' + iwtd, owtd, ewtd = select([sock], [], [], 5.0) + if sock not in iwtd: + print >> sys.stderr, 'udp connecting: timed out' + return + #print 'decoding more' + data += sock.recv() + #print 'now data is', repr(data) + if data[:-2] != expected: + print >> sys.stderr, 'udp connecting: bad data' + return + sock.sendall('[bnb s->c]' + data[-2:]) + sock.flush() + #print 'waiting for the last dot...' + while 1: + iwtd, owtd, ewtd = select([sock], [], [], 5.0) + if sock not in iwtd: + print >> sys.stderr, 'udp connecting: timed out' + return + data = sock.recv(200) + if data: + break + if data != '^': + print >> sys.stderr, 'udp connecting: bad data' + return + #print 'done!' + self.game.newclient_threadsafe(sock, addr) + + import thread + thread.start_new_thread(connect, (origin, secret, port)) + + def msg_ping(self, origin, *rest): + # ping time1 --> pong time2 time1 + self.s.sendall(message(MMSG_ROUTE, origin, + RMSG_PONG, float2str(time.time()), *rest)) + + def msg_sync(self, origin, clientport, time3, time2, time1, *rest): + time4 = time.time() + s = socket(AF_INET, SOCK_STREAM) + s.bind(('', INADDR_ANY)) + _, serverport = s.getsockname() + self.s.sendall(message(MMSG_ROUTE, origin, + RMSG_CONNECT, serverport, clientport)) + #print 'times:', time1, time2, time3, time4 + doubleping = (str2float(time3)-str2float(time1)) + (time4-str2float(time2)) + connecttime = time4 + doubleping / 4.0 + def connect(origin, port, connecttime, s): + host, _ = origin.split(':') + addr = host, port + delay = connecttime - time.time() + #print 'sleep(%r)' % delay + if 0.0 <= delay <= 10.0: + time.sleep(delay) + print >> sys.stderr, 'synconnecting to', addr + try: + s.connect(addr) + except error, e: + print >> sys.stderr, 'synconnecting:', str(e) + else: + self.game.newclient_threadsafe(s, addr) + import thread + thread.start_new_thread(connect, (origin, clientport, connecttime, s)) + + MESSAGES = { + RMSG_CONNECT: msg_connect, + RMSG_WAKEUP: msg_wakeup, + RMSG_PING: msg_ping, + RMSG_SYNC: msg_sync, + RMSG_UDP_CONN:msg_udp_conn, + } + +metaclisrv = None + +def meta_register(game): + global metaclisrv + import gamesrv + info = {} + if game.FnDesc: + info['desc'] = game.FnDesc or '' + info['extradesc'] = game.FnExtraDesc() or '' + + s = gamesrv.opentcpsocket() + hs = gamesrv.openhttpsocket() + port = int(gamesrv.displaysockport(s)) + info['httpport'] = gamesrv.displaysockport(hs) + + if not metaclisrv or metaclisrv.closed: + s = connect() + if not s: + return + metaclisrv = MetaClientSrv(s, game) + metaclisrv.s.sendall(message(MMSG_INFO, encodedict(info)) + + message(MMSG_START, port)) + +def meta_unregister(game): + global metaclisrv + if metaclisrv: + metaclisrv.close() + metaclisrv = None + + +# ____________________________________________________________ +# Game Clients + +class Event: + def __init__(self): + import thread + self.lock = thread.allocate_lock() + self.lock.acquire() + def signal(self): + try: + self.lock.release() + except: + pass + def wait1(self): + self.lock.acquire() + + +class MetaClientCli: + fatalerror = False + + def __init__(self, serverkey, backconnectport): + self.resultsocket = None + self.serverkey = serverkey + self.backconnectport = backconnectport + self.threads = {} + + def run(self): + import thread + print >> sys.stderr, 'Trying to connect to', self.serverkey + self.ev = Event() + self.ev2 = Event() + self.buffer = "" + self.sendlock = thread.allocate_lock() + self.recvlock = thread.allocate_lock() + self.inputmsgqueue = [] + self.gotudpport = None + if not (PORTS.get('CLIENT') or PORTS.get('sendudpto')): + self.s = connect() + thread.start_new_thread(self.acquire_udp_port, ()) + else: + self.s = None + self.ev2.signal() + self.startthread(self.try_udp_connect) + + thread.start_new_thread(self.bipbip, ()) + self.startthread(self.try_direct_connect, 0.75) + self.startthread(self.try_indirect_connect, 1.50) + while self.resultsocket is None: + self.threadsleft() + self.ev.wait1() + self.ev2.wait1() + return self.resultsocket + + def done(self): + sys.setcheckinterval(4096) + + def bipbip(self): + while self.resultsocket is None: + time.sleep(0.31416) + self.ev.signal() + + def startthread(self, fn, sleep=0.0, args=()): + import thread + def bootstrap(fn, atom, sleep, args): + try: + time.sleep(sleep) + if self.resultsocket is None: + fn(*args) + finally: + del self.threads[atom] + self.ev.signal() + atom = object() + self.threads[atom] = time.time() + thread.start_new_thread(bootstrap, (fn, atom, sleep, args)) + + def threadsleft(self): + if self.fatalerror: + sys.exit(1) + now = time.time() + TIMEOUT = 11 + for starttime in self.threads.values(): + if now < starttime + TIMEOUT: + break + else: + if self.threads: + print >> sys.stderr, '*** time out, giving up.' + else: + print >> sys.stderr, '*** failed to connect.' + sys.exit(1) + + def try_direct_connect(self): + host, port = self.serverkey.split(':') + port = int(port) + s = socket(AF_INET, SOCK_STREAM) + try: + s.connect((host, port)) + except error, e: + print >> sys.stderr, 'direct connexion failed:', str(e) + else: + print >> sys.stderr, 'direct connexion accepted.' + self.resultsocket = s + + def try_indirect_connect(self): + import thread, time + if not self.s: + self.s = connect() + if not self.s: + return + self.routemsg(RMSG_WAKEUP) + self.startthread(self.try_backconnect) + self.socketcache = {} + tries = [0.6, 0.81, 1.2, 1.69, 2.6, 3.6, 4.9, 6.23] + for delay in tries: + self.startthread(self.send_ping, delay) + while self.resultsocket is None: + msg = self.inputmsg() + now = time.time() + if self.resultsocket is not None: + break + if msg[0] == RMSG_CONNECT: + # connect serverport clientport + self.startthread(self.try_synconnect, args=msg[1:]) + if msg[0] == RMSG_PONG: + # pong time2 time1 --> sync port time3 time2 time1 + if len(self.socketcache) < len(tries): + s = socket(AF_INET, SOCK_STREAM) + s.bind(('', INADDR_ANY)) + _, port = s.getsockname() + self.socketcache[port] = s + self.routemsg(RMSG_SYNC, port, float2str(now), *msg[2:]) + + def sendmsg(self, data): + self.sendlock.acquire() + try: + self.s.sendall(data) + finally: + self.sendlock.release() + + def routemsg(self, *rest): + self.sendmsg(message(MMSG_ROUTE, self.serverkey, *rest)) + + def _readnextmsg(self, blocking): + self.recvlock.acquire() + try: + while 1: + msg, self.buffer = decodemessage(self.buffer) + if msg is not None: + if msg[0] == RMSG_UDP_ADDR: + if len(msg) > 2: + self.gotudpport = msg[1], int(msg[2]) + continue + if msg[0] == RMSG_NO_HOST and msg[1] == self.serverkey: + print >> sys.stderr, ('*** server %r is not registered' + ' on the meta-server' % (msg[1],)) + self.fatalerror = True + sys.exit() + self.inputmsgqueue.append(msg) + return + iwtd, owtd, ewtd = select([self.s], [], [], 0) + if not iwtd: + if self.inputmsgqueue or not blocking: + return + data = self.s.recv(2048) + if not data: + print >> sys.stderr, 'disconnected from the meta-server' + sys.exit() + self.buffer += data + finally: + self.recvlock.release() + + def inputmsg(self): + self._readnextmsg(blocking=True) + return self.inputmsgqueue.pop(0) + + def try_backconnect(self): + s1 = socket(AF_INET, SOCK_STREAM) + s1.bind(('', self.backconnectport or INADDR_ANY)) + s1.listen(1) + _, port = s1.getsockname() + self.routemsg(RMSG_CONNECT, port) + print >> sys.stderr, 'listening for backward connection' + iwtd, owtd, ewtd = select([s1], [], [], 7.5) + if s1 in iwtd: + s, addr = s1.accept() + print >> sys.stderr, 'accepted backward connection from', addr + self.resultsocket = s + + def send_ping(self): + sys.stderr.write('. ') + self.routemsg(RMSG_PING, float2str(time.time())) + + def try_synconnect(self, origin, remoteport, localport, *rest): + sys.stderr.write('+ ') + s = self.socketcache[localport] + remotehost, _ = origin.split(':') + remoteaddr = remotehost, remoteport + try: + s.connect(remoteaddr) + except error, e: + print >> sys.stderr, 'SYN connect failed:', str(e) + return + print >> sys.stderr, ('simultaneous SYN connect succeeded with %s:%d' % + remoteaddr) + self.resultsocket = s + + def try_udp_connect(self): + for i in range(3): # three attempts + self.attempt_udp_connect() + if self.resultsocket is not None: + break + + def attempt_udp_connect(self): + if '*udpsock*' in PORTS: + s, (host, port) = PORTS['*udpsock*'] + else: + s = socket(AF_INET, SOCK_DGRAM) + s.bind(('', PORTS.get('CLIENT', INADDR_ANY))) + host, port = s.getsockname() + if 'sendudpto' in PORTS: + host = PORTS['sendudpto'] + secret = originalsecret = random.randrange(0, 65536) + self.routemsg(RMSG_UDP_CONN, secret, port) + secret = 'B' + chr(secret & 0xFF) + chr(secret >> 8) + while True: + iwtd, owtd, ewtd = select([s], [], [], 2.94) + if s not in iwtd: + return + packet, addr = s.recvfrom(200) + if packet.startswith(secret) and len(packet) == 5: + break + s.connect(addr) + #print 'got', repr(packet) + remotesecret = ord(packet[3]) | (ord(packet[4]) << 8) + secret = random.randrange(0, 65536) + secret = chr(secret & 0xFF) + chr(secret >> 8) + packet = '[bnb c->s]' + packet[3:5] + secret + for name in ('*udpsock*', 'CLIENT'): + if name in PORTS: + del PORTS[name] + from socketoverudp import SocketOverUdp + sock = SocketOverUdp(s, (originalsecret, remotesecret)) + #print 'sending', repr(packet) + sock.sendall(packet) + sock.flush() + data = '' + expected = '[bnb s->c]' + secret + while len(data) < len(expected): + #print 'waiting' + iwtd, owtd, ewtd = select([sock], [], [], 2.5) + if sock not in iwtd: + print >> sys.stderr, 'socket-over-udp timed out' + return + #print 'we get:' + data += sock.recv() + #print repr(data) + if data != expected: + print >> sys.stderr, 'bad udp data from', addr + else: + sock.sendall('^') + sock.flush() + print 'udp connexion handshake succeeded' + self.resultsocket = sock + + def acquire_udp_port(self): + try: + s = socket(AF_INET, SOCK_DGRAM) + s.bind(('', INADDR_ANY)) + randomdata = hex(random.randrange(0, sys.maxint)) + for i in range(5): + s.sendto(randomdata, METASERVER_UDP) + time.sleep(0.05) + self.sendmsg(message(MMSG_UDP_ADDR, randomdata)) + time.sleep(0.05) + self._readnextmsg(blocking=False) + if self.gotudpport: + PORTS['*udpsock*'] = s, self.gotudpport + udphost, udpport = self.gotudpport + print >> sys.stderr, ('udp port %d is visible from ' + 'outside on %s:%d' % ( + s.getsockname()[1], udphost, udpport)) + self.startthread(self.try_udp_connect) + break + finally: + self.ev2.signal() + + +def meta_connect(serverkey, backconnectport=None): + global METASERVER + if PORTS.get('SSH_RELAY'): + METASERVER = PORTS['SSH_RELAY'] + c = MetaClientCli(serverkey, backconnectport) + s = c.run() + c.done() + return s + +def print_server_list(): + s = connect() + if s is not None: + s.sendall(message(MMSG_LIST)) + buffer = "" + while decodemessage(buffer)[0] is None: + buffer += s.recv(8192) + s.close() + msg = decodemessage(buffer)[0] + assert msg[0] == RMSG_LIST + entries = decodedict(msg[1]) + if not entries: + print >> sys.stderr, 'No registered server.' + else: + print + print ' %-25s | %-30s | %s' % ( + 'server', 'game', 'players') + print '-'*27+'+'+'-'*32+'+'+'-'*11 + for key, value in entries.items(): + if ':' in key: + try: + addr, _, _ = gethostbyaddr(key[:key.index(':')]) + except: + pass + else: + addr = '%-27s' % (addr,) + if len(addr) < 28: addr += '|' + addr = '%-60s' % (addr,) + if len(addr) < 61: addr += '|' + print addr + value = decodedict(value) + print ' %-25s | %-30s | %s' % ( + key, value.get('desc', ''), + value.get('extradesc', '')) + print + +if __name__ == '__main__': + print_server_list() diff --git a/metaserver/metaserver.py b/metaserver/metaserver.py new file mode 100644 index 0000000..cb4ea19 --- /dev/null +++ b/metaserver/metaserver.py @@ -0,0 +1,365 @@ +from socket import * +import os, sys, time, random +from select import select +from cStringIO import StringIO +from weakref import WeakValueDictionary +from metastruct import * +from common import httpserver, stdlog + +if __name__ == '__main__': + os.chdir(os.path.dirname(sys.argv[0]) or os.curdir) + +META_SERVER_HTTP_PORT = 8050 +META_SERVER_PORT = 8055 +META_SERVER_UDP_PORT = 8055 +IMAGE_DIR = "../bubbob/doc/images" +ICONS = [open(os.path.join(IMAGE_DIR, s), 'rb').read() + for s in os.listdir(IMAGE_DIR) if s.endswith('.png')] +assert ICONS, "you need to run ../bubbob/doc/bonus-doc.py" +MAX_SERVERS = 50 +MAX_CONNEXIONS = 60 + + +serversockets = {} + +class MetaServer: + + def __init__(self, port=META_SERVER_PORT, udpport=META_SERVER_UDP_PORT): + s = socket(AF_INET, SOCK_STREAM) + s.bind(('', port)) + s.listen(5) + self.parentsock = s + self.ServersDict = {} + self.ServersList = [] + serversockets[s] = self.clientconnect, sys.exit + self.udpsock = socket(AF_INET, SOCK_DGRAM) + self.udpsock.bind(('', udpport)) + serversockets[self.udpsock] = self.udp_message, None + self.udpdata = [] + + def detach(self): + pid = os.fork() + if pid: + print pid + os._exit(0) + # in the child process + os.setsid() + logfile = stdlog.LogFile(limitsize=131072) + if logfile: + print >> logfile + print "Logging to", logfile.filename + fd = logfile.f.fileno() + try: + # detach from parent + os.dup2(fd, 1) + os.dup2(fd, 2) + os.dup2(fd, 0) + except OSError: + pass + logfile.close() + # record pid + f = open('pid', 'w') + print >> f, os.getpid() + f.close() + + def clientconnect(self): + s, addr = self.parentsock.accept() + Connexion(s, addr) + + def publish(self, server): + key = server.serverkey + if key in self.ServersDict: + current = self.ServersDict[key] + if current is server: + return + self.ServersList.remove(current) + elif len(self.ServersDict) >= MAX_SERVERS: + raise OverflowError + self.ServersList.append(server) + self.ServersDict[key] = server + print '+', key + + def unpublish(self, server): + key = server.serverkey + if key in self.ServersDict: + current = self.ServersDict[key] + if current is server: + del self.ServersDict[key] + self.ServersList.remove(server) + print '-', key + + def makelist(self): + items = {} + for srv in self.ServersList: + items[srv.serverkey] = encodedict(srv.serverinfo) + return encodedict(items) + + def getserver(self, key): + return self.ServersDict[key] + + def udp_message(self): + data, addr = self.udpsock.recvfrom(32) + self.udpdata.append((data, addr)) + if len(self.udpdata) > 50: + del self.udpdata[0] + + +class Connexion(MessageSocket): + + def __init__(self, s, addr): + MessageSocket.__init__(self, s) + self.serverinfo = { + 'time': int(time.time()), + 'icon': random.choice(ICONS), + 'iconformat': 'png', + } + self.addr = addr + self.key = '%s:%d' % addr + self.serverkey = None + print '[', self.key + self.backlinks = WeakValueDictionary() + if len(serversockets) >= MAX_CONNEXIONS: + self.disconnect() + raise OverflowError + serversockets[s] = self.receive, self.disconnect + + def disconnect(self): + metaserver.unpublish(self) + try: + del serversockets[self.s] + except KeyError: + pass + print ']', self.key + + def msg_serverinfo(self, info, *rest): + print '|', self.key + if len(info) > 15000: + raise OverflowError + info = decodedict(info) + self.serverinfo.update(info) + + def msg_startserver(self, port, *rest): + serverkey = '%s:%d' % (self.addr[0], port) + if self.serverkey and self.serverkey != serverkey: + metaserver.unpublish(self) + self.serverkey = serverkey + metaserver.publish(self) + + def msg_stopserver(self, *rest): + metaserver.unpublish(self) + + def msg_list(self, *rest): + self.s.sendall(message(RMSG_LIST, metaserver.makelist())) + + def msg_route(self, targetkey, *rest): + try: + target = metaserver.getserver(targetkey) + except KeyError: + try: + target = self.backlinks[targetkey] + except KeyError: + self.s.sendall(message(RMSG_NO_HOST, targetkey)) + return + target.route(self, *rest) + + def route(self, origin, msgcode, *rest): + self.backlinks[origin.key] = origin + self.s.sendall(message(msgcode, origin.key, *rest)) + + def msg_traceback(self, tb, *rest): + f = stdlog.LogFile('tb-%s.log' % (self.addr[0],)) + if f: + print >> f, tb + f.close() + + def msg_udp_addr(self, pattern, *rest): + for data, addr in metaserver.udpdata: + if data == pattern: + try: + host, port = addr + port = int(port) + except ValueError: + continue + self.s.sendall(message(RMSG_UDP_ADDR, host, port)) + return + else: + self.s.sendall(message(RMSG_UDP_ADDR)) + + MESSAGES = { + MMSG_INFO: msg_serverinfo, + MMSG_START: msg_startserver, + MMSG_STOP: msg_stopserver, + MMSG_LIST: msg_list, + MMSG_ROUTE: msg_route, + MMSG_TRACEBACK: msg_traceback, + MMSG_UDP_ADDR: msg_udp_addr, + } + + +# ____________________________________________________________ + +import htmlentitydefs +text_to_html = {} +for key, value in htmlentitydefs.entitydefs.items(): + text_to_html[value] = '&' + key + ';' +for i in range(32): + text_to_html[chr(i)] = '?' +def htmlquote(s, getter=text_to_html.get): + lst = [getter(c, c) for c in s if ' ' <= c < '\x7F'] + return ''.join(lst) +def txtfilter(s, maxlen=200): + s = str(s)[:maxlen] + l = [c for c in s if c in "!$*,-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "^_`abcdefghijklmnopqrstuvwxyz{|}"] + return ''.join(l) + +f = open('index.html', 'r') +HEADER, ROW, FOOTER = f.read().split('\\') +f.close() + +def makehosthtml(srv, bottommsg, join): + info = srv.serverinfo + hostname, port = srv.serverkey.split(':') + try: + fullhostname = gethostbyaddr(hostname)[0] + except: + fullhostname = hostname + url = None + if join: + url = "http://%s/join.html?host=%s&port=%s&httpport=%s&m=%s" % ( + join, hostname, port, info.get('httpport') or 'off', time.time()) + else: + try: + httpport = int(info.get('httpport')) + except: + pass + else: + url = "http://%s:%s/" % (hostname, httpport) + javamsg = """ +

Click on a server above to join the game. This only works if: +

  • your browser understands Java; +
  • the server is not behind a firewall; +
  • you don't mind not hearing the nice background music. +

+

Alternatively, install the +Python version +of the client, which can cope with all of the above problems.

+
""" + if javamsg not in bottommsg: + bottommsg.append(javamsg) + result = '%s:%s' % (fullhostname, port) + if url: + result = '%s' % (url, result) + return result + +def indexloader(headers, join=[], head=[], **options): + if join: + join = join[0] + data = [HEADER % (head and head[0] or '')] + bottommsg = [] + if metaserver.ServersList: + counter = 0 + for srv in metaserver.ServersList: + info = srv.serverinfo + icon = '' % srv.serverkey + bgcolor = ('#C0D0D0', '#E0D0A8')[counter&1] + hosthtml = makehosthtml(srv, bottommsg, join) + desc = htmlquote(info.get('desc')) or '' + extradesc = htmlquote(info.get('extradesc')) or '' + if isinstance(info.get('time'), int): + stime = time.strftime('%a %b %d
%H:%M GMT', + time.gmtime(info['time'])) + else: + stime = '' + data.append(ROW % locals()) + counter += 1 + else: + data.append(''' + Sorry, there is no registered server at the moment. + ''') + if join: + extrafooter = '''

+ Back to local games

''' % ( + join, time.time()) + else: + extrafooter = '' + bottommsg = '\n'.join(bottommsg) + tbfiles = [s for s in os.listdir('.') if s.startswith('tb-')] + if tbfiles: + tbfiles = len(tbfiles) + else: + tbfiles = '' + data.append(FOOTER % locals()) + return StringIO(''.join(data)), 'text/html' + +def icoloader(key, **options): + srv = metaserver.getserver(key[0]) + iconformat = txtfilter(srv.serverinfo['iconformat'], 32) + return StringIO(srv.serverinfo['icon']), 'image/' + iconformat + +httpserver.register('', indexloader) +httpserver.register('index.html', indexloader) +httpserver.register('bub-n-bros.html', indexloader) +httpserver.register('ico', icoloader) +httpserver.register('mbub.png', httpserver.fileloader('mbub.png', 'image/png')) +httpserver.register('home.png', httpserver.fileloader('home.png', 'image/png')) + +def openhttpsocket(port = META_SERVER_HTTP_PORT): + from BaseHTTPServer import HTTPServer + class ServerClass(HTTPServer): + def get_request(self): + sock, addr = self.socket.accept() + sock.settimeout(5.0) + return sock, addr + HandlerClass = httpserver.MiniHandler + server_address = ('', port) + httpd = ServerClass(server_address, HandlerClass) + s = httpd.socket + serversockets[s] = httpd.handle_request, None + + +# ____________________________________________________________ + +def mainloop(): + while 1: + iwtd = serversockets.keys() + iwtd, owtd, ewtd = select(iwtd, [], iwtd) + #print iwtd, owtd, ewtd, serversockets + for s in iwtd: + if s in serversockets: + input, close = serversockets[s] + try: + input() + except: + import traceback + print "-"*60 + traceback.print_exc() + print "-"*60 + for s in ewtd: + if s in serversockets: + input, close = serversockets[s] + try: + close() + except: + import traceback + print "-"*60 + traceback.print_exc() + print "-"*60 + + +if __name__ == '__main__': + metaserver = MetaServer() + if sys.argv[1:2] == ['-f']: + metaserver.detach() + try: + openhttpsocket() + print 'listening to client port tcp %d / http %d / udp %d.' % ( + META_SERVER_PORT, + META_SERVER_HTTP_PORT, + META_SERVER_UDP_PORT) + mainloop() + finally: + if metaserver.ServersList: + print '*** servers still connected, waiting 5 seconds' + time.sleep(5) + print '*** leaving at', time.ctime() diff --git a/metaserver/metastruct.py b/metaserver/metastruct.py new file mode 100644 index 0000000..13240cc --- /dev/null +++ b/metaserver/metastruct.py @@ -0,0 +1,75 @@ +import sys, os +LOCALDIR = __file__ +LOCALDIR = os.path.abspath(os.path.dirname(LOCALDIR)) +sys.path.insert(0, os.path.dirname(LOCALDIR)) + +from common.msgstruct import * +from socket import error + +MMSG_INFO = 'I' +MMSG_START = '+' +MMSG_STOP = '-' +MMSG_LIST = 'L' +MMSG_ROUTE = 'R' +MMSG_TRACEBACK= 'T' +MMSG_UDP_ADDR = 'U' + +RMSG_WAKEUP = 'w' +RMSG_PING = 'p' +RMSG_PONG = 'o' +RMSG_SYNC = 'y' +RMSG_CONNECT = 'c' +RMSG_LIST = 'l' +RMSG_UDP_ADDR = 'u' +RMSG_UDP_CONN = 'd' +RMSG_NO_HOST = '?' + + +def encodedict(dict): + data = [] + for key, value in dict.items(): + data.append(message('#', key, value)) + return ''.join(data) + +def encodelist(list): + return message('[', *list) + +def decodedict(buffer): + result = {} + while 1: + msg, buffer = decodemessage(buffer) + if msg is None or len(msg) < 3 or msg[0] != '#': + break + result[msg[1]] = msg[2] + return result + +def decodelist(buffer): + msg, buffer = decodemessage(buffer) + assert msg[0] == '[' + return list(msg[1:]) + + +class MessageSocket: + + def __init__(self, s): + self.s = s + self.buffer = "" + + def receive(self): + try: + data = self.s.recv(2048) + except error: + data = '' + if not data: + self.disconnect() + return + self.buffer += data + while 1: + msg, self.buffer = decodemessage(self.buffer) + if msg is None: + break + if msg[0] not in self.MESSAGES: + print >> sys.stderr, 'unknown message %r' % (msg[0],) + else: + fn = self.MESSAGES[msg[0]] + fn(self, *msg[1:]) diff --git a/metaserver/pipelayer.py b/metaserver/pipelayer.py new file mode 100644 index 0000000..9baf78a --- /dev/null +++ b/metaserver/pipelayer.py @@ -0,0 +1,337 @@ +#import os +import struct +from collections import deque +from zlib import crc32 + + +class InvalidPacket(Exception): + pass + + +FLAG_NAK1 = 0xE0 +FLAG_NAK = 0xE1 +FLAG_REG = 0xE2 +FLAG_CFRM = 0xE3 + +FLAG_RANGE_START = 0xE0 +FLAG_RANGE_STOP = 0xE4 + +max_old_packets = 200 # must be <= 256 + + +class PipeLayer(object): + timeout = 1 + headersize = 4 + + def __init__(self, initialcrcs=(0, 0)): + #self.localid = os.urandom(4) + #self.remoteid = None + self.cur_time = 0 + self.out_queue = deque() + self.out_nextseqid = 0 + self.out_nextrepeattime = None + self.in_nextseqid = 0 + self.in_outoforder = {} + self.out_oldpackets = deque() + self.out_flags = FLAG_REG + self.out_resend = 0 + self.out_resend_skip = False + self.in_crc, self.out_crc = initialcrcs + + def queue(self, data): + if data: + self.out_queue.appendleft(data) + + def queue_size(self): + total = 0 + for data in self.out_queue: + total += len(data) + return total + + def in_sync(self): + return not self.out_queue and self.out_nextrepeattime is None + + def settime(self, curtime): + self.cur_time = curtime + if self.out_queue: + if len(self.out_oldpackets) < max_old_packets: + return 0 # more data to send now + if self.out_nextrepeattime is not None: + return max(0, self.out_nextrepeattime - curtime) + else: + return None + + def is_congested(self): + return len(self.out_oldpackets) >= max_old_packets + + def encode(self, maxlength): + #print ' '*self._dump_indent, '--- OUTQ', self.out_resend, self.out_queue + if len(self.out_oldpackets) >= max_old_packets: + # congestion, stalling + payload = 0 + else: + payload = maxlength - 8 + if payload <= 0: + raise ValueError("encode(): buffer too small") + if (self.out_nextrepeattime is not None and + self.out_nextrepeattime <= self.cur_time): + # no ACK received so far, send a packet (possibly empty) + if not self.out_queue: + payload = 0 + else: + if not self.out_queue: # no more data to send + return None + if payload == 0: # congestion + return None + # prepare a packet + seqid = self.out_nextseqid + flags = self.out_flags + self.out_flags = FLAG_REG # clear out the flags for the next time + #if flags in (FLAG_NAK, FLAG_NAK1): + # print 'out_flags NAK', hex(flags) + if payload > 0: + self.out_nextseqid = (seqid + 1) & 0xFFFF + data = self.out_queue.pop() + packetlength = len(data) + if self.out_resend > 0: + if packetlength > payload + 4: + raise ValueError("XXX need constant buffer size for now") + self.out_resend -= 1 + if self.out_resend_skip: + if self.out_resend > 0: + self.out_queue.pop() + self.out_resend -= 1 + self.out_nextseqid = (seqid + 2) & 0xFFFF + self.out_resend_skip = False + packetpayload = data + else: + packet = [] + while packetlength <= payload: + packet.append(data) + if not self.out_queue: + break + data = self.out_queue.pop() + packetlength += len(data) + else: + rest = len(data) + payload - packetlength + packet.append(data[:rest]) + self.out_queue.append(data[rest:]) + packetpayload = ''.join(packet) + self.out_crc = crc32(packetpayload, self.out_crc) + packetpayload += struct.pack("!I", self.out_crc & 0xffffffff) + self.out_oldpackets.appendleft(packetpayload) + #print ' '*self._dump_indent, '--- OLDPK', self.out_oldpackets + else: + # a pure ACK packet, no payload + if self.out_oldpackets and flags == FLAG_REG: + flags = FLAG_CFRM + packetpayload = '' + packet = struct.pack("!BBH", flags, + self.in_nextseqid & 0xFF, + seqid) + packetpayload + if self.out_oldpackets: + self.out_nextrepeattime = self.cur_time + self.timeout + else: + self.out_nextrepeattime = None + #self.dump('OUT', packet) + return packet + + def decode(self, rawdata): + if len(rawdata) < 4: + raise InvalidPacket + #print ' '*self._dump_indent, '------ out %d (+%d) in %d' % (self.out_nextseqid, self.out_resend, self.in_nextseqid) + #self.dump('IN ', rawdata) + in_flags, ack_seqid, in_seqid = struct.unpack("!BBH", rawdata[:4]) + if not (FLAG_RANGE_START <= in_flags < FLAG_RANGE_STOP): + raise InvalidPacket + in_diff = (in_seqid - self.in_nextseqid ) & 0xFFFF + ack_diff = (self.out_nextseqid + self.out_resend - ack_seqid) & 0xFF + if in_diff >= max_old_packets: + return '' # invalid, but can occur as a late repetition + if ack_diff != len(self.out_oldpackets): + # forget all acknowledged packets + if ack_diff > len(self.out_oldpackets): + return '' # invalid, but can occur with packet reordering + while len(self.out_oldpackets) > ack_diff: + #print ' '*self._dump_indent, '--- POP', repr(self.out_oldpackets[-1]) + self.out_oldpackets.pop() + if self.out_oldpackets: + self.out_nextrepeattime = self.cur_time + self.timeout + else: + self.out_nextrepeattime = None # all packets ACKed + if in_flags == FLAG_NAK or in_flags == FLAG_NAK1: + #print 'recv NAK', hex(in_flags) + # this is a NAK: resend the old packets as far as they've not + # also been ACK'ed in the meantime (can occur with reordering) + while self.out_resend < len(self.out_oldpackets): + self.out_queue.append(self.out_oldpackets[self.out_resend]) + self.out_resend += 1 + self.out_nextseqid = (self.out_nextseqid - 1) & 0xFFFF + #print ' '*self._dump_indent, '--- REP', self.out_nextseqid, repr(self.out_queue[-1]) + self.out_resend_skip = in_flags == FLAG_NAK1 + elif in_flags == FLAG_CFRM: + # this is a CFRM: request for confirmation + self.out_nextrepeattime = self.cur_time + # receive this packet's payload if it is the next in the sequence + if in_diff == 0: + if len(rawdata) > 8: + #print ' '*self._dump_indent, 'RECV ', self.in_nextseqid, repr(rawdata[4:]) + payload = rawdata[4:-4] + crc, = struct.unpack("!I", rawdata[-4:]) + if crc != (crc32(payload, self.in_crc) & 0xffffffff): + self.bad_crc() + return '' # bad crc! drop packet + self.in_nextseqid = (self.in_nextseqid + 1) & 0xFFFF + self.in_crc = crc + result = [payload] + while self.in_nextseqid in self.in_outoforder: + rawdata = self.in_outoforder.pop(self.in_nextseqid) + payload = rawdata[4:-4] + crc, = struct.unpack("!I", rawdata[-4:]) + if crc != (crc32(payload, self.in_crc) & 0xffffffff): + # bad crc! clear all out-of-order packets + self.bad_crc() + break + self.in_nextseqid = (self.in_nextseqid + 1) & 0xFFFF + self.in_crc = crc + result.append(payload) + return ''.join(result) + else: + # we missed at least one intermediate packet: send a NAK + if len(rawdata) > 4: + self.in_outoforder[in_seqid] = rawdata + if ((self.in_nextseqid + 1) & 0xFFFF) in self.in_outoforder: + self.out_flags = FLAG_NAK1 + else: + self.out_flags = FLAG_NAK + self.out_nextrepeattime = self.cur_time + return '' + + def bad_crc(self): + import sys + print >> sys.stderr, "warning: bad crc on udp connexion" + self.in_outoforder.clear() + self.out_flags = FLAG_NAK + self.out_nextrepeattime = self.cur_time + + _dump_indent = 0 + def dump(self, dir, rawdata): + in_flags, ack_seqid, in_seqid = struct.unpack("!BBH", rawdata[:4]) + print ' ' * self._dump_indent, dir, + if in_flags == FLAG_NAK: + print 'NAK', + elif in_flags == FLAG_NAK1: + print 'NAK1', + elif in_flags == FLAG_CFRM: + print 'CFRM', + #print ack_seqid, in_seqid, '(%d bytes)' % (len(rawdata)-4,) + print ack_seqid, in_seqid, repr(rawdata[4:]) + + +def pipe_over_udp(udpsock, send_fd=-1, recv_fd=-1, + timeout=1.0, inactivity_timeout=None): + """Example: send all data showing up in send_fd over the given UDP + socket, and write incoming data into recv_fd. The send_fd and + recv_fd are plain file descriptors. When an EOF is read from + send_fd, this function returns (after making sure that all data was + received by the remote side). + """ + import os + from select import select + from time import time + p = PipeLayer() + p.timeout = timeout + iwtdlist = [udpsock] + if send_fd >= 0: + iwtdlist.append(send_fd) + running = True + while running or not p.in_sync(): + delay = delay1 = p.settime(time()) + if delay is None: + delay = inactivity_timeout + iwtd, owtd, ewtd = select(iwtdlist, [], [], delay) + if iwtd: + if send_fd in iwtd: + data = os.read(send_fd, 1500 - p.headersize) + if not data: + # EOF + iwtdlist.remove(send_fd) + running = False + else: + #print 'queue', len(data) + p.queue(data) + if udpsock in iwtd: + packet = udpsock.recv(65535) + #print 'decode', len(packet) + p.settime(time()) + data = p.decode(packet) + i = 0 + while i < len(data): + i += os.write(recv_fd, data[i:]) + elif delay1 is None: + break # long inactivity + p.settime(time()) + packet = p.encode(1500) + if packet: + #print 'send', len(packet) + #if os.urandom(1) >= '\x08': # emulate packet losses + udpsock.send(packet) + + +class PipeOverUdp(object): + + def __init__(self, udpsock, timeout=1.0): + import thread, os + self.os = os + self.sendpipe = os.pipe() + self.recvpipe = os.pipe() + thread.start_new_thread(pipe_over_udp, (udpsock, + self.sendpipe[0], + self.recvpipe[1], + timeout)) + + def __del__(self): + os = self.os + if self.sendpipe: + os.close(self.sendpipe[0]) + os.close(self.sendpipe[1]) + self.sendpipe = None + if self.recvpipe: + os.close(self.recvpipe[0]) + os.close(self.recvpipe[1]) + self.recvpipe = None + + close = __del__ + + def send(self, data): + if not self.sendpipe: + raise IOError("I/O operation on a closed PipeOverUdp") + return self.os.write(self.sendpipe[1], data) + + def sendall(self, data): + i = 0 + while i < len(data): + i += self.send(data[i:]) + + def recv(self, bufsize): + if not self.recvpipe: + raise IOError("I/O operation on a closed PipeOverUdp") + return self.os.read(self.recvpipe[0], bufsize) + + def recvall(self, bufsize): + buf = [] + while bufsize > 0: + data = self.recv(bufsize) + buf.append(data) + bufsize -= len(data) + return ''.join(buf) + + def fileno(self): + if not self.recvpipe: + raise IOError("I/O operation on a closed PipeOverUdp") + return self.recvpipe[0] + + def ofileno(self): + if not self.sendpipe: + raise IOError("I/O operation on a closed PipeOverUdp") + return self.sendpipe[1] diff --git a/metaserver/socketoverudp.py b/metaserver/socketoverudp.py new file mode 100644 index 0000000..2df4a75 --- /dev/null +++ b/metaserver/socketoverudp.py @@ -0,0 +1,174 @@ +from time import time as now +from pipelayer import PipeLayer, InvalidPacket +from pipelayer import FLAG_RANGE_START, FLAG_RANGE_STOP +import socket, struct + +SOU_RANGE_START = FLAG_RANGE_START +SOU_MIXED_DATA = FLAG_RANGE_STOP + 0 +SOU_SHUTDOWN = FLAG_RANGE_STOP + 1 +SOU_RANGE_STOP = FLAG_RANGE_STOP + 2 + +SHUTDOWN_PACKET = chr(SOU_SHUTDOWN) + '**' # < 4 characters + +CONGESTION_TIMEOUT = 20.0 +#CONSOLIDATE_DELAY = 0.1 + + +class SocketOverUdp(object): + RECV_CAN_RETURN_EMPTY = True + PACKETSIZE = 996 + MIXEDPACKETSIZE = 1080 + + def __init__(self, udpsock, initialcrcs): + self.udpsock = udpsock + self.pl = PipeLayer(initialcrcs) + self.congested_since = None + #self.consolidate_sends = None + #self.encode_delayed_until = now() + + def close(self): + try: + self.udpsock.send(SHUTDOWN_PACKET) + except socket.error: + pass + self.udpsock.close() + + def _progress(self): + if self.pl.settime(now()) == 0.0: + self._encode() + + def _encode(self): + #if self.consolidate_sends: + # if self.pl.cur_time < self.encode_delayed_until: + # return False + # self.encode_delayed_until = self.pl.cur_time + CONSOLIDATE_DELAY + packet = self.pl.encode(self.PACKETSIZE) + if packet is not None: + #print 'send:', repr(packet) + if self.pl.is_congested(): + if self.congested_since is None: + self.congested_since = now() + else: + if now() > self.congested_since + CONGESTION_TIMEOUT: + self.udpsock.send(SHUTDOWN_PACKET) + raise socket.error("peer not responding, timing out") + else: + self.congested_since = None + #print repr(packet[:10]) + #print "out:", len(packet) + #print ' ---' + self.udpsock.send(packet) + + def _decode(self, packet): + try: + data = self.pl.decode(packet) + #print ' ~~~' + return data + except InvalidPacket: + if len(packet) >= 4: + hdr, reserved, size = struct.unpack("!BBH", packet[:4]) + if hdr == SOU_MIXED_DATA: + #print ' ~~~[unmix%d/%d]' % (len(packet[4+size:]), + # len(packet)) + self.udp_over_udp_decoder(packet[4:4+size]) + return self._decode(packet[4+size:]) + else: + # non-tiny packets with no recognized hdr byte are + # assumed to be pure video traffic + #print ' ~~~[video]' + self.udp_over_udp_decoder(packet) + return '' + elif packet == SHUTDOWN_PACKET: + raise socket.error("received an end-of-connexion packet") + else: + #print ' ~~~[INVALID%d]' % (len(packet),) + return '' + + def fileno(self): + self._progress() + return self.udpsock.fileno() + + def flush(self): + while self.pl.settime(now()) == 0.0: + #self.encode_delayed_until = self.pl.cur_time + self._encode() + + def recv(self, _ignoredbufsize=None): + #print 'recv:' + packet = self.udpsock.recv(65535) + #print " in:", len(packet), hex(ord(packet[0])) + #print repr(packet) + self.pl.settime(now()) + data = self._decode(packet) + #print 'which is really', repr(data) + self._encode() + #if data: + # print " IN:", len(data) + return data + + def sendall(self, data): + #print 'queuing', repr(data) + #print ' OUT:', len(data) + self.pl.queue(data) + #self._progress() + return len(data) + + send = sendall + + def send_video_data(self, udpdata): + forced_embedded = SOU_RANGE_START <= ord(udpdata[0]) < SOU_RANGE_STOP + self.pl.settime(now()) + packet = self.pl.encode(self.PACKETSIZE) or '' + if not forced_embedded and not packet: + # no PipeLayer packet, send as plain udp data + datagram = udpdata + elif len(packet) + len(udpdata) <= self.MIXEDPACKETSIZE: + # fits in a single mixed data packet + datagram = (struct.pack("!BBH", SOU_MIXED_DATA, 0, len(udpdata)) + + udpdata + packet) + #print ' ---[mix%d/%d]' % (len(packet), len(datagram)) + else: + # two packets needed + #print repr(packet[:10]) + #print "out:", len(packet) + #print ' ---' + self.udpsock.send(packet) + datagram = udpdata + #print repr(datagram[:10]) + #print "out:", len(datagram), hex(ord(datagram[0])) + self.udpsock.send(datagram) + #self.encode_delayed_until = self.pl.cur_time + CONSOLIDATE_DELAY + #if self.consolidate_sends is None: + # self.consolidate_sends = True + return len(udpdata) + + def udp_over_udp_mixer(self): + return UdpOverUdpMixer(self) + + def udp_over_udp_decoder(self, data): + pass # method overridden by pclient.py + + def getpeername(self): + return self.udpsock.getpeername() + + def getsockname(self): + return self.udpsock.getsockname() + + def setsockopt(self, level, opt, value): + # note that TCP_NODELAY is set by the bub-n-bros client, not the server + #if level == socket.SOL_TCP and opt == socket.TCP_NODELAY: + # self.consolidate_sends = not value + #else: + # ignored + pass + + def setblocking(self, _ignored): + pass # XXX good enough for common/gamesrv.py + + +class UdpOverUdpMixer(object): + def __init__(self, sockoverudp): + self.send = sockoverudp.send_video_data + + def setsockopt(self, *args): + pass # ignored -- cgit v1.2.3