#! /usr/bin/env python
#
###
# Application: SpaceWarpy
# File:        spacewarpy.py
# Description: Main code (spacewarpy)
# Copyright (c) 2002-2003   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.
#
"""
spacewarpy
"""
#imports
import pygame
from pygame.locals import *

from constants import SpaceWarpyConstants
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 sun import Sun
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 spacewarpy code"""
    #spacewarpy constants class
    consts = SpaceWarpyConstants()
    #initialize everything
    gs = GameState(consts)
    pygame.init()
    #setup display
    pygame.display.set_caption('SpaceWarpy %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')
    thrust2 = PycadiaSound('thrust2.wav')
    fire1 = PycadiaSound('fire1.wav')
    fire2 = PycadiaSound('fire2.wav')
    explode1 = PycadiaSound('explode1.wav')
    explode2 = PycadiaSound('explode2.wav')
    idle1 = PycadiaSound('idle.wav')
    idle2 = PycadiaSound('idle.wav')
    big_explode1 = PycadiaSound('big_explode1.wav')
    big_explode2 = PycadiaSound('big_explode2.wav')

    #sprite groups
    p1_hz = pygame.sprite.Group()
    p2_hz = pygame.sprite.Group()
    shots1 = pygame.sprite.Group()
    shots2 = pygame.sprite.Group()
    debris = pygame.sprite.Group()
    explosions = pygame.sprite.Group()
    explo_nokill = pygame.sprite.Group()
    asteroids = pygame.sprite.Group()
    sun = pygame.sprite.Group()
    all = pygame.sprite.RenderUpdates()
    
    #containers, etc
    Actor.gs = gs
    Player.containers = all
    Flame.containers = all
    Debris.containers = all
    Explode.containers = all
    Shot.containers = all
    Sun.containers = sun, all
    Asteroid.containers = all

    #initialize game actors
    flame1 = Flame(consts,
                   (20,20),
                   gd_objects.flamePlayer1Verts,
                   gd_objects.flamePlayer1Lines,
                   gd_objects.flamePlayer1Scale)
    flame2 = Flame(consts,
                   (20,20),
                   gd_objects.flamePlayer2Verts,
                   gd_objects.flamePlayer2Lines,
                   gd_objects.flamePlayer2Scale)
    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)    
    player2 = Player(consts,
                     (30,30), 
                     consts.PLAYER2_START, 
                     consts.PLAYER2_SPIN,
                     gd_objects.shipPlayer2Verts, 
                     gd_objects.shipPlayer2FrontLines, 
                     gd_objects.shipPlayer2RightLines,
                     gd_objects.shipPlayer2LeftLines, 
                     gd_objects.shipPlayer2Scale,
                     flame2,
                     8,
                     p2_hz)
    sun1 = Sun(consts, (20,20), gd_objects.sunALines, 75, 6)
    sun2 = Sun(consts, (20,20), gd_objects.sunBLines, 180, -6)
    
    #group player related classes into lists for input routines
    #order is: inpPlayer, inpShots, inpFlame, inpThrust, inpFire, inpIdle
    lInputP1 = [player1, shots1, flame1, thrust1, fire1, idle1]
    lInputP2 = [player2, shots2, flame2, thrust2, fire2, idle2]    
    
    #add one asteroid to start with, and set timer to create another one
    asteroids.add(Asteroid(consts, gs.rnd, 1, (consts.SCREENRECT.width/2), (consts.SCREENRECT.height-6)))
    asteroid_timer = gs.rnd.randrange(consts.ASTEROID_APPEAR-(3*consts.FRAMES_PER_SEC), 
                                      consts.ASTEROID_APPEAR+(3*consts.FRAMES_PER_SEC), 
                                      10)

    #init vector text class
    messages = pygame.sprite.Group()
    Char.containers = messages, all
    
    #display player lives & game name
    Message(consts, 'SpaceWarpy', 1.5, 0, 9, 1)
    msgLives1 = Message(consts, player1.lives, 1.5, 30, 9)
    msgLives2 = Message(consts, player2.lives, 1.5, (consts.SCREENRECT.width-40), 9)
    
    #init joysticks
    player1.joy = DummyJoy()
    player2.joy = DummyJoy()
    pygame.joystick.init()
    num_joy = pygame.joystick.get_count()
    p1_joyid = gs.config.getint('player1', 'joy_id')
    p2_joyid = gs.config.getint('player2', '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()
    if num_joy >= p2_joyid and p2_joyid >= 0: 
        player2.joy = pygame.joystick.Joystick(p2_joyid)
        player2.joy.init()
        #print "player 2: ", player2.joy.get_name()

    #main loop
    while 1:
        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 = player1.hyper = 0

        if player2.alive:
            player2.left = keys[pkeys.p2k_left] or eval(pkeys.p2j_left)
            player2.right = keys[pkeys.p2k_right] or eval(pkeys.p2j_right)
            player2.thrust = keys[pkeys.p2k_thrust] or eval(pkeys.p2j_thrust)
            player2.fire = keys[pkeys.p2k_fire] or eval(pkeys.p2j_fire)
            player2.hyper = keys[pkeys.p2k_hyper] or eval(pkeys.p2j_hyper)
        else:
            player2.left = player2.right = player2.thrust = player2.fire = player2.hyper = 0

        #act on player input
        for inpPlayer, inpShots, inpFlame, inpThrust, inpFire, inpIdle in lInputP1, lInputP2:
            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
        
        #create a new asteroid?
        asteroid_timer -= 1
        if asteroid_timer <= 0:
            asteroids.add(Asteroid(consts, gs.rnd, 1, (consts.SCREENRECT.width/2), (consts.SCREENRECT.height-6)))
            asteroid_timer = gs.rnd.randrange(consts.ASTEROID_APPEAR-(3*consts.FRAMES_PER_SEC), 
                                              consts.ASTEROID_APPEAR+(3*consts.FRAMES_PER_SEC), 
                                              10)

        #collision detection...
        #bullets hitting sun
        hit_ss1 = pygame.sprite.spritecollide(sun1, shots1, 1)
        hit_ss2 = pygame.sprite.spritecollide(sun1, shots2, 1)
        #bullets hitting asteroids
        hit_as = pygame.sprite.groupcollide(asteroids, shots1, 0, 1)
        hit_as.update(pygame.sprite.groupcollide(asteroids, shots2, 0, 1))
        #asteroids hitting sun
        hit_sa = pygame.sprite.spritecollide(sun1, asteroids, 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
                    PycadiaSound('explode1.wav').play(volume=0.8)
                    explosions.add(Explode(consts, 
                                           a, 
                                           gd_objects.asteroidLines, 
                                           (0.5*consts.FRAMES_PER_SEC), 
                                           (2.5*consts.FRAMES_PER_SEC), debris))
                    a.kill()

        #bullets & asteroid hitting player1
        if player1.alive:
            hp1 = pygame.sprite.groupcollide(p1_hz, shots2, 0, 1)
            #or if not, player1 hitting asteroids
            if hp1 == {}: 
                hp1 = pygame.sprite.groupcollide(p1_hz, asteroids, 0, 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
    
            #player 1 hitting sun
            collision = pygame.sprite.spritecollide(sun1, p1_hz, 0)
            if collision != []:
                player1.alive = 0

        #bullets & asteroid hitting player2
        if player2.alive:
            #bullets hitting player2
            hp2 = pygame.sprite.groupcollide(p2_hz, shots1, 0, 1)
            #or if not, player2 hitting asteroids
            if hp2 == {}: 
                hp2 = pygame.sprite.groupcollide(p2_hz, asteroids, 0, 0)
            for hz in hp2:
                if hz.name == 'front':
                    #front of ship hit
                    player2.alive = 0
                else:
                    #left or right side hit
                    hz.hit = 1
                    player2.damaged = 1
                    p2_hz.remove(hz)
                    #explode
                    explode2.play(volume=0.5)
                    if hz.name == 'left':
                        #left side of ship
                        explo_nokill.add(Explode(consts, 
                                               player2,
                                               gd_objects.shipPlayer2LeftLines,
                                               (1.5*consts.FRAMES_PER_SEC), 
                                               (1.5*consts.FRAMES_PER_SEC), 
                                               debris))
                    else:
                        #right side of ship
                        explo_nokill.add(Explode(consts, 
                                               player2,
                                               gd_objects.shipPlayer2RightLines,
                                               (1.5*consts.FRAMES_PER_SEC), 
                                               (1.5*consts.FRAMES_PER_SEC), 
                                               debris))
            #update lines
            if hp2 != {}:
                player2.lines = []
                for hz in p2_hz.sprites():
                    player2.lines += hz.lines
    
            #player 2 hitting sun
            collision = pygame.sprite.spritecollide(sun1, p2_hz, 0)
            if collision != []:
                player2.alive = 0
    
        #players colliding with each other
        if player1.alive and player2.alive:
            collision = pygame.sprite.groupcollide(p1_hz, p2_hz, 0, 0) != {}
            if collision:
                player1.alive = 0
                player2.alive = 0
        #players colliding with an exploding object
        if player1.alive: 
            player1.alive = pygame.sprite.groupcollide(explosions, p1_hz, 0, 0) == {}
        if player2.alive: 
            player2.alive = pygame.sprite.groupcollide(explosions, p2_hz, 0, 0) == {}

        #is a player 1 dead?
        if not(player1.alive) and not(player1.exploding):
            #p1 dead
            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))
        #is a player 2 dead?
        if not(player2.alive) and not(player2.exploding):
            #p2 dead
            thrust2.stop()
            idle2.stop()
            big_explode2.play()
            player2.visible = 0
            player2.flame.visible = 0
            player2.exploding = 1
            player2.lives -= 1
            all.remove([player2, flame2])
            explosions.add(Explode(consts, 
                                   player2, 
                                   player2.lines,
                                   (4*consts.FRAMES_PER_SEC), 
                                   (2*consts.FRAMES_PER_SEC), 
                                   debris))
        #is anybody dead?
        if not(player1.alive and player2.alive) and len(explosions) == 0 and len(debris)==0:
            #finish the game if a player has no lives left
            if player1.lives == 0 or player2.lives == 0:
                break
            #reset ships, etc
            for s in shots1.sprites(): all.remove(s)
            for s in shots2.sprites(): all.remove(s)
            shots1.empty()
            shots2.empty()
            msgLives1.update_message(player1.lives)
            msgLives2.update_message(player2.lives)
            if not player1.alive: 
                all.add([player1, flame1])
            if not player2.alive: 
                all.add([player2, flame2])
            player1.reset(p1_hz)
            player2.reset(p2_hz)
            player1.alive = player1.lives > 0
            player2.alive = player2.lives > 0

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

    #game over
    #check result
    if player1.lives == player2.lives:
        result = 'Draw'
    elif player1.lives > player2.lives:
        result = '%s Wins' % (gs.config.get('player1', 'name'))
    else:
        result = '%s Wins' % (gs.config.get('player2', 'name'))

    #show game over text and final result
    game_over = Message(consts, 'Game Over', 4, 0, 150, 1)
    winner = Message(consts, result, 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__':
    #go!
    main()
    
