from __future__ import generators
import random, os, sys, math
import gamesrv
import images

CELL = 16    # this constant is inlined at some places, don't change
HALFCELL = CELL//2
FRAME_TIME = 0.025
#DEFAULT_LEVEL_FILE = 'levels/scratch.py'

BOARD_BKGND = 1    # 0 = black, 1 = darker larger wall tiles


class Copyable:
    pass   # see bonuses.py, class Clock


class Board(Copyable):
    letter    = 0
    fire      = 0
    lightning = 0
    water     = 0
    top       = 0
    
    WIND_DELTA = HALFCELL

    def __init__(self, num):
        # the subclasses should define 'walls', 'winds', 'monsters'
        self.walls = walls = [line for line in self.walls.split('\n') if line]
        self.winds = winds = [line for line in self.winds.split('\n') if line]
        self.num = num
        self.width = len(walls[0])
        self.height = len(walls)
        for line in walls:
            assert len(line) == self.width, "some wall lines are longer than others"
        for line in winds:
            assert len(line) == self.width, "some wind lines are longer than others"
        #assert walls[0] ==  walls[-1], "first and last lines must be identical"
        assert len(winds) == self.height, "wall and wind heights differ"
        self.walls_by_pos = {}
        self.sprites = {}
        if self.top:
            testline = self.walls[0]
        else:
            testline = self.walls[-1]
        self.holes = testline.find('  ') >= 0
        self.playingboard = 0
        self.bonuslevel = not self.monsters
        self.cleaning_gen_state = 0

    def set_musics(self, prefix=[]):
        if (self.num+1) % 20 < 10:
            gamesrv.set_musics(prefix + [images.music_intro], [images.music_game],
                               reset=0)
        else:
            gamesrv.set_musics(prefix + [], [images.music_game2], reset=0)

    def writesprites(self, name, xyicolist):
        sprlist = self.sprites.setdefault(name, [])
        xyicolist = xyicolist[:]
        xyicolist.reverse()
        for s in sprlist[:]:
            if xyicolist:
                s.move(*xyicolist.pop())
            else:
                s.kill()
                sprlist.remove(s)
        while xyicolist:
            x, y, ico = xyicolist.pop()
            sprlist.append(gamesrv.Sprite(ico, x, y))

    def enter(self, complete=1, inplace=0):
        global curboard
        if inplace:
            print "Re -",
        print "Entering board", self.num+1
        self.set_musics()
        # add board walls
        l = self.sprites.setdefault('walls', [])
        bl = self.sprites.setdefault('borderwalls', [])
        if inplace:
            deltay = 0
        else:
            deltay = bheight
        wnx = wny = 1
        while haspat((self.num, wnx, 0)):
            wnx += 1
        while haspat((self.num, 0, wny)):
            wny += 1
        self.wnx = wnx
        self.wny = wny

        if haspat((self.num, 'l')):
            lefticon = patget((self.num, 'l'))
            if haspat((self.num, 'r')):
                righticon = patget((self.num, 'r'))
            else:
                righticon = lefticon
            xrange = range(2, self.width-2)
        else:
            xrange = range(self.width)
            lefticon = righticon = None

        if BOARD_BKGND == 1:
            gl = self.sprites.setdefault('background', [])
            xmax = (self.width-2)*CELL
            ymax = self.height*CELL
            y = -HALFCELL
            ystep = 0
            firstextra = 1
            while y < ymax:
                x = 2*CELL+HALFCELL
                xstep = 0
                while x < xmax:
                    bitmap, rect = loadpattern((self.num, xstep, ystep),
                                               images.KEYCOL)
                    bitmap, rect = images.makebkgndpattern(bitmap, rect)
                    if firstextra:
                        # special position where a bit of black might show up
                        x -= rect[2]
                        xstep = (xstep-1) % wnx
                        firstextra = 0
                        continue
                    bkgndicon = bitmap.geticon(*rect)
                    w = gamesrv.Sprite(bkgndicon, x, y + deltay)
                    gl.append(w)
                    x += rect[2]
                    xstep = (xstep+1) % wnx
                y += rect[3]
                ystep = (ystep+1) % wny
        else:
            gl = []

        if lefticon is not None:
            for y in range(0, self.height, lefticon.h // CELL):
                bl.append(gamesrv.Sprite(lefticon, 0, y*CELL + deltay))

        for y in range(self.height):
            for x in xrange:
                c = self.walls[y][x]
                if c == '#':
                    wallicon = patget((self.num, x%wnx, y%wny), images.KEYCOL)
                    w = gamesrv.Sprite(wallicon, x*CELL, y*CELL + deltay)
                    l.append(w)
                    self.walls_by_pos[y,x] = w

        if not l:
            # self.sprites['walls'] must not be empty, for putwall
            wallicon = patget((self.num, 0, 0), images.KEYCOL)
            w = gamesrv.Sprite(wallicon, 0, -wallicon.h)
            l.append(w)

        if righticon is not None:
            for y in range(0, self.height, lefticon.h // CELL):
                bl.append(gamesrv.Sprite(righticon, (self.width-2)*CELL, y*CELL + deltay))

        while deltay:
            dy = -min(deltay, 8)
            for w in gl:
                w.step(0, dy)
            for w in l:
                w.step(0, dy)
            for w in bl:
                w.step(0, dy)
            deltay += dy
            yield 1

        if inplace:
            for w in images.ActiveSprites:
                w.to_front()

        curboard = self
        if gamesrv.game:
            gamesrv.game.updateboard()
        if not complete:
            return
        # add players
        from player import BubPlayer, scoreboard
        if not inplace:
            random.shuffle(BubPlayer.PlayerList)
        scoreboard(1, inplace=inplace)
        playing = []
        for p in BubPlayer.PlayerList:
            if p.isplaying():
                p.enterboard(playing)
                p.zarkon()
                playing.append(p)
        # add monsters
        import monsters
        f_monsters = gamesrv.game.f_monsters
        if f_monsters < 0.1:
            f_monsters = max(1.0, min(2.0, (len(playing)-2)/2.2+1.0))
        for mdef in self.monsters:
            yield 2
            cls = getattr(monsters, mdef.__class__.__name__)
            dir = mdef.dir
            i = random.random()
            while i < f_monsters:
                cls(mdef, dir=dir)
                dir = -dir
                i += 1.0
        self.playingboard = 1

    def putwall(self, x, y, w=None):
        wallicon = patget((self.num, x%self.wnx, y%self.wny), images.KEYCOL)
        if w is None:
            w = gamesrv.Sprite(wallicon, 0, bheight)
            l = self.sprites['walls']
            w.to_back(l[-1])
            l.append(w)
        self.walls_by_pos[y,x] = w
        if y >= 0:
            line = self.walls[y]
            self.walls[y] = line[:x] + '#' + line[x+1:]

    def killwall(self, x, y, kill=1):
        w = self.walls_by_pos[y,x]
        if kill:
            l = self.sprites['walls']
            if len(l) > 1:
                l.remove(w)
                w.kill()
            else:
                # self.sprites['walls'] must never be empty
                # or putwall will crash!
                w.move(0, -bheight)
        del self.walls_by_pos[y,x]
        line = self.walls[y]
        self.walls[y] = line[:x] + ' ' + line[x+1:]
        return w

    def reorder_walls(self):
        walls_by_pos = self.walls_by_pos
        items = [(yx, w1.ico) for yx, w1 in walls_by_pos.items()]
        if not items:
            return   # otherwise self.sprites['walls'] would be emptied
        items.sort()
        l = self.sprites['walls']
        while len(l) > len(items):
            l.pop().kill()
        assert len(items) == len(l)
        for ((y,x), ico), w2 in zip(items, l):
            w2.move(x*CELL, y*CELL, ico)
            walls_by_pos[y,x] = w2

    def leave(self, inplace=0):
        global curboard
        if not gamesrv.has_loop_music():
            gamesrv.fadeout(1.5)
        from player import BubPlayer
        for p in BubPlayer.PlayerList:
            if p.isplaying():
                p.savecaps()
        if BubPlayer.LeaveBonus:
            for t in BubPlayer.LeaveBonus:
                yield t
            BubPlayer.LeaveBonus = None
        curboard = None
        if inplace:
            i = -1
        else:
            while images.ActiveSprites:
                s = random.choice(images.ActiveSprites)
                s.kill()
                yield 0.9
            i = 0
        sprites = []
        for l in self.sprites.values():
            sprites += l
        self.sprites.clear()
        self.walls_by_pos.clear()
        random.shuffle(sprites)
        for s in sprites:
            s.kill()
            if i:
                i -= 1
            else:
                yield 0.32
                i = 3
        if not inplace:
            for p in BubPlayer.PlayerList:
                if p.isplaying():
                    p.zarkoff()
            yield 4

    def clean_gen_state(self):
        self.cleaning_gen_state = 1
        while len(BoardGen) > 1:
            #yield force_singlegen()
            #if 'flood' in self.sprites:
            #    for s in self.sprites['flood']:
            #        s.kill()
            #    del self.sprites['flood']
            yield normal_frame()
        self.cleaning_gen_state = 0

def bget(x, y):
    if 0 <= x < curboard.width:
        if y < 0 or y >= curboard.height:
            y = 0
        return curboard.walls[y][x]
    else:
        return '#'

def wget(x, y):
    delta = curboard.WIND_DELTA
    x = (x + delta) // 16
    y = (y + delta) // 16
    if 0 <= x < curboard.width:
        if y < 0:
            y = 0
        elif y >= curboard.height:
            y = -1
        return curboard.winds[y][x]
    elif x < 0:
        return '>'
    else:
        return '<'

def onground(x, y):
    if y & 15:
        return 0
    x0 = (x+5) // 16
    x1 = (x+16) // 16
    x2 = (x+27) // 16
    y0 = y // 16 + 2

    if x0 < 0 or x2 >= curboard.width:
        return 0
    y1 = y0 - 1
    if not (0 < y0 < curboard.height):
        if y0 != curboard.height:
            y1 = 0
        y0 = 0
    y0 = curboard.walls[y0]
    y1 = curboard.walls[y1]
    return (' ' == y1[x0] == y1[x1] == y1[x2] and
            not (' ' == y0[x0] == y0[x1] == y0[x2]))
    #return (' ' == bget(x0,y0-1) == bget(x1,y0-1) == bget(x2,y0-1) and
    #        not (' ' == bget(x0,y0) == bget(x1,y0) == bget(x2,y0)))
    #return (bget(x1,y0-1)==' ' and
    #        ((bget(x1,y0)=='#') or
    #         (bget(x0,y0)=='#' and bget(x0,y0-1)==' ') or
    #         (bget(x2,y0)=='#' and bget(x2,y0-1)==' ')))

def onground_nobottom(x, y):
    return onground(x, y) and y+32 < bheight

def underground(x, y):
    if y % CELL:
        return 0
    x0 = (x+5) // CELL
    x1 = (x+CELL) // CELL
    x2 = (x+2*CELL-5) // CELL
    y0 = y // CELL

    if x0 < 0 or x2 >= curboard.width:
        return 0
    y1 = y0 - 1
    if not (0 < y0 < curboard.height):
        if y0 != curboard.height:
            y1 = 0
        y0 = 0
    y0 = curboard.walls[y0]
    y1 = curboard.walls[y1]
    return (' ' == y0[x0] == y0[x1] == y0[x2] and
            not (' ' == y1[x0] == y1[x1] == y1[x2]))

def x2bounds(x):
    if x < 32:
        return 32
    elif x > bwidth - 64:
        return bwidth - 64
    else:
        return x

def vertical_warp(nx, ny):
    if ny >= bheight:
        ny -= bheightmod
    elif ny < -32:
        ny += bheightmod
    return nx, ny

def vertical_warp_sprite(spr):
    if spr.y >= bheight:
        spr.step(0, -bheightmod)
    elif spr.y < -32:
        spr.step(0, bheightmod)

##def vertical_warp(nx, ny):
##    if ny >= bheight:
##        ny -= bheightmod
##    elif ny < -32:
##        ny += bheightmod
##    else:
##        return (nx, ny), 0
##    from player import BubPlayer
##    if BubPlayer.Moebius:
##        nx = bwidth - 2*CELL - nx
##        return (nx, ny), 1
##    else:
##        return (nx, ny), 0


MODULES = ['boards', 'bonuses', 'bubbles', 'images',
           'mnstrmap', 'monsters', 'player', 'ranking',
           'binboards', 'macbinary', 'boarddef',
           'ext1', 'ext2', 'ext3', 'ext4', 'ext5', 'ext6', 'ext7']

def loadmodules(force=0):
    levelfilename = gamesrv.game.levelfile
    modulefiles = {None: levelfilename}
    for m in MODULES:
        if os.path.isfile(m+'.py'):
            modulefiles[m] = m+'.py'
        elif os.path.isfile(os.path.join(m, '__init__.py')):
            modulefiles[m] = os.path.join(m, '__init__.py')
    mtimes = {}
    for m, mfile in modulefiles.items():
        mtimes[m] = os.stat(mfile).st_mtime
    reload = force or (mtimes != getattr(sys, 'ST_MTIMES', None))
    import player
    playerlist = player.BubPlayer.PlayerList
    if reload:
        delete = hasattr(sys, 'ST_MTIMES')
        sys.ST_MTIMES = mtimes
        if delete:
            print "Reloading modules."
            for m, mfile in modulefiles.items():
                if m is not None and m in sys.modules:
                    del sys.modules[m]

    # Clear
    clearallsprites()

    import player
    for p in playerlist:
        player.upgrade(p)
    for n in range(len(playerlist), images.MAX):
        playerlist.append(player.BubPlayer(n))
    player.BubPlayer.PlayerList = playerlist
    if reload:
        import boards
        from images import haspat, loadpattern
        boards.haspat = haspat
        boards.loadpattern = loadpattern
        del boards.BoardList[:]
        if levelfilename.lower().endswith('.py'):
            levels = {}
            print 'Source level file:', levelfilename
            execfile(levelfilename, levels)
            if 'Levels' in levels:
                levels = levels['Levels']
                if isinstance(levels, list):
                    levels = dict(zip(range(len(levels)), levels))
        else:
            import binboards
            levels = binboards.load(levelfilename)
        boards.register(levels)
    return reload

def clearallsprites():
    gamesrv.clearsprites()
    import images
    del images.ActiveSprites[:]
    images.SpritesByLoc.clear()

def wait_for_one_player():
    from player import BubPlayer
    clearallsprites()
    nimages = None
    while not [p for p in BubPlayer.PlayerList if p.isplaying()]:
        yield 3
        if not nimages:
            desc = getattr(gamesrv.game, 'FnDesc', '?')
            host, port = getattr(gamesrv.game, 'address', ('?', '?'))
            images.writestrlines([
                "Welcome to",
                desc.upper(),
                "at %s:%s" % (host.lower(), port),
                None,
                "Click on your Favorite Color's Dragon",
                "Choose four keys: Right, Left, Jump, Shoot",
                "and Let's Go!",
                None,
                "Click again for more than one player",
                "on the same machine.",
                ])
            
            from mnstrmap import PlayerBubbles
            nimages = [PlayerBubbles.bubble[0],
                       PlayerBubbles.bubble[1],
                       PlayerBubbles.bubble[1],
                       PlayerBubbles.bubble[0],
                       PlayerBubbles.bubble[2],
                       PlayerBubbles.bubble[2]]
            screenwidth = bwidth + 9*CELL
            screenheight = bheight

            def welcomebubbling(self):
                fx = self.x
                dx = (random.random() - 0.5) * 1.9
                for y in range(self.y-3, -self.ico.h, -3):
                    fx += dx
                    self.move(int(fx), y)
                    yield None
                    if y == self.ypop:
                        from mnstrmap import PlayerBubbles
                        self.setimages(None)
                        self.gen.append(self.die(PlayerBubbles.explosion))
                self.kill()

            yield 10
            gamesrv.set_musics([], [images.music_game2], reset=0)
        
        if ((not images.ActiveSprites or random.random() < 0.05678) and
            gamesrv.clients):
            # make sure the extension's images are loaded too
            # NB. this is also needed for import auto-detection
            import ext1; import ext2; import ext3
            import ext4; import ext5; import ext6
            import ext7
            
            ico = images.sprget(nimages[0])
            s = images.ActiveSprite(ico,
                                    random.randrange(0, screenwidth-ico.w),
                                    screenheight)
            s.ypop = random.randrange(-ico.h, screenheight)
            s.gen = [welcomebubbling(s)]
            s.setimages(s.cyclic(nimages, speed=1))
            if random.random() > 0.4321:
                try:
                    key, (filename, (x, y, w, h)) = random.choice(
                        images.sprmap.items())
                except:
                    w = h = 0
                if w == h == 32:
                    s2 = images.ActiveSprite(images.sprget(key), -32, 0)
                    s2.gen = [s2.following(s, (s.ico.w-32)//2, (s.ico.h-32)//2)]
                    s.ypop = None
        images.action(images.ActiveSprites[:])

def patget(n, keycol=None):
    bitmap, rect = loadpattern(n, keycol)
    return bitmap.geticon(*rect)

def get_lives():
    return gamesrv.game.limitlives

BoardList = []
curboard = None

def next_board(num=0, complete=1):
    yield force_singlegen()
    set_frametime(1.0)
    brd = curboard
    inplace = 0
    if brd:
        inplace = brd.bonuslevel
        num = brd.num
        if not brd.bonuslevel:
            num += gamesrv.game.stepboard
            if num >= len(BoardList):
                num = len(BoardList)-1
        for t in brd.leave(inplace=inplace):
            yield t

    # reset global board state
    from player import BubPlayer
    BubPlayer.__dict__.update(BubPlayer.INIT_BOARD_CAP)
    if not inplace:
        del BubPlayer.MonsterList[:]
        # wait for at least one player
        for t in wait_for_one_player():
            yield t
        # reload modules if changed
        if loadmodules():
            import boards
            boards.BoardGen = [boards.next_board(num)]
            return

    if num < 0:
        num = 0
    elif num >= len(BoardList):
        num = len(BoardList)-1
    brd = BoardList[num](num)
    for t in brd.enter(complete, inplace=inplace):
        yield t

    if brd.bonuslevel:
        gen = bonus_play
    else:
        gen = normal_play
    BoardGen[0] = gen()

def set_frametime(ft):
    from player import BubPlayer
    BubPlayer.BaseFrametime = ft
    images.loadsounds(1.0 / ft)

def extra_boardgen(gen, at_end=0):
    if curboard.playingboard:
        if at_end or not BoardGen:
            BoardGen.append(gen)
        else:
            BoardGen.insert(1, gen)

def replace_boardgen(gen, force=0):
    if curboard.playingboard or force:
        curboard.playingboard = 0
        BoardGen[0] = gen

def force_singlegen():
    del BoardGen[1:]
    return 0

def has_singlegen():
    return len(BoardGen) <= 1

def display_hat(p, d):
    if p.team == -1 or getattr(d,'isdying',0) or hasattr(d,'no_hat'):
        return
    try:
        bottom_up = d.bottom_up()
    except AttributeError:
        bottom_up = 0
    try:
        image = ('hat', p.team, d.dir, d.hatangle)
    except AttributeError:
        image = ('hat', p.team)
    if bottom_up:
        image = 'vflip', image
        y = d.y
    else:
        y = d.y - 16
    ico = images.sprget(image)
    if (getattr(d,'hatsprite',None) is None or
        not d.hatsprite.alive):
        d.hatsprite = images.ActiveSprite(ico, d.x, y)
    else:
        d.hatsprite.to_front()
        d.hatsprite.move(d.x, y, ico)
    d.hatsprite.gen = [d.hatsprite.die([None])]

def normal_frame():
    from player import BubPlayer
    BubPlayer.FrameCounter += 1

    # main generator dispatch loop
    images.action(images.ActiveSprites[:])
    
    frametime = 10
    for p in BubPlayer.PlayerList:
        if p.isplaying():
            frametime = BubPlayer.BaseFrametime
            p.zarkon()
            for d in p.dragons:
                d.to_front()
                display_hat(p, d)
                d.prefix(p.pn)
    if not (BubPlayer.FrameCounter & 31):
        gamesrv.compactsprites()
        reset = getattr(BubPlayer, 'MultiplyerReset', 0)
        if reset and BubPlayer.FrameCounter >= reset:
            BubPlayer.MultiplyerReset = 0
            set_frametime(1.0)
    return frametime

def normal_play():
    from player import BubPlayer
    import bonuses
    import bubbles
    framecounter = 0
    while BubPlayer.MonsterList:
        if random.random() < 0.04:
            bonuses.cheatnew()
            if random.random() < 0.15:
                bonuses.newbonus()
            else:
                bubbles.newbubble()
        yield normal_frame()
        if not BubPlayer.DragonList:
            continue
        framecounter += 1
        BASE = 500
        if not (framecounter % BASE):
            if framecounter == 4*BASE:
                from monsters import Monster
                from mnstrmap import BigImages
                ico = images.sprget(BigImages.hurryup[1])
                s = images.ActiveSprite(ico, (bwidth-ico.w)//2, (bheight-ico.h)//2)
                s.setimages(s.die(BigImages.hurryup * 12, 2))
                images.Snd.Hurry.play()
                mlist = [s for s in images.ActiveSprites
                         if (isinstance(s, Monster) and s.regular() and
                             not s.angry)]
                if mlist:
                    s = random.choice(mlist)
                    s.angry = [s.genangry()]
                    s.resetimages()
            if framecounter >= 6*BASE:
                mlist = [s for s in images.ActiveSprites
                         if isinstance(s, Monster) and s.regular() and s.angry]
                if mlist:
                    images.Snd.Hell.play()
                    gamesrv.set_musics([], [])
                    s = random.choice(mlist)
                    s.become_ghost()
                    framecounter = -200
                else:
                    framecounter = 2*BASE
            if framecounter == 0:
                curboard.set_musics()
    replace_boardgen(last_monster_killed())

##def normal_play():
##    # TESTING!!
##    from player import BubPlayer
##    for p in BubPlayer.PlayerList:
##        if not p.icons:
##            p.loadicons(p.icons, images.sprget)
##    results = {BubPlayer.PlayerList[0]: 100,
##               BubPlayer.PlayerList[1]: 200,
##               BubPlayer.PlayerList[2]: 300,
##               BubPlayer.PlayerList[3]: 400,
##               BubPlayer.PlayerList[4]: 100,
##               BubPlayer.PlayerList[5]: 200,
##               BubPlayer.PlayerList[6]: 300,
##               BubPlayer.PlayerList[7]: 400,
##               BubPlayer.PlayerList[8]:1000,
##               BubPlayer.PlayerList[9]:1000,
##               }
##    maximum = None
##    for t in result_ranking(results, maximum):
##        yield t

def last_monster_killed(end_delay=390, music=None):
    from player import BubPlayer
    for t in exit_board(music=music):
        yield t
    if curboard.bonuslevel:
        curboard.playingboard = 1
        play_again = bonus_play()
        for t in play_again:
            yield t
            end_delay -= 1
            if end_delay <= 0:
                replace_boardgen(next_board(), 1)
                break
    else:
        for i in range(end_delay):
            yield normal_frame()
        replace_boardgen(next_board(), 1)

##def bonus_play():
##    from player import BubPlayer
##    import bubbles
##    while BubPlayer.LimitScoreColor is None:
##        yield normal_frame()
##        players = [(p.points, p.pn) for p in BubPlayer.PlayerList
##                   if p.isplaying()]
##        if players:
##            players.sort()
##            points, BubPlayer.LimitScoreColor = players[-1]
##            BubPlayer.LimitScore = ((points + limit) // 100000) * 100000
##    for p in BubPlayer.PlayerList:
##        if p.isplaying():
##            p.givepoints(0)  # check LimitScore and update scoreboard()
##    while not (BubPlayer.BubblesBecome or BubPlayer.MegaBonus):
##        if random.random() < 0.06:
##            bubbles.newbonusbubble()
##        yield normal_frame()
##    # special board end
##    import monsters
##    monsters.argh_em_all()
##    replace_boardgen(last_monster_killed())

class TimeCounter(Copyable):
    def __init__(self, limittime, blink=0):
        from player import BubPlayer
        self.saved_time = BubPlayer.LimitTime
        self.time = limittime / FRAME_TIME
        self.prev = None
        self.blink = blink
    def update(self, t):
        from player import BubPlayer, scoreboard
        self.time -= t
        if self.time < 0.0:
            self.time = 0.0
        BubPlayer.LimitTime = self.time * FRAME_TIME
        next = int(BubPlayer.LimitTime)
        if self.blink and BubPlayer.LimitTime - next >= 0.5:
            BubPlayer.LimitTime = next = None
        if self.prev != next:
            scoreboard()
            self.prev = next
    def restore(self):
        from player import BubPlayer
        BubPlayer.LimitTime = self.saved_time

def bonus_play():
    from player import BubPlayer
    import bubbles
    BubPlayer.MegaBonus = None
    BubPlayer.BubblesBecome = None
    Time0 = 5.0 / FRAME_TIME  # when to slow down time
    tc = TimeCounter(BubPlayer.LimitTime or 180.9)   # 3:00
    prev = None
    while not (BubPlayer.BubblesBecome or BubPlayer.MegaBonus):
        if random.random() < 0.099:
            bubbles.newbonusbubble()
        t = normal_frame()
        tc.update(t)
        if tc.time < Time0:
            if tc.time <= 0.5:
                tc.time = 0.5
                BubPlayer.LimitTime = 0.0
            t *= math.sqrt(Time0 / tc.time)
        yield t
        if tc.time == 0.5:
            gamesrv.game.End = 'gameover'
            gamesrv.game.updateboard()
            replace_boardgen(game_over(), 1)
            return
    # special board end
    import monsters
    monsters.argh_em_all()
    replace_boardgen(last_monster_killed())

def game_over():
    yield force_singlegen()
    from player import scoreboard
    import ranking
    images.Snd.Extralife.play()
    gamesrv.set_musics([], [images.music_potion])
    scoreboard()
    for t in ranking.game_over():
        yield t

def game_reset():
    import time
    from player import BubPlayer
    t1 = time.time()
    while 1:
        yield 0
        if BubPlayer.LimitTime and BubPlayer.LimitTime >= 1.0:
            # someone else ticking the clock, try again later
            return
        if abs(time.time() - t1) > 2.0:
            break
    # anyone playing ?
    if not gamesrv.game.End:
        return  # yes -> cancel game_reset()
    # let's tick the clock !
    tc = TimeCounter(60.9, blink=1)   # 1:00
    t1 = time.time()
    while tc.time:
        yield 0
        # anyone playing now ?
        if not gamesrv.game.End:
            tc.restore()
            return  # yes -> cancel game_reset()
        t = time.time()  # use real time
        deltat = (t-t1)/FRAME_TIME
        if deltat < 1.0:
            deltat = 1.0
        elif deltat > 100.0:
            deltat = 100.0
        tc.update(deltat)
        t1 = t
    gamesrv.game.reset()

##def wasting_play():
##    from player import BubPlayer, scoreboard
##    import bubbles
##    curboard.wastingplay = {}
##    for p in BubPlayer.PlayerList:
##        if p.isplaying():
##            p.letters = {}
##            p.bonbons = p.points // 50000
##    scoreboard()
    
##    while len(BubPlayer.DragonList) > 1:
##        if random.random() < 0.03:
##            bubbles.newbubble(1)
##        yield normal_frame()
##    for d in BubPlayer.DragonList:
##        curboard.wastingplay[d.bubber] = len(curboard.wastingplay)
##    for i in range(50):
##        yield normal_frame()

##    total = len(curboard.wastingplay)
##    results = [(total-n, p) for p, n in curboard.wastingplay.items()]
##    results.sort()
##    results = [(p, str(n)) for n, p in results]
##    for t in display_ranking(results):
##        yield t
##    # never ending

def skiplevels(blink, skip):
    # (not used any more)
    saved = BoardGen[:]
    while skip:
        skip -= 1
        BoardGen[:] = saved
        for i in range(10):  # frozen pause
            yield 3
            if blink:
                blink.step(-bwidth, 0)
                yield 3.33
                blink.step(bwidth, 0)
        blink = None
        for t in next_board(complete=(skip==0)):
            yield t

def exit_board(delay=8, music=None, repeatmusic=[]):
    from bubbles import Bubble
    from bonuses import RandomBonus
    from player import BubPlayer
    from monsters import Monster
    curboard.playingboard = 0
    actives = images.ActiveSprites[:]
    for s in actives:
        if ((isinstance(s, Monster) and s.still_playing())
            or isinstance(s, RandomBonus)):
            s.kill()
    music = music or []
    if BubPlayer.MegaBonus:
        music[:1] = [images.music_modern]
    if music or repeatmusic:
        gamesrv.set_musics(music, repeatmusic)
    for i in range(delay):
        yield normal_frame()
    bubble_outcome = BubPlayer.BubblesBecome or Bubble.pop
    for s in actives:
        if isinstance(s, Bubble):
            bubble_outcome(s)
            yield normal_frame()
    if BubPlayer.MegaBonus:
        BubPlayer.MegaBonus()

def potion_fill(blist, big=0):
    from player import BubPlayer
    from bonuses import Bonus
    #timeleft = 1680.0
    for t in exit_board(0, music=[images.music_potion]):
        #timeleft -= t
        yield t
    notes = all_notes = []
    y = 1
    while y < 11 or (y < height-2 and (len(all_notes) < 10 or big)):
        for x in range(2, width-3, 2):
            if ' ' == bget(x,y) == bget(x+1,y) == bget(x,y+1) == bget(x+1,y+1):
                b = Bonus(x*CELL, y*CELL, falling=0, *blist[((x+y)//2)%len(blist)])
                b.timeout = (444,666)[big]
                all_notes.append(b)
        for i in range(2):
            t = normal_frame()
            #timeleft -= t
            yield t
        y += 2
    while notes: #and timeleft > 0.0:
        notes = [b for b in notes if b.alive]
        t = normal_frame()
        #timeleft -= t
        yield t
    for i in range(10):
        t = normal_frame()
        #timeleft -= t
        yield t
    results = {}
    for b in all_notes:
        for d in b.taken_by:
            bubber = d.bubber
            results[bubber] = results.get(bubber, 0) + 1
    for t in result_ranking(results, len(all_notes)):
        yield t
    #fadeouttime = 3.33
    #fullsoundframes = bonusframes - 10 - int(fadeouttime / FRAME_TIME)
    #for i in range(fullsoundframes):
    #    yield normal_frame()
    #gamesrv.fadeout(fadeouttime)
    #for i in range(fullsoundframes, 490):
    #    yield normal_frame()

def result_ranking(results, maximum=None, timeleft=200):
    import ranking
    results = ranking.ranking_picture(results, maximum, timeleft is not None)
    if curboard.bonuslevel and timeleft is not None:
        play_again = bonus_play()
    else:
        play_again = None
    for t in ranking.display(results, timeleft, play_again):
        yield t
    if gamesrv.game.End != 'gameover':
        gamesrv.set_musics([], [])
        replace_boardgen(next_board(), 1)

def extra_water_flood():
    from mnstrmap import Flood
    from monsters import Monster
    waves_icons = [images.sprget(n) for n in Flood.waves]
    fill_icon = images.sprget(Flood.fill)
    bspr = []
    if 'flood' in curboard.sprites:
        return    # only one flooding at a time
    curboard.sprites['flood'] = bspr
    waves_sprites = [gamesrv.Sprite(waves_icons[0], x, bheight-CELL)
                     for x in range(0, bwidth, CELL)]
    bspr += waves_sprites
    fill_by_line = []
    poplist = [None]
    while waves_sprites[0].y > 0:
        yield 0
        waves_icons.insert(0, waves_icons.pop())
        for s in waves_sprites:
            s.seticon(waves_icons[0])
        yield 0
        sprites = [gamesrv.Sprite(fill_icon, s.x, s.y) for s in waves_sprites]
        bspr += sprites
        fill_by_line.append(sprites)
        for s in waves_sprites:
            s.step(0, -16)
        for s in images.touching(0, waves_sprites[0].y, bwidth, bheight):
            if isinstance(s, Monster):
                s.argh(poplist)
    while 1:
        for i in range(2):
            yield 0
            waves_icons.insert(0, waves_icons.pop())
            for s in waves_sprites:
                s.seticon(waves_icons[0])
        if not fill_by_line:
            break
        for s in fill_by_line.pop():
            s.kill()
        for s in waves_sprites:
            s.step(0, 16)
    for s in waves_sprites:
        s.kill()
    del curboard.sprites['flood']

def extra_walls_falling():
    walls_by_pos = curboard.walls_by_pos
    moves = 1
    while moves and not curboard.cleaning_gen_state:
        moves = 0
        for y in range(height-3, -1, -1):
            for x in range(2, width-2):
                if ((y,x) in walls_by_pos and
                    (y+1,x) not in walls_by_pos and
                    (y+2,x) not in walls_by_pos):
                    y0 = y
                    while (y0-1,x) in walls_by_pos:
                        y0 -= 1
                    w = curboard.killwall(x, y0, 0)
                    curboard.putwall(x, y+1, w)
                    moves = 1
        curboard.reorder_walls()
        for y in range(6):
            yield 0

def single_blocks_falling(xylist):
    walls_by_pos = curboard.walls_by_pos
    while xylist:
        newlist = []
        for x, y in xylist:
            if ((y,x) in walls_by_pos and (y+1,x) not in walls_by_pos and
                y < curboard.height-1):
                newlist.append((x, y+1))
        for x, y in newlist:
            w = curboard.killwall(x, y-1, 0)
            curboard.putwall(x, y, w)
        xylist = newlist
        curboard.reorder_walls()
        for i in range(7):
            yield 0

def extra_display_repulse(cx, cy, dlimit=5000, dfactor=1000):
    offsets = {}
    for s in gamesrv.sprites_by_n.values():
        x, y = s.getdisplaypos()
        if x is not None:
            dx = x - cx
            dy = y - cy
            d = dx*dx + dy*dy + 100
            if d <= dlimit:
                dx = (dx*dfactor)//d
                dy = (dy*dfactor)//d
                offsets[s] = dx, dy
                s.setdisplaypos(x+dx, y+dy)
    yield 0
    yield 0
    while offsets:
        prevoffsets = offsets
        offsets = {}
        for s, (dx, dy) in prevoffsets.items():
            if s.alive:
                if dx < 0:
                    dx += max(1, (-dx)//5)
                elif dx:
                    dx -= max(1, dx//5)
                if dy < 0:
                    dy += max(1, (-dy)//5)
                elif dy:
                    dy -= max(1, dy//5)
                if dx or dy:
                    offsets[s] = dx, dy
                s.setdisplaypos(s.x+dx, s.y+dy)
        yield 0

def extra_bkgnd_black(cx, cy):
    gl = curboard.sprites.get('background')
    dist = 0
    while gl:
        dist += 17
        dist2 = dist * dist
        gl2 = []
        for s in gl:
            if (s.x-cx)*(s.x-cx) + (s.y-cy)*(s.y-cy) < dist2:
                s.kill()
            else:
                gl2.append(s)
        gl[:] = gl2
        yield 0

def extra_light_off(timeout, icocache={}):
    for i in range(timeout):
        if curboard.cleaning_gen_state:
            break
        dragons = {}
        import player
        playerlist = player.BubPlayer.PlayerList
        for bubber in playerlist:
            for dragon in bubber.dragons:
                dragons[dragon] = True
        for s in gamesrv.sprites_by_n.values():
            try:
                ico = icocache[s.ico, s in dragons]
            except KeyError:
                ico = images.make_darker(s.ico, s in dragons)
                icocache[s.ico, s in dragons] = ico
            s.setdisplayicon(ico)
        yield 0
    for s in gamesrv.sprites_by_n.values():
        s.setdisplayicon(s.ico)

def extra_swap_up_down(N=27):
    # unregister all walls
    walls = curboard.walls_by_pos.items()
    walls.sort()
    if not walls:
        return
    curboard.walls_by_pos.clear()
    emptyline = '##' + ' '*(width-4) + '##'
    curboard.walls = [emptyline] * height
    l = curboard.sprites['walls']
    wallicon = l[0].ico
    wallpool = l[:]
    l[:] = [gamesrv.Sprite(wallicon, 0, -wallicon.h)]
    
    # force the top half of the walls on front
    #for (y,x), w in walls:
    #    if y*2 < height:
    
    # show the walls swapping up/down
    ycenter = ((height-1)*CELL) // 2
    for i in range(N):
        alpha = math.cos((math.pi*(i+1))/N)
        ymap = {}
        for y in range(height):
            ymap[y] = int(alpha*(y*CELL-ycenter)) + ycenter
        for (y,x), w in walls:
            if y in ymap:
                w.move(x*CELL, ymap[y])
        yield 0
        if i == (N+1)//2:
            # reorder the wall sprites in the middle of the swap
            walls = [((-y,x), w) for (y,x), w in walls]
            walls.sort()
            for i in range(len(walls)):
                (y,x), w = walls[i]
                walls[i] = (y,x), wallpool[i]
            walls = [((-y,x), w) for (y,x), w in walls]
            walls.sort()
            # reverse all dragons!
            from player import BubPlayer
            for dragon in BubPlayer.DragonList:
                dragon.dcap['gravity'] *= -1.0
    
    # freeze the walls in their new position
    i = 0
    for (y,x), w in walls:
        y = height-1 - y
        if 0 <= y < height and (y,x) not in curboard.walls_by_pos:
            w = wallpool[i]
            i += 1
            curboard.putwall(x, y, w)
    l[:0] = wallpool[:i]
    for w in wallpool[i:]:
        w.kill()
    curboard.reorder_walls()

def extra_catch_all_monsters(dragons=[], everything=False):
    from monsters import Monster
    from bubbles import BigBubbleCatcher
    from bonuses import Bonus, BonusMaker
    if not dragons:
        from player import BubPlayer
        dragons = BubPlayer.DragonList
    i = 0
    give_up = 33
    for s in images.ActiveSprites[:]:
        if curboard.cleaning_gen_state:
            break
        while not dragons:
            give_up -= 1
            if give_up == 0:
                return
            yield 0
        if not s.alive or not s.touchable:
            continue
        if isinstance(s, Bonus):
            ok = s.bubblable
        elif isinstance(s, BonusMaker):
            ok = everything
        else:
            ok = isinstance(s, Monster)
        if ok:
            dragon = dragons[i%len(dragons)]
            BigBubbleCatcher(dragon, s, 542 + 17*i)
            i += 1
            yield 0
            yield 0


def register(dict):
    global width, height, bwidth, bheight, bheightmod
    items = dict.items()
    items.sort()
    for name, board in items:
        try:
            if not issubclass(board, Board) or board is Board:
                continue
        except TypeError:
            continue
        BoardList.append(board)
    # check sizes
    assert BoardList, "board file does not define any board"
    B = BoardList[0]
    try:
        test = B(-1)
        width = test.width
        height = test.height
        for B in BoardList[1:]:
            test = B(-1)
            assert test.width == width, "some boards have a different width"
            assert test.height == height, "some boards have a different height"
    except Exception, e:
        print 'Caught "%s" in level "%s":' % (e, B.__name__)
        raise e
    bwidth = width*CELL
    bheight = height*CELL
    bheightmod = (height+2)*CELL

##def define_boards(filename):
##    global curboard, boards, width, height, bwidth, bheight, bheightmod
##    curboard = None
##    boards = []
##    def board((wallfile, wallrect), shape):
##        lines = shape.strip().split('\n')
##        bmp = gamesrv.getbitmap(wallfile)
##        wallicon = bmp.geticon(*wallrect)
##        boards.append(Board(lines, wallicon))
##    d = {'board': board}
##    execfile(filename, d)
##    assert boards, "board file does not define any board"
##    width = boards[0].width
##    height = boards[0].height
##    for b in boards[1:]:
##        assert b.width == width, "some boards have a different width"
##        assert b.height == height, "some boards have a different height"
##    bwidth = width*CELL
##    bheight = height*CELL
##    bheightmod = len(boards[0].lines)*CELL


#try:
#    import psyco
#except ImportError:
#    pass
#else:
#    psyco.bind(normal_frame)
