summaryrefslogtreecommitdiff
path: root/bubbob/bubbles.py
diff options
context:
space:
mode:
Diffstat (limited to 'bubbob/bubbles.py')
-rw-r--r--bubbob/bubbles.py1284
1 files changed, 1284 insertions, 0 deletions
diff --git a/bubbob/bubbles.py b/bubbob/bubbles.py
new file mode 100644
index 0000000..9734826
--- /dev/null
+++ b/bubbob/bubbles.py
@@ -0,0 +1,1284 @@
+from __future__ import generators
+import random, math
+import gamesrv
+import images
+import boards
+from boards import *
+from images import ActiveSprite
+from mnstrmap import GreenAndBlue, LetterBubbles, Stars
+from mnstrmap import Lightning, Water, Fire, SpinningBalls, PlayerBubbles
+
+
+bubble_wind = {
+ '<': (-1, 0),
+ '>': (+1, 0),
+ '^': ( 0,-1),
+ 'v': ( 0,+1),
+ 'x': ( 0, 0),
+ }
+
+
+class Bubble(ActiveSprite):
+ exploding_bubbles = range(131,136)
+ red_bubbles = [156, 157, 156, 155]
+ white_bubbles = [164, 165, 164, 163]
+ pink_bubbles = [172, 173, 172, 171]
+ check_onbubble = ([(0,-1)], [(0,1)])
+
+ touchable = 1
+ warp = 0
+ default_windless = None
+ catch_dragons = None
+ nimages = GreenAndBlue.normal_bubbles
+
+ def touched(self, dragon):
+ dx, dy = dragon.x, dragon.y
+ o = []
+ if abs(self.x - dx) >= 25:
+ if self.x < dx:
+ o.append((1,0))
+ else:
+ o.append((-1,0))
+ if abs(self.y - dy) >= 25:
+ if self.y < dy:
+ o.append((0,1))
+ else:
+ o.append((0,-1))
+ if o:
+ self.obstacle = o
+ elif (self.catch_dragons and
+ abs(self.x - dx) < 15 and abs(self.y - dy) < 15):
+ if dragon not in self.catch_dragons:
+ self.catch_dragons.append(dragon)
+## elif not self.pop(getattr(dragon, 'poplist', None)):
+## if self.x < dx:
+## o.append((1,0))
+## else:
+## o.append((-1,0))
+## if self.y < dy:
+## o.append((0,1))
+## else:
+## o.append((0,-1))
+## self.obstacle = o
+ else:
+ self.pop(getattr(dragon, 'poplist', None))
+ return o == self.check_onbubble[dragon.bottom_up()]
+
+ def can_catch_dragons(self, author, catch_myself=0):
+ self.catch_dragons = [author]
+ if catch_myself:
+ self.catch_dragons.append(author)
+ self.move(author.x, author.y)
+ self.gen.append(self.catching_dragons())
+
+ def catching_dragons(self):
+ from player import Dragon
+ yield None # time to catch several dragons
+ dragons = [d for d in self.catch_dragons if isinstance(d, Dragon)]
+ self.catch_dragons = None
+ if len(dragons) >= 2:
+ import bonuses
+ author = dragons.pop(0)
+ imglist = [self.nimages[d.dcap.get('bubbericons',
+ d.bubber).pn][i]
+ for d in dragons for i in [1,2,1,0]]
+ self.setimages(self.cyclic(imglist))
+ self.warp = 1
+ caught = [(bonus.points, bonus) for d in dragons
+ if d.bubber is not author.bubber
+ for bonus in d.listcarrybonuses()
+ if isinstance(bonus, CatchNote)]
+ caught.sort()
+ # count caught dragons, excluding team mates, but including self
+ count = 0
+ for d in dragons:
+ if (d.bubber is author.bubber or
+ not d.bubber.sameteam(author.bubber)):
+ count += 1
+ if count:
+ if count == 1:
+ points = 250
+ else:
+ self.play(images.Snd.Extra)
+ if count == 2:
+ points = 10000
+ elif count == 3:
+ points = 30000
+ else:
+ points = 70000
+ caught.append((points, CatchNote(points)))
+ caught = caught[-3:]
+ for points, bonus in caught:
+ author.carrybonus(bonus, 111)
+ bonuses.points(self.x, self.y-HALFCELL, author, points)
+ for d in dragons:
+ d.become_bubblingeyes(self)
+ s_catch = author.bubber.stats.setdefault('catch', {})
+ s_catch[d.bubber] = s_catch.get(d.bubber, 0) + 1
+
+ def pop(self, poplist=None):
+ if self.touchable:
+ self.play(images.Snd.Pop)
+ self.poplist = poplist
+ self.untouchable()
+ self.gen = [self.die(Bubble.exploding_bubbles)]
+ if poplist:
+ dragon = poplist[0]
+ points = self.popped(dragon)
+ if dragon:
+ dragon.bubber.givepoints(points)
+ dragon.bubber.stats['bubble'] += 1
+ self.gen.append(self.poprec())
+ return 1
+ else:
+ return 0
+
+ def popped(self, dragon):
+ return 10
+
+ def poprec(self):
+ yield None
+ for s in self.touching(0):
+ if isinstance(s, Bubble):
+ s.pop(self.poplist)
+
+ def normal_movements(self, dx=0, dy=-1, timeout=800):
+ self.obstacle = []
+ time = 0
+ touchbubble = timeout = (timeout or 0) * 2
+ while 1:
+ del self.obstacle[:]
+ yield None
+ timeout -= 2
+ if not timeout:
+ self.setimages(self.bubble_red())
+ if timeout > touchbubble:
+ continue
+ if timeout&2:
+ for s in self.touching(13+(timeout&6)):
+ if isinstance(s, Bubble) and s is not self:
+ if (s.x-self.x)*dx > 0 or (s.y-self.y)*dy > 0:
+ touchbubble = timeout - (timeout&12) - 3
+ break
+ if timeout > touchbubble:
+ continue
+ if (dx,dy) not in self.obstacle:
+ if dx==dy==0:
+ if len(self.obstacle)==1:
+ dx1, dy1 = self.obstacle[0]
+ self.step(-dx1, -dy1)
+ else:
+ self.step(dx, dy)
+ if self.y < -32 or self.y >= boards.bheight:
+ if not self.warp:
+ self.poplist = None
+ self.kill()
+ return
+ self.vertical_warp()
+## dx = -dx
+ w = wget(self.x, self.y)
+ if w != ' ':
+ dx, dy = bubble_wind[w]
+ elif self.default_windless:
+ dx, dy = self.default_windless
+
+ if dx == dy == 0:
+ # this is the same as the whole loop but runs faster
+ while len(self.obstacle) != 1:
+ del self.obstacle[:]
+ yield None
+ timeout -= 2
+ if not timeout:
+ self.setimages(self.bubble_red())
+
+ def bubble_red(self, speed=5):
+ for n in self.imgseq(Bubble.white_bubbles, repeat=3):
+ yield n
+ for n in self.imgseq(Bubble.pink_bubbles, repeat=4):
+ yield n
+ for n in self.imgseq(Bubble.red_bubbles, repeat=4):
+ yield n
+ for n in self.imgseq([Bubble.pink_bubbles[0], Bubble.red_bubbles[0]],
+ speed=2, repeat=10):
+ yield n
+ self.pop()
+
+ def startnormalbubble(self, dx=0, dy=-1, timeout=800):
+ self.touchable = 1
+ self.gen.append(self.normal_movements(dx=dx, dy=dy, timeout=timeout))
+ imglist = GreenAndBlue.normal_bubbles[self.d.bubber.pn]
+ self.setimages(self.cyclic([imglist[1],
+ imglist[2],
+ imglist[1],
+ imglist[0]]))
+
+ def startsnookerbubble(self, timeout, hspeed, monsterpoplist=None):
+ self.gen.append(self.snooker_movements(dir=hspeed, timeout=timeout))
+ self.gen.append(self.kill_touching_monsters(monsterpoplist))
+ colorname = random.choice(Stars.COLORS)
+ imglist = [('smstar', colorname, k) for k in range(2)]
+ self.to_front()
+ s = images.ActiveSprite(images.sprget(imglist[-1]), self.x, self.y)
+ s.setimages(s.cyclic(imglist, speed=2))
+ s.gen.append(s.following(self))
+ def to_front():
+ Bubble.to_front(self)
+ s.to_front()
+ self.to_front = to_front
+
+ def snooker_movements(self, dir, dy=0.3, timeout=500):
+ icons = [images.sprget(n)
+ for n in GreenAndBlue.normal_bubbles[self.d.bubber.pn]]
+ icotimeout = 0
+ ico = icons[1]
+ yfrac = 0.0
+ self.dragon_jumped = False
+ for i in xrange(timeout):
+ hspeed = random.randrange(2, 4)
+ if ico is not icons[1]:
+ icotimeout += 1
+ if icotimeout >= 16:
+ ico = icons[1]
+ icotimeout = 0
+ elif abs(dy) > 16:
+ if dy > 0:
+ dy = 16.0
+ else:
+ dy = -16.0
+ self.touchable = 1
+ if self.dragon_jumped:
+ self.untouchable()
+ if self.dragon_jumped[1]: # bottom-up dragon
+ dy = -abs(dy)
+ else:
+ dy = abs(dy)
+ self.dragon_jumped = False
+ stepx = 0
+ y1 = (self.y + 27) // CELL
+ if dir < 0:
+ x1 = (self.x + 5) // CELL
+ if bget(x1, y1) == ' ' == bget(x1, y1-1):
+ stepx = -hspeed
+ else:
+ ico = icons[0]
+ dir = 1
+ else:
+ x1 = (self.x + 26) // CELL
+ if bget(x1, y1) == ' ' == bget(x1, y1-1):
+ stepx = hspeed
+ else:
+ ico = icons[0]
+ dir = -1
+ x = self.x
+ deltay = yfrac
+ if x < 32:
+ x += hspeed
+ dir = 1
+ elif x > boards.bwidth - 64:
+ x -= hspeed
+ dir = -1
+ else:
+ x += stepx
+ dy += 0.21
+ deltay = yfrac + dy
+ y = self.y
+ while deltay >= 1.0:
+ deltay -= 1.0
+ if onground(x, y-4):
+ ico = icons[2]
+ deltay = -deltay
+ dy = -abs(dy)
+ else:
+ y += 1
+ while deltay < 0.0:
+ deltay += 1.0
+ if underground(x, y+4):
+ ico = icons[2]
+ dy = abs(dy) * 0.95
+ break
+ y -= 1
+ self.move(x, y, ico)
+ self.vertical_warp()
+ yfrac = deltay
+ yield None
+ self.pop()
+
+ def kill_touching_monsters(self, poplist=None):
+ from monsters import Monster
+ poplist = poplist or [self.d]
+ while 1:
+ yield None
+ for s in self.touching(10):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ yield None
+
+
+class NormalBubble(Bubble):
+ warp = 1
+
+ def __init__(self, dragon, x, y, timeout=800):
+ imglist1 = GreenAndBlue.new_bubbles[dragon.bubber.pn]
+ Bubble.__init__(self, images.sprget(imglist1[0]), x, y)
+ self.d = dragon
+ self.startnormalbubble(timeout=timeout)
+
+class SnookerBubble(Bubble):
+ warp = 1
+ touchable = 0
+
+ def __init__(self, dragon, x, y, timeout=500):
+ imglist1 = GreenAndBlue.new_bubbles[dragon.bubber.pn]
+ Bubble.__init__(self, images.sprget(imglist1[0]), x, y)
+ self.d = dragon
+ self.startsnookerbubble(timeout=timeout, hspeed=dragon.dir)
+
+
+class BigBubbleCatcher(ActiveSprite):
+ numlist = [(PlayerBubbles.explosion[2], 1),
+ (PlayerBubbles.explosion[1], 2),
+ (PlayerBubbles.explosion[0], 2),
+ (PlayerBubbles.bubble[1], 5),
+ (PlayerBubbles.appearing[4], 3),
+ (PlayerBubbles.appearing[3], 3),
+ (PlayerBubbles.appearing[2], 2),
+ (PlayerBubbles.appearing[1], 2),
+ (PlayerBubbles.appearing[0], 2)]
+
+ def __init__(self, dragon, target, timeout):
+ img = images.sprget(PlayerBubbles.explosion[2])
+ ActiveSprite.__init__(self, img, -img.w, 0)
+ self.dragon = dragon
+ self.target = target
+ self.timeout = timeout
+ self.gen.append(self.follow())
+ self.recenter(PlayerBubbles.explosion[2])
+ for imgnum, delay in self.numlist:
+ images.sprget(imgnum) # preload
+
+ def recenter(self, imgnum):
+ s = self.target
+ if not s.alive or not s.touchable:
+ self.kill()
+ else:
+ img = images.sprget(imgnum)
+ self.move(s.x + (s.ico.w - img.w) // 2,
+ s.y + (s.ico.h - img.h) // 2,
+ img)
+
+ def follow(self):
+ for imgnum, delay in self.numlist:
+ for t in range(delay):
+ self.recenter(imgnum)
+ yield None
+ self.recenter(imgnum)
+ if self.alive:
+ s = self.target
+ s.in_bubble(NormalBubble(self.dragon, s.x, s.y, self.timeout))
+ self.kill()
+
+
+class CatchNote:
+ def __init__(self, points):
+ self.points = points
+ def endaction(self, dragon):
+ pass
+
+
+class DragonBubble(Bubble):
+ touchable = 0
+
+ def __init__(self, d, x, y, dir, special_bubble=None, angle=0,
+ thrustfactor=None, shootthrust=None):
+ self.d = d
+ pn = d.bubber.pn
+ imglist1 = GreenAndBlue.new_bubbles[pn]
+ imglist2 = GreenAndBlue.normal_bubbles[pn]
+ if angle:
+ asin, acos = math.sin(angle), math.cos(angle)
+ else:
+ asin, acos = 0, 1
+ Bubble.__init__(self, images.sprget(imglist1[0]), x + 12*dir, y)
+ self.setimages(self.imgseq(imglist1[1:] + imglist2[2:3], 4))
+ if shootthrust is None:
+ shootthrust = d.dcap['shootthrust']
+ hspeed = dir * shootthrust
+ if thrustfactor is not None:
+ negative = hspeed < 0
+ hspeed = (abs(hspeed) - 4.0) * thrustfactor + 4.0
+ if negative:
+ hspeed = -hspeed
+ self.gen.append(self.throw_bubble(hspeed, special_bubble, (acos,asin)))
+
+ def throw_bubble(self, hspeed, special_bubble=None, (acos,asin)=(1,0)):
+ from monsters import Monster
+ nx = self.x
+ ny = self.y
+ stop = 0
+ withmonster = 0
+ specialangle = (acos,asin) != (1,0)
+ if special_bubble == 'BigFireBubble':
+ if not specialangle:
+ BigFireBubble(self.x, self.y, hspeed, self.d)
+ self.kill()
+ return
+
+ self.warp = 0
+ monsterpoplist = [self.d]
+ while abs(hspeed) >= 4.0:
+ touched_monsters = [s for s in self.touching(9)
+ if isinstance(s, Monster)]
+ if touched_monsters:
+ if special_bubble == 'SnookerBubble':
+ for monster in touched_monsters:
+ monster.argh(monsterpoplist)
+ else:
+ monster = random.choice(touched_monsters)
+ in_bubble = monster.in_bubble(self)
+ withmonster = self.withmonster = 1
+ if in_bubble is None:
+ self.warp = 1
+ try:
+ key = monster.mdef.jailed[0]
+ except AttributeError:
+ pass
+ else:
+ s_monster = self.d.bubber.stats.setdefault(
+ 'monster', {})
+ s_monster[key] = s_monster.get(key, 0) + 1
+ break
+ if specialangle:
+ nx, ny = vertical_warp(nx + hspeed*acos, ny + hspeed*asin)
+## if moebius:
+## acos = -acos
+ else:
+ nx += hspeed
+ hspeed *= 0.965
+ xc = int(nx-3.8)//CELL+1
+ yc = (self.y+HALFCELL)//CELL
+ if bget(xc,yc) == '#' == bget(xc, yc+1):
+ stop += 1
+ if stop <= 1:
+ self.move(int(nx+0.5), int(ny+0.5))
+ yield None
+
+ if special_bubble == 'SnookerBubble':
+ if stop > 1:
+ hspeed = -hspeed
+ self.startsnookerbubble(self.d.dcap['bubbledelay'] or 800,
+ hspeed, monsterpoplist)
+ return
+ if not withmonster:
+ from bonuses import Bonus, BonusMaker
+ touched_bonuses = [s for s in self.touching(15)
+ if isinstance(s, Bonus) and s.bubblable]
+ if touched_bonuses:
+ random.choice(touched_bonuses).in_bubble(self)
+ withmonster = 1
+ else:
+ touched_bonuses = [s for s in self.touching(7)
+ if isinstance(s, BonusMaker)]
+ if touched_bonuses:
+ bonusmaker = random.choice(touched_bonuses)
+ if bonusmaker.in_bubble(self):
+ withmonster = 1
+ if not self.alive:
+ return
+ if special_bubble:
+ cls = globals()[special_bubble]
+ if not withmonster:
+ b = cls(self.d.bubber.pn)
+ b.move(self.x, self.y)
+ b.can_catch_dragons(self.d, hspeed == 0)
+ self.kill()
+ return
+ bubbledelay = self.d.dcap['bubbledelay']
+ if bubbledelay:
+ timeout = 1
+ if bubbledelay > 1:
+ self.gen.append(self.delayed_pop(7))
+ else:
+ timeout = 800
+ self.startnormalbubble(timeout=timeout)
+ if not withmonster:
+ self.can_catch_dragons(self.d, hspeed == 0)
+
+ def delayed_pop(self, delay):
+ for i in range(delay):
+ yield None
+ self.pop()
+
+
+class FishBubble(Bubble):
+ touchable = 0
+
+ def __init__(self, dragon):
+ ico = images.sprget(GreenAndBlue.new_bubbles[dragon.bubber.pn][0])
+ Bubble.__init__(self, ico, dragon.x + dragon.dir*12, dragon.y)
+ timeout = random.randrange(50, 150)
+ self.gen.append(self.normal_movements(timeout=timeout))
+ self.gen.append(self.fuzz())
+
+ def fuzz(self):
+ while 1:
+ prevx = self.x
+ yield None
+ yield None
+ yield None
+ if prevx == self.x:
+ self.step(random.choice([-1, 1]), 0)
+
+ def bubble_red(self, *args, **kwds):
+ self.gen.append(self.die([]))
+
+
+class BubblingEyes(ActiveSprite):
+
+ def __init__(self, bubber, saved_caps, bubble):
+ ico = images.sprget(('eyes', 0, 0))
+ ActiveSprite.__init__(self, ico, bubble.x, bubble.y)
+ self.bubber = bubber
+ self.dcap = saved_caps
+ self.gen = [self.playing_bubble(bubble)]
+
+ def bottom_up(self):
+ return self.dcap['gravity'] < 0.0
+
+ def playing_bubble(self, bubble):
+ from player import Dragon
+ bottom_up = self.bottom_up()
+ flip = 'vflip'*bottom_up
+ timer = 0
+ red = 0
+ normalbub = bubble.imgsetter
+ redblinker = bubble.cyclic([Bubble.pink_bubbles[0], Bubble.red_bubbles[0]], 2)
+ bubber = self.bubber
+ ndir = random.choice([-1, 1])
+ prev_dx_dy = None
+ while not hasattr(bubble, 'poplist'):
+ dx = bubber.wannago(self.dcap)
+ if dx:
+ ndir = dx
+ if bubber.key_jump:
+ dy = -1
+ else:
+ dy = 0
+ if bubber.key_fire:
+ red += 1
+ if red > 20:
+ d = Dragon(bubber, self.x, self.y, ndir, self.dcap)
+ Bubble.pop(bubble, [d]) # hack to pop SolidBubbles too
+ d.kill()
+ break
+ if bubble.imgsetter is not redblinker:
+ normalbub = bubble.imgsetter
+ bubble.setimages(redblinker)
+ else:
+ #red = 0
+ if bubble.imgsetter is redblinker:
+ bubble.setimages(normalbub)
+ key = ('eyes', dx, dy)
+ if timer < 50:
+ if (timer % 9) < 3:
+ key = 'eyes-blink'
+ elif random.random() < 0.1:
+ key = 'eyes-blink'
+ timer += 1
+ if bubble.x <= 3*HALFCELL and dx < 0:
+ dx = 0
+ if bubble.x >= boards.bwidth - 7*HALFCELL and dx > 0:
+ dx = 0
+ if bottom_up:
+ dy = -dy
+ nx = bubble.x + dx
+ ny = bubble.y + dy
+ if timer&1:
+ nx += dx
+ else:
+ ny += dy
+ nx, ny = vertical_warp(nx, ny)
+ bubble.move(nx, ny)
+ self.move(nx+dx, ny+dy, images.sprget((flip, key)))
+## if moebius:
+## self.dcap['left2right'] *= -1
+ if dx == dy == 0:
+ bubble.default_windless = prev_dx_dy
+ else:
+ prev_dx_dy = dx, dy
+ bubble.default_windless = 0, 0
+ yield None
+ # jumping out of the bubble
+ if bottom_up:
+ kw = {'gravity': -0.3}
+ else:
+ kw = {}
+ from player import BubPlayer
+ bi = self.dcap.get('bubbericons', bubber)
+ if BubPlayer.SuperFish and 'fish' in bi.transformedicons:
+ self.setimages(None)
+ self.seticon(bi.transformedicons['fish'][0, +1])
+ else:
+ self.setimages(self.cyclic(
+ [(flip, n) for n in GreenAndBlue.comming[bi.pn]], 2))
+ dxy = [(random.random()-0.5) * 9.0,
+ (random.random()+0.5) * (-5.0,5.0)[bottom_up]]
+ for n in self.parabolic(dxy, 1, **kw):
+ yield n
+ if dxy[1] * (1,-1)[bottom_up] >= 4.0:
+ break
+ if dxy[0] < 0:
+ ndir = -1
+ else:
+ ndir = 1
+ d = Dragon(bubber, self.x, self.y, ndir, self.dcap)
+ d.dcap['shield'] = 50
+ bubber.dragons.append(d)
+ self.kill()
+
+ def kill(self):
+ try:
+ self.bubber.dragons.remove(self)
+ except ValueError:
+ pass
+ ActiveSprite.kill(self)
+
+
+class BonusBubble(Bubble):
+ max = None
+ timeout = None
+ flip = ''
+
+ def __init__(self, pn, nimages=None, top=None):
+ if nimages is None:
+ nimages = self.nimages[pn]
+ b = boards.curboard
+ if top is None:
+ top = b.top
+ if top == 0:
+ testline = b.walls[-1]
+ x, y = self.findhole(testline), boards.bheight
+ dx, dy = 0, -1
+ elif top == 1:
+ testline = b.walls[0]
+ x, y = self.findhole(testline), -2*CELL
+ dx, dy = 0, 1
+ elif top == 2:
+ x, y = -2*CELL, random.randint(2*CELL, boards.bheight-4*CELL)
+ dx, dy = 1, 0
+ else: # top == 3:
+ x, y = (boards.bwidth - CELL,
+ random.randint(2*CELL, boards.bheight-4*CELL))
+ dx, dy = -1, 0
+ Bubble.__init__(self, images.sprget((self.flip, nimages[0])), x, y)
+ self.gen.append(self.normal_movements(dx=dx, dy=dy,
+ timeout=self.timeout))
+ if len(nimages) == 3:
+ nimages = [nimages[1], nimages[2], nimages[1], nimages[0]]
+ if len(nimages) > 1:
+ self.setimages(self.cyclic([(self.flip, n) for n in nimages]))
+
+ def findhole(self, testline):
+ holes = [x for x in range(len(testline)-1) if testline[x:x+2]==' ']
+ if not holes:
+ holes = range(2, len(testline)-3)
+ return random.choice(holes) * CELL
+
+ def thrown_bubble(self, x, y, hspeed, acossin):
+ self.untouchable()
+ self.move(x, y)
+ self.gen = [self.throwing_bubble(hspeed, acossin, self.imgsetter)]
+
+ def throwing_bubble(self, hspeed, (acos,asin), restore_img):
+ nx = self.x
+ ny = self.y
+ while abs(hspeed) >= 4.0:
+ nx, ny = vertical_warp(nx + hspeed*acos, ny + hspeed*asin)
+## if moebius:
+## acos = -acos
+ if nx <= CELL:
+ acos = abs(acos)
+ if nx >= boards.bwidth-3*CELL:
+ acos = -abs(acos)
+ hspeed *= 0.965
+ self.move(int(nx+0.5), int(ny+0.5))
+ yield None
+ self.touchable = 1
+ self.gen.append(self.normal_movements(timeout=self.timeout))
+ self.setimages(restore_img)
+
+
+class PlainBubble(BonusBubble):
+ timeout = 500
+ def condition():
+ return boards.curboard.holes
+
+def extend_name(l):
+ text = 'extend'
+ return text[:l] + text[l].upper() + text[l+1:]
+
+class LetterBubble(BonusBubble):
+ max = 2
+ def condition():
+ return boards.curboard.letter
+ def __init__(self, pn, l=None):
+ if l is None:
+ l = random.randint(0,5)
+ self.l = l
+ lettername = extend_name(self.l)
+ BonusBubble.__init__(self, pn, nimages=getattr(LetterBubbles, lettername))
+ def popped(self, dragon):
+ if dragon:
+ dragon.bubber.giveletter(self.l)
+ return 50
+
+class FireFlame(ActiveSprite):
+ timeout = 17
+ def __init__(self, x0, y0, poplist, dirs=None, countdown=0, flip=''):
+ ico = images.sprget((flip, Fire.ground[0]))
+ ActiveSprite.__init__(self, ico, x0*CELL, y0*CELL)
+ if not countdown:
+ dirs = []
+ self.poplist = poplist
+ self.gen.append(self.burning(dirs, countdown))
+ self.setimages(self.cyclic([(flip, n) for n in Fire.ground], 1))
+ def burning(self, dirs, countdown):
+ from monsters import Monster
+ x0 = self.x//CELL
+ y0 = self.y//CELL
+ for dir in dirs:
+ if bget(x0+dir, y0+1) == '#' and bget(x0+dir, y0) == ' ':
+ FireFlame(x0+dir, y0, self.poplist, [dir], countdown-1)
+ for i in range(self.timeout):
+ yield None
+ if self.poplist:
+ for s in self.touching(0):
+ if isinstance(s, Monster):
+ s.argh(self.poplist)
+ yield None
+ self.kill()
+
+class FireDrop(ActiveSprite):
+ def __init__(self, x, y, poplist=None):
+ ActiveSprite.__init__(self, images.sprget(Fire.drop), x, y)
+ self.poplist = poplist or [None]
+ self.gen.append(self.dropping())
+ def dropping(self):
+ x0 = self.x//CELL
+ while bget(x0, self.y//CELL) == '#' or bget(x0, self.y//CELL+1) != '#':
+ if self.y >= boards.bheight:
+ self.kill()
+ return
+ self.move(self.x, (self.y + 8) & ~7)
+ yield None
+ y0 = self.y//CELL
+ #if bget(x0-1, y0) == ' ':
+ FireFlame(x0, y0, self.poplist, [-1, 1], 5)
+ self.kill()
+
+class FireBubble(BonusBubble):
+ max = 4
+ nimages = GreenAndBlue.fire_bubbles
+ def condition():
+ return boards.curboard.fire
+ def popped(self, dragon):
+ if dragon:
+ x0 = self.x // CELL + 1
+ y0 = self.y // CELL + 1
+ if bget(x0, y0) == '#':
+ x1 = (self.x + HALFCELL) // CELL
+ if x1 == x0:
+ tries = [x1+1, x1-1]
+ else:
+ tries = [x1, x1+2]
+ for x1 in tries:
+ if bget(x1, y0) == ' ':
+ x0 = x1
+ break
+ FireDrop(x0*CELL, self.y)
+ return 10
+
+##class BombBubble(FireBubble):
+## flip = 'vflip'
+## def popped(self, dragon):
+## if dragon:
+## import bonuses
+## bonuses.bomb_explosion(self.x, self.y + CELL, starmul=1)
+## return 100
+
+##class WaterCell(ActiveSprite):
+## ICONS = {
+## ( 0,1, None) : Water.bottom,
+## ( 1,0, None) : Water.start_left,
+## (-1,0, None) : Water.start_right,
+## ( 0,0, None) : Water.bottom,
+
+## (0,1, 0,1) : Water.v_flow,
+## (0,1, 1,0) : Water.bl_corner,
+## (0,1, -1,0) : Water.br_corner,
+
+## (-1,0, 0,1) : Water.tl_corner,
+## (-1,0, 1,0) : Water.start_right,
+## #(-1,0, -1,0) : Water.h_flow,
+
+## (1,0, 0,1) : Water.tr_corner,
+## #(1,0, 1,0) : Water.h_flow,
+## (1,0, -1,0) : Water.start_left,
+
+## (0,0, 0,1) : Water.top,
+## (0,0, 1,0) : Water.top,
+## (0,0, -1,0) : Water.top,
+
+## (None, 0,1) : Water.top,
+## (None, -1,0) : Water.start_left,
+## (None, 1,0) : Water.start_right,
+## (None, 0,0) : Water.top,
+## }
+
+## def __init__(self, x, y):
+## ActiveSprite.__init__(self, images.sprget(Water.top), x, y)
+## self.touchable = 1
+
+## def ready(self, celllist):
+## self.gen.append(self.flooding(celllist))
+
+## def flooding(self, celllist):
+## from monsters import Monster
+## x0 = self.x // 16
+## y0 = self.y // 16
+## ping = 0
+## dir = random.choice([-1, 1])
+## take_with_us = [[] for cell in celllist]
+## poplist = [None]
+## icons = {}
+## for key, value in self.ICONS.items():
+## icons[key] = images.sprget(value)
+## icodef = images.sprget(Water.h_flow)
+## stop = 0
+## while not stop:
+## dx = dy = 0
+## if bget(x0, y0+1) == ' ':
+## dy = y0*16 < boards.bheight
+## ping = 0
+## elif bget(x0+dir, y0) == ' ':
+## dx = dir
+## elif bget(x0-dir, y0) == ' ':
+## ping += 1
+## if ping < 3:
+## dir = -dir
+## dx = dir
+## # change the head icon
+## head = celllist[0]
+## second = celllist[1]
+## head.seticon(icons.get((x0-second.x//16, y0-second.y//16,
+## dx, dy), icodef))
+## # move the tail to the new head position
+## x0 += dx
+## y0 += dy
+## newhead = celllist.pop()
+## celllist.insert(0, newhead)
+## newhead.move(x0*16, y0*16, icons.get((dx,dy, None), icodef))
+## # change the new tail icon
+## tail = celllist[-1]
+## second = celllist[-2]
+## tail.seticon(icons.get((None, (second.x-tail.x)//16,
+## (second.y-tail.y)//16), icodef))
+## # take monsters with us
+## for i in range(0, len(celllist), 3):
+## for s in celllist[i].touching(0):
+## if isinstance(s, Monster):
+## s.untouchable()
+## s.gen = []
+## take_with_us[i].append(s)
+## elif isinstance(s, Bubble):
+## s.pop(poplist)
+## yield 0
+## stop = dx == dy == 0
+## for cell, takelist in zip(celllist, take_with_us):
+## stop &= cell.x == newhead.x and cell.y == newhead.y
+## for s in takelist:
+## if s.alive:
+## s.move(x2bounds(cell.x-8), cell.y-16)
+## if stop:
+## s.argh(poplist, onplace=1)
+## for c in celllist:
+## c.kill()
+## def touched(self, dragon):
+## dragon.watermove(x2bounds(self.x-HALFCELL), self.y-CELL+1)
+## return 1
+
+class WaterCell(ActiveSprite):
+ TESTLIST = [(-CELL,0), (CELL,0), (0,CELL), (0,-CELL)]
+ ICONS = [Water.v_flow,
+ Water.start_left,
+ Water.start_right,
+ Water.h_flow,
+ Water.top,
+ Water.tr_corner,
+ Water.tl_corner,
+ Water.h_flow,
+
+ Water.bottom,
+ Water.br_corner,
+ Water.bl_corner,
+ Water.h_flow,
+ Water.v_flow,
+ Water.v_flow,
+ Water.v_flow,
+ Water.v_flow]
+
+ def __init__(self, x, y, dir, watercells, poplist, repeat):
+ ActiveSprite.__init__(self, images.sprget(Water.top), x, y)
+ self.poplist = poplist
+ self.take_with_me = []
+ self.ping = 0
+ self.repeat = repeat
+ self.watercells = watercells
+ self.touchable = repeat % 3 == 1
+ if (x, y, dir) not in watercells:
+ watercells[x,y,dir] = self
+ if None not in watercells or not watercells[None].alive:
+ self.in_charge()
+ else:
+ watercells[x,y,dir].join(self)
+
+ def join(self, other):
+ self.take_with_me += other.take_with_me
+ self.ping = min(self.ping, other.ping)
+ self.repeat += other.repeat
+ self.touchable = self.touchable or other.touchable
+ del other.take_with_me[:]
+ other.kill()
+
+ def in_charge(self):
+ self.gen = [self.flooding()]
+ self.watercells[None] = self
+
+ def kill(self):
+ from monsters import Monster
+ for s in self.take_with_me[:]:
+ if isinstance(s, Monster) and s.alive:
+ s.argh(self.poplist, onplace=1)
+ del self.take_with_me[:]
+ ActiveSprite.kill(self)
+ if not self.watercells[None].alive:
+ del self.watercells[None]
+ for s in self.watercells.values():
+ if s.alive:
+ s.in_charge()
+ break
+
+ def flooding(self):
+ from monsters import Monster
+ watercells = self.watercells
+ while watercells[None] is self:
+
+ new = []
+ nwatercells = {None: self}
+ for key, s in watercells.items():
+ if key:
+ x, y, dir = key
+ if s.repeat:
+ new.append((x, y, dir, watercells,
+ s.poplist, s.repeat-1))
+ s.repeat = 0
+ x0 = x // CELL
+ y0 = y // CELL
+ if bget(x0, y0+1) == ' ':
+ if y >= boards.bheight:
+ s.kill()
+ continue
+ s.ping = 0
+ y += CELL
+ elif bget(x0+dir, y0) == ' ':
+ x += dir*CELL
+ elif bget(x0-dir, y0) == ' ':
+ s.ping += 1
+ if s.ping == 3:
+ s.kill()
+ continue
+ dir = -dir
+ x += dir*CELL
+ else:
+ s.kill()
+ continue
+ key = x, y, dir
+ if key in nwatercells:
+ nwatercells[key].join(s)
+ else:
+ nwatercells[key] = s
+
+ watercells.clear()
+ watercells.update(nwatercells)
+ for args in new:
+ WaterCell(*args)
+
+ for key, s in watercells.items():
+ if key:
+ x, y, dir = key
+ flag = 0
+ for k in range(4):
+ dx, dy = s.TESTLIST[k]
+ if ((x+dx, y+dy, -1) in watercells or
+ (x+dx, y+dy, 1) in watercells):
+ flag += 1<<k
+ ico = images.sprget(s.ICONS[flag])
+ s.move(x, y, ico)
+ if s.touchable:
+ for s1 in s.touching(0):
+ if isinstance(s1, Monster):
+ s1.untouchable()
+ s1.gen = []
+ s.take_with_me.append(s1)
+ elif isinstance(s1, Bubble):
+ s1.pop(s.poplist)
+ for s1 in s.take_with_me:
+ if s1.alive:
+ s1.move(x2bounds(x-HALFCELL), y-CELL)
+ yield None
+ if not watercells[None].alive:
+ self.in_charge()
+
+ def touched(self, dragon):
+ dragon.watermove(x2bounds(self.x-HALFCELL), self.y-CELL+1)
+ return 1
+
+def watercell(x, y, poplist, dir=None, repeat=4):
+ b = boards.curboard
+ if not hasattr(b, 'watercells'):
+ b.watercells = {}
+ dir = dir or random.choice([-1, 1])
+ WaterCell(x, y, dir, b.watercells, poplist, repeat)
+
+class WaterBubble(BonusBubble):
+ max = 4
+ nimages = GreenAndBlue.water_bubbles
+ def condition():
+ return boards.curboard.water
+ def popped(self, dragon):
+ if dragon:
+ x0 = self.x // CELL + 1
+ y0 = self.y // CELL + 1
+ for x1 in [x0, x0+1, x0-1]:
+ if bget(x1,y0) == ' ' or bget(x1,y0+1) == ' ':
+ x0 = x1
+ break
+ watercell(x0*CELL, y0*CELL, [None], repeat=19)
+ return 10
+
+##class SolidBubble(WaterBubble):
+## timeout = 450
+## solidbubble = 1
+## flip = 'vflip'
+
+## def bubble_red(self, *args):
+## self.solidbubble = 0
+## return WaterBubble.bubble_red(self, *args)
+
+## def pop(self, poplist=None):
+## return (not (self.solidbubble and poplist is not None)
+## and WaterBubble.pop(self, poplist))
+
+## def popped(self, dragon):
+## return 100
+
+class FiredLightning(ActiveSprite):
+ def __init__(self, x, y, dir, poplist, diry=0):
+ ActiveSprite.__init__(self, images.sprget(Lightning.fired), x, y)
+ self.dir = int(13*dir)
+ self.diry = int(13*diry)
+ self.gen.append(self.moving(poplist))
+ def moving(self, poplist):
+ from monsters import Monster
+ while (-2*CELL < self.x < boards.bwidth and
+ -2*CELL < self.y < boards.bheight):
+ for s in self.touching(2):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ elif isinstance(s, Bubble):
+ s.pop(poplist)
+ self.step(self.dir, self.diry)
+ yield None
+ self.kill()
+
+class LightningBubble(BonusBubble):
+ max = 4
+ nimages = GreenAndBlue.light_bubbles
+ def condition():
+ return boards.curboard.lightning
+ def popped(self, dragon):
+ if dragon:
+ FiredLightning(self.x, self.y, -dragon.dir, self.poplist)
+ return 10
+
+class BigLightBubble(LightningBubble):
+ flip = 'vflip'
+ def popped(self, dragon):
+ base = random.random() * 2.0*math.pi
+ for a in range(7):
+ FiredLightning(self.x, self.y, math.cos(base+a), self.poplist,
+ diry = -math.sin(base+a))
+ return 100
+
+
+class BigFireBubble(ActiveSprite):
+ touchable = 1
+
+ def __init__(self, x, y, hspeed, author):
+ if hspeed > 0:
+ imgs = PlayerBubbles.right_weapon
+ else:
+ imgs = PlayerBubbles.left_weapon
+ ActiveSprite.__init__(self, images.sprget(imgs[-1]), x, y)
+ self.setimages(self.cyclic(imgs, 2))
+ self.author = author
+ self.gen.append(self.moving(hspeed))
+ self.play(images.Snd.Shh)
+
+ def moving(self, hspeed):
+ from monsters import Monster
+ if abs(hspeed) < 3:
+ if hspeed > 0:
+ hspeed = 3
+ else:
+ hspeed = -3
+ fx = self.x
+ poplist = [self.author]
+ while 1:
+ fx += hspeed
+ self.move(int(fx), self.y)
+ xc = int(fx)//CELL + 1
+ yc = (self.y+HALFCELL)//CELL
+ if bget(xc,yc) == '#' == bget(xc, yc+1):
+ break
+ yield None
+ for s in self.touching(4):
+ if isinstance(s, Monster):
+ s.argh(poplist)
+ self.kill()
+
+ def touched(self, dragon):
+ if dragon is not self.author:
+ import bonuses
+ bonuses.repulse_dragon(dragon)
+
+
+class SpinningBall(ActiveSprite):
+ def __init__(self, x, y, poplist):
+ ActiveSprite.__init__(self, images.sprget(SpinningBalls.free[-1]), x,y)
+ self.poplist = poplist
+ self.gen.append(self.dropping())
+ imgs = SpinningBalls.free
+ if random.random() < 0.5:
+ imgs = list(imgs)
+ imgs.reverse()
+ self.setimages(self.cyclic(imgs, random.randrange(2,5)))
+ self.touchable = 1
+ def dropping(self):
+ from monsters import Monster
+ for ny in range(self.y, boards.bheight, 5):
+ self.move(self.x, ny)
+ yield None
+ for s in self.touching(0):
+ if isinstance(s, Monster):
+ s.argh(self.poplist)
+ elif isinstance(s, Bubble):
+ s.pop(self.poplist)
+ self.kill()
+ def touched(self, dragon):
+ dragon.die()
+
+class StarBubble(BonusBubble):
+ timeout = 250
+ def __init__(self, pn):
+ self.colorname = random.choice(Stars.COLORS)
+ BonusBubble.__init__(self, pn, [('starbub', self.colorname, i)
+ for i in range(3)])
+## def __init__(self, pn):
+## BonusBubble.__init__(self, pn)
+## self.colorname = random.choice(Stars.COLORS)
+## starimg = [('smstar', self.colorname, 0),
+## ('smstar', self.colorname, 1)]
+## smallstar = ActiveSprite(images.sprget(starimg[-1]),
+## self.x+8, self.y+8)
+## smallstar.setimages(smallstar.cyclic(starimg))
+## smallstar.gen.append(smallstar.following(self, 8, 8))
+ def popped(self, dragon):
+ if dragon:
+ from bonuses import BonusMaker, AllOutcomes, Parabolic2
+ BonusMaker(self.x, self.y, getattr(Stars, self.colorname),
+ outcome=random.choice(AllOutcomes))
+ for i in range(2):
+ Parabolic2(self.x, self.y, [('smstar', self.colorname, i)
+ for i in range(2)])
+ return 100
+
+class MonsterBubble(BonusBubble):
+ timeout = 100
+ def __init__(self, pn, mcls):
+ import monsters, mnstrmap
+ BonusBubble.__init__(self, pn)
+ mdef = getattr(mnstrmap, mcls.__name__)
+ m = mcls(mdef, self.x, self.y, 1)
+ m.in_bubble(self)
+
+class MoreBubblesBubble(BonusBubble):
+
+ def can_catch_dragons(self, author, catch_myself=0):
+ # at this point, explode the bubble into more bubbles
+ d = author
+ for angle in [math.pi, math.pi/2, -math.pi/2, 0]:
+ for factor in [0.2, 0.55, 0.9, 1.25, 1.6]:
+ DragonBubble(d, self.x, self.y, d.dir,
+ angle = angle, thrustfactor = factor)
+ self.pop()
+
+
+Classes = ([PlainBubble] * 7 +
+ [FireBubble, WaterBubble, LightningBubble] * 4 +
+ [LetterBubble])
+
+def newbubble(): #force=0):
+ #if force:
+ #cls = PlainBubble
+ #else:
+ cls = random.choice(Classes)
+ if not cls.__dict__['condition']():
+ return
+ if cls.max is not None:
+ others = [s for s in images.ActiveSprites if isinstance(s, cls)]
+ if len(others) >= cls.max:
+ return
+ sendbubble(cls)
+
+def newforcedbubble():
+ choices = [PlainBubble] * 5
+ for cls in [FireBubble, WaterBubble, LightningBubble]:
+ if cls.__dict__['condition']():
+ n = 4
+ else:
+ n = 1
+ choices.extend([cls] * n)
+ cls = random.choice(choices)
+ return sendbubble(cls)
+
+def sendbubble(cls, *args, **kw):
+ from player import BubPlayer
+ players = [p for p in BubPlayer.PlayerList if p.isplaying()]
+ if not players:
+ return None
+ pn = random.choice(players).pn
+ return cls(pn, *args, **kw)
+
+def newbonusbubble():
+ boards.curboard.top = random.choice([0,0,0, 1,1,1, 2,2, 3,3])
+ r = random.random()
+ if r < 0.14:
+ sendbubble(random.choice(Classes))
+ elif r < 0.16:
+ from player import BubPlayer
+ import monsters
+ mcls = random.choice(monsters.MonsterClasses)
+ for d in BubPlayer.DragonList:
+ sendbubble(MonsterBubble, mcls)
+ else:
+ sendbubble(StarBubble)