# pygsear
# Copyright (C) 2003 Lee Harr
#
#
# This file is part of pygsear.
#
# pygsear is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# pygsear is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pygsear; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import time, random, math

import pygame
import pygame.draw
from pygame.locals import K_ESCAPE

from pygsear import Game, Drawable, Path, Cursor, Widget, Event, conf
from pygsear.locals import BLACK

conf.MAX_WINWIDTH = 800
conf.MIN_WINWIDTH = 800
conf.MAX_WINHEIGHT = 600
conf.MIN_WINHEIGHT = 600
conf.WINWIDTH = 800
conf.WINHEIGHT = 600
conf.WINSIZE = [800, 600]

class Level:
    def __init__(self, level=0):
        self.level = level
        self.speed = (level * 2) + 40
        self.makeBalloonSpeed = 300 / (level + 1)
        self.maxDelay = 5000 - (level * 500)
        if self.maxDelay < 300:
            self.maxDelay = 300
        self.delay = 0
        self.maxBalloons = int(level + 1)
        self.balloonsToNextLevel = int(level * 5) + 15
        self.bullets = int(self.balloonsToNextLevel * 1.2)
        self.unusedBulletsPoints = int(level * 5) + 5
        self.hitBalloons = []
        self.missedBalloons = []
        try:
            # "Extra lives" at certain levels
            self.balloonsToMissAddition = [3, 0, 0, 0, 1, 0, 0, 0, 1][level]
        except IndexError:
            self.balloonsToMissAddition = 0
        self.windSpeed = random.randint(-level, level)
        self.windGustSpeed = (level * 1.2)


class GameOver(Drawable.String):
    def __init__(self, w):
        Drawable.String.__init__(self, w, "GAME OVER",
                                    fontSize=40, color=BLACK)
        self.set_position((250, 200))

class BulletsLine(Widget.ProgressBar):
    def __init__(self, w, steps):
        Widget.ProgressBar.__init__(self, w, steps)

    def shoot(self):
        self.step()

class BalloonsToMissLine(Drawable.StationaryStack):
    def __init__(self, balloonsToMiss):
        Drawable.StationaryStack.__init__(self)
        perBalloon = 40
        x, y = 30, conf.WINHEIGHT - 20
        for b in range(balloonsToMiss):
            balloon = Drawable.Image(filename='balloon_yellow_0.png')
            balloon.set_position((x, y))
            stationary = Drawable.Stationary(sprite=balloon)
            self.push(stationary)
            x += perBalloon

    def shoot(self):
        self.pop()

class Balloon(Drawable.Multi):
    def __init__(self, w, level):
        Drawable.Multi.__init__(self, w)
        path = Path.AccelerationPath()
        self.set_path(path)
        xPosition = random.randrange(30, conf.WINWIDTH-30)
        yPosition = conf.WINHEIGHT + 50
        self.set_position(xPosition, yPosition)
        self.initImages(w)
        variation = random.randint(-100, 100) / 10.0
        self.set_speed(self.speed+variation)

    def initImages(self, w):
        self.balloon = Drawable.Image(w, self.filename)
        self.addSprite(self.balloon)
        self.string = Drawable.Image(w, 'balloon_string_0.png')
        self.addSprite(self.string, yOffset=30)

    def kill(self):
        self.balloon.kill()
        self.string.kill()

    def set_speed(self, speed):
        self.speed = speed
        self.path.set_velocity(vy=-speed/1000.0)

    def set_wind(self, wind):
        self.path.set_velocity(vx=wind)
    
    def move(self):
        Drawable.Multi.move(self)
        if not self.onscreen(200):
            raise Path.EndOfPath


class YellowBalloon(Balloon):
    def __init__(self, w, level):
        self.filename = 'balloon_yellow_0.png'
        self.speed = level.speed
        Balloon.__init__(self, w, level.level)
        self.points = 10

class RedBalloon(Balloon):
    def __init__(self, w, level):
        self.filename = 'balloon_red_0.png'
        self.speed = 2 * level.speed
        Balloon.__init__(self, w, level.level)
        self.points = 16


class Cloud(Drawable.Image):
    def __init__(self, w):
        #Drawable.Image.__init__(self, w, 'cloud01.png', alpha=200)
        Drawable.Image.__init__(self, filename='cloud01.png', convert=0)
        self.reset_path()

    def reset_path(self):
        h = random.randint(30, 230)
        path = Path.LinePath((-self.image.get_width(), h),
                        (conf.WINWIDTH, h), 25, 640)
        self.set_path(path)
        #a = random.randint(100, 250)
        #self.image.set_alpha(a)


class BalloonGame(Game.Game):
    def waitFor(self, key=None, timeout=None):
        """Pause the game, waiting for a keystroke,
        or a mouse click.
        Still allows the game to be ended by clicking the
        window close button."""
        self.done = 0
        timedOut = 0
        if timeout is not None:
            startTime = pygame.time.get_ticks()
        clearQ = pygame.event.get()

        events = Event.EventGroup()
        events.add(Event.QUIT_Event(callback=self._quit))
        events.add(Event.KEYUP_Event(key=K_ESCAPE, callback=self.configure))
        events.add(Event.MOUSEBUTTONDOWN_Event(button=1, callback=self.isDone))

        while not self.done:
            if timeout is not None:
                timeNow = pygame.time.get_ticks()
                if timeNow - startTime >= timeout:
                    timedOut = 1
                    self.done = 1
            self.cursorGroup.clear()
            events.check()
            for cur in self.cursorGroup.sprites():
                cur.move()
            dirty = self.sprites.draw()
            dirty += self.cursorGroup.draw()
            pygame.display.update(dirty)

        if timedOut:
            return 1
        else:
            return 0


    def set_level(self, level=0):
        self.level = Level(level)
        self.levelScore.set_points(level)
        self.levelScore.updateScore()

        if self.bulletsLine is not None:
            self.sprites.remove(self.bulletsLine)
        self.bulletsLine = BulletsLine(self.window, self.level.bullets)
        self.sprites.add(self.bulletsLine)

        self.balloonsToMiss += self.level.balloonsToMissAddition
        if self.balloonsToMissLine is not None:
            self.balloonsToMissLine.empty()
        self.balloonsToMissLine = BalloonsToMissLine(self.balloonsToMiss)

        self.makeCloud()


    def showLevel(self):
        self.sprites.add(self.levelScore)
        self.waitFor(timeout=2000)
        self.sprites.clear()
        self.sprites.remove(self.levelScore)
        pygame.display.update(self.levelScore.rect)

    def showGameOver(self):
        gameOver = GameOver(self.window)
        self.sprites.add(gameOver)
        self.waitFor(timeout=7000)
        self.sprites.clear()
        self.sprites.remove(gameOver)
        pygame.display.update(gameOver.rect)


    def interlevel(self):
        self.sprites.clear()
        self.sprites.remove(self.cloud)
        dirty = self.sprites.draw()
        pygame.display.update(dirty)

        x = 100
        y = 100
        dontWait = 0
        hitBalloons = Drawable.StationaryStack(self.window)
        for balloon in self.level.hitBalloons:
            balloon.balloon.set_position((x, y))
            hitBalloons.push(balloon.balloon)
            if not self.waitFor(timeout=100) or self.quit:
                dontWait = 1
                break
            x += 15
            if x > conf.WINWIDTH - 100:
                x = 100
                y += 15

        if not dontWait and not self.quit:
            x = 100
            y = 300
            missedBalloons = Drawable.StationaryStack(self.window)
            for balloon in self.level.missedBalloons:
                balloon.balloon.set_position((x, y))
                missedBalloons.push(balloon.balloon)
                if not self.waitFor(timeout=100) or self.quit:
                    dontWait = 1
                    break
                x += 15
                if x > conf.WINWIDTH - 100:
                    x = 100
                    y += 15

        if len(self.level.missedBalloons) == 0:
            self.level.unusedBulletsPoints *= 10
        if not dontWait and not self.quit:
            while self.bulletsLine.stepsLeft:
                self.screen.blit(self.bg, self.score.rect, self.score.rect)
                self.score.addPoints(self.level.unusedBulletsPoints)
                self.score.updateScore()
                self.bulletsLine.shoot()
                self.screen.blit(self.bg, self.bulletsLine.rect, self.bulletsLine.rect)
                self.screen.blit(self.bulletsLine.image, self.bulletsLine.rect)
                if not self.waitFor(timeout=100) or self.quit:
                    dontWait = 1
                    break

        self.balloonsToMiss -= len(self.level.missedBalloons)
        if self.balloonsToMiss <= 0:
            self.gameOver = 1

        if not dontWait and not self.quit:
            self.waitFor(timeout=2000)

        if not self.quit:
            for balloon in self.level.hitBalloons:
                balloon.kill()
            for balloon in self.level.missedBalloons:
                balloon.kill()
            hitBalloons.empty()
            missedBalloons.empty()


    def makeBalloon(self, t):
        if len(self.balloons) >= self.level.maxBalloons:
            return
        else:
            shouldMakeBalloon = random.expovariate(1.0/(t * self.level.makeBalloonSpeed)) > 1000
            if self.level.balloonsToNextLevel > 0:
                if (shouldMakeBalloon == 1 or
                        self.level.delay >= self.level.maxDelay):
                    self.level.delay = 0

                    ballooner = random.choice(self.balloonSet)
                    balloon = ballooner(self.window, self.level)

                    self.balloons.append(balloon)
                    self.balloonSprites.add(balloon.balloon)
                    self.sprites.add(balloon)

                    self.level.balloonsToNextLevel -= 1
                else:
                    self.level.delay += t


    def makeCloud(self):
        self.cloud = Cloud(self.window)
        self.sprites.add(self.cloud)

    def removeBalloon(self, b):
        balloon = b.balloon
        string = b.string
        self.balloons.remove(b)
        self.sprites.remove(balloon)
        self.sprites.remove(string)

        if not self.level.balloonsToNextLevel and not len(self.balloons):
            dirty = b.draw()
            b.clear()
            pygame.display.update(dirty)
            self.interlevel()
            if not self.quit and not self.gameOver:
                self.set_level(self.level.level+1)
                self.showLevel()


    def shoot(self, ev=None):
        self.level.bullets -= 1
        self.bulletsLine.shoot()
        if self.level.bullets >= 0:
            place = self.cursor.get_hotspot()
            balloonsShot = []
            for b in self.balloons:
                if b.balloon.rect.collidepoint(place):
                    self.level.hitBalloons.append(b)
                    self.score.addPoints(b.points)
                    self.score.updateScore()
                    balloonsShot.append(b)
            for b in balloonsShot:
                self.removeBalloon(b)
        else:
            print "CLICK"

    def isDone(self, ev=None):
        self.done = 1        

    def splash_screen(self):
        pass

    def splash_screen_poof(self):
        pass

    def splash(self):
        self.done = 0
        d = Drawable.Image(filename='gun2.png', convert=0)
        w, h = d.image.get_size()
        x = (conf.WINWIDTH - w) / 2
        y = (conf.WINHEIGHT - h) / 2
        d.set_position((x, y))
        dude = Drawable.Stationary(sprite=d)
        dude.draw()

        title = Drawable.String(message='Bubble Popper 2K2+1', fontSize=40)
        w, h = title.image.get_size()
        x = (conf.WINWIDTH - w) / 2
        y = (conf.WINHEIGHT - h) / 2
        titlepath = Path.CirclePath((x, y+50), size=20, steps=100,
                                startDirection=3.14,
                                clockwise=0, duration=0.5)
        title.set_path(titlepath)
        try:
            title.runPath()
        except Path.EndOfPath:
            pass

        blist = self.addGroup()
        sprites = self.sprites
        holdlist = self.addGroup()
        visible = self.addGroup()
        visible.add(title)

        for count in range(10):
            b = Drawable.Image(self.window, 'balloon_red_0.png')
            holdlist.add(b)

        ticks0 = pygame.time.get_ticks()

        events = Event.EventGroup()
        events.add(Event.QUIT_Event(callback=self._quit))
        events.add(Event.KEYUP_Event(key=K_ESCAPE, callback=self.configure))
        events.add(Event.MOUSEBUTTONDOWN_Event(button=1, callback=self.isDone))

        while not self.done:
            visible.clear()
            events.check()
            if self.quit:
                import sys
                sys.exit()

            ticks = pygame.time.get_ticks() - ticks0
            if ticks < 5000 and ticks > len(blist) * 500:
                p = Path.CirclePath((400, 100), size=200, duration=5)
                p.set_loop(-1)
                b = holdlist.pop()
                b.set_path(p)
                blist.add(b)
                visible.add(b)
            elif ticks > 13000 + (10-len(blist))*1000:
                b = blist.pop()
                visible.remove(b)
                holdlist.add(b)
                if len(blist) == 0:
                    b.uclear()
                    try:
                        title.runPath()
                    except Path.EndOfPath:
                        pass
                    ticks0 = pygame.time.get_ticks()

            for b in blist.sprites():
                b.move()

            dirty = visible.draw()
            pygame.display.update(dirty)

        for b in blist.sprites():
            blist.remove(b)
            visible.remove(b)
            sprites.remove(b)
        for b in holdlist.sprites():
            holdlist.remove(b)
            sprites.remove(b)

        visible.remove(title)
        visible.clear()
        dude.clear()
        pygame.display.update()


    def resize_reset(self):
        self.set_background('mountain800x600.png')
        pygame.display.update()


    def initialize(self):
        pygame.display.set_caption('balloons')

        self.set_background('mountain800x600.png')

        self.events.add(Event.MOUSEBUTTONDOWN_Event(button=1, callback=self.shoot))

        self.allowTicks = 1000 / conf.MAX_FPS

        self.splash()

        self.hideMouse()

        self.balloons = []
        self.balloonSprites = self.addGroup()

        self.balloonSet = [YellowBalloon]*8 +  [RedBalloon]*2

        cursor = Cursor.CrosshairCursor(self.window, 23)
        self.cursorGroup = self.addGroup(cursor)
        self.cursor = cursor

        self.score = Widget.Score(self.window, (50, conf.WINHEIGHT-80),
                                        color=BLACK)
        self.sprites.add(self.score)

        self.gameOver = 0
        self.balloonsToMiss = 0
        self.balloonsToMissLine = None

        self.bulletsLine = None
        self.levelScore = Widget.Score(self.window, (250, 200), text="Level:",
                                        color=BLACK, digits=0)
        self.set_level()


    def mainloop(self):
        sprites = self.sprites
        cursor = self.cursorGroup
        screen = self.screen
        bg = self.bg
        update = pygame.display.update

        get_ticks = pygame.time.get_ticks
        allowTicks = self.allowTicks
        ticks = 0
        ticks_old = 0

        self.showLevel()
        windGust = 0

        while not self.quit and not self.gameOver:
            sprites.clear()
            cursor.clear()
            self.checkEvents()

            ticks = get_ticks()
            self.makeBalloon(ticks - ticks_old)
            if ticks - ticks_old > allowTicks:
                if self.level.windGustSpeed:
                    if random.randint(0, 10) > 5:
                        windGust = ((2 * random.random()) *
                                self.level.windGustSpeed) - self.level.windGustSpeed

                ticks_old = ticks
                for balloon in self.balloons[:]:
                    if windGust:
                        balloon.path.set_acceleration(ax=windGust/100000.0)
                    try:
                        balloon.move()
                    except Path.EndOfPath:
                        self.level.missedBalloons.append(balloon)
                        self.balloonsToMissLine.pop()
                        self.removeBalloon(balloon)

            try:
                self.cloud.move()
            except Path.EndOfPath:
                self.cloud.reset_path()

            for cur in cursor.sprites():
                cur.move()

            dirty = sprites.draw()
            dirty += cursor.draw()
            update(dirty)

        if self.gameOver:
            self.showGameOver()


def main():
    random.seed()

    g = BalloonGame()
    g.mainloop()

if __name__ == '__main__':
    main()


