summaryrefslogtreecommitdiff
path: root/bubbob/boards.py
diff options
context:
space:
mode:
Diffstat (limited to 'bubbob/boards.py')
-rw-r--r--bubbob/boards.py1470
1 files changed, 1470 insertions, 0 deletions
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)