# 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 math
import random

import pygame
from pygame.locals import K_UP, K_LEFT, K_RIGHT, K_LCTRL, K_RETURN

from pygsear import Game, conf
from pygsear.locals import *

from pygsear.Drawable import MultiRotated
from pygsear.Drawable import Image
from pygsear.Drawable import Circle
from pygsear.Widget import VProgressBar
from pygsear.Widget import Score
from pygsear.Drawable import String
from pygsear.Drawable import Stationary, StationaryStack
from pygsear.Path import AccelerationPath
from pygsear import Event
from pygsear import Util
from pygsear import Sound


class Fuel(VProgressBar):
    """
    """

    def __init__(self):
        """
        """

        self.steps = 500
        VProgressBar.__init__(self, steps=self.steps, color=LBLUE)
        self.seconds = 20 # seconds of fuel
        self.rate = self.steps / (self.seconds * 1000.0)
        self.t_part = 0
        self.warning = Util.load_sound('wupwupwup.wav')
        self.warning.set_volume(0)
        self.warning.play(-1)

    def empty(self):
        """
        """

        return not self.stepsLeft

    def flow(self, t):
        """
        """

        t_part = self.t_part
        t_part += t * self.rate
        self.t_part = t_part
        while self.t_part > 1:
            self.t_part -= 1
            self.step()
            if self.stepsLeft == 75:
                self.warning.set_volume(1)
                self.set_color(RED)

    def addFuel(self, t):
        """
        """

        t_part = self.t_part
        t_part += t * self.rate
        self.t_part = t_part
        while self.t_part > 1:
            self.t_part -= 1
            self.unstep()
            if self.stepsLeft == 76:
                self.warning.set_volume(0)
                self.set_color(GRAY)

    def reset(self):
        self.warning.set_volume(0)
        VProgressBar.reset(self)


class Lander(MultiRotated):
    """
    """

    def __init__(self):
        """
        """

        MultiRotated.__init__(self, filenames=['lander_new0.png',
                                                'lander_new1.png',
                                                'lander_new2.png',
                                                'lander_new3.png',
                                                'lander_new4.png'],
                                steps=60,
                                colorkey=TRANSPARENT)

        self.path.set_restriction(speed=500)

        self.reset()
        self.fuel = Fuel()
        self.uprightImage = self.image
        
        self._flip_ticks = 0
        
        # make crect a square with side length of the smaller of
        # the height or the width of the ship. Then shrink it by
        # a few pixels
        m = min(self.crect.h, self.crect.w)
        self.crect.h = m
        self.crect.w = m
        self.crect.inflate_ip(-4, -4)

        self.sound = Util.load_sound('test_sound.wav')
        #self.sound = Util.load_sound('lander.wav')
        self.sound.set_volume(0)
        self.sound.play(-1)

    def reset(self):
        """
        """

        self.crashed = 0

        ticks = conf.ticks
        self.ticks = ticks
        self.path.ticks = ticks

        self.path.set_direction(PI/2)
        self.set_rotation(PI/2)
        self.set_closest()
        self.path.set_velocity(vx=0, vy=-5)
        self.path.set_acceleration(ax=0, ay=0)

        self.turningRight = 0
        self.turningLeft = 0
        self.turning = ''
        self.turnRate = 2.0 / 1000.0
        self.turnTicks = 0

        self.accel = 0
        self.accelRate = 277
        self.gravRate = 117
        self.set_gravity(self.gravRate)
        self.accelTicks = ticks

        self.landed = 0
        self.turn()
        self.turnTicks = 0

    def set_gravity(self, rate):
        self.path.set_gravity(gx=0, gy=rate)

    def set_randomPosition(self):
        """
        """

        x = random.randrange(100, conf.WINWIDTH-150)
        y = 100
        self.set_position((x, y))

    def refuel(self):
        """
        """

        self.fuel.reset()

    def move(self):
        """
        """

        ticks = conf.ticks
        if not self.landed:
            self.turn()
            self.accelerate(ticks)
            MultiRotated.move(self)

        self.fuel.flow(ticks/50.0)

    def unpause(self):
        self.engineOff()
        self.noRight()
        self.noLeft()
        MultiRotated.unpause(self)

    def accelerate(self, ticks):
        """
        """

        ax = 0
        ay = 0
        if self.accel and not self.fuel.empty() and not self.crashed:
            #self.sound.set_volume(1)
            self._flip_ticks += ticks
            if self._flip_ticks > 90:
                self.flip_images()
                self._flip_ticks -= 50
            self.fuel.flow(ticks)
            d = self.get_rotation()
            ax += self.accelRate * math.cos(d)
            ay -= self.accelRate * math.sin(d)

        else:
            self.sound.set_volume(0)
            self.switch_images('lander_new0.png')
        self.path.set_acceleration(ax=ax, ay=ay)
        #print self.path.ax, self.path.ay

    def engineOn(self, pygame_event):
        """
        """

        self.accel = 1

    def engineOff(self, pygame_event=None):
        """ """

        self.accel = 0
        self.sound.set_volume(0)

    def right(self, pygame_event=None):
        """ """

        if not self.crashed:
            self.turningRight = 1

    def noRight(self, pygame_event=None):
        """ """

        if not self.crashed:
            self.turningRight = 0

    def left(self, pygame_event=None):
        """ """

        if not self.crashed:
            self.turningLeft = 1

    def noLeft(self, pygame_event=None):
        """ """

        if not self.crashed:
            self.turningLeft = 0

    def turn(self):
        """ """

        if self.turningRight and not self.turningLeft:
            self.rotate_right()
        elif self.turningLeft and not self.turningRight:
            self.rotate_left()
        else:
            self.rotate_stop()
            d = self.get_rotation()
            if 0 < abs(d - PI/2) <= 0.1:
                self.set_rotation(PI/2)
                #print 'upping',self.get_rotation()
        #self.set_closest()

    def upright(self):
        """
        """

        d = self.get_rotation()
        u = abs(d - PI/2)
        if u < 0.07 or self.image == self.uprightImage:
            return 1+u
        else:
            #print d, u, id(self.image), id(self.uprightImage)
            return 0

    def land(self, py):
        """
        """

        self.landed = 1
        self.path.set_direction(PI/2)
        self.set_rotation(PI/2)
        self.set_closest()
        self.path.set_velocity(vx=0, vy=0)

        # make sure lander stays on top of pad
        x, y = self.get_position()
        w, h = self.image.get_size()
        dy = h / 2.0
        self.set_position((x, py-dy))

    def crash(self):
        """
        """

        if not self.crashed:
            self.right()
            self.crashed = 1
            self.path.set_velocity(vx=190, vy=-350)
            self.fuel.flow(2000) # 2 seconds of fuel penalty


class UfoPath(AccelerationPath):
    def __init__(self):
        self.sound = Util.load_sound('ufo.wav')
        self.sound.set_volume(0)
        self.sound.play(-1)

        AccelerationPath.__init__(self)
        self.reset()

    def reset(self):
        AccelerationPath.reset(self)
        self.set_position((-90, -90))
        self.set_acceleration(0, 0)
        self.set_velocity(0, 0)
        self.hideTime = random.randrange(4, 14) * 1000 # ticks
        self.hiddenTime = 0
        self.seeTime = random.randrange(2, 6) * 1000 # ticks
        self.seenTime = 0
        self.hidden = 1
        self.wasSeen = 0
        self.zooming = 0

        self.sound.set_volume(0)

    def next(self):
        ticks = conf.ticks
        if self.hidden:
            self.hiddenTime += ticks
            if self.hiddenTime > self.hideTime:
                self.choosePath()
                self.hidden = 0
                self.wasSeen = 1
        else:
            self.sound.set_volume(1)
            self.seenTime += ticks
            if self.seenTime > self.seeTime:
                self.zoom()

        if self.wasSeen and self.zooming:
            if not self.onscreen(200):
                self.reset()

        return AccelerationPath.next(self, ticks)

    def zoom(self):
        self.zooming = 1
        self.set_acceleration(0.003, -0.005)

    def choosePath(self):
        self.set_acceleration(0, 0)
        side = random.choice((0, 1, 2, 3))
        if side == 0:
            x = -10
            y = random.randrange(0, conf.WINHEIGHT / 4)
            vx = random.randrange(20, 250) / 1000.0
            vy = (random.randrange(1, 40) - 20) / 1000.0
        elif side == 1:
            x = random.randrange(0, conf.WINWIDTH/2)
            y = -10
            vx = random.randrange(60, 250) / 1000.0
            vy = random.randrange(1, 30) / 1000.0
        elif side == 2:
            x = random.randrange(conf.WINWIDTH/2, conf.WINWIDTH)
            y = -10
            vx = -random.randrange(60, 250) / 1000.0
            vy = random.randrange(1, 30) / 1000.0
        else:
            x = conf.WINWIDTH + 5
            y = random.randrange(0, conf.WINHEIGHT / 4)
            vx = -random.randrange(20, 250) / 1000.0
            vy = (random.randrange(1, 40) - 20) / 1000.0
        self.set_position((x, y))
        self.set_velocity(vx, vy)


class Ufo(Image):
    def __init__(self):
        Image.__init__(self, filename='ufo2.png')
        self.set_position(-90, -90)
        path = UfoPath()
        self.set_path(path)
        self.droppedFuelPod = 0

    def move(self):
        if not self.droppedFuelPod:
            if self.path.zooming:
                self.droppedFuelPod = 1
        elif not self.onscreen(100):
            self.reset()
        Image.move(self)

    def reset(self):
        self.droppedFuelPod = 0
        self.path.reset()


class FuelPod(Image):
    def __init__(self):
        Image.__init__(self, filename='fuel_pod2.png')
        path = AccelerationPath(startLocation=(-100,-100))
        self.set_path(path)
        gravRate = 0.00013
        self.path.set_gravity(0, gravRate)

    def reset(self):
        self.path.reset()

    def gone(self):
        x, y = self.get_position()
        if y > conf.WINHEIGHT + 200:
            return 1
        else:
            return 0


class Ground(Stationary):
    """
    """

    def __init__(self, filename, pos):
        """
        """

        i = Image(filename=filename, colorkey=TRANSPARENT2)
        i.set_position(pos)
        Stationary.__init__(self, sprite=i)
        self.draw()


class Pad(Ground):
    """
    """

    def __init__(self, pos):
        """
        """

        Ground.__init__(self, 'lunarpad4.png', pos)


class Rock(Ground):
    """
    """

    def __init__(self, pos):
        """
        """

        img = ['a', 'b', 'c']
        i = random.choice(img)
        Ground.__init__(self, 'lunarrock5%s.png'%i, pos)


class LunarLanderGame(Game.Game):
    """
    """

    def relaunch(self, pygame_event=None):
        if self.lander.landed:
            self.startMsg.clear()
            self.startMsg = None
            self.landingMsg.clear()
            self.landingMsg = None
            self.delFuelPod()
            self.makeBoard()
            self.ufo.reset()
            self.reset_lander()

    def makeGround(self):
        """
        """

        pad = None
        rockrects = []
        y = random.randrange(conf.WINHEIGHT-90, conf.WINHEIGHT-20)
        p = random.randrange(50, conf.WINWIDTH-120)
        x = 0
        while x < conf.WINWIDTH:
            y += random.randrange(-17, 17)
            if y > conf.WINHEIGHT-30:
                y = conf.WINHEIGHT-30
            if pad is None and x > p:
                pad = Pad((x, y))
                self.pad = pad
                x += 80
            else:
                r = Rock((x, y))
                rockrects.append(r.rect)
                x += 10

        self.pad = pad
        self.rockrects = rockrects

    def makeEarth(self):
        """
        """

        i = Image(filename='lunarearth.png')
        i.set_positionRandom()
        s = Stationary(self.window, i)
        s.draw()

    def makeStars(self):
        """
        """

        n = random.randrange(20, 50)
        for star in range(n):
            size = random.randrange(1, 4)
            s = Circle(self.window, size)
            s.set_positionRandom()
            stat = Stationary(self.window, s)
            stat.draw()

    def makeLander(self):
        """
        """

        if self.lander is None:
            self.lander = Lander()
        self.reset_lander()
        self.lander.refuel()
        self.sprites.add(self.lander)
        self.sprites.add(self.lander.fuel, level=-1)

    def reset_lander(self):
        """
        """

        self.lander.reset()
        self.positionLander()
        self.lander.pause()

    def positionLander(self):
        """
        """

        padX, padY = self.pad.get_position()

        lander = self.lander
        lander.set_randomPosition()
        x, y = lander.get_position()
        while padX-100 < x < padX+180:
            lander.set_randomPosition()
            x, y = lander.path.get_position()
        lander.move()

    def makeFuelPod(self):
        fuelPod = FuelPod()
        fuelPod.path.set_position(self.ufo.path.get_position())
        fuelPod.move()
        self.sprites.add(fuelPod)
        self.fuelPod = fuelPod

    def delFuelPod(self):
        if self.fuelPod is not None:
            self.sprites.remove(self.fuelPod)
        self.fuelPod = None

    def makeUfo(self):
        if self.ufo is None:
            self.ufo = Ufo()
            self.sprites.add(self.ufo)
        self.ufo.reset()

    def makeScore(self):
        score = Score(self.window, (20, 40), text="Landings:", color=GRAY)
        self.score = score
        self.sprites.add(score)

    def restart(self):
        Sound.unpause()
        #self.sprites.remove(self.ufo)
        self.delFuelPod()
        self.fuelPod = None
        self.sprites.remove(self.score)
        self.sprites.remove(self.lander)
        self.sprites.remove(self.lander.fuel)
        self.start()

    def showLandingMessage(self):
        messages = ['Good landing',
                        'Nice landing',
                        'Success!',
                        'Good job',
                        'Excellent',
                        'Nice!',
                        'Well done']
        message = random.choice(messages)
        landingMsg = String(message=message, fontSize=46, color=LBLUE)
        landingMsg.center()
        self.landingMsg = Stationary(sprite=landingMsg)
        self.landingMsg.draw()
        
        startMsg = String(message="Press [ENTER]", fontSize=30, color=LGREEN)
        startMsg.center(dy=40)
        self.startMsg = Stationary(sprite=startMsg)
        self.startMsg.draw()

    def checkCollision(self):
        """
        """

        if self.lander.collide(self.pad):
            x, y = self.lander.get_position()
            cx = self.lander.cx
            upright = self.lander.upright()
            px, py = self.pad.get_position()
            if x-cx > px and x-cx < px + 26 and upright:
                if not self.lander.landed:
                    self.showLandingMessage()
                    self.score.addPoints(1)
                    self.score.updateScore()
                self.lander.land(py)
                return
            else:
                self.lander.crash()
                return
        if self.lander.crect.collidelist(self.rockrects) != -1:
            self.lander.crash()
        if self.lander.collide(self.ufo):
            self.lander.crash()
            self.ufo.path.zoom()
        if self.fuelPod is not None:
            if not self.lander.crashed and not self.lander.landed:
                #print self.lander.crect, self.fuelPod.crect
                if self.lander.collide(self.fuelPod):
                    self.lander.fuel.reset()
                    self.delFuelPod()
                    self.fuelPod = None

    def start(self):
        self.makeBoard()

        self.makeLander()
        self.makeScore()
        self.makeUfo()

    def initialize(self):
        """
        """

        self.window.set_title('Lunar Lander')

        self.over = 0
        self.lander = None
        self.ufo = None
        self.fuelPod = None
        self.start()

        # EVENTS
        # UP, LEFT-CONTROL - Engine
        self.events.add(Event.KEYDOWN_Event(key=K_UP, callback=self.lander.engineOn))
        self.events.add(Event.KEYDOWN_Event(key=K_LCTRL, callback=self.lander.engineOn))
        self.events.add(Event.KEYUP_Event(key=K_UP, callback=self.lander.engineOff))
        self.events.add(Event.KEYUP_Event(key=K_LCTRL, callback=self.lander.engineOff))

        # LEFT, RIGHT - Turn left/right
        self.events.add(Event.KEYDOWN_Event(key=K_LEFT, callback=self.lander.left))
        self.events.add(Event.KEYDOWN_Event(key=K_RIGHT, callback=self.lander.right))
        self.events.add(Event.KEYUP_Event(key=K_LEFT, callback=self.lander.noLeft))
        self.events.add(Event.KEYUP_Event(key=K_RIGHT, callback=self.lander.noRight))
        # RETURN - Restart after landing
        self.events.add(Event.KEYUP_Event(key=K_RETURN, callback=self.relaunch))

        self.reset_lander()

    def makeBoard(self):
        """
        """

        self.set_background(color=BLACK)
        self.makeStars()
        self.makeEarth()
        self.makeGround()

    def mainloop(self):
        """
        """

        over = 0
        empty = 0
        crashed = 0
        self.lander.unpause()
        while not self.quit:
            conf.ticks = self.clock.tick(conf.MAX_FPS)
            self.sprites.clear()
            self.checkEvents()
            self.sprites.move()
            if not self.lander.onscreen(150):
                self.reset_lander()
                if self.lander.crashed:
                    pygame.time.wait(900)
                self.lander.unpause()
            self.checkCollision()

            if self.fuelPod is None:
                if self.ufo.droppedFuelPod:
                    self.makeFuelPod()
            elif self.fuelPod.gone():
                self.delFuelPod()

            if self.lander.fuel.empty():
                if self.lander.landed:
                    over = 1
                elif empty and self.lander.crashed:
                    crashed = 1
                elif empty and crashed and not self.lander.crashed:
                    over = 1
                else:
                    empty = 1

            dirty = self.sprites.draw()
            pygame.display.update(dirty)

            if over:
                self.ufo.path.sound.set_volume(0)
                self.lander.fuel.warning.set_volume(0)
                Sound.pause()

                import time
                if hasattr(self, 'landingMsg') and self.landingMsg is not None:
                    self.landingMsg.clear()
                    self.landingMsg = None
                    self.startMsg.clear()
                    self.startMsg = None
                self.gameOver()
                if not self.quit:
                    self.restart()
                over = 0
                empty = 0
                crashed = 0


def main():
    """
    """

    g = LunarLanderGame()
    g.mainloop()

if __name__ == '__main__':
    main()
