#! /usr/bin/env python
#
###
# Application: Vektoroids
# File:        vektoroids.py
# Description: Main code (vektoroids)
# Copyright (c) 2002-2003 Paul Buckle <ashberbin @ hotmail.com>
#                         Andy Balcombe <http://www.anti-particle.com>
###
#
# This program 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.
#
# This program 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 Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
"""
vektoroids
"""
#imports
import pygame
from pygame.locals import *

from constants import VektoroidsConstants
from game_state import GameState, Point, DummyJoy
from sound import PycadiaSound
from vector_text import Char, Message
from actor import Actor
from player import Player, Flame
from explosion import Debris, Explode
from shot import Shot
from asteroid import Asteroid
from saucer import Saucer
import gd_objects
from pyg_keys import GameKeys
from config import _version

if not pygame.image.get_extended():
    raise SystemExit, "Requires the extended image loading from SDL_image"
if not pygame.font: print 'Warning, fonts disabled'
if not pygame.mixer: print 'Warning, sound disabled'

#
def main():
    """main vektoroids code"""
    #spacewarpy constants class
    consts = VektoroidsConstants()
    #initialize everything
    gs = GameState(consts)
    pygame.init()
    #setup display
    pygame.display.set_caption('Vektoroids %s' % (_version))
    pygame.mouse.set_visible(0)
    pygame.key.set_repeat(30,30)
    clock = pygame.time.Clock()
    #get player keys
    pkeys = GameKeys()

    #create the backgound
    gs.background = pygame.Surface(gs.screen.get_size())
    gs.background = gs.background.convert()
    gs.background.fill((0, 0, 0))
    #do stars
    gs.background.lock()
    for i in range(50):
        x = gs.rnd.randrange(1, consts.SCREENRECT.width, 1)
        y = gs.rnd.randrange(20, consts.SCREENRECT.height, 1)
        col = gs.rnd.randrange(100,255,1)
        gs.background.set_at((x,y), (col,col,col))
    #draw line at top of screen
    r=pygame.draw.line(gs.background, (255,255,255), (0,20), (consts.SCREENRECT.width,20), 1)
    gs.background.unlock()

    if consts.DEBUG:
        global glob_background, glob_screen
        glob_background = gs.background
        glob_screen = gs.screen

    #display the background
    gs.screen.blit(gs.background, (0,0))
    pygame.display.flip()
    
    #sounds
    thrust1 = PycadiaSound('thrust1.wav')
    fire1 = PycadiaSound('fire1.wav')
    explode1 = PycadiaSound('explode1.wav')
    idle1 = PycadiaSound('idle.wav')
    big_explode1 = PycadiaSound('big_explode1.wav')
    saucer_sound = PycadiaSound('ufo1.wav')
    saucer_fire = PycadiaSound('ufo_shot.wav')

    #sprite groups
    p1_hz = pygame.sprite.Group()
    shots1 = pygame.sprite.Group()
    debris = pygame.sprite.Group()
    explosions = pygame.sprite.Group()
    explo_nokill = pygame.sprite.Group()
    asteroids = pygame.sprite.Group()
    saucer = pygame.sprite.Group()
    saucer_shots = pygame.sprite.Group()
    all = pygame.sprite.RenderUpdates()
    
    #
    Actor.gs = gs
    Player.containers = all
    Flame.containers = all
    Debris.containers = all
    Explode.containers = all
    Shot.containers = all
    Asteroid.containers = all
    Saucer.containers = all

    #initialize game actors
    flame1 = Flame(consts,
                   (20,20),
                   gd_objects.flamePlayer1Verts,
                   gd_objects.flamePlayer1Lines,
                   gd_objects.flamePlayer1Scale)
    player1 = Player(consts,
                     (30,30), 
                     consts.PLAYER1_START, 
                     consts.PLAYER1_SPIN,
                     gd_objects.shipPlayer1Verts, 
                     gd_objects.shipPlayer1FrontLines, 
                     gd_objects.shipPlayer1RightLines,
                     gd_objects.shipPlayer1LeftLines, 
                     gd_objects.shipPlayer1Scale,
                     flame1,
                     5,
                     p1_hz)
                     
    #group player related classes into lists for input routines
    #order is: inpPlayer, inpShots, inpFlame, inpThrust, inpFire, inpIdle
    lInputP1 = [player1, shots1, flame1, thrust1, fire1, idle1]

    #asteroid counter
    num_asteroids = 0
    #set timer to create initial wave of asteroids after a short delay
    asteroid_timer = 0.25 * consts.FRAMES_PER_SEC
    #set saucer stuff
    saucer_timer = 15 * consts.FRAMES_PER_SEC
    saucer_count = 0
    #init player score
    score = 0
    
    #init vector text class
    messages = pygame.sprite.Group()
    Char.containers = messages, all

    #display status line (scores, etc)
    Message(consts, 'Vektoroids', 1.5, 0, 9, 1)
    msgLivesTxt = Message(consts, 'Lives', 1, 30, 9)
    msgLives = Message(consts, player1.lives, 1.5, 65, 9)
    msgScoreTxt = Message(consts, 'Score', 1, (consts.SCREENRECT.width-100), 9)
    msgScore = Message(consts, score, 1.5, (consts.SCREENRECT.width-40), 9)
    
    #init joysticks
    player1.joy = DummyJoy()
    pygame.joystick.init()
    num_joy = pygame.joystick.get_count()
    p1_joyid = gs.config.getint('player1', 'joy_id')
    if num_joy >= p1_joyid and p1_joyid >= 0: 
        player1.joy = pygame.joystick.Joystick(p1_joyid)
        player1.joy.init()
        #print "player 1: ", player1.joy.get_name()

    #main loop
    while player1.lives > 0:
        gs.dT = clock.tick(consts.FRAMES_PER_SEC)
        
        # clear/erase the last drawn sprites
        all.clear(gs.screen, gs.background)
        
        #update all the sprites
        all.update()

        #handle keys
        pygame.event.pump()
        keys=pygame.key.get_pressed()
        if keys[K_ESCAPE] or pygame.event.peek(QUIT):
            break

        #players doing something
        if player1.alive:
            player1.left = keys[pkeys.p1k_left] or eval(pkeys.p1j_left)
            player1.right = keys[pkeys.p1k_right] or eval(pkeys.p1j_right)
            player1.thrust = keys[pkeys.p1k_thrust] or eval(pkeys.p1j_thrust)
            player1.fire = keys[pkeys.p1k_fire] or eval(pkeys.p1j_fire)
            player1.hyper = keys[pkeys.p1k_hyper] or eval(pkeys.p1j_hyper)
        else:
            player1.left = player1.right = player1.thrust = player1.fire = 0

        #act on player input
        for inpPlayer, inpShots, inpFlame, inpThrust, inpFire, inpIdle in [lInputP1]:
            if inpPlayer.left: 
                #spin left
                inpPlayer.spin_deg = inpPlayer.spin_deg - consts.SHIP_ROTATE_SPEED
            if inpPlayer.right: 
                #spin right
                inpPlayer.spin_deg = inpPlayer.spin_deg + consts.SHIP_ROTATE_SPEED
            if inpPlayer.left or inpPlayer.right: 
                #spinning either way
                inpIdle.play(-1)
            elif not (inpPlayer.left and inpPlayer.right):
                #not spinning 
                inpIdle.stop()
            if inpPlayer.thrust: 
                #thrust
                inpPlayer.calc_speed(gs.dT)
                inpFlame.visible = 1
                inpThrust.play(-1)
            if not(inpPlayer.thrust) and inpFlame.visible: 
                #no thrust
                inpFlame.visible = 0
                inpThrust.stop()           
            if inpPlayer.fire and not inpPlayer.reloading and len(inpShots) < consts.MAX_SHOTS: 
                #fire
                inpShots.add(Shot(consts, inpPlayer, inpPlayer.spin_deg, consts.SHOT_LIFE))
                inpFire.play()
            if inpPlayer.hyper and inpPlayer.hyper_timer == 0: 
                #hyper-space
                inpPlayer.hyper_timer = 10 * consts.FRAMES_PER_SEC
                #set speed to 0, and randomize screen-pos
                inpPlayer.speed.x = 0
                inpPlayer.speed.y = 0
                inpPlayer.pos.x = gs.rnd.randint(50, consts.SCREENRECT.width - 50)
                inpPlayer.pos.y = gs.rnd.randint(50, consts.SCREENRECT.height - 50)
            #do hyperspace countdown
            if inpPlayer.hyper_timer > 0:
                inpPlayer.hyper_timer -= 1 
            #don't fire again until player has released fire key
            inpFire.playing = inpPlayer.reloading = inpPlayer.fire
            
        #decrement asteroid timer if we're between waves
        if asteroid_timer > 0:
            asteroid_timer -= 1
        #any asteroids left?
        if num_asteroids <= 0:
            #no, create another wave after a small delay
            if asteroid_timer < 0:
                asteroid_timer = 1 * consts.FRAMES_PER_SEC
            elif asteroid_timer == 0:
                #create a new wave of asteroids
                for i in range(consts.ASTEROID_WAVE):
                    num_asteroids += 1
                    asteroids.add(Asteroid(consts, gs.rnd, 3, 0, 0))
                #reset saucer stuff
                saucer_timer = 15 * consts.FRAMES_PER_SEC
                saucer_count = 0

        #add a ufo
        if saucer_timer > 0:
            saucer_timer -= 1
            if saucer_timer <= 0 and len(saucer) == 0:
                saucer_count += 1
                if saucer_count >= 2:
                    saucer_size = 1
                else:
                    saucer_size = 2
                saucer.add(Saucer(consts, gs.rnd, saucer_size, 0, gs.rnd.randint(50, consts.SCREENRECT.height - 50)))
                saucer_sound.play(-1)
                    
        #collision detection...
        #bullets hitting asteroids
        hit_as = pygame.sprite.groupcollide(asteroids, shots1, 0, 1)
        #bullets hitting saucer
        hit_ufo = pygame.sprite.groupcollide(saucer, shots1, 0, 1)
        
        #explode asteroid if its been shot enough times
        if len(hit_as) > 0: 
            for a in hit_as:
                a.hits += 1
                if a.hits >= consts.ASTEROID_HITS:
                    #explode the asteroid
                    num_asteroids -= 1
                    PycadiaSound('explode_short.wav').play(volume=0.8)
                    explosions.add(Explode(consts, a, gd_objects.asteroidLines, 0, (1.0*consts.FRAMES_PER_SEC), debris))
                    a.kill()
                    if a.size == 3:
                        #size 3 asteroid hit, create 2 size 2 asteroids
                        score += 25
                        num_asteroids += 2
                        asteroids.add(Asteroid(consts, gs.rnd, 2, a.rect.centerx, a.rect.centery))
                        asteroids.add(Asteroid(consts, gs.rnd, 2, a.rect.centerx, a.rect.centery))
                    elif a.size == 2:
                        #size 2 asteroid hit, create 2 size 1 asteroids
                        score += 50
                        num_asteroids += 2
                        asteroids.add(Asteroid(consts, gs.rnd, 1, a.rect.centerx, a.rect.centery))
                        asteroids.add(Asteroid(consts, gs.rnd, 1, a.rect.centerx, a.rect.centery))
                    elif a.size == 1:
                        score += 100
                    msgScore.update_message(score)

        #explode saucer if its been shot enough times
        if len(hit_ufo) > 0:
            for ufo in hit_ufo:
                ufo.hits += 1
                if ufo.hits >= consts.ASTEROID_HITS:
                    ufo.alive = 0
                    
        #asteroid, saucer or saucer bullets hitting player
        if player1.alive:
            hp1 = pygame.sprite.groupcollide(p1_hz, asteroids, 0, 0)
            #or saucer shots
            if hp1 == {}:
                hp1 = pygame.sprite.groupcollide(p1_hz, saucer_shots, 0, 1)
                #or if not, player hitting saucer
                if hp1 == {}: 
                    hp1 = pygame.sprite.groupcollide(p1_hz, saucer, 0, 0)
                    if hp1 != {}:
                        #destroy the saucer
                        for l in hp1.values():
                            for ufo in l:
                                ufo.alive = 0
            for hz in hp1:
                if hz.name == 'front':
                    #front of ship hit
                    player1.alive = 0
                else:
                    #left or right side hit
                    hz.hit = 1
                    player1.damaged = 1
                    p1_hz.remove(hz)
                    #explode
                    explode1.play(volume=0.5)
                    if hz.name == 'left': 
                        #left side of ship
                        explo_nokill.add(Explode(consts, player1, gd_objects.shipPlayer1LeftLines, (1.5*consts.FRAMES_PER_SEC), (1.5*consts.FRAMES_PER_SEC), debris))
                    else:
                        #right side of ship
                        explo_nokill.add(Explode(consts, player1, gd_objects.shipPlayer1RightLines, (1.5*consts.FRAMES_PER_SEC), (1.5*consts.FRAMES_PER_SEC), debris))
            #update lines
            if hp1 != {}:
                player1.lines = []
                for hz in p1_hz.sprites():
                    player1.lines += hz.lines
    
        #players colliding with an exploding object
        if player1.alive: 
            player1.alive = pygame.sprite.groupcollide(explosions, p1_hz, 0, 0) == {}

        #is a player 1 dead?
        if not(player1.alive) and not(player1.exploding):
            thrust1.stop()
            idle1.stop()
            big_explode1.play()
            player1.visible = 0
            player1.flame.visible = 0
            player1.exploding = 1
            player1.lives -= 1
            all.remove([player1, flame1])
            explosions.add(Explode(consts, player1, player1.lines, (4*consts.FRAMES_PER_SEC), (2*consts.FRAMES_PER_SEC), debris))

        #any saucers to explode?
        if len(saucer) > 0:
            for ufo in saucer.sprites():
                if ufo.alive:
                    #fire a shot?
                    if len(saucer_shots) == 0:
                        #aim...
                        aim_deg = ufo.calculate_angle(player1)
                        #...and launch the missile
                        saucer_shots.add(Shot(consts, ufo, aim_deg, consts.SHOT_LIFE * 2))
                        saucer_fire.play()
                else:
                    #explode the saucer
                    saucer_sound.stop()
                    PycadiaSound('explode1.wav').play(volume=0.8)
                    explosions.add(Explode(consts, ufo, gd_objects.saucerLines, (0.25*consts.FRAMES_PER_SEC), (1.0*consts.FRAMES_PER_SEC), debris))
                    ufo.kill()
                    saucer_timer = 15 * consts.FRAMES_PER_SEC
                    if ufo.size == 2:
                        score += 200
                    else:
                        score += 1000
                    msgScore.update_message(score)
                
        #is anybody dead?
        if not(player1.alive) and len(explosions) == 0 and len(debris)==0:
            #finish the game if a player has no lives left
            if player1.lives == 0:
                break
            #reset ships, etc
            for s in shots1.sprites(): 
                all.remove(s)
            shots1.empty()
            msgLives.update_message(player1.lives)
            msgScore.update_message(score)
            if not player1.alive: 
                all.add([player1, flame1])
            player1.reset(p1_hz)
            player1.alive = player1.lives > 0

        #redraw the whole scene
        dirty = all.draw(gs.screen)
        pygame.display.update(dirty)

    #game over, set hiscore
    #hs = hiscores.HiScore( 10 )
    #hs.load()
    #then, to add a score, do:
    #hs.add_hiscore( "~Player1", score )
    #to save hi_scores (maybe at end of program):
    #hs.save()
    
    #show game over text and final result
    game_over = Message(consts, 'Game Over', 4, 0, 150, 1)
    sMsg = '%s scored %s' % (gs.config.get('player1', 'name'), score) 
    winner = Message(consts, sMsg, 3, 0, (consts.SCREENRECT.height/2)+50, 1)

    #display messages
    dirty = all.draw(gs.screen)
    pygame.display.update(dirty)

    #pause, then exit
    pygame.time.wait(1500)
    pygame.quit()
    

#start the ball rolling...
if __name__ == '__main__':
    main()
    
