from __future__ import generators
import random, os, math
import random as random_module
import gamesrv
import images
import boards
from boards import *
from images import ActiveSprite
from mnstrmap import GreenAndBlue, Bonuses, Diamonds, Stars, BigImages
from mnstrmap import PotionBonuses, Fire
from player import BubPlayer


questionmarklist = ['questionmark3',
                    'questionmark4',
                    'questionmark5',
                    'questionmark4',
                    'questionmark3',
                    'questionmark2',
                    'questionmark1',
                    'questionmark2']

class Bonus(ActiveSprite):
    bubblable = 1
    touchable = 1
    points = 750
    timeout = 250
    sound = 'Fruit'
    endaction = None
    multiply = 1
    killgens = 1

    def __init__(self, x, y, nimage=None, points=None, falling=1):
        if nimage is not None:
            self.nimage = nimage
        if points is not None:
            self.points = points
        ActiveSprite.__init__(self, images.sprget(self.nimage), x, y)
        self.taken_by = []
        self.gen.append(self.timeouter())
        if falling:
            self.gen.append(self.faller())

    def buildoutcome(self):
        return (self.__class__,)

    def faller(self):
        while self.y < boards.bheight:
            if onground_nobottom(self.x, self.y):
                yield None
                yield None
            else:
                self.move(self.x, (self.y+4) & ~3)
            yield None
        self.kill()

    def timeouter(self):
        for i in range(self.timeout):
            yield None
        if self.timeout:
            self.kill()

    def touched(self, dragon):
        dx, dy, dw, dh = dragon.x, dragon.y, dragon.ico.w, dragon.ico.h
        if (dx + dw > self.x + 10 and
            dy + dh > self.y + 8  and
            self.x + self.ico.w > dx + 10 and
            self.y + self.ico.h > dy + 10):
            self.reallytouched(dragon)

    def reallytouched(self, dragon):
        if not self.taken_by:
            if self.killgens:
                self.gen = []
            self.gen.append(self.taking())
            sound = self.sound
            if sound:
                if isinstance(sound, str):
                    sound = getattr(images.Snd, sound)
                self.play(sound)
        if dragon not in self.taken_by:
            self.taken_by.append(dragon)
            if isinstance(self, (RandomBonus, MonsterBonus)):
                s_bonus = dragon.bubber.stats.setdefault('bonus', {})
                s_bonus[self.nimage] = s_bonus.get(self.nimage, 0) + 1

    def taking(self, follow_dragons=0, delay=1):
        from player import Dragon
        for t in range(delay):
            yield None   # time to be taken by several dragons
        if self.points:
            for p in self.taken_by:
                if follow_dragons and p.alive:
                    s = p
                else:
                    s = self
                points(s.x + s.ico.w//2, s.y + s.ico.h//2 - CELL, p, self.points)
        dragons = [d for d in self.taken_by if isinstance(d, Dragon)]
        if self.taken1(dragons) != -1:
            self.kill()

    def taken1(self, dragons):
        for d in dragons * self.multiply:
            if d.alive:
                self.taken(d)

    def taken(self, dragon):
        pass

    def in_bubble(self, bubble):
        self.untouchable()
        bubble.move(self.x, self.y)
        bubble.to_front()
        self.to_front()
        self.gen = [self.bubbling(bubble, self.ico)]
        self.move(bubble.x+8, bubble.y+8, images.sprget('questionmark3'))
        self.setimages(self.cyclic(questionmarklist, 2))

    def bubbling(self, bubble, ico):
        while not hasattr(bubble, 'poplist'):
            self.move(bubble.x+8, bubble.y+8)
            yield None
        if bubble.poplist is not None:
            dragon = bubble.poplist[0]
            if dragon is not None:
                self.play(images.Snd.Yippee)
                if dragon not in self.taken_by:
                    self.taken_by.append(dragon)
                if self.points > 10:
                    dragon.bubber.givepoints(self.points - 10)
                    pn = dragon.bubber.pn
                    if self.points in GreenAndBlue.points[pn]:
                        Points(bubble.x + bubble.ico.w//2, bubble.y, pn, self.points)
                self.taken1(BubPlayer.DragonList)
                p = Parabolic(ico, bubble.x, bubble.y)
                p.gen.append(p.moving(-1.0))
        self.kill()

    def is_on_ground(self):
        return onground(self.x, self.y)


def points(x, y, dragon, points):
    dragon.bubber.givepoints(abs(points))
    pn = dragon.bubber.pn
    if points in GreenAndBlue.points[pn]:
        Points(x, y, pn, points)

class Points(ActiveSprite):

    def __init__(self, x, y, pn, points):
        ico = images.sprget(GreenAndBlue.points[pn][points])
        ActiveSprite.__init__(self, ico, x - ico.w//2, max(8, y))
        self.nooverlap = 1
        self.gen.append(self.raiser())

    def raiser(self):
        wait = 0
        for s in images.ActiveSprites:
            if s is self:
                break
            if (isinstance(s, Points) and s.nooverlap and
                abs(self.x-s.x)<self.ico.w*2//3 and
                abs(self.y-s.y)<self.ico.h):
                wait += 5
        for t in range(wait):
            yield None
        for i in range(25):
            if i == 7:
                self.nooverlap = 0
            self.step(0, -2)
            yield None
            if self.y <= 0:
                break
        for i in range(20):
            yield None
        self.kill()


class Parabolic(ActiveSprite):
    fallstraight = 0
    fallspeed = 4
    
    def moving(self, y_amplitude = -8.0):
        bottom_up = self.fallspeed < 0
        dxy = [(random.random()-0.5) * 15.0,
               (random.random()+0.5) * y_amplitude * (1,-1)[bottom_up]]
        if bottom_up:
            kw = {'gravity': -0.3}
        else:
            kw = {}
        for n in self.parabolic(dxy, self.fallstraight, **kw):
            progress = self.parabole_progress = dxy[1] * (1,-1)[bottom_up]
            yield n
            if progress >= 4.0 and self.fallstraight:
                del self.parabole_progress
                self.gen.append(self.falling())
                return
        self.kill()

    def falling(self):
        nx, ny = vertical_warp(self.x, self.y & ~3)
        if self.fallspeed < 0:
            groundtest = underground
        else:
            groundtest = onground
        while not groundtest(nx, ny):
            ny += self.fallspeed
            nx, ny1 = vertical_warp(nx, ny)
            if ny1 != ny:
                ny = ny1
                self.wrapped_around()
            self.move(nx, ny)
            yield None
        self.move(nx, ny)
        self.build()
        self.kill()

    def killmonsters(self, poplist):
        from monsters import Monster
        while 1:
            for s in self.touching(0):
                if isinstance(s, Monster):
                    s.argh(poplist)
            yield None

    def build(self):
        pass

    def wrapped_around(self):
        pass


class Parabolic2(Parabolic):
    points = 0

    def __init__(self, x, y, imglist, imgspeed=3, onplace=0, y_amplitude=-8.0):
        Parabolic.__init__(self, images.sprget(imglist[0]), x, y)
        if onplace:
            self.gen.append(self.falling())
        else:
            self.gen.append(self.moving(y_amplitude))
        if len(imglist) > 1:
            self.setimages(self.cyclic(imglist, imgspeed))

    def touched(self, dragon, rect=None):
        if self.points:
            points(self.x + self.ico.w/2, self.y + self.ico.h/2 - CELL,
                   dragon, self.points)
            self.kill()


class BonusMaker(Parabolic2):
    fallstraight = 1
    touchable = 1

    def __init__(self, x, y, imglist, imgspeed=3, onplace=0, outcome=None):
        assert outcome
        self.outcome = outcome
        if outcome == (Flower2,):
            self.fallspeed = -self.fallspeed
        Parabolic2.__init__(self, x, y, imglist, imgspeed, onplace)

    def falling(self):
        cls = self.outcome[0]
        if issubclass(cls, Megabonus):
            self.build()
            return self.die([])
        else:
            return Parabolic2.falling(self)

    def wrapped_around(self):
        cls = self.outcome[0]
        if issubclass(cls, RandomBonus) and not boards.curboard.playingboard:
            self.kill()

    def build(self):
        cls = self.outcome[0]
        args = self.outcome[1:]
        if issubclass(cls, RandomBonus) and not boards.curboard.playingboard:
            return None
        else:
            return cls(self.x, self.y, *args)

    def touched(self, dragon, rect=None):
        pass

    def in_bubble(self, bubble):
        bonus = self.build()
        self.kill()
        if bonus:
            bonus.in_bubble(bubble)
        return bonus

class BonusMakerExtraStar(ActiveSprite):

    def __init__(self, x, y, sx, sy, colorname):
        self.sx = sx
        self.sy = sy
        imglist = [('smstar', colorname, k) for k in range(2)]
        ActiveSprite.__init__(self, images.sprget(imglist[-1]),
                                    x + HALFCELL, y + HALFCELL)
        self.setimages(self.cyclic(imglist, speed=2))

    def follow_bonusmaker(self, bm):
        for t in range(4):
            yield None
            if hasattr(bm, 'parabole_progress'):
                break
        else:
            self.kill()
            return
        start = bm.parabole_progress
        if start < 3.9:
            while bm.alive and hasattr(bm, 'parabole_progress'):
                f = (bm.parabole_progress-start) / (4.0-start)
                self.move(bm.x + HALFCELL + int(f*self.sx),
                          bm.y + HALFCELL + int(f*self.sy))
                yield None
        self.kill()


class MonsterBonus(Bonus):

    def __init__(self, x, y, multiple, forceimg=0):
        self.level = multiple
        if multiple >= len(Bonuses.monster_bonuses):
            multiple = len(Bonuses.monster_bonuses) - 1
        img, pts = Bonuses.monster_bonuses[multiple]
        Bonus.__init__(self, x, y, forceimg or img, pts)

    def buildoutcome(self):
        return (self.__class__, self.level)

    def taken(self, dragon):
        dragon.carrybonus(self, 543)

class IceMonsterBonus(MonsterBonus):

    def __init__(self, x, y, multiple):
        self.level = multiple
        if multiple >= 1:
            img, pts = Bonuses.violet_ice, 750
        else:
            img, pts = Bonuses.cyan_ice, 700
        Bonus.__init__(self, x, y, img, pts)



class DustStar(ActiveSprite):
    localrandom = random.Random()

    def __init__(self, x, y, basedx, basedy, big=1, clock=0):
        self.colorname = self.localrandom.choice(Stars.COLORS)
        self.imgspeed = self.localrandom.randrange(3, 6)
        self.rotation_reversed = self.localrandom.random() < 0.5
        ico, imggen = self.select_ico(getattr(Stars, self.colorname))
        ActiveSprite.__init__(self, ico, x, y)
        self.setimages(imggen)
        self.gen.append(self.fly(basedx, basedy, big))
        if not big:
            self.make_small()
        elif clock:
            self.setimages(None)
            self.seticon(images.sprget(Bonuses.clock))

    def select_ico(self, imglist):
        if self.rotation_reversed:
            imglist = list(imglist)
            imglist.reverse()
        return (images.sprget(imglist[-1]),
                self.cyclic(imglist, self.imgspeed))

    def make_small(self):
        images = [('smstar', self.colorname, k) for k in range(2)]
        ico, imggen = self.select_ico(images)
        self.seticon(ico)
        self.setimages(imggen)

    def fly(self, dx, dy, big):
        random = self.localrandom
        dx += (random.random() - 0.5) * 2.8
        dy += (random.random() - 0.5) * 2.8
        fx = self.x
        fy = self.y
        if big:
            j = 0
        else:
            j = 2
        while j < 3:
            ttl = random.expovariate(1.0 / 12)
            if ttl > 35:
                ttl = 35
            for i in range(int(ttl)+4):
                fx += dx
                fy += dy
                self.move(int(fx), int(fy))
                yield None
            if j == 0:
                self.make_small()
                fx += 8
                fy += 8
            j += 1
        self.kill()


class RandomBonus(Bonus):
    timeout = 500

class TemporaryBonus(RandomBonus):
    captime = 0
    bonusleveldivider = 2
    def taken(self, dragon):
        dragon.dcap[self.capname] += 1
        self.carried(dragon)
    def carried(self, dragon):
        captime = self.captime
        if boards.curboard.bonuslevel:
            captime = (captime or 999) // self.bonusleveldivider
        if captime:
            dragon.carrybonus(self, captime)
        else:
            dragon.carrybonus(self)
            self.endaction = None
    def endaction(self, dragon):
        if dragon.dcap[self.capname] >= 1:
            dragon.dcap[self.capname] -= 1


class ShoeSpeed(RandomBonus):
    "Fast Runner. Cumulative increase of horizontal speed."
    nimage = Bonuses.shoe
    bigbonus = {'multiply': 3}
    bigdoc = "Run Really Fast."
    def taken(self, dragon):
        dragon.dcap['hspeed'] += 1
        dragon.carrybonus(self)

class CoffeeSpeed(RandomBonus):
    "Caffeine. Cumulative increase of the horizontal speed and fire rate."
    nimage = Bonuses.coffee
    big = 0
    bigbonus = {'big': 1, 'multiply': 3}
    bigdoc = "Super-Excited!  Break through walls!"
    def taken(self, dragon):
        dragon.dcap['hspeed'] += 0.5
        dragon.dcap['firerate'] += 1
        if self.big:
            dragon.dcap['breakwalls'] = 1
        dragon.carrybonus(self)

class Butterfly(TemporaryBonus):
    "Lunar Gravity. Allows you to jump twice as high as before."
    nimage = Bonuses.butterfly
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Butterflies all around."
    def taken1(self, dragons):
        if self.big:
            import mnstrmap, monsters
            for i in range(17):
                monsters.Butterfly(mnstrmap.Butterfly,
                                   self.x + random.randrange(-40, 41),
                                   self.y + random.randrange(-30, 31),
                                   random.choice([-1, 1]))
        else:
            TemporaryBonus.taken1(self, dragons)
    def taken(self, dragon):
        dragon.dcap['gravity'] *= 0.5
        self.carried(dragon)
    def endaction(self, dragon):
        dragon.dcap['gravity'] *= 2.0

class Cocktail(TemporaryBonus):
    "Short Lived Bubbles. Makes your bubbles explode more quickly."
    nimage = Bonuses.cocktail
    points = 2000
    capname = 'bubbledelay'
    bigbonus = {'multiply': 3}
    bigdoc = "Makes your bubbles explode at once.  Dangerous!"

class Extend(RandomBonus):
    "E X T E N D. Gives you your missing letters and clear the level. "
    nimage = Bonuses.extend
    points = 0
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "A lot of letter bubbles! Run! Run!"

    def taken1(self, dragons):
        if self.big:
            self.letterexplosion()
        else:
            RandomBonus.taken1(self, dragons)
            
    def taken(self, dragon):
        from bubbles import extend_name
        names = [extend_name(l) for l in range(6)]
        missing = [name for name in names if name not in dragon.bubber.letters]
        x = dragon.x + dragon.ico.w//2
        y = dragon.y
        points(x, y, dragon, 10000*len(missing))
        for l in range(6):
            if extend_name(l) in missing:
                dragon.bubber.giveletter(l, promize=0)

    def letterexplosion(self):
        from bubbles import LetterBubble
        playercount = len([p for p in BubPlayer.PlayerList if p.isplaying()])
        N = 3 + (playercount > 3)
        angles = [i*(2.0*math.pi/N) for i in range(N)]
        for l, dx, dy in [(0,  5,  9), (1, 16, 10), (2, 26,  8),
                          (3,  7, 23), (4, 15, 24), (5, 25, 24)]:
            delta = 2.0*math.pi * random.random()
            angles = [angle-delta for angle in angles]
            x = self.x + self.ico.w//2 + 3*(dx-16)
            y = self.y + self.ico.h//2 + 3*(dy-16)
            for angle in angles:
                bubble = LetterBubble(None, l)
                bubble.thrown_bubble(x, y, 7.0 + 4.0 * random.random(),
                                     (math.cos(angle), math.sin(angle)))

class HeartPoison(RandomBonus):
    "Heart Poison. Freeze all free monsters."
    nimage = Bonuses.heart_poison
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Freeze all other players too!"
    def taken1(self, dragons):
        import monsters
        monsters.freeze_em_all()
        if self.big:
            def heart_pause(dragon, gen):
                for i in range(222):
                    yield None
                dragon.gen = gen
            for d in BubPlayer.DragonList:
                if d not in dragons:
                    d.gen = [heart_pause(d, d.gen)]

class VioletNecklace(RandomBonus):
    "Monster Duplicator. Double the number of free monsters."
    points = 650
    nimage = Bonuses.violet_necklace
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "This level's boring, let's bring even more monsters..."
    def taken1(self, dragons):
        if self.big:
            import monsters, mnstrmap
            mlist = 2*['Nasty', 'Monky', 'Springy', 'Orcy', 'Gramy', 'Blitzy']
            wrange = (boards.bwidth - 8*CELL) // 2
            for dir in [1, -1]:
                for i in range(len(mlist)):
                    name = mlist[i]
                    mdef = getattr(mnstrmap, name)
                    cls = getattr(monsters, name)
                    x = wrange * i // len(mlist)
                    if dir == 1:
                        x = 2*CELL + HALFCELL + x
                    else:
                        x = boards.bwidth - 4*CELL - HALFCELL - x
                    y = -2*CELL - i * 2*CELL
                    cls(mdef, x, y, dir)
        else:
            for s in BubPlayer.MonsterList[:]:
                if s.regular():
                    for i in range(self.multiply):
                        s.__class__(s.mdef, s.x, s.y, -s.dir * (-1)**i)

class WandBonus(RandomBonus):
    "Wand/Chest. Turn the bubble into bonuses at the end of the level."
    nimages = [Bonuses.brown_wand,   Bonuses.yellow_wand, Bonuses.green_wand,
               Bonuses.violet_wand,  Bonuses.blue_wand,   Bonuses.red_wand,
               Bonuses.violet_chest, Bonuses.blue_chest,  Bonuses.red_chest,
               Bonuses.yellow_chest,
               ]
    Modes = [
        (Bonuses.brown_wand,   750,  Bonuses.cyan_ice,    700,  BigImages.cyan_ice,    20000),
        (Bonuses.yellow_wand,  750,  Bonuses.violet_ice,  750,  BigImages.violet_ice,  20000),
        (Bonuses.green_wand,   750,  Bonuses.peach2,      800,  BigImages.peach2,      30000),
        (Bonuses.violet_wand,  750,  Bonuses.pastec2,     850,  BigImages.pastec2,     30000),
        (Bonuses.blue_wand,    750,  Bonuses.cream_pie,   900,  BigImages.cream_pie,   40000),
        (Bonuses.red_wand,     750,  Bonuses.sugar_pie,   950,  BigImages.sugar_pie,   40000),
        (Bonuses.violet_chest, 2000, Diamonds.violet,     6000, BigImages.violet,      60000),
        (Bonuses.blue_chest,   2000, Diamonds.blue,       7000, BigImages.blue,        60000),
        (Bonuses.red_chest,    2000, Diamonds.red,        8000, BigImages.red,         70000),
        (Bonuses.yellow_chest, 2000, Diamonds.yellow,     9000, BigImages.yellow,      70000),
        ]
    def __init__(self, x, y):
        self.mode = random.choice(WandBonus.Modes)
        RandomBonus.__init__(self, x, y, *self.mode[:2])
    def taken1(self, dragons):
        BubPlayer.BubblesBecome = self.bubble_outcome
        BubPlayer.MegaBonus     = self.mega_bonus
    def bubble_outcome(self, bubble):
        if bubble.pop():
            x = bubble.x
            if x < 2*CELL:
                x = 2*CELL
            elif x > boards.bwidth - 4*CELL:
                x = boards.bwidth - 4*CELL
            Bonus(x, bubble.y, *self.mode[2:4])
    def mega_bonus(self):
        nico, npoints = self.mode[4:6]
        ico = images.sprget(nico)
        x = random.randrange(0, boards.bwidth-ico.w)
        mb = Megabonus(x, -ico.h, nico, npoints)
        mb.outcome = (Bonus,) + self.mode[2:4]
        mb.outcome_image = self.mode[2]
WandBonus1 = WandBonus  # increase probability

class Megabonus(Bonus):
    touchable = 0
    vspeed = 6
    sound = 'Extra'
    coverwithbonus = 99
    fallerdelay = 71
    
    def faller(self):
        self.fullpoints = self.points
        self.bubbles = {}
        for t in range(self.fallerdelay):
            yield None
        self.ready_to_go()
        self.bubbles_pos = list(self.bubbles_position())
        self.gen.append(self.animate_bubbles())
        y0 = self.y - HALFCELL
        ymax = boards.bheight - CELL - self.ico.h
        self.touchable = 1
        ny = self.y
        while self.y >= y0:
            if self.vspeed:
                ny += self.vspeed
                if ny > ymax:
                    ny = ymax
                    self.vspeed = 0
                self.move(self.x, int(ny))
            yield None
        self.kill()

    def ready_to_go(self):
        pass

    def is_on_ground(self):
        return self.y == boards.bheight - CELL - self.ico.h

    def kill(self):
        for bubble in self.bubbles.values():
            bubble.pop()
        Bonus.kill(self)

    def taken(self, dragon):
        poplist = [dragon]
        for bubble in self.bubbles.values():
            bubble.pop(poplist)

    def bubbles_position(self):
        import time; start=time.time()
        cx = self.ico.w//2 - CELL
        cy = self.ico.h//2 - CELL
        positions = []
        pi2 = math.pi * 2
        dist = 10.0
        for i in range(31):
            while 1:
                angle = random.random() * pi2
                nx = cx + int(dist*math.sin(angle))
                ny = cy + int(dist*math.cos(angle))
                for ox, oy in positions:
                    if (nx-ox)*(nx-ox) + (ny-oy)*(ny-oy) < 220:
                        dist += 0.3
                        break
                else:
                    break
            positions.append((nx, ny))
        #print time.time()-start
        return positions
##        nx = 5
##        ny = 6
##        xmargin = 2
##        ymargin = 7
##        xstep = (self.ico.w+2*xmargin-2*CELL) / float(nx-1)
##        ystep = (self.ico.h+2*ymargin-2*CELL) / float(ny-1)
##        for dx in range(nx):
##            corner = dx in [0, nx-1]
##            for dy in range(corner, ny-corner):
##                dx1 = int(dx*xstep)-xmargin
##                dy1 = int(dy*ystep)-ymargin
##                yield (dx1 + random.randrange(-2,3),
##                       dy1 + random.randrange(-2,3))

    def nearest_free_point(self, x0, y0):
        distlst = [((x0-x)*(x0-x)+(y0-y)*(y0-y)+random.random(), x, y)
                   for x, y in self.bubbles_pos if (x, y) not in self.bubbles]
        if distlst:
            ignored, dx, dy = min(distlst)
            return dx, dy
        else:
            return None, None

    def in_bubble(self, bubble):
        if not self.touchable:
            return   # bubbling a BonusMaker about to make a big bonus
        dx, dy = self.nearest_free_point(bubble.x-self.x, bubble.y-self.y)
        if dx is not None:
            self.cover_bubble(dx, dy, bubble.d.bubber)
            self.gen.append(self.cover_bubbles(bubble.d.bubber))
        bubble.kill()

    def cover_bubbles(self, bubber):
        while 1:
            for t in range(2):
                yield None
            bubbles = [dxy for dxy, b in self.bubbles.items()
                           if b.bubber is bubber]
            if not bubbles:
                break
            dx, dy = self.nearest_free_point(*random.choice(bubbles))
            if dx is None:
                break
            self.cover_bubble(dx, dy, bubber)
        self.untouchable()

    def cover_bubble(self, dx, dy, bubber):
        if (dx, dy) in self.bubbles:
            return
        from bubbles import Bubble
        if len(self.bubbles) & 1:
            MegabonusBubble = Bubble
        elif self.coverwithbonus:
            self.coverwithbonus -= 1
            outcome = self.outcome
            outcome_image = self.outcome_image
            class MegabonusBubble(Bubble):
                def popped(self, dragon):
                    BonusMaker(self.x, self.y, [outcome_image], outcome=outcome)
                    return 10
        else:
            MegabonusBubble = Bubble
        
        nimages = GreenAndBlue.normal_bubbles[bubber.pn]
        b = MegabonusBubble(images.sprget(nimages[1]), self.x+dx, self.y+dy)
        b.dx = dx
        b.dy = dy
        b.bubber = bubber
        b.nimages = nimages
        self.bubbles[dx, dy] = b
        self.timeout = 0
        f = float(len(self.bubbles)) / len(self.bubbles_pos)
        self.vspeed = -0.73*f + self.vspeed*(1.0-f)
        self.points = int(self.fullpoints*(1.0-f) / 10000.0 + 0.9999) * 10000

    def animate_bubbles(self):
        if 0:  # disabled clipping
            d = {}
            for dx, dy in self.bubbles_pos:
                d[dx] = d[dy] = None
            north = d.copy()
            south = d.copy()
            west = d.copy()
            east = d.copy()
            del d
            for dx, dy in self.bubbles_pos:
                lst = [y for x, y in self.bubbles_pos if x==dx and y<dy]
                if lst: north[dy] = max(lst)
                lst = [y for x, y in self.bubbles_pos if x==dx and y>dy]
                if lst: south[dy] = min(lst)
                lst = [x for x, y in self.bubbles_pos if x<dx and y==dy]
                if lst: west[dx] = max(lst)
                lst = [x for x, y in self.bubbles_pos if x>dx and y==dy]
                if lst: east[dx] = min(lst)
            W = 2*CELL
            H = 2*CELL
        bubbles = self.bubbles
        while 1:
            for cycle in [1]*8 + [2]*10 + [1]*8 + [0]*10:
                yield None
                for (dx, dy), bubble in bubbles.items():
                    if not hasattr(bubble, 'poplist'):
                        if 0:   # disabled clipping
                            if (dx, north[dy]) in bubbles:
                                margin_n = (north[dy]+H-dy)//2
                            else:
                                margin_n = 0
                            if (dx, south[dy]) in bubbles:
                                margin_s = (dy+H-south[dy])//2
                            else:
                                margin_s = 0
                            if (west[dx], dy) in bubbles:
                                margin_w = (west[dx]+W-dx)//2
                            else:
                                margin_w = 0
                            if (east[dx], dy) in bubbles:
                                margin_e = (dx+W-east[dx])//2
                            else:
                                margin_e = 0
                            r = (margin_w,
                                 margin_n,
                                 W-margin_w-margin_e,
                                 H-margin_n-margin_s)
                            bubble.move(self.x + bubble.dx + margin_w,
                                        self.y + bubble.dy + margin_n,
                                        images.sprget_subrect(
                                            bubble.nimages[cycle], r))
                        else:
                            bubble.move(self.x + bubble.dx,
                                        self.y + bubble.dy,
                                        images.sprget(bubble.nimages[cycle]))
                    elif len(bubbles) == len(self.bubbles_pos):
                        self.pop_bubbles(bubble.poplist)
                        return

    def pop_bubbles(self, poplist):
        def bubble_timeout(bubble, vspeed):
            ny = bubble.y
            for t in range(random.randrange(15,25)):
                if hasattr(bubble, 'poplist'):
                    return
                ny += vspeed
                bubble.move(bubble.x, int(ny))
                yield None
            bubble.pop(poplist)

        for bubble in self.bubbles.values():
            bubble.gen.append(bubble_timeout(bubble, self.vspeed))
        self.bubbles.clear()
        self.kill()

class Cactus(RandomBonus):
    "Cactus. Drop a big version of a random bonus."
    points = 600
    nimage = 'cactus'
    extra_cheat_arg = None
    bigbonus = {'multiply': 3}
    bigdoc = "Let's get more big bonuses!"
    
    def taken1(self, dragons):
        count = 0
        while count < self.multiply:
            args = ()
            if self.extra_cheat_arg:
                cls = globals()[self.extra_cheat_arg]
                self.extra_cheat_arg = None
            elif bigclockticker and bigclockticker.state == 'pre':
                cls = Clock
                args = (1,)
            else:
                cls = random.choice(Classes)
            if makecactusbonus(cls, *args):
                count += 1
        cactusbonussound()

#Cactus1 = Cactus # increase probability

OFFSCREEN = -3*CELL
def makecactusbonus(cls, *args):
    bonus = cls(OFFSCREEN, 0, *args)
    if not bonus.alive or getattr(bonus, 'bigbonus', None) is None:
        if bonus.alive:
            bonus.kill()
        return None
    bonus.__dict__.update(bonus.bigbonus)
    bonus.untouchable()
    bonus.gen = []
    megacls = bonus.bigbonus.get('megacls', Cactusbonus)
    mb = megacls(0, -3*CELL, 'cactus', 10000)   # temp image
    mb.outcome = (cls,) + (args or bonus.bigbonus.get('outcome_args', ()))
    mb.outcome_image = bonus.nimage
    mb.bonus = bonus
    mb.gen.append(mb.prepare_image())
    mb.gen.append(mb.remove_if_no_bonus())
    return mb

def cactusbonussound():
    gamesrv.set_musics([], [])
    boards.curboard.set_musics(prefix=[images.music_modern])
    boards.curboard.set_musics()

class Cactusbonus(Megabonus):
    coverwithbonus = 5

    def prepare_image(self):
        while images.computebiggericon(self.bonus.ico) is None:
            yield None

    def remove_if_no_bonus(self):
        while self.bonus.alive:
            yield None
        self.kill()

    def ready_to_go(self):
        ico = images.biggericon(self.bonus.ico)
        x = random.randrange(0, boards.bwidth-ico.w)
        self.move(x, -ico.h, ico)

    def taken1(self, dragons):
        d1 = list(dragons)
        Megabonus.taken1(self, dragons)
        if self.bonus.alive:
            x = self.x + self.ico.w//2 - CELL
            y = self.y + self.ico.h//2 - CELL
            self.bonus.move(x, y)
            res = self.bonus.taken1(d1)
            self.untouchable()
            if res == -1:
                self.taken_by = []
                self.gen.append(self.touchdelay(10))
                self.bonus.move(OFFSCREEN, 0)
            else:
                self.bonus.kill()
            return res

    def kill(self):
        Megabonus.kill(self)
        if self.bonus.alive:
            self.bonus.kill()

class LongDurationCactusbonus(Cactusbonus):
    timeout = 500
    killgens = 0

def starexplosion(x, y, multiplyer, killmonsters=0, outcomes=[]):
    outcomes = list(outcomes)
    poplist = [None]
    for i in range(multiplyer):
        colors = list(Stars.COLORS)
        random.shuffle(colors)
        for colorname in colors:
            images = getattr(Stars, colorname)
            if outcomes:
                outcome = outcomes.pop()
                extra_stars = []
                if hasattr(outcome[0], 'extra_stars_location'):
                    for sx, sy in outcome[0].extra_stars_location:
                        extra_stars.append(BonusMakerExtraStar(x, y, sx, sy,
                                                               colorname))
                bm = BonusMaker(x, y, images, outcome=outcome)
                for star in extra_stars:
                    star.gen.append(star.follow_bonusmaker(bm))
            else:
                b = Parabolic2(x, y, images)
                if killmonsters:
                    b.gen.append(b.killmonsters(poplist))

class HomingStar(ActiveSprite):
    def __init__(self, x, y, colorname, poplist):
        imglist = getattr(Stars, colorname)
        ActiveSprite.__init__(self, images.sprget(imglist[0]), x, y)
        self.colorname = colorname
        self.setimages(self.cyclic(imglist, 2))
        self.gen.append(self.homing(poplist))

    def homing(self, poplist):
        from monsters import Monster
        target = None
        vx = (random.random() - 0.5) * 6.6
        vy = (random.random() - 0.5) * 4.4
        nx = self.x
        ny = self.y
        counter = 10
        while 1:
            if random.random() < 0.02:
                target = None
            if target is None or not target.alive:
                bestdist = 1E10
                for s in BubPlayer.MonsterList:
                    if isinstance(s, Monster):
                        dx = s.x - nx
                        dy = s.y - ny
                        dist = dx*dx + dy*dy + (random.random() * 25432.1)
                        if dist < bestdist:
                            bestdist = dist
                            target = s
                if target is None:
                    break
            dx = target.x - nx
            dy = target.y - ny
            dist = dx*dx + dy*dy
            if dist <= 3*CELL*CELL:
                target.argh(poplist)
                break
            yield None
            vx = (vx + dx * 0.005) * 0.96
            vy = (vy + dy * 0.005) * 0.96
            nx += vx
            ny += vy
            self.move(int(nx), int(ny))
            if counter:
                counter -= 1
            else:
                img = ('smstar', self.colorname, random.randrange(2))
                s = ActiveSprite(images.sprget(img), self.x + 8, self.y + 8)
                s.gen.append(s.die([None], speed=10))
                counter = 3
        self.kill()

class Book(RandomBonus):
    "Magic Bomb. Makes a magical explosion killing touched monsters."
    points = 2000
    nimage = Bonuses.book
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Homing Magical Stars."
    def taken1(self, dragons):
        if self.big:
            poplist = [None]
            x = self.x + (self.ico.w - 2*CELL) // 2
            y = self.y + (self.ico.h - 2*CELL) // 2
            colors = list(Stars.COLORS)
            random.shuffle(colors)
            for colorname in colors + colors[len(colors)//2:]:
                HomingStar(x, y, colorname, poplist)
        else:
            starexplosion(self.x, self.y, self.multiply, killmonsters=1)

class Potion(RandomBonus):
    "Potions. Clear the level and fill its top with bonuses."
    nimages = [Bonuses.red_potion, Bonuses.green_potion, Bonuses.yellow_potion,
               'potion4']
    Potions = [(Bonuses.red_potion,    150,  [(PotionBonuses.coin,        350),
                                              (PotionBonuses.rainbow,     600)]),
               (Bonuses.green_potion,  350,  [(PotionBonuses.flower,     1000),
                                              (PotionBonuses.trefle,     2000)]),
               (Bonuses.yellow_potion, 550,  [(PotionBonuses.green_note, 2000),
                                              (PotionBonuses.blue_note,  3000)]),
               ('potion4',             750,  None),
               ]
    LocalDir = os.path.dirname(__file__) or os.curdir
    Extensions = [s for s in os.listdir(LocalDir)
                    if s.startswith('ext') and
                       os.path.isdir(os.path.join(LocalDir, s))]
    random.shuffle(Extensions)
    extra_cheat_arg = None
    big = 0
    bigdoc = "Fill the whole level with bonuses."

    def __init__(self, x, y):
        p_normal = 3
        if boards.curboard.bonuslevel:
            p_extension = 2       # make extensions rare in the bonus level
        else:
            p_extension = 5
        if self.extra_cheat_arg:
            Potion.Extensions.append(self.extra_cheat_arg)
            p_normal = 0
        if not Potion.Extensions:
            p_extension = 0
        choices = []
        for mode in Potion.Potions:
            if mode[2] is None:
                p = p_extension
            else:
                p = p_normal
            choices += [mode] * p
        self.mode = random.choice(choices)
        if self.mode[2] is not None:
            self.bigbonus = {'big': 1}
        RandomBonus.__init__(self, x, y, *self.mode[:2])
    def taken1(self, dragons):
        blist = self.mode[2]
        if blist is not None:
            if random.random() < 0.6:
                blist = [random.choice(blist)]
            boards.replace_boardgen(boards.potion_fill(blist, self.big))
        else:
            n_players = len([p for p in BubPlayer.PlayerList if p.isplaying()])
            while Potion.Extensions:
                ext = Potion.Extensions.pop()
                #print "Trying potion:", ext
                ext = __import__(ext, globals(),locals(), ['run','min_players'])
                if n_players >= ext.min_players:
                    ext.run()
                    boards.BoardGen.append(boards.extra_bkgnd_black(self.x, self.y))
                    #print "Accepted because:", n_players, ">=", ext.min_players
                    break
                else:
                    #print "Rejected because:", n_players, "<", ext.min_players
                    pass

class FireBubble(RandomBonus):
    "Fire Bubbles. Makes you fire napalm bubbles."
    nimage = Bonuses.hamburger
    bubkind = 'FireBubble'
    bubcount = 10
    bigbonus = {'bubkind': 'BigFireBubble'}
    bigdoc = "Makes you shoot fire - you're a dragon after all."
    def taken(self, dragon):
        dragon.dcap['shootbubbles'] = [self.bubkind] * self.bubcount
        dragon.carrybonus(self)

class WaterBubble(FireBubble):
    "Water Bubbles. Your bubbles will now be filled with water."
    nimage = Bonuses.beer
    bubkind = 'WaterBubble'
    bigbonus = {'bubkind': 'SnookerBubble'}
    bigdoc = "Snooker balls."

class LightningBubble(FireBubble):
    "Lightning Bubbles."
    nimage = Bonuses.french_fries
    bubkind = 'LightningBubble'
    bigbonus = {'bubkind': 'BigLightBubble'}
    bigdoc = "Even-more-lightning Bubbles."

class Megadiamond(Megabonus):
    nimage = BigImages.red
    points = 20000
    fallerdelay = 0
    outcome = (MonsterBonus, -1)
    outcome_image = Bonuses.monster_bonuses[-1][0]
    extra_stars_location = [ (-24,-28),(0,-28),(24,-28),
                            (-40,-11),          (40,-11),
                              (-32, 8),       (32, 8),
                                 (-16,23), (16,23),
                                       (0,38),         ]
    def __init__(self, x, y):
        ico = images.sprget(self.nimage)
        x -= (ico.w - 2*CELL) // 2
        y -= (ico.h - 2*CELL) // 2
        Megabonus.__init__(self, x, y)

class Door(RandomBonus):
    "Magic Door. Let bonuses come in!"
    points = 1000
    nimage = Bonuses.door
    diamond_outcome = (MonsterBonus, -1)
    bigbonus = {'diamond_outcome': (Megadiamond,)}
    bigdoc = "Let bigger bonuses come in!"
    def taken1(self, dragons):
        starexplosion(self.x, self.y, 2,
                      outcomes = [self.diamond_outcome] * 10)

class LongFire(RandomBonus):
    "Long Fire. Increase the range of your bubble throw out."
    nimage = Bonuses.softice1
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Throw bubbles that split into more bubbles."
    def taken(self, dragon):
        if self.big:
            dragon.dcap['shootbubbles'] = ['MoreBubblesBubble'] * 10
        else:
            dragon.dcap['shootthrust'] *= 1.5
        dragon.carrybonus(self)

class Glue(RandomBonus):
    "Glue.  Triple fire."
    nimage = 'glue'
    points = 850
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Heptuple fire. (That's 7.)"
    def taken(self, dragon):
        if self.big:
            dragon.dcap['flower'] = -16   # heptuple fire
        elif dragon.dcap['flower'] >= 0:
            dragon.dcap['flower'] = -1    # triple fire
        else:
            dragon.dcap['flower'] -= 1    # cumulative effect
        dragon.carrybonus(self)

class ShortFire(RandomBonus):
    "Short Fire. Shorten the range of your bubble throw out."
    nimage = Bonuses.softice2
    points = 300
    factor = 1 / 1.5
    bigbonus = {'factor': 0}
    bigdoc = "What occurs if you throw bubbles at range zero?"
    def taken(self, dragon):
        dragon.dcap['shootthrust'] *= self.factor
        dragon.carrybonus(self)

class HighSpeedFire(RandomBonus):
    "High Speed Fire. Increase your fire rate."
    nimage = Bonuses.custard_pie
    points = 700
    bigbonus = {'multiply': 4}
    bigdoc = "Machine-gun speed!"
    def taken(self, dragon):
        dragon.dcap['firerate'] += 1.5
        dragon.carrybonus(self)

class Mushroom(TemporaryBonus):
    "Bouncy Bouncy. Makes you jump continuously."
    nimage = Bonuses.mushroom
    points = 900
    capname = 'pinball'
    captime = 625
    bigbonus = {'captime': captime*2, 'multiply': 2}
    bigdoc = "The same, but even more annoying."

class AutoFire(TemporaryBonus):
    "Auto Fire. Makes you fire continuously."
    nimage = Bonuses.rape
    points = 800
    capname = 'autofire'
    captime = 675
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Adds many bubbles to the level."
    def taken1(self, dragons):
        if self.big:
            boards.extra_boardgen(boards.extra_bubbles(900))
        else:
            TemporaryBonus.taken1(self, dragons)

class Insect(RandomBonus):
    "Crush World."
    nimage = Bonuses.insect
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "What if the level looked like that instead... Or like that... Or..."
    def taken1(self, dragons):
        if self.big:
            if dragons:
                d = random.choice(dragons)
                cx, cy = d.x, d.y
            else:
                cx, cy = None, None
            boards.extra_boardgen(boards.extra_make_random_level(cx, cy))
        else:
            boards.extra_boardgen(boards.extra_walls_falling())

class Ring(TemporaryBonus):
    "The One Ring."
    nimage = Bonuses.ring
    points = 4000
    capname = 'ring'
    captime = 700
    bonusleveldivider = 5
    bigbonus = {'multiply': 3}
    bigdoc = "Where am I?"

class GreenPepper(TemporaryBonus):
    "Hot Pepper. Run! Run! That burns."
    nimage = Bonuses.green_pepper
    capname = 'hotstuff'
    captime = 100
    bigbonus = {'captime': 250, 'multiply': 2}
    bigdoc = "That burns a lot!"

class Lollipop(TemporaryBonus):
    "Yo Man! Makes you walk backward."
    nimage = Bonuses.lollipop
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Just swapping 'left' and 'right' is not confusing enough."
    def taken(self, dragon):
        dragon.dcap['left2right'] = -dragon.dcap['left2right']
        if self.big:
            perm = range(4)
            while perm[0] == 0 or perm[1] == 1 or perm[2] == 2 or perm[3] == 3:
                random.shuffle(perm)
            names = ('key_left', 'key_right', 'key_jump', 'key_fire')
            dragon.dcap['key_right'] = names[perm[0]]
            dragon.dcap['key_left']  = names[perm[1]]
            dragon.dcap['key_jump']  = names[perm[2]]
            dragon.dcap['key_fire']  = names[perm[3]]
        self.carried(dragon)
    def endaction(self, dragon):
        dragon.dcap['left2right'] = -dragon.dcap['left2right']
        for name in ('key_left', 'key_right', 'key_jump', 'key_fire'):
            dragon.dcap[name] = name

class Chickpea(TemporaryBonus):
    "Basilik. Allows you to touch the monsters."
    nimage = Bonuses.chickpea
    points = 800
    capname = 'overlayglasses'
    captime = 400
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Turn off the light."

    def taken1(self, dragons):
        if self.big:
            boards.extra_boardgen(boards.extra_light_off(597), 1)
        else:
            TemporaryBonus.taken1(self, dragons)

    def taken(self, dragon):
        TemporaryBonus.taken(self, dragon)
        dragon.dcap['shield'] += 420

class IceCream(RandomBonus):
    "Icecream. An icecream which is so good you'll always want more."
    nimages = [Bonuses.icecream6, Bonuses.icecream5,
               Bonuses.icecream4, Bonuses.icecream3]
    IceCreams = [(Bonuses.icecream6,  250),
                 (Bonuses.icecream5,  500),
                 (Bonuses.icecream4,  1000),
                 (Bonuses.icecream3,  2000)]
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "BIG ice creams!"
    def __init__(self, x, y, generation=0):
        self.generation = generation
        RandomBonus.__init__(self, x, y, *self.IceCreams[generation])
    def taken1(self, dragons):
        nextgen = self.generation + 1
        if nextgen < len(self.IceCreams):
            for i in range(2):
                if self.big:
                    makecactusbonus(IceCream, nextgen)
                else:
                    x, y = chooseground(200)
                    if x is None:
                        return
                    IceCream(x, y, nextgen)
            if self.big:
                cactusbonussound()

class Grenade(RandomBonus):
    "Barbecue."
    nimage = Bonuses.grenade
    points = 550
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "360-degree flames."
    def taken1(self, dragons):
        from bubbles import FireFlame
        poplist = [None]
        for y in range(1, boards.height-1):
            for x in range(2, boards.width-2):
                if bget(x,y) != ' ':
                    continue
                if bget(x,y+1) == '#':
                    FireFlame(x, y, poplist)
                elif self.big:
                    if bget(x,y-1) == '#':
                        FireFlame(x, y, poplist, flip='vflip')
                    elif bget(x-1,y) == '#':
                        FireFlame(x, y, poplist, flip='cw')
                    elif bget(x+1,y) == '#':
                        FireFlame(x, y, poplist, flip='ccw')

class Conch(RandomBonus):
    "Sea Shell. Let's bring the sea here!"
    nimage = Bonuses.conch
    points = 650
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Aquarium."
    def taken1(self, dragons):
        if self.big:
            gen = boards.extra_aquarium
        else:
            gen = boards.extra_water_flood
        boards.extra_boardgen(gen())

def fire_rain(x, poplist):
    from bubbles import FireDrop
    FireDrop(x, -CELL, poplist)

def water_rain(x, poplist):
    from bubbles import watercell
    watercell(x, 0, poplist)

def ball_rain(x, poplist):
    from bubbles import SpinningBall
    SpinningBall(x, -CELL, poplist)

class Umbrella(RandomBonus):
    "Umbrellas. Beware of what's going to fall on everyone's head!"
    nimages = [Bonuses.brown_umbrella, Bonuses.grey_umbrella,
               Bonuses.violet_umbrella]
    Umbrellas = [(Bonuses.brown_umbrella,  900,  fire_rain,  10, 60),
                 (Bonuses.grey_umbrella,   950,  water_rain, 5,  60),
                 (Bonuses.violet_umbrella, 1000, ball_rain,  9, 120)]
    bigbonus = {'multiply': 3.1416}
    bigdoc = "It's raining hard."
    def __init__(self, x, y):
        self.mode = random.choice(Umbrella.Umbrellas)
        RandomBonus.__init__(self, x, y, *self.mode[:2])
    def taken1(self, dragons):
        for i in range(self.multiply):
            boards.extra_boardgen(self.raining())
    def raining(self):
        builder, drops, timemax = self.mode[2:]
        timemax = int(timemax * math.sqrt(self.multiply))
        drops = int(drops * self.multiply)
        times = [random.randrange(0, timemax) for i in range(drops)]
        poplist = [None]
        for t in range(timemax):
            for i in range(times.count(t)):
                x = random.randrange(2*CELL, bwidth-3*CELL+1)
                builder(x, poplist)
            yield 0

class Fruits(RandomBonus):
    "Fruits. A small little bonus. But the size doesn't matter, does it? If you're lucky enough you might get a great shower!"
    nimages = [Bonuses.kirsh, Bonuses.erdbeer, Bonuses.tomato,
               Bonuses.apple, Bonuses.corn, Bonuses.radish]
    bubblable = 0
    sound = 'Extra'
    Fruits = [(Bonuses.kirsh,      100),
              #(Bonuses.icecream1,  150),
              (Bonuses.erdbeer,    150),
              #(Bonuses.fish1,      250),
              (Bonuses.tomato,     200),
              #(Bonuses.donut,      250),
              (Bonuses.apple,      250),
              (Bonuses.corn,       300),
              #(Bonuses.icecream2,  600),
              (Bonuses.radish,     350),
              ]
    def __init__(self, x, y):  # x and y ignored !
        fine = 0
        for i in range(20):
            x0 = random.randint(3, boards.width-5)
            y0 = random.randint(1, boards.height-3)
            for xt in range(x0-1, x0+3):
                if xt == x0-1 or xt == x0+2:
                    yplus = 1
                else:
                    yplus = 0
                for yt in range(y0+yplus, y0+4-yplus):
                    if bget(xt,yt) != ' ':
                        break
                else:
                    continue
                break
            else:
                x, y = x0*CELL, y0*CELL
                fine = 1
                break
        mode = random.choice(Fruits.Fruits)
        RandomBonus.__init__(self, x, y, falling=0, *mode)
        self.repeatcount = 0
        if not fine:
            self.kill()
        elif random.random() < 0.04:
            self.superfruit = mode
            self.sound = 'Shh'
            self.points = 0
            self.repeatcount = random.randrange(50,100)
    def taken1(self, dragons):
        if self.repeatcount:
            image, points = self.superfruit
            f = Parabolic2(self.x, self.y, [image], y_amplitude = -1.5)
            f.points = points
            f.touchable = 1
            self.repeatcount -= 1
            self.gen.append(self.taking(1, 2))
            return -1
Fruits1 = Fruits  # increase probability
Fruits2 = Fruits
Fruits3 = Fruits
Fruits4 = Fruits
Fruits5 = Fruits
Fruits6 = Fruits

class BlueNecklace(RandomBonus):
    "Self Duplicator. Mirror yourself."
    points = 1000
    nimage = Bonuses.blue_necklace
    copies = 1
    bigbonus = {'copies': 3}
    bigdoc = "Mirrors vertically too."
    def taken(self, dragon):
        dragons = [dragon]
        modes = [(-1, 1), (1, -1), (-1, -1)][:self.copies]
        modes.reverse()
        dcap = dragon.dcap.copy()
        for sign, gravity in modes:
            if len(dragon.bubber.dragons) >= 7:
                break  # avoid burning the server with two much dragons
            d1 = self.makecopy(dragon, sign, gravity, dcap)
            dragons.append(d1)
        d1 = random.choice(dragons)
        d1.carrybonus(self, 250)

    def makecopy(self, dragon, sign=-1, gravity=1, dcap=None):
        from player import Dragon
        dcap = dcap or dragon.dcap
        d = Dragon(dragon.bubber, dragon.x, dragon.y, -dragon.dir, dcap)
        d.dcap['left2right'] = sign * dcap['left2right']
        d.dcap['gravity'] = gravity * dcap['gravity']
        d.up = dragon.up
        s = (dcap['shield'] + 12) & ~3
        dragon.dcap['shield'] = s+2
        if sign*gravity > 0:
            s += 2
        d.dcap['shield'] = s
        dragon.bubber.dragons.append(d)
        return d

class Monsterer(RandomBonus):
    "Monsterificator. Let's play on the other side!"
    nimages = [Bonuses.red_crux, Bonuses.blue_crux]
    Sizes = [(Bonuses.red_crux, 800), (Bonuses.blue_crux, 850)]
    mlist = [['Nasty',  'Monky',  'Springy', 'Orcy'],
             ['Ghosty', 'Flappy', 'Gramy',   'Blitzy']
             ]
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Ta, ta ta, ta, taaaaaa..."
    def __init__(self, x, y):
        self.mode = random.choice([0,1])
        RandomBonus.__init__(self, x, y, *self.Sizes[self.mode])
    def taken(self, dragon):
        mcls = random.choice(self.mlist[self.mode])
        dragon.become_monster(mcls, self.big)

Monsterer1 = Monsterer # increase probability

class Bubblizer(RandomBonus):
    "Bubblizer."
    points = 750
    nimage = Bonuses.gold_crux
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Special powers for your bubble."
    def taken(self, dragon):
        args = (dragon.bubber.pn,)
        if self.big:
            from bubbles import SnookerBubble, BigLightBubble
            bcls = random.choice([SnookerBubble, BigLightBubble])
            if bcls is SnookerBubble:
                args = (dragon, dragon.x, dragon.y, 1000000)
        else:
            from bubbles import FireBubble, WaterBubble, LightningBubble
            bcls = random.choice([FireBubble, WaterBubble, LightningBubble])
        b = bcls(*args)
        b.move(dragon.x, dragon.y)
        if not dragon.become_bubblingeyes(b):
            b.kill()

class Carrot(RandomBonus):
    "Angry Monster. Turns all free monsters angry."
    nimage = Bonuses.carrot
    points = 950
    ghost = 0
    bigbonus = {'ghost': 1}
    bigdoc = "What do angry monsters turn into if you don't hurry up?"
    def taken1(self, dragons):
        from monsters import Monster
        lst = [s for s in images.ActiveSprites
               if isinstance(s, Monster) and s.regular()]
        if lst:
            if self.ghost:
                images.Snd.Hell.play()
                for s in lst:
                    s.become_ghost()
            else:
                for s in lst:
                    s.angry = [s.genangry()]
                    s.resetimages()

class Egg(RandomBonus):
    "Teleporter. Exchange yourself with somebody else."
    nimage = Bonuses.egg
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Exchange colors too."
    def taken1(self, dragons):
        if self.big:
            self.exchange_bubbers()
        else:
            self.exchange_dragons(dragons)

    def exchange_dragons(self, dragons):
        dragons = [d for d in dragons if d in d.bubber.dragons]
        alldragons = [d for d in BubPlayer.DragonList if d in d.bubber.dragons]
        others = [d for d in alldragons if d not in dragons]
        xchg = {}
        random.shuffle(dragons)
        random.shuffle(others)
        while dragons and others:
            d1 = dragons.pop()
            d2 = others.pop()
            xchg[d1] = d2.bubber
            xchg[d2] = d1.bubber
        if len(dragons) > 1:
            copy = dragons[:]
            for i in range(10):
                random.shuffle(copy)
                for j in range(len(dragons)):
                    if dragons[j] == copy[j]:
                        break
                else:
                    break
            for d1, d2 in zip(dragons, copy):
                xchg[d1] = d2.bubber
        elif len(dragons) == 1:
            x, y = chooseground(200)
            if x is not None:
                d1 = dragons[0]
                d1.move(x, y)
                d1.dcap['shield'] = 50
        for d1, bubber2 in xchg.items():
            d1.bubber.dragons.remove(d1)
            d1.bubber = bubber2
            bubber2.dragons.append(d1)
            d1.dcap['shield'] = 50

    def exchange_bubbers(self):
        self.exchange_dragons(list(BubPlayer.DragonList))
        players = [p for p in BubPlayer.PlayerList
                   if p.isplaying()]
        if len(players) > 1:
            while 1:
                copy = players[:]
                random.shuffle(copy)
                for j in range(len(players)):
                    if players[j] is copy[j]:
                        break
                else:
                    break
            for b1, b2 in zip(players, copy):
                for d in b1.dragons:
                    d.dcap['bubbericons'] = b2

class Bomb(RandomBonus):
    "Baaoouuuummmm! Explode that wall!"
    nimage = Bonuses.bomb
    bigbonus = {'multiply': 3.8}
    bigdoc = "Makes a BIG hole."
    def taken1(self, dragons):
        bomb_explosion(self.x, self.y, self.multiply)

def bomb_explosion(x0, y0, multiply=1, starmul=2):
    RADIUS = 3.9 * CELL * math.sqrt(multiply)
    Radius2 = RADIUS * RADIUS
    brd = boards.curboard
    cx = x0 + HALFCELL
    cy = y0 + HALFCELL - RADIUS/2
    for y in range(0, brd.height):
        dy1 = abs(y*CELL - cy)
        dy2 = abs((y-(brd.height-1))*CELL - cy)
        dy3 = abs((y+(brd.height-1))*CELL - cy)
        dy = min(dy1, dy2, dy3)
        for x in range(2, brd.width-2):
            dx = x*CELL - cx
            if dx*dx + dy*dy < Radius2:
                try:
                    brd.killwall(x,y)
                except KeyError:
                    pass
    brd.reorder_walls()
    starexplosion(x0, y0, starmul)
    gen = boards.extra_display_repulse(x0+CELL, y0+CELL,
                                       15000 * multiply,
                                       1000 * multiply)
    boards.extra_boardgen(gen)

class Ham(RandomBonus):
    "Protein. Let's build something!"
    nimage = Bonuses.ham
    bigbonus = {'multiply': 3.4}
    bigdoc = "Builds something BIG."
    def taken1(self, dragons):
        RADIUS = 3.9 * CELL * math.sqrt(self.multiply)
        Radius2 = RADIUS * RADIUS
        brd = boards.curboard
        cx = self.x + HALFCELL
        cy = self.y + HALFCELL - RADIUS/2
        xylist = []
        for y in range(0, brd.height):
            dy1 = abs(y*CELL - cy)
            dy2 = abs((y-(brd.height-1))*CELL - cy)
            dy3 = abs((y+(brd.height-1))*CELL - cy)
            dy = min(dy1, dy2, dy3)
            for x in range(2, brd.width-2):
                dx = x*CELL - cx
                if dx*dx + dy*dy < Radius2:
                    if (y,x) not in brd.walls_by_pos and random.random() < 0.5:
                        brd.putwall(x,y)
                        xylist.append((x, y))
        brd.reorder_walls()
        boards.extra_boardgen(boards.single_blocks_falling(xylist))
        gen = boards.extra_display_repulse(self.x+CELL, self.y+CELL,
                                           5000 * self.multiply,
                                           1000 * self.multiply)
        boards.extra_boardgen(gen)

class Chestnut(RandomBonus):
    "Relativity. Speed up or slow down the game."
    nimage = Bonuses.chestnut
    sound = None
    big = 0
    bigbonus = {'big': 1}
    bigdoc = "Relative relativity - not the same one for players and monsters."
    def taken1(self, dragons):
        timeout = 500
        if not self.big:
            ft = random.choice([0.5, 2.0])
            boards.set_frametime(ft)
            if ft == 2.0:
                timeout = 430
        else:
            if random.randrange(0, 2) == 1:
                # super-fast game
                boards.set_frametime(0.25)
                timeout = 1800
            else:
                # board unchanged, players slower
                boards.set_frametime(1.0, privtime=250)
                timeout = 800
        BubPlayer.MultiplyerReset = BubPlayer.FrameCounter + timeout
        self.play(images.Snd.Fruit)


try:
    import statesaver
except ImportError:
    print "'statesaver' module not compiled, no clock bonus"
    Clock = None
else:
    import new
    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__,
            gamesrv.sprites,
            gamesrv.sprites_by_n,
            ps,
            images.Snd.__dict__,
            )
        #import pdb; pdb.set_trace()
        return statesaver.copy(topstate)

    def restoregamestate(savedstate):
        (boards.BoardGen,
         boards.curboard,
         images.ActiveSprites,
         images.SpritesByLoc,
         BubPlayer.__dict__,
         gamesrv.sprites,
         gamesrv.sprites_by_n,
         ps,
         images.Snd.__dict__,
         ) = savedstate

        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
