#!/usr/bin/env python
"""
-----=====SpacePong=>

Copyright 2004,2005 Shlomi Loubaton
Graphics 2004,2005 Eero Tamminen
Music and Sounds copyright 2005 David Tweet
SpacePong is free and open source, released under the GPL license.

Thanks goes to Dovix for Mandriva RPMs

Pygame Powered
"""


__author__ = "Shlomi Loubaton"
__copyright__ = "Copyright (c)2004 Shlomi Loubaton"
__license__ = "GNU GPL version 2"
__version__ = "1"

#
#   TODO: 0) Build Windows port (py2exe ?)
#         1) Add highscore Table
#         2) find an artist who can draw some original hud and sprites
#         3) Add a menu to outer game loop
#         4) add a pause game event
#         5) option to switch between fullscreen/windowed
#         6) powerups? 
#         7) background ? sparkling stars maybe .. 


import os, sys

# detect SpacePong data directory
if os.path.isdir("/usr/share/games/spacepong/data"):
    datadir = '/usr/share/games/spacepong/data'
    sys.path.insert(0,'/usr/share/games/spacepong/data')
elif os.path.isdir("/usr/share/spacepong/data"):
    datadir = '/usr/share/spacepong/data'
    sys.path.insert(0,'/usr/share/spacepong/data')
elif os.path.isdir("data"):
    datadir = 'data'
    sys.path.insert(0, "data")
#datadir = 'data'
#sys.path.insert(0, "data")

import math
import pygame
import random
from random import randint
from pygame.locals import *



from level_data import levels_data
from level_data import NUM_OF_LEVELS

#globals and constants
SPACE_RANGE = [25,25,502,430]
TIME_TO_SCORE_FACTOR = 1000
MAX_SPEED = 70
X = 0 
Y = 1
screen = None
S_DIVIDER = 120

# Animations structures:

anim_list = ('boxes','explosion','fire','spikeball','asteroid',"little-numbers","ship")
anim_images = {}

###################################### Functions:
###################################### Functions:
###################################### Functions:


def play_music(name, num_times=0):
    if not pygame.mixer or not pygame.mixer.get_init():
        return False
    fullname = os.path.join(datadir, name)
    if os.path.exists(fullname) == True:
        pygame.mixer.music.load(fullname)
        pygame.mixer.music.play(num_times)
    else:
        print 'File does not exist:', fullname
        return False
    return True

def load_sound(name):
    class No_Sound:
        def play(self): pass

    if not pygame.mixer or not pygame.mixer.get_init():
        return No_Sound()

    fullname = os.path.join(datadir, name)
    if os.path.exists(fullname) == True:
        sound = pygame.mixer.Sound(fullname)
    else:
        print 'File does not exist:', fullname
        return No_Sound

    return sound


def build_minefield_group(level,group):
    global Animobj
    lvl_dat = (levels_data[level]['minefield_map']).split("\n")
    mine_group = pygame.sprite.RenderPlain()
    j = 0
    for line in lvl_dat:
        j += 1
        for i in range(0,len(line)):
            if line[i] == 'x' :
                group.add(Animobj((25+i*25,j*25-25)))
    return mine_group
    


## These 3 functions were adopted from SolarWolf

def load_image(name, colorkey=None):
    fullname = os.path.join(datadir, name)
    try:
        image = pygame.image.load(fullname)
    except pygame.error, message:
        print 'Cannot load image:', name
        raise SystemExit, message
    image = image.convert()
    if colorkey is not None:
        if colorkey is -1:
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image, image.get_rect()

def optimize(img):
    if not screen.get_flags() & HWSURFACE:
        clear = img.get_colorkey()
        if clear:
            img.set_colorkey(clear, RLEACCEL)
    return img.convert()

def animstrip(img, width=0):
    if not width:
        width = img.get_height()
    size = width, img.get_height()
    images = []
    origalpha = img.get_alpha()
    origckey = img.get_colorkey()
    img.set_colorkey(None)
    img.set_alpha(None)
    for x in range(0, img.get_width(), width):
        i = pygame.Surface(size)
        i.blit(img, (0, 0), ((x, 0), size))
        if origalpha:
            i.set_colorkey((0,0,0))
        elif origckey:
            i.set_colorkey(origckey)
        images.append(optimize(i))
    img.set_alpha(origalpha)
    img.set_colorkey(origckey)
    return images

    
def load_game_resources():
    global anim_images,anim_list
    for anim in anim_list:
        (img , tmp_rect) = load_image(anim + '.png')
        images = []
        images.extend(animstrip(img))
        anim_images[anim] = images

###################################### Objects:
###################################### Objects:
###################################### Objects:

class StarField:
    def __init__(self, surface, num_of_stars = 80,star_range = SPACE_RANGE):
        self.surface = surface
        self.num_of_stars = num_of_stars
        self.star_range = Rect(star_range)
        self.stars_xy = []
        self.stars_a = []
        self.offset_vec = [0,0]
        for i in range(0,self.num_of_stars-1) :
            self.stars_xy.append([random.randint(-500,500),random.randint(-100,700)])
            self.stars_a.append(random.randint(60,250))

    def display(self, offset_vec=[0,0] ):
        self.offset_vec = [round((offset_vec[X]*2+self.offset_vec[X]/2)/3),round((offset_vec[Y]*2+self.offset_vec[Y]/2)/3)]
        for i in range(0,self.num_of_stars-1) :
            star_xy = [int(round(self.stars_xy[i][X]-round(self.offset_vec[X]*self.stars_a[i])/S_DIVIDER)),int(round(self.stars_xy[i][Y]-round(self.offset_vec[Y]*self.stars_a[i])/S_DIVIDER))]
            if self.star_range.collidepoint(star_xy[X],star_xy[Y]) :
                self.surface.set_at(star_xy,[self.stars_a[i],self.stars_a[i],self.stars_a[i],100])
        

class Numbox:
    def __init__(self,num, num_of_digits = 5, pos=[0,0]):
        self.group = pygame.sprite.RenderPlain()
        self.num_of_digits = num_of_digits
        self.images = []
        for i in range(0,num_of_digits-1) :
            self.images += [pygame.sprite.Sprite()]
            pygame.sprite.Sprite.__init__(self.images[i])
            self.images[i].image = anim_images['little-numbers'][0]
            self.images[i].rect = self.images[i].image.get_rect()
            self.images[i].rect.topleft =  (pos[X]+8*(num_of_digits-1-i),pos[Y])
            self.group.add(self.images[i])
        self.set_num(num)
    
    def set_num(self,number):
        self.value = number
        for i in range(0,self.num_of_digits-1) :
            digit = number % 10
            number = int(number / 10)
            self.images[i].image = anim_images['little-numbers'][digit]


class Animobj(pygame.sprite.Sprite):
    
    def __init__(self,pos = [200,200], img_name='spikeball'):
        global anim_images
        pygame.sprite.Sprite.__init__(self)
        self.pos = pos    
        self.image_name = img_name
        self.image_index = 0
        self.image = anim_images[self.image_name][self.image_index]
        self.image.set_alpha(50)
        self.alpha = 50
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        self.anim_delay = 2
        self.anim_counter = 0
    
    def update(self):
        global anim_images
        self.anim_counter += 1
        self.anim_counter = (self.anim_counter % self.anim_delay)
        if self.anim_counter == 0 :
            self.image_index += 1
        if self.image_index == len(anim_images[self.image_name]):
            self.image_index = 0
        self.image = anim_images[self.image_name][self.image_index]
        if self.alpha < 255:
            self.alpha += 3
            self.image.set_alpha(self.alpha)
        else:
            self.image.set_alpha(255)
        self.rect.center = self.pos
        
    def checkhit(self , ship):
        if self.rect.collidepoint(ship.rect.center) :
            self.kill()
            return 1
        return 0



class Explode(pygame.sprite.Sprite):
    
    def __init__(self, pos = [200,200], img_name='explosion', fade_rate=0):
        global anim_images
        pygame.sprite.Sprite.__init__(self)
        self.image_name = img_name
        self.fade_rate = fade_rate
        self.alpha = 255
        self.pos = pos
        self.image_index = 0
        self.image = anim_images[img_name][self.image_index]
        self.rect = self.image.get_rect()
        self.rect.center = pos
    
    def update(self):
        global anim_images
        self.rect.center = self.pos
        self.image_index += 1
        if self.image_index == len(anim_images[self.image_name]):
            self.kill()
            return
        self.image = anim_images[self.image_name][self.image_index]
        self.alpha = self.alpha - self.fade_rate
        if self.alpha < 0 :
            self.alpha = 0
        self.image.set_alpha(self.alpha)

class Hud_data:
    lives_image = None
    def __init__(self):
        self.clicksound = load_sound('click.wav')
        self.max_lives = 5
        self.group = pygame.sprite.RenderPlain()
        self.lives_array = []
        if Hud_data.lives_image is None:
            Hud_data.lives_image, lives_image_rect = load_image('ship-small.png', -1)
        self.lvlmsg = pygame.sprite.Sprite()
        pygame.sprite.Sprite.__init__(self.lvlmsg)

        for i in range(0,self.max_lives) :
            self.lives_array += [pygame.sprite.Sprite()]
            pygame.sprite.Sprite.__init__(self.lives_array[i])
            self.lives_array[i].image = Hud_data.lives_image.convert(Hud_data.lives_image)
            self.lives_array[i].rect = self.lives_array[i].image.get_rect()
            self.lives_array[i].rect.center = (560+i*self.lives_array[i].rect.width,80)
            self.group.add(self.lives_array[i])

    def set_lives(self,num_of_lives_left):
        for i in range(0,self.max_lives) :
            if i < num_of_lives_left :
                self.lives_array[i].image.set_alpha(255)
            else:
                self.lives_array[i].image.set_alpha(50)

    def draw_cargo_bars(self,srf,needed,current):
        global levels_data
        col = 250
        for i in range(1,needed+1):
            if i > current:
                col = 100
            pygame.draw.rect(srf , (100,col,100), [555,310-(i-1)*5,75,3] , 0) 

    def show_message(self,screen,bg,message_pic):
        "returns false if quit event recieved"
        messages_group = pygame.sprite.RenderPlain()
        self.lvlmsg.image , self.lvlmsg.rect = load_image(message_pic, -1)
        messages_group.add(self.lvlmsg)
        self.lvlmsg.image.set_alpha(255)
        for i in range(-50,220,10) :
            self.lvlmsg.rect.center = (230,i)
#            pygame.time.delay(2)
            screen.blit(bg, (0, 0))
            messages_group.draw(screen)
            pygame.display.flip()
        clicked = False
        while not clicked :    
            for event in pygame.event.get():
                if event.type == MOUSEBUTTONUP:
                    clicked = True
                elif event.type == QUIT:
                    return False
                elif event.type == KEYDOWN and event.key == K_ESCAPE:
                    return False

        self.clicksound.play()

        for i in range(0,255,10) :
            self.lvlmsg.image.set_alpha(255-i)
            screen.blit(bg, (0, 0))
            messages_group.draw(screen)
            pygame.display.flip()

        return True

            
        
class Asteroid(Animobj):
    """
    direction = direction vector
    space_range = the range it exist in
    """
    def __init__(self,pos = [200,200],img_name='asteroid'):
        Animobj.__init__(self, pos, img_name) 
        self.direction = [0,0]
        self.space_range = Rect(SPACE_RANGE)
        self.bounce = 1

    def updatepos(self):
        self.pos = [ self.pos[X] + self.direction[X],  self.pos[Y] + self.direction[Y] ]

    def bounce_proc(self):
        pass
        
    def update(self):
        self.updatepos()
        
        # bounce on edges
        if self.bounce :
            if self.pos[X] < self.space_range.left :
                self.pos[X] = self.space_range.left 
                self.direction[X] *= -1
                self.bounce_proc()
            if self.pos[X] > self.space_range.right :
                self.pos[X] = self.space_range.right
                self.direction[X] *= -1
                self.bounce_proc()
            if self.pos[Y] < self.space_range.top :
                self.pos[Y] = self.space_range.top
                self.direction[Y] *= -1
                self.bounce_proc()
            if self.pos[Y] > self.space_range.bottom :
                self.pos[Y] = self.space_range.bottom
                self.direction[Y] *= -1
                self.bounce_proc()

        Animobj.update(self)
        

class Spaceship(Asteroid):
    "The player's object"

    def __init__(self):
        Asteroid.__init__(self,[200,200],'ship')
        self.maxspeed = 4
        pygame.mouse.get_rel()
        self.alpha = 255
        self.disabled = False
        self.bounce_sound = load_sound('bounce.wav')
    
    def updatepos(self):
        self.pos = [ self.pos[X] + self.direction[X]/6 ,  self.pos[Y] + self.direction[Y]/6 ]

    def bounce_proc(self):
        Asteroid.bounce_proc(self)
        self.bounce_sound.play()

    def update(self):
        if not self.disabled : 
            Asteroid.update(self)
            if self.maxspeed < MAX_SPEED :
                self.maxspeed += 1

            (mouse_vector_x , mouse_vector_y) = pygame.mouse.get_rel()
            if (mouse_vector_x, mouse_vector_y) != (0,0):
                self.direction = [ self.direction[X] + mouse_vector_x/2 , self.direction[Y] + mouse_vector_y/2]
            
                # Normalize vector
                vector_length = math.sqrt(self.direction[X]**2+self.direction[Y]**2)
                if vector_length > self.maxspeed :
                    self.direction = [ round((self.direction[X]/vector_length)*self.maxspeed), 
                                        round((self.direction[Y]/vector_length)*self.maxspeed) ]

        

###################################### Main:
###################################### Main:
###################################### Main:

def main():
    ################################################## Game setup:
    global screen
    random.seed()
    pygame.init()
    screen = pygame.display.set_mode((640, 480), FULLSCREEN)
#    screen = pygame.display.set_mode((640, 480))
    pygame.display.set_caption('SpacePong 0.0.2')

    # Create some objects
    clock = pygame.time.Clock()
    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((0, 0, 0))
    load_game_resources()
    spaceship = Spaceship()
    starfield = StarField(screen);
    allsprites = pygame.sprite.RenderPlain((spaceship))

    # Game's groups
    spaceships = pygame.sprite.RenderPlain((spaceship))
    effects = pygame.sprite.RenderPlain()
    mines = pygame.sprite.RenderPlain()

    # Draw the game's gui
    bg = pygame.sprite.Sprite()
    bg_group = pygame.sprite.RenderPlain((bg))
    (bg.image,bg.rect) = load_image('hud.png', -1)
    bg_group.draw(background)
    
    # Time/Score Label creation
    time_label = Numbox(0,4,[575,165])
    score_label = Numbox(0,8,[560,125])
    allsprites.add(score_label.group)
    allsprites.add(time_label.group)

    # Hud widgets creation
    hud_data = Hud_data()
    allsprites.add(hud_data.group)
    hud_data.set_lives(5)

    explode_sound = load_sound('explode.wav')
    boxhit_sound = load_sound('boxhit.wav')
    blip_sound = load_sound('blip.wav')
    
    # Grab+hide mouse 
    pygame.mouse.set_visible(0)
    
    pygame.draw.rect(background, (0,0,0), [8,8,534,464] , 0)
    hud_data.show_message(screen,background,'msg_intro.png')
    


    ################################################## Level setup:

    exit_game = False
    while not exit_game :

        current_lives = 5
        current_level = 0
        current_score = 0
        hud_data.set_lives(current_lives)
        score_label.set_num(current_score)
    
        game_over = False

        while not game_over :
            # Redraw background 
            bg_group.draw(background)
            pygame.draw.rect(background, (0,0,0), [8,8,534,464] , 0)

            if not hud_data.show_message(screen,background,'pre-level.png') :
                break
            # Number of cargo that was collected
            current_cargo_collected = 0
        
            for ast_data in levels_data[current_level]['asteroids'] : 
                ast = Asteroid()
                ast.pos[X], ast.pos[Y], ast.direction[X], ast.direction[Y] = ast_data
                allsprites.add((ast))
                mines.add((ast))
        
            # Create minefield
            build_minefield_group(current_level,mines)
            allsprites.add(mines)
    
            # Setup timelimit variable
            levelstart_time = pygame.time.get_ticks()
            level_time_limit = levels_data[current_level]['time_limit'] * 1000
    
            # Set spaceship location
            spaceship.pos = levels_data[current_level]['ship_xy']
            spaceship.direction= [0,0]
    
            # Set number of lives in display
            hud_data.set_lives(current_lives)
    
            # Cargo object - will be created in internal loop
            cargo = None
        
            status_time_over = False
            status_got_hit = False
            status_all_collected = False
            level_over = False
    
            # Zero location so ship wont start flying
            pygame.mouse.get_rel()
            allsprites.update()

            # this will allow to show few more frames after level should exit
            frames_after_end = 0
            
            spaceship.disabled = False
            spaceship.maxspeed = 4

            play_music('spacepongtheme.ogg', -1)
            pygame.event.set_grab(1)
            while (not level_over) or (level_over and frames_after_end != 0):
                
                if frames_after_end > 0 :
                    frames_after_end -= 1

                # Calculate time and update time label
                time_passed =  pygame.time.get_ticks() - levelstart_time
                time_label.set_num((level_time_limit - time_passed)/1000)
                if (level_time_limit - time_passed) < 0 :
                    level_over = status_time_over = True
     
                # No more than 60 fps
                clock.tick(60)
        
                # Check if player took the cargo
                if cargo == None or cargo.checkhit(spaceship) :
                    if cargo != None :
                        current_score += 50
                        score_label.set_num(current_score)
                        current_cargo_collected += 1
                        boxhit_sound.play()
                    hud_data.draw_cargo_bars(background, levels_data[current_level]['cargo_needed'],current_cargo_collected)
                    if current_cargo_collected == levels_data[current_level]['cargo_needed'] :
                        level_over = status_all_collected  = True
        
                    cargo = Animobj((random.randint(20,530),random.randint(20,460)) , 'boxes')
                    while pygame.sprite.spritecollide(cargo, allsprites, 0) :
                        cargo.pos = cargo.rect.center = (random.randint(20,530),random.randint(20,460))
                    allsprites.add(cargo)
        
                # draw fire trail 
                effects.add(Explode((spaceship.pos[X]+randint(-5,5),spaceship.pos[Y]+randint(-5,5)),'fire',30))

                # Check if player got hit by mines
                if pygame.sprite.spritecollide(spaceship, mines, 0) != [] :
                    level_over = status_got_hit = True
                    # draw explosion
                    play_music('frowny.ogg')
                    
                    allsprites.add(Explode((spaceship.pos[X]+randint(-5,5),spaceship.pos[Y]+randint(-5,5))))
                    allsprites.add(Explode((spaceship.pos[X]+randint(-5,5),spaceship.pos[Y]+randint(-5,5))))
                    allsprites.add(Explode((spaceship.pos[X]+randint(-5,5),spaceship.pos[Y]+randint(-5,5))))
                    explode_sound.play()
                    # hide spaceship
                    spaceship.disabled = True
                    spaceship.pos = spaceship.rect.center = [2000,2000]
                    frames_after_end = 40
        
                # Print data to screen
                effects.update()
                allsprites.update()
                screen.blit(background, (0, 0))
                starfield.display(spaceship.direction)
                effects.draw(screen)
                allsprites.draw(screen)
                spaceships.draw(screen)
                pygame.display.flip()
                
                # Handle Events
                for event in pygame.event.get():
                    if event.type == QUIT:
                        return
                    elif event.type == KEYDOWN and event.key == K_ESCAPE:
                        return
                
            #### End of level loop
            pygame.event.set_grab(0)

            # level ended because player ran out of time:
            if status_time_over :
                play_music('frowny.ogg')
                current_lives -= 1
                if not hud_data.show_message(screen,background,'msg_time_over.png') :
                    game_over = True

            # level ended becasue player got hit by enemy:
            if status_got_hit :
                # frowny music already started when explosion was drawn
                current_lives -= 1

            # level ended because player collected all the needed cargo:
            if status_all_collected :
		play_music('happy.ogg')
                pygame.time.delay(4100)
                seconds_left = int((level_time_limit - time_passed)/1000)
                current_score += seconds_left*TIME_TO_SCORE_FACTOR
                # present points sum up: 
                for i in range(seconds_left) :
                    time_label.set_num(seconds_left-i)
                    score_label.set_num(i*TIME_TO_SCORE_FACTOR)
                    screen.blit(background, (0, 0))
                    time_label.group.draw(screen)
                    score_label.group.draw(screen)
                    pygame.time.delay(50)
                    pygame.display.flip()
                    blip_sound.play()
                if not hud_data.show_message(screen,background,'msg_good_job.png') :
                    game_over = True
                current_level +=1
                if current_level > NUM_OF_LEVELS-1 :
                    hud_data.show_message(screen,background,'msg_game_finished.png')
                    game_over = True 

            # if player used all his life then it's "game over"
            if current_lives < 0 :
                game_over = True
            
            # destroy old mines and object that will be recreated anyway
            for mine in mines.sprites() :
                mine.kill()
            cargo.kill()
    
        #### End of game loop
        if not hud_data.show_message(screen,background,'msg_new_game.png') :
            exit_game = True
    return            
            
#this calls the 'main' function when this script is executed
if __name__ == '__main__': main()
