#Importacion de librerias de python
from time import time, localtime
start_time = time()
from math import pi, atan2, sqrt
from sys import argv as sys_argv
from traceback import print_exc
from multiprocessing import Process, Event, Lock #Dolores de cabeza => MODE ON
from random import randint
from random import seed as random_seed

#Libreria incluida no presente en python
from vectypes import vec2, length, normalize

#Libreria externa
try:
    import pygame
    from pygame.colordict import THECOLORS 
    from pygame import locals as pygame_locals
    import pygcurse
except ImportError:
    pygame = None

#Constantes
WINDOW_NAME = "CharSpaceFighter"
WINDOW_ICON = "icon.png"
LOG_DEBUG = not True
LOG_OOB = False
STOP_ON_CRASH = True

REFRESH_PER_SECOND = 60  #Refrescos de imagen por segundo
UPDATES_PER_SECOND = 30  #Cuantas update por segundo
MAP_GEN_PER_SECOND = 5   #Cuantas generate_terrain() por segundo
STANDARD_SIZE = (60, 40)       #Tamano "standard" de las ventana
PAUSE_MENU_SIZE = (38, 11)
TERRAIN_RECT  = (0, 1, STANDARD_SIZE[0], STANDARD_SIZE[1])  #Rect del terreno
TERRAIN_HEIGHT_MIN = STANDARD_SIZE[1] / 5 #Altura minima del terreno (a partir de donde se genera la hierba, solo tierra)
TERRAIN_HEIGHT_MAX = STANDARD_SIZE[1] / 3        #Altura maxima del terreno (no se genera la hierba a partir de este limite)
TERRAIN_HEIGHT_DIFFERENCE = 2  #Diferencia maxima en cada linea
TERRAIN_HEIGHT_MUTATION = 3    #Distancia maxima hasta que ocurra una mutacion en la altura
TERRAIN_GENERATION_STEP = 1    #Cantidad de columnas por generate_terrain()

GRAVITY_VECTOR = (0, 25)
ANGLE_STEP = pi/8
AIR_FRICTION = 0.95
PLAYER_FRICTION = 0.25
PHYSICS_DIVISOR = 20
ARROWS_MULTIPLIER = 8
EXPLOSION_DURATION = UPDATES_PER_SECOND / 2
PROJECTILE_EXPLOSION_DURATION = UPDATES_PER_SECOND / 8
GAME_OVER_WAIT = UPDATES_PER_SECOND * 1

ENEMIES_MAX = 6
ENEMIES_SPEED = (-4, 0)
ENEMIES_OCTOPUS_SPEED = (-2, 0)
ENEMIES_SPAWN_HEIGHT = 15
ENEMIES_SPAWN_RATE = UPDATES_PER_SECOND / 1 #Por cada cuanto update_all() spawnear un enemigo
ENEMIES_OCTOPUS_SPAWNSCORE = 100 #A partir de que puntuacion spawnear Octopus

KEY_REPEAT_INTERVAL = 0.05

KEY_MENU = "\n"     #Enter
KEY_PLASMA = "q" 
KEY_MISSILE = "e" 
KEY_UP   = "w"
KEY_DOWN = "s"
KEY_LEFT = "a"
KEY_RIGHT = "d"

EVENT_QUIT = "E_QUIT"
EVENT_MENU_PRESS = "E_MENU_P"
EVENT_MENU_RELEASE = "E_MENU_R"

#Aun no inicializadas
COLOR_NAME = None
CHAR_NAME = None

#Funciones
def initialize_color_pair(name, pair):
    if not name in COLOR_NAME:
        fg = THECOLORS[pair[0]]
        bg = THECOLORS[pair[1]]
        COLOR_NAME[name] = (fg, bg)

def initialize_main_chars_colors():
    global COLOR_NAME
    global CHAR_NAME

    #Resetear dicts
    COLOR_NAME = {}   #Colores
    CHAR_NAME = {}   #Caracteres 

    COLORS = { 
        "AIR":("white", "black"),
        "BLOCK":("white", "white"),
        "GROUND":("brown", "black"),
        "GRASS":("green", "black"),
        "UNKNOW":("black", "white"),
        "ERROR":("white", "red"),
        "BORDER":("black", "white"),
        "STATUSBAR":("green", "gray30"),
        "MENU_BACK":("black","gray30"),
        "MENU_BUTTON":("white","gray10"),
        "MENU_BUTTON_HIGH":("white","gray60"),
        "MENU_BUTTON_PRESSED":("black","white"),
        "MENU_SCORE":("white","gray10"),
        "MENU_COPYMSG":("white","gray30"),
    }
    CHAR_NAME = {
        "GROUND":"#",
        "GRASS":"I",
        "AIR":" ",
        "UNKNOW":"?",
        "ERROR":"E",
        "BORDER":"B",
    }

    for name in COLORS:
        pair = COLORS[name]
        initialize_color_pair(name, pair)

def fill_chars(text, length = 2, char = "0", reverse = False):
    text = str(text)
    diff = length - len(text)
    if diff > 0:
        if reverse:
            text = text + (char * diff) 
        else:
            text = (char * diff) + text
    return text

def total_time():
    diff = time()-start_time
    num, flt = str(diff).split(".")
    return fill_chars(num, 4) + "." + flt[0:3]

def create_grid(size, fill):
    _, _, sx, sy = size
    grid = []
    for x in xrange(sx):
        grid.append([])
        for _ in xrange(sy):
            grid[x].append(fill)
    return grid

def int_round(num):
    return int(round(num))

def round_vector(vec):
    return (int_round(vec[0]), int_round(vec[1]))

def controlled_exception(function, *args, **kwargs):
    global crash_flag
    no_error = False
    try:
        function(*args, **kwargs)
        no_error = True
    except Exception:
        now = localtime()
        currenttime = "%s-%s-%s %s:%s:%s" % (now.tm_year, fill_chars(now.tm_mon), fill_chars(now.tm_mday), fill_chars(now.tm_hour), fill_chars(now.tm_min), fill_chars(now.tm_sec))
        log.write("\n" + currenttime + "\n")
        print_exc(file = log)
    except KeyboardInterrupt:
        log.debug("KeyboardInterrupt raised!")
    except SystemExit:
        log.debug("SystemExit raised!")
    if not no_error and STOP_ON_CRASH:
        crash_flag.set()

def iterate_circle(pos, radius, threshold = 11, displacement = (0, -1)): #Basado en http://canhazcode.blogspot.com.es/2011/10/ascii-circles.html
    points = []
    for y in xrange(int(-radius*1.3), int(radius*1.3), 2):
        line = []
        for x in xrange(int(-radius*1.3), int(radius*1.3)):
            distance = abs(x*x+y*y) / (radius/2)
            if distance < threshold:
                line.append(x)
        points.append(line)

    sy = int_round(len(points)/2)
    for i, y in enumerate(xrange(-sy, sy)):
        for x in points[i]:
            yield ((pos[0] + displacement[0]) + x, (pos[1] + displacement[1]) + y)

def create_alive(alive):
    alive.name = "%s_%s_%s" % (alive.name, alive.__class__, hex(id(alive)))
    log.debug("Created %s: %s" % (alive.type, alive.name))
    alive_list.append(alive)
    
def iterate_line(pos1, pos2):
    x1, y1 = int(pos1[0]), int(pos1[1])
    x2, y2 = int(pos2[0]), int(pos2[1])
    points = []
    issteep = abs(y2-y1) > abs(x2-x1)
    if issteep:
        x1, y1 = y1, x1
        x2, y2 = y2, x2
    rev = False
    if x1 > x2:
        x1, x2 = x2, x1
        y1, y2 = y2, y1
        rev = True
    deltax = x2 - x1
    deltay = abs(y2-y1)
    error = int(deltax / 2)
    y = y1
    ystep = None
    if y1 < y2:
        ystep = 1
    else:
        ystep = -1
    for x in range(x1, x2 + 1):
        if issteep:
            points.append((y, x))
        else:
            points.append((x, y))
        error -= deltay
        if error < 0:
            y += ystep
            error += deltax
    # Reverse the list if the coordinates were reversed
    if rev:
        points.reverse()
    return points

def inside_rect(pos, rect):
    px, py, sx, sy = rect
    x, y = pos 
    return x >= px and y >= py and x < sx and y < sy

def swap_char(string, index, newchar):
    if index >= len(string): 
        raise IndexError("string index out of range")
    string = list(string)
    string[index] = newchar
    return "".join(string)

def get_distance_vec2(vec1, vec2):
    sx, sy = vec1
    ex, ey = vec2
    dx = ex - sx
    dz = ey - sy
    return sqrt(dx*dx + dz*dz)

def get_velocity_vec2(vec1, vec2, speed = 1):
    sx, sy = vec1
    ex, ey = vec2
    dx = ex - sx
    dy = ey - sy
    dist = sqrt(dx*dx + dy*dy)
    vx = dx/dist * speed
    vy = dy/dist * speed
    return Vector(vx, vy), dist

def get_enemy_name(entity):
    cls_name = entity.__name__
    if not "Enemy" in cls_name:
        return None
    else:
        return cls_name.split("Enemy")[0]
        
class FileLog(object):
    def __init__(self):
        fileopt = "w"
        self.__log = open(sys_argv[0] + ".log", fileopt)
        self.__log_lock = Lock()
        
    def close(self):
        #Limpiar el lock
        self.__log_lock.acquire(False)
        self.__log_lock.release()
        self.__log.close()
        
    def write(self, *msgs):
        self.__log_lock.acquire()
        message = ""
        for msg in msgs:
            message = message + str(msg) + " "
        message = message[:-1] #Quitar el ultimo espacio
        if message[-1] == "\n": message = message[:-1] #Quitar el ultimo newline si tiene
        print message
        self.__log.write(message + "\n")
        self.__log.flush()
        self.__log_lock.release()
        
    def format(self, tag):
        return tuple(["[%s][%s]" % (total_time(), tag)])

    def debug(self, *msgs):
        if LOG_DEBUG:
            msgs = self.format("DEBUG") + msgs
            self.write(*msgs)

    def info(self, *msgs):
        msgs = self.format("INFO") + msgs
        self.write(*msgs)

    def warning(self, *msgs):
        msgs = self.format("WARNING") + msgs
        self.write(*msgs)

    def error(self, *msgs):
        msgs = self.format("ERROR") + msgs
        self.write(*msgs)

    def oob(self, *msgs):
        if LOG_OOB:
            msgs = self.format("Out Of Bounds") + msgs
            self.error(*msgs)

class GenericEventListener(object):
    def init(self):
        self.__buffer = [] #buffer FIFO
        self.__interval = time()

    def __iter__(self):
        return self

    def append(self, event):
        self.__buffer.append(event)
        
    def check_interval(self):
        if time() - self.__interval >= KEY_REPEAT_INTERVAL:
            self.__interval = time()
            return True
        else:
            return False
        
    def next(self):
        if 0 < len(self.__buffer):
            return self.__buffer.pop(0) #Sacar el primer elemento del buffer
        else:
            raise StopIteration() #No queda nada, detener el iterador

class PygameEventListener(GenericEventListener):
    def __init__(self):
        self.init()
        self.key_cache = set() #Los botones que estan pulsados
        self.refresh_flag = True
        
    def next(self):
        if self.check_interval():
            for key in self.key_cache:
                try:
                    if   key == pygame_locals.K_UP:
                        key = "w"
                    elif key == pygame_locals.K_DOWN:
                        key = "s"
                    elif key == pygame_locals.K_RIGHT:
                        key = "d"
                    elif key == pygame_locals.K_LEFT:
                        key = "a"
                    elif key == pygame_locals.K_KP8:
                        key = "w"
                    elif key == pygame_locals.K_KP5:
                        key = "s"
                    elif key == pygame_locals.K_KP6:
                        key = "d"
                    elif key == pygame_locals.K_KP4:
                        key = "a"
                    elif key == pygame_locals.K_KP7:
                        key = "q"
                    elif key == pygame_locals.K_KP9:
                        key = "e"
                    else:
                        key = chr(key)
                    self.append(key)
                except ValueError:
                    pass
        
        if self.refresh_flag:
            self.refresh_flag = False #Solo ejecutar 1 vez por iterador
            for pygame_event in pygame.event.get():
                event = None
                if pygame_event.type == pygame_locals.QUIT:
                    event = EVENT_QUIT
                elif pygame_event.type == pygame_locals.KEYDOWN:
                    if pygame_event.key == pygame_locals.K_ESCAPE:
                        event = EVENT_MENU_PRESS
                    elif pygame_event.key == pygame_locals.K_RETURN:
                        event = EVENT_MENU_PRESS
                    else:
                        self.key_cache.add(pygame_event.key)
                elif pygame_event.type == pygame_locals.KEYUP:
                    if pygame_event.key == pygame_locals.K_ESCAPE:
                        event = EVENT_MENU_RELEASE
                    elif pygame_event.key == pygame_locals.K_RETURN:
                        event = EVENT_MENU_RELEASE
                    else:
                        try: self.key_cache.remove(pygame_event.key)
                        except KeyError: log.debug("Key %i was not in cache when released" % pygame_event.key)
                            
                if event != None:
                    self.append(event)
                    
        try:
            return super(PygameEventListener, self).next()
        except StopIteration: #No hay mas en el buffer, resetear el flag y pasar StopIteration
            self.refresh_flag = True
            raise StopIteration()
                    
    def stop(self):
        self.__stop_flag.set()
        self.terminate()

class FrameRegulator(Process):
    def __init__(self, fps):
        super(FrameRegulator, self).__init__()
        self.type = "Process"
        self._frame_flag = Event()
        self._stop_flag = Event()
        self._fps = fps
        self._frame_flag.set()
        create_alive(self)
        super(FrameRegulator, self).start()

    def run(self): #Llamado automaticamente desde Process al ejecutar start()
        controlled_exception(self.main)

    def main(self):
        self._last_time = time()
        while not self._stop_flag.is_set():
            #Basado en http://stackoverflow.com/questions/2660919/python-animation-timing
            new_time = time()
            # see how many milliseconds we have to sleep for
            # then divide by 1000.0 since time.sleep() uses seconds
            sleep_time = ((1000.0 / self._fps) - (new_time - self._last_time)) / 1000.0
            if sleep_time > 0:
                self._stop_flag.wait(sleep_time) #Espera un tiempo, en caso de que ocurra un stop, el "sleep" se cancelara
                self._frame_flag.set()
            self._last_time = new_time
        if self.name[-1] == 1: log.debug(sleep_time)

    def check_frame(self):
        state = self._frame_flag.is_set()
        if state: self._frame_flag.clear()
        return state

    def wait_frame(self):
        self._frame_flag.wait()
        self._frame_flag.clear()

    def stop(self):
        self._frame_flag.clear()
        self._stop_flag.set()
        self.terminate()

class Vector(vec2): #Funciona como un vec2, salvo que se puede pasar un tuple/list como arg
    def __init__(self, *args):
        if len(args) == 1: 
            arg = args[0]
            if "list" in str(type(arg)) or "tuple" in str(type(arg)):
                args = arg
        super(Vector, self).__init__(*args)

class Entity(object):
    def __init__(self, function_container, pos):
        self.pos = Vector(pos)
        self.oldpos = Vector(pos)
        self.speed = Vector(0, 0)
        self.gravity = Vector(GRAVITY_VECTOR)
        self.health = 100
        self.total_health = None
        self.char = CHAR_NAME["UNKNOW"]
        self.color = COLOR_NAME["UNKNOW"]
        self.collision_whitelist = set()
        self.collision_whitelist.add(self)
        self.projectile_watchlist = set()
        self.functions = function_container
        self.friction = AIR_FRICTION
        self.ignore_damage = False
        self.active = True
        self.displace = False
        self.other_ignore_collision = False
        self.score = 0
        self.border_collided = False
        self.supress_explosion = False
        self.parent = None
        self.exploded_midair = True
        self.explosion_duration = EXPLOSION_DURATION

    def collision_status(self, pos):
        up     = Vector(pos.x, pos.y - 1)
        bottom = Vector(pos.x, pos.y + 1)
        right  = Vector(pos.x + 1, pos.y)
        left   = Vector(pos.x - 1, pos.y)

        c_collision = self.functions.get_collision(pos)
        u_collision = self.functions.get_collision(up)
        b_collision = self.functions.get_collision(bottom)
        r_collision = self.functions.get_collision(right)
        l_collision = self.functions.get_collision(left)

        return c_collision, u_collision, b_collision, l_collision, r_collision

    def border_status(self, pos):
        c_collision, u_collision, b_collision, l_collision, r_collision = self.collision_status(pos)

        c_border = c_collision[0] == CHAR_NAME["BORDER"] and c_collision[1]
        u_border = u_collision[0] == CHAR_NAME["BORDER"] and u_collision[1]
        b_border = b_collision[0] == CHAR_NAME["BORDER"] and b_collision[1]
        r_border = r_collision[0] == CHAR_NAME["BORDER"] and r_collision[1]
        l_border = l_collision[0] == CHAR_NAME["BORDER"] and l_collision[1]

        return c_border, u_border, b_border, l_border, r_border, c_collision, u_collision, b_collision, l_collision, r_collision

    def ground_status(self, pos):
        checking = 0
        while True:
            checking = checking + 1
            c_collision = self.functions.get_collision(round_vector([pos.x, pos.y + checking]))
            if c_collision[0] != CHAR_NAME["AIR"] and c_collision[1]:
                break
        return checking - 1

    def physics(self):
        self.pos   = self.pos   + (self.speed / PHYSICS_DIVISOR)
        self.speed = self.speed * self.friction
        self.speed = self.speed + (self.gravity / PHYSICS_DIVISOR)

    def collision(self, pos):
        c_border, _, _, _, _, c_collision, _, b_collision, _, _  = self.border_status(pos)

        if c_collision[0] and c_collision[1]: #Estoy atrapado en terreno?
            self.pos.y = self.pos.y - 1
            self.speed = Vector(0, 0)
        elif b_collision[0] and b_collision[1]:
            self.speed = Vector(0, 0)

        if c_border:
            return True

    def update(self):
        if self.total_health == None:
            self.total_health = self.health
        damage = self.movement_collision()
        if damage:
            if "bool" in str(type(damage)):
                damage = self.health
            self.collision_damage(damage)

        if self.health <= 0:
            self.health = 0
            return True
        elif self.health > self.total_health:
            self.health = self.total_health
            
        return False

    def collision_damage(self, dmg):
        if not self.ignore_damage:
            self.health = self.health - dmg
        
    def movement_processing(self):
        self.oldpos = Vector(self.pos)
        self.physics()
        return iterate_line(round_vector(self.oldpos), round_vector(self.pos))

    def movement_collision(self):
        damage = False
        for pos in iterate_line(round_vector(self.oldpos), round_vector(self.pos)):
            result = self.collision(Vector(pos))
            if result:
                damage = result
                self.pos = Vector(pos)
                break
        return damage
    
    def share_collision_whitelist(self, entity):
        for whitelisted in self.collision_whitelist:
            entity.collision_whitelist.add(whitelisted)

    def draw(self):
        if LOG_DEBUG:
            for pos in iterate_line(round_vector(self.oldpos), round_vector(self.pos)):
                x, y = round_vector(pos)
                self.functions.set_video(x, y, CHAR_NAME["AIR"], COLOR_NAME["UNKNOW"])
            x, y = round_vector(self.oldpos)
            self.functions.set_video(x, y, "O", COLOR_NAME["UNKNOW"])
        args = round_vector(self.pos) + (self.char, self.color)
        self.functions.set_video(*args)

    def displace_update(self, displace):
        if self.displace:
            self.pos.x = self.pos.x - displace
    
    def delete(self, explosion):
        c_border, _, _, l_border, r_border, _, _, _, _, _  = self.border_status(self.pos)
        self.border_collided = (c_border or l_border or r_border) and self != self.functions.get_player_entity()
        if self.parent:
            if self in self.parent.projectile_watchlist:
                self.parent.projectile_watchlist.remove(self) #Nos quitamos del watchlist del nuestro lanzador
            if self in self.parent.collision_whitelist:
                self.parent.collision_whitelist.remove(self) #Nos quitamos del collision whitelist
            self.border_collided = self.parent.border_collided
            self.supress_explosion = self.parent.supress_explosion
        if not self.supress_explosion and explosion and not self.border_collided:
            self.functions.create_entity(Explosion, self.pos,  self.explosion_duration, self.exploded_midair)

class Explosion(Entity):
    def __init__(self, function_container, pos, health, midair = True):
        super(Explosion, self).__init__(function_container, pos)
        self.gravity = Vector(0, 0)
        initialize_color_pair("EXPLOSION", ("red", "black"))
        self.color = COLOR_NAME["EXPLOSION"]
        self.health = float(health)
        self.initial_health = float(self.health)
        self.midair = midair
        self.landed = False
        self.ignore_damage = True
        self.other_ignore_collision = True
        self.displace = True
        self.supress_explosion = True

    def update(self):
        self.health = self.health - 1
        _, _, b_collision, _, _ = self.collision_status(self.pos)
        
        if b_collision[0] and b_collision[1]:
            x, y = round_vector(self.pos)
            self.functions.set_terrain(x, y, CHAR_NAME["AIR"], COLOR_NAME["AIR"])

        if not self.midair and not self.landed:
            if not (b_collision[0] and b_collision[1]):
                ground = self.ground_status(self.pos)
                self.pos.y = self.pos.y + ground

            elif (b_collision[0] and b_collision[1]):
                self.landed = True

        if self.health <= 0:
            return True

    def draw(self):
        if (self.initial_health / 4) * 3 < self.health:
            char = "."
        elif (self.initial_health / 4) * 2 < self.health:
            char = "+"
        elif (self.initial_health / 4) * 1 < self.health:
            char = "o"
        else:
            char = "*"
        self.char = char
        super(Explosion, self).draw()
        
    def delete(self, explosion):  # @UnusedVariable
        super(Explosion, self).delete(False) #La explosion no debe causar otra explosion o entraremos en un ciclo infinito

class Projectile(Entity):
    def __init__(self, function_container, pos, speed, parent):
        super(Projectile, self).__init__(function_container, pos)
        self.speed = Vector(speed)
        self.damage = 1
        self.explosion = (10, 1)
        self.displacement = (0, -1)
        self.min_speed = 2
        self.exploded_midair = True
        self.parent = parent
        self.explosion_duration = PROJECTILE_EXPLOSION_DURATION

    def update(self):
        if length(self.speed) < self.min_speed:
            return True
        return super(Projectile, self).update()

    def collision(self, pos):
        c_collision = self.functions.get_collision(pos)
        if c_collision[0] != None:
            if c_collision[1]: #Es el terreno?
                if c_collision[0] != CHAR_NAME["BORDER"]:
                    self.exploded_midair = False
                    for cpos in iterate_circle(pos, self.explosion[0], self.explosion[1], self.displacement):
                        cpos = Vector(cpos)
                        x, y = round_vector(cpos)
                        self.functions.set_terrain(x, y, CHAR_NAME["AIR"], COLOR_NAME["AIR"])
                        
                        b_collision = self.functions.get_collision(Vector(x, y + 1))
                        if b_collision[0] != None and b_collision[1] == True:
                            self.functions.create_entity(Explosion, cpos, PROJECTILE_EXPLOSION_DURATION, self.exploded_midair)
                return True
            else:
                for colliding in c_collision[0]:
                    if colliding != self.parent and colliding not in self.collision_whitelist and colliding not in self.parent.projectile_watchlist:
                        colliding.collision_damage(self.damage) #Entidad
                        if self.parent == self.functions.get_player_entity():
                            self.functions.add_score(colliding)
                        return True

class PlasmaProjectile(Projectile):
    def __init__(self, function_container, pos, speed, parent):
        super(PlasmaProjectile, self).__init__(function_container, pos, speed, parent)
        self.char = "@"
        initialize_color_pair(self.char, ("orange", "black"))
        self.color = COLOR_NAME[self.char]
        self.gravity = Vector(0, 0)
        self.damage = 50
        
class MissileProjectile(Projectile):
    def __init__(self, function_container, pos, speed, parent):
        super(MissileProjectile, self).__init__(function_container, pos, speed, parent)
        initialize_color_pair("MISSILE", ("white", "black"))
        self.color = COLOR_NAME["MISSILE"]
        self.gravity = Vector(GRAVITY_VECTOR)
        self.damage = 100
        self.explosion = (8, 6)

    def draw(self):
        x, y = normalize(self.speed)
        direction = atan2(y, x)
        char = "+" #Cuando no hay velocidad
        if   -ANGLE_STEP * 8 < direction and direction < -ANGLE_STEP * 7:
            char = "-"
        if   -ANGLE_STEP * 7 < direction and direction < -ANGLE_STEP * 5:
            char = "\\"
        elif -ANGLE_STEP * 5 < direction and direction < -ANGLE_STEP * 3:
            char = "|"
        elif -ANGLE_STEP * 3 < direction and direction < -ANGLE_STEP:
            char = "/"
        elif -ANGLE_STEP     < direction and direction <  ANGLE_STEP:
            char = "-"
        elif  ANGLE_STEP     < direction and direction <  ANGLE_STEP * 3:
            char = "\\"
        elif  ANGLE_STEP * 3 < direction and direction <  ANGLE_STEP * 5:
            char = "|"
        elif  ANGLE_STEP * 5 < direction and direction <  ANGLE_STEP * 7:
            char = "/"
        elif  ANGLE_STEP * 7 < direction and direction <  ANGLE_STEP * 8:
            char = "-"
        self.char = char
        super(MissileProjectile, self).draw()
 
class EnergyWeapon(object):
    def __init__(self, projectile, parent, fire_speed, energy_consumption, energy_storage, energy_reload):
        self.projectile = projectile
        self.parent = parent #El portador del arma
        self.fire_speed = Vector(fire_speed)
        self.energy_consumption = energy_consumption #Energia consumida por cada bala
        self.energy_storage = energy_storage #Capacidad maxima de energia del arma
        self.energy_reload = energy_reload #Energia cargada por cada segundo
        self.energy_status = energy_storage #Estado del energia (carga actual)
        
    def can_fire(self):
        if self.parent.fire_cooldown_status == 0:
            if self.energy_status >= self.energy_consumption: #Hay suficiente energia para disparar?
                return True
        return False
            
    def used_projectile(self):
        self.energy_status = self.energy_status - self.energy_consumption
        self.parent.fired_weapon()

    def fire_weapon(self): #Dispara el proyectil, devuelve la entidad o None
        if self.can_fire():
            instance = self.parent.functions.create_entity(self.projectile, self.parent.pos, self.fire_speed + self.parent.speed, self.parent)
            self.parent.projectile_watchlist.add(instance)
            self.used_projectile()
            return instance
        else:
            return None
            
    def update(self):
        if self.energy_status > self.energy_storage:
            self.energy_status = self.energy_storage
        elif self.energy_status < self.energy_storage:
            self.energy_status = self.energy_status + self.energy_reload
        
class Ship(Entity):
    def __init__(self, function_container, pos):
        super(Ship, self).__init__(function_container, pos)
        self.gravity = Vector(0, 0)
        self.health = 500
        self.weapons = {}
        self.fire_cooldown = UPDATES_PER_SECOND / 2 #Tiempo necesario entre distintos disparos
        self.fire_cooldown_status = 0

    def collision(self, pos):
        c_collision, _, _, _, _ = self.collision_status(pos)
        if c_collision[0] != None:
            if c_collision[1]: #Es terreno?
                return True
            else:
                for colliding in c_collision[0]:
                    if colliding not in self.collision_whitelist and colliding not in self.projectile_watchlist:
                        if not isinstance(colliding, Projectile): #Los proyectiles ya tienen su sistema de dano
                            return True
                    
    def add_weapon(self, wid, weapon_cls, *args):
        self.weapons[wid] = weapon_cls(*args)
    
    def del_weapon(self, wid):
        self.weapons[wid] = None
        
    def use_weapon(self, wid):
        return self.weapons[wid].fire_weapon()
        
    def fired_weapon(self):
        self.fire_cooldown_status = self.fire_cooldown
            
    def update(self):
        if self.fire_cooldown_status > 0:
            self.fire_cooldown_status = self.fire_cooldown_status - 1
        for weapon in self.weapons.values():
            weapon.update()
        return super(Ship, self).update()

class PlayerShip(Ship):
    def __init__(self, function_container, pos):
        super(PlayerShip, self).__init__(function_container, pos)
        self.char = ">"
        initialize_color_pair(self.char, ("cyan", "black"))
        self.color = COLOR_NAME[self.char]
        self.arrow_vector = Vector(0, 0)
        self.friction = PLAYER_FRICTION
        self.add_weapon("missile", EnergyWeapon, MissileProjectile, self, (50, 0), 600, 1000, 4)
        self.add_weapon("plasma", EnergyWeapon, PlasmaProjectile, self, (50, 0), 200, 1000, 4)
        self.total_health = 999

    def physics(self):
        self.speed = self.speed + (self.arrow_vector * ARROWS_MULTIPLIER)
        self.arrow_vector = Vector(0, 0)
        return super(PlayerShip, self).physics()

    def movement_collision(self):
        c_border, _, _, _, _, _, _, _, _, _ = self.border_status(self.pos)
        if c_border:
            safe_try = 50
            while safe_try > 0:
                c_border, u_border, b_border, l_border, r_border, _, _, _, _, _ = self.border_status(self.pos)
    
                if c_border:
                    self.speed = Vector(0, 0)
                    safe_try = safe_try - 1
                    if safe_try <= 0:
                        self.pos = self.oldpos #No se pudo "resolver", volver a la posicion antigua
                else: #No hay mas borde, salir del loop
                    break

                if u_border or b_border:
                    self.speed.y = 0
                    
                if   c_border and not u_border:
                    self.pos.y = self.pos.y - 1
                elif c_border and not b_border:
                    self.pos.y = self.pos.y + 1
                elif c_border and not l_border:
                    self.pos.x = self.pos.x - 1
                elif c_border and not r_border:
                    self.pos.x = self.pos.x + 1
        else:
            return super(PlayerShip, self).movement_collision()
                
    def keypress(self, key):
        key = key.lower()
        if   key == KEY_UP:
            self.arrow_vector.y = -1
        elif key == KEY_DOWN:
            self.arrow_vector.y = 1
        elif key == KEY_LEFT:
            self.arrow_vector.x = -1
        elif key == KEY_RIGHT:
            self.arrow_vector.x = 1
        elif key == KEY_PLASMA:
            self.use_weapon("plasma")
        elif key == KEY_MISSILE:
            self.use_weapon("missile")
        else:
            return False
        return True

class GenericEnemy(Ship):
    def __init__(self, function_container, pos, speed, parent = None):
        super(GenericEnemy, self).__init__(function_container, pos)
        self.check_distance = 18
        self.speed = Vector(speed)
        self.friction = 1
        self.parent = parent
        self.slaves = []
            
    def create_slave(self, slave_cls, pos, char):
        slave = self.functions.create_entity(slave_cls, pos, self.speed, self)
        slave.char = char
        self.slaves.append(slave)
        self.collision_whitelist.add(slave)
        self.share_collision_whitelist_slaves()
        
    def share_collision_whitelist_slaves(self):
        for other_slave in self.slaves:
            self.share_collision_whitelist(other_slave)

    def check_front(self):
        entity_player = self.functions.get_player_entity()
        start_pos = self.pos
        end_pos = (self.pos.x - self.check_distance, self.pos.y)
        player = False
        obstacle = False
        for pos in iterate_line(start_pos, end_pos):
            c_collision, _, _, _, _ = self.collision_status(Vector(pos))
            if c_collision[0] != None:
                if c_collision[1]: #Es terreno?
                    if c_collision[0] != CHAR_NAME["BORDER"]:
                        obstacle = True
                else:
                    for colliding in c_collision[0]:
                        if colliding not in self.collision_whitelist and colliding not in self.projectile_watchlist:
                            if colliding == entity_player:
                                player = True
                            else:
                                obstacle = True
        return player, obstacle
    
    def update(self):
        if self.parent:
            if not self.parent.active:
                return True
    
        return super(GenericEnemy, self).update() 
    
class ShooterEnemy(GenericEnemy):
    def __init__(self, function_container, pos, speed, parent = None):
        super(ShooterEnemy, self).__init__(function_container, pos, speed, parent)
        self.char = "<"
        initialize_color_pair(self.char, ("red", "black"))
        self.color = COLOR_NAME[self.char]
        self.health = 50
        self.score = 10
        self.add_weapon("plasma", EnergyWeapon, PlasmaProjectile, self, (-30, 0), 400, 1000, 2)
        self.move_status = 0
        self.move_time = 0
        self.move_limit = randint(1, 4)
        self.move_step = randint(-1, 1)
        self.move_original = self.pos.y
        
    def update(self):
        player, obstacle = self.check_front()
        if player and not obstacle:
            self.use_weapon("plasma")
            
        if self.move_time <= 0:
            self.move_time = UPDATES_PER_SECOND / 2
            if abs(self.move_status) >= self.move_limit:
                self.move_step = -self.move_step
            self.move_status = self.move_status + self.move_step
            
            next_pos = Vector(self.pos.x, self.move_original + self.move_status)
            c_collision, _, _, _, _ = self.collision_status(next_pos)
            if c_collision[0] == None:
                self.pos = next_pos
            else:
                self.move_step = -self.move_step
                self.move_status = self.move_status + self.move_step
        else:
            self.move_time = self.move_time - 1

        return super(ShooterEnemy, self).update() 
        
class SwarmEnemy(GenericEnemy):
    def __init__(self, function_container, pos, speed, parent = None):
        super(SwarmEnemy, self).__init__(function_container, pos, speed, parent)
        self.char = "("
        initialize_color_pair(self.char, ("red", "black"))
        self.color = COLOR_NAME[self.char]
        self.health = 100
        self.score = 30
        self.add_weapon("plasma", EnergyWeapon, PlasmaProjectile, self, (-30, 0), 400, 1000, 3)
        if not self.parent:
            self.create_slave(SwarmEnemy, Vector(self.pos.x, self.pos.y - 1), "/")
            self.create_slave(SwarmEnemy, Vector(self.pos.x, self.pos.y + 1), "\\")

    def update(self):
        player, obstacle = self.check_front()
        if player and not obstacle:
            self.use_weapon("plasma")
            
        return super(SwarmEnemy, self).update() 
    
class OctopusEnemy(GenericEnemy):
    def __init__(self, function_container, pos, speed, parent = None):
        super(OctopusEnemy, self).__init__(function_container, pos, speed, parent)
        self.speed = Vector(ENEMIES_OCTOPUS_SPEED)
        self.char = "O"
        initialize_color_pair(self.char, ("red", "black"))
        self.color = COLOR_NAME[self.char]
        if not self.parent:
            self.health = 150
            self.score = 30
            self.create_slave(OctopusEnemy, Vector(self.pos.x, self.pos.y - 1), "|")
            self.create_slave(OctopusEnemy, Vector(self.pos.x, self.pos.y + 1), "|")
            self.create_slave(OctopusEnemy, Vector(self.pos.x - 1, self.pos.y), "~")
            self.create_slave(OctopusEnemy, Vector(self.pos.x + 1, self.pos.y), "~")
            self.create_slave(OctopusEnemy, Vector(self.pos.x - 1, self.pos.y - 1), "\\")
            self.create_slave(OctopusEnemy, Vector(self.pos.x - 1, self.pos.y + 1), "/")
            self.create_slave(OctopusEnemy, Vector(self.pos.x + 1, self.pos.y - 1), "/")
            self.create_slave(OctopusEnemy, Vector(self.pos.x + 1, self.pos.y + 1), "\\")
            self.add_weapon("missile", EnergyWeapon, MissileProjectile, self, (0, 0), 600, 1000, 4)
        else:
            self.health = 100
            self.score = 20

    def update(self):
        if not self.parent:
            entity_player = self.functions.get_player_entity()
            if entity_player:
                speed, dist = get_velocity_vec2(self.pos, entity_player.pos, 60)
                if dist <= self.check_distance:
                    projectile = self.use_weapon("missile")
                    if projectile:
                        self.share_collision_whitelist(projectile)
                        projectile.speed = Vector(speed)
            
        return super(OctopusEnemy, self).update() 
    
class BaseGUI(object):
    def __init__(self, rect, set_video):
        self.set_video = set_video
        self.rect = tuple(rect)
        self.cursor = 0
        
    def write_text(self, text, color, pos = None):
        if not pos:
            pos = self.rect
        for char in text:
            self.set_video(pos[0] + self.cursor, pos[1], char, color)
            self.cursor = self.cursor + 1
            
    def write_text_vert(self, text, color, pos = None):
        if not pos:
            pos = self.rect
        for i, char in enumerate(text):
            self.set_video(pos[0], pos[1] + i, char, color)
        
    def generate_progress(self, size, total, value, mark, fchar, uchar, mchar = None):
        percentage = (value * 100.0) / total
        step = 100.0 / size
        filled = int(round(percentage / step))
        unfilled = size - filled
        
        line = (filled * fchar) + (unfilled * uchar)
        if mark != None and mchar != None:
            mark_percentage = (mark * 100.0) / total
            mark_step = mark_percentage / size
            if mark_step < size:
                state = 0
                for i, char in enumerate(line):
                    state = state + 1
                    if state == int(round(mark_step)):
                        state = 0
                        if char == fchar:
                            line = swap_char(line, i, mchar)
            else:
                if line[len(line)-1] == fchar:
                    line = swap_char(line, len(line) - 1, mchar)
        return line
                        
    def fill(self, char, color):
        for x in range(self.rect[2]):
            for y in range(self.rect[3]):
                self.set_video(self.rect[0] + x, self.rect[1] + y, char, color)
                
    def clear(self):
        self.fill(" ", COLOR_NAME["AIR"])
        
    def draw(self):
        self.clear()
        
class ButtonGUI(BaseGUI):
    def __init__(self, rect, set_video, text, colors):
        super(ButtonGUI, self).__init__(rect, set_video)
        self.text = text
        self.state = 0
        self.colors = colors
        
    def draw(self):
        self.cursor = 0
        if self.state > len(self.colors) - 1:
            self.state = 0
        color = self.colors[self.state]
        self.write_text(self.text, color)
        
class MenuGUI(BaseGUI):    
    def __init__(self, rect, set_video, start_game, unpause_game, terminate_game, score, game_over, spawning_enemies, kill_table):
        super(MenuGUI, self).__init__(rect, set_video)
        self.start_game = start_game
        self.unpause_game = unpause_game
        self.terminate_game = terminate_game
        self.score = score
        self.game_over = game_over
        self.spawning_enemies = spawning_enemies
        self.kill_table = kill_table
        self.selection = 1
        self.buttons = []
        self.buttons_funcs = {}
        self.buttons_uninitiated = True
        self.button_size = (10, 1)
        self.pressing = None
        self.move_time = 0.3 #Solo mover cada X segs
        self.last_move = time()
        
    def create_buttons(self):
        rect = (self.rect[0] + 1, self.rect[1] + self.rect[3] - 2, self.button_size[0], self.button_size[1])
        text = "[Reiniciar]"
        function = self.start_game
        self.add_button(rect, text, function)
        
        rect = (rect[0] + self.button_size[0] + 3, rect[1], self.button_size[0], self.button_size[1])
        text = "[Continuar]"
        function = self.unpause_game
        self.add_button(rect, text, function)
        
        rect = (rect[0] + self.button_size[0] + 3, rect[1], self.button_size[0], self.button_size[1])
        text = "[Terminar]"
        function = self.terminate_game
        self.add_button(rect, text, function)
        
        self.buttons[self.selection].state = 1
        
    def add_button(self, rect, text, function, colors = None):
        if not colors:
            colors = (COLOR_NAME["MENU_BUTTON"], COLOR_NAME["MENU_BUTTON_HIGH"], COLOR_NAME["MENU_BUTTON_PRESSED"])
        button = ButtonGUI(rect, self.set_video, text, colors)
        self.buttons.append(button)
        self.buttons_funcs[button] = function
        
    def keypress(self, key):
        lkey = key.lower()
        last = len(self.buttons) - 1
        if   lkey == KEY_LEFT and self.pressing == None and self.last_move + self.move_time <= time():
            self.last_move = time()
            self.buttons[self.selection].state = 0
            self.selection = self.selection - 1
            if self.selection < 0:
                self.selection = last
            self.buttons[self.selection].state = 1
        elif lkey == KEY_RIGHT and self.pressing == None and self.last_move + self.move_time <= time():
            self.last_move = time()
            self.buttons[self.selection].state = 0
            self.selection = self.selection + 1
            if self.selection > last:
                self.selection  = 0
            self.buttons[self.selection].state = 1
        elif key == EVENT_MENU_PRESS and self.pressing == None:
            if len(self.buttons):
                self.pressing = self.buttons[self.selection]
                self.pressing.state = 2
        elif key == EVENT_MENU_RELEASE and self.pressing != None:
            function = self.buttons_funcs[self.pressing]
            function()
            self.pressing = None
        return True
    
    def draw(self):
        if self.buttons_uninitiated:
            self.buttons_uninitiated = False
            self.create_buttons()
            
        color_back = COLOR_NAME["MENU_BACK"]
        self.fill(" ", color_back)
        
        self.cursor = 0
        color_score = COLOR_NAME["MENU_SCORE"]
        score = fill_chars(self.score, 6, "0")
        basepos = (self.rect[0] + (self.rect[2] / 2) - 11, self.rect[1] + 1)
        self.write_text("Puntuacion - [%s]" % score, color_score, basepos)
        
        printed_list = set()
        index = 0
        for entity in self.spawning_enemies:
            enemy = get_enemy_name(entity)
            if enemy not in printed_list:
                index = index + 1
                printed_list.add(enemy)
                count = self.kill_table[enemy]
                line = "%s - [%s]" % (fill_chars(enemy, 10, " ", True), fill_chars(count, 6, "0"))
                pos = (basepos[0], basepos[1] + 1 + index)
                self.cursor = 0
                self.write_text(line, color_score, pos)

        self.cursor = 0
        pos = (self.rect[0] + (self.rect[2] / 2) - 18, self.rect[1] + self.rect[3] - 4)
        color = COLOR_NAME["MENU_COPYMSG"]
        self.write_text("2013 (c) - Cubex - All bugs reserved", color, pos)
        
        for button in self.buttons:
            button.draw() 
        
class BarGUI(BaseGUI):
    def __init__(self, rect, set_video, player):
        super(BarGUI, self).__init__(rect, set_video)
        self.player = player
        
    def draw(self):
        super(BarGUI, self).draw()
        color = COLOR_NAME["STATUSBAR"]
        self.cursor = 0
        
        self.write_text(">  ", color)
        
        player_health = 0
        if self.player.active: player_health = self.player.health
        self.write_text("Salud [%s]" % fill_chars(player_health, 3, " "), color)
        
        self.write_text(" - ", color)
        
        plasmaweapon = self.player.weapons["plasma"]
        line = self.generate_progress(10, plasmaweapon.energy_storage, plasmaweapon.energy_status, plasmaweapon.energy_consumption, "#", " ", "|")
        self.write_text("Plasma [%s]" % line, color)
        
        self.write_text(" - ", color)
        
        missileweapon = self.player.weapons["missile"]
        line = self.generate_progress(10, missileweapon.energy_storage, missileweapon.energy_status, missileweapon.energy_consumption, "#", " ", "|")
        self.write_text("Misil [%s]" % line, color)
        
        self.write_text("  <", color)

class PygcursesScreen(object):
    def __init__(self):
        #Tamano de la ventana
        self.video_size = STANDARD_SIZE    #Tamano inmutable de la memoria de video
        #Referencia de la ventana
        self.window = pygcurse.PygcurseWindow(STANDARD_SIZE[0], STANDARD_SIZE[1], font = pygame.font.Font("freesansbold.ttf", 14))
        self.window.autoupdate = False
        #Inicializa/resetea definiciones de color
        initialize_main_chars_colors()
        #Escucha de teclas
        self.eventlistener = PygameEventListener()

    def get_window_size(self):
        size = self.window.getmaxyx()  #La funcion getmaxyx() devuelve en formato (y,x)
        return (size[1], size[0])      #Damos la vuelta al tuple

    def get_size(self):
        wy, wx = self.window.getmaxyx()  #La funcion getmaxyx() devuelve en formato (y,x)
        sx, sy = self.video_size
        if sx > wx: sx = wx
        if sy > wy: sy = wy
        return sx, sy

    def set_video(self, x, y, char, color = "AIR"):
        sx, sy = self.video_size
        if x < 0 or y < 0 or x >= sx or y >= sy: 
            log.oob("Video Set: (%i, %i) [%s]" % (x, y, str(char)))
        else:
            value = self.window.putchar(char, x, y, color[0], color[1])
            if value != char:
                log.error("Pushing video to console at (%i, %i) with [%i] value, return doesn't match[%i]" % (x, y, char))
                
    def update(self):
        if self.resize_flag:
            self.resize_flag = False #Quitar el flag
            self.resize_hash = hash(self.get_size()) #Actualizar el hash
            self.resized()
        elif self.resize_hash != hash(self.get_size()):
            self.resize_flag = True #Activar el flag

    def resized(self):
        #Acciones necesarias despues de tener un resize
        pass
    
    def caption(self, text):
        pygame.display.set_caption(text)
        
    def icon(self, filename):
        surf = pygame.image.load(filename)
        pygame.display.set_icon(surf)

    def refresh(self):
        self.window.update()
    
class Map(object):
    def __init__(self, set_video):
        self.set_video = set_video
        #Terreno
        self.terrain_size = (0, 0, TERRAIN_RECT[2] - TERRAIN_RECT[0], TERRAIN_RECT[3] - TERRAIN_RECT[1])
        self.terrain_rect = TERRAIN_RECT
        self.terrain_char = create_grid(self.terrain_size, CHAR_NAME["AIR"])
        self.terrain_color = create_grid(self.terrain_size, COLOR_NAME["AIR"])
        self.terrain_height = 0
        self.terrain_mutation = 0
        #Mapa generado en total
        self.terrain_generated = 0 

    def get_terrain(self, x, y, color = False): 
        if not inside_rect((x, y), self.terrain_size): 
            log.oob("Terrain Get: (%i, %i)" % (x, y))
            return CHAR_NAME["BORDER"]
        elif color:
            return self.terrain_char[x][y], self.terrain_color[x][y]
        else:
            return self.terrain_char[x][y]

    def set_terrain(self, x, y, char, color = None):
        if not inside_rect((x, y), self.terrain_size): 
            log.oob("Terrain Set: (%i, %i) [%s]" % (x, y, char))
        else:
            self.terrain_char[x][y] = char
            if color:
                self.terrain_color[x][y] = color

    def get_next_height(self, initial = False):
        ground = self.terrain_size[3] - TERRAIN_HEIGHT_MIN
        if initial:
            self.terrain_generated  = 1
            self.terrain_height = randint(ground - TERRAIN_HEIGHT_MAX, ground)
        else:
            self.terrain_generated  = self.terrain_generated + 1
            if self.terrain_mutation > 0:
                self.terrain_mutation =- 1
            else:        
                self.terrain_mutation = randint(0, TERRAIN_HEIGHT_MUTATION)
                height = self.terrain_height + randint(-TERRAIN_HEIGHT_DIFFERENCE, TERRAIN_HEIGHT_DIFFERENCE)
                if ground - TERRAIN_HEIGHT_MAX > height:
                    height = ground - TERRAIN_HEIGHT_MAX
                elif height > ground:
                    height = ground
                self.terrain_height = height
    
    def generate_terrain(self, displace):
        px, _, sx, sy = self.terrain_size
        for nx in xrange(sx):
            #Copiamos desde ox a nx, si nx supera el tamano, generar nuevo
            ox = nx + displace
            out = px > ox or ox > (sx - 1)
            if out:
                self.get_next_height()
                self.terrain_char[nx] = []
                self.terrain_color[nx] = []
                for y in xrange(sy):
                    if y == self.terrain_height:
                        name = "GRASS"
                    elif y > self.terrain_height:
                        name = "GROUND"
                    elif y < self.terrain_height:
                        name = "AIR"
                    char = CHAR_NAME[name]
                    color = COLOR_NAME[name]
                    self.terrain_char[nx].append(char)
                    self.terrain_color[nx].append(color)
            else:
                #Mover la referencia de la antigua columna a la nueva
                self.terrain_char[nx] = self.terrain_char[ox] 
                self.terrain_color[nx] = self.terrain_color[ox]

    def push_terrain_to_video(self):
        _, _, sx, sy = self.terrain_size
        for x in xrange(sx):
            for y in xrange(sy):
                self.set_rect_video(x, y, self.terrain_char[x][y], self.terrain_color[x][y])
                
    def set_rect_video(self, x, y, char, color = "AIR"): #Una version de set_video localizada al rect
        px, py, _, _ = self.terrain_rect
        self.set_video(px + x, py + y, char, color)

class Game(object):
    def __init__(self, screen_handler):
        #Semilla de random
        self.random_seed_number = time()
        random_seed(self.random_seed_number)
        #Controlador de pantalla
        self.screen = screen_handler
        self.screen.caption(WINDOW_NAME)
        #self.screen.icon(WINDOW_ICON)
        #Mapa
        self.map = Map(self.screen.set_video)
        #Esta ejecutandose?
        self.running = True
        #Inicia los timers
        self.refresh_control = FrameRegulator(REFRESH_PER_SECOND)
        self.update_control  = FrameRegulator(UPDATES_PER_SECOND)
        self.map_gen_control = FrameRegulator(MAP_GEN_PER_SECOND)
        #Lista de entidades
        self.entities = set()
        self.entities_pos = {}
        self.entities_mask = set()
        self.entities_movement = {}
        self.enemy_entities = 0
        #Estados del jugador
        self.player = None
        self.game_over = False
        self.game_over_wait = GAME_OVER_WAIT
        #Contador de spawn para enemigos
        self.enemy_spawnrate_count = 0
        #Barra de informacion y menu (sin inicializar)
        self.statusbar = None
        self.pausemenu = None
        #Puntuacion
        self.score = 0
        #Los enemigos que se spawnearan
        self.spawning_enemies = ([ShooterEnemy] * 8) \
                              + ([SwarmEnemy] * 3) \
                              + ([OctopusEnemy] * 1)
        #Cantidad de veces que ha matado cierto enemigo
        self.kill_table = {}
        
    def start_game(self):
        log.debug("start_game() Called")
        #Reiniciamos el random seed
        self.random_seed_number = time()
        random_seed(self.random_seed_number)
        self.map.get_next_height(True) #Generar altura inicial aleatoria
        self.map.generate_terrain(self.map.terrain_size[2])
        #Reestablecemos el contador y el game over
        self.game_over = False
        self.game_over_wait = GAME_OVER_WAIT
        #Eliminamos las entidades antiguas sin que causen explosiones
        for entity in frozenset(self.entities):
            self.delete_entity(entity, False)
        #Creamos la nave protagonista
        self.player = self.create_entity(PlayerShip, (10, 10))
        #(re)inicializamos la barra de informacion
        self.statusbar = BarGUI((0, 0, STANDARD_SIZE[0], 1), self.screen.set_video, self.player)
        #Borramos el menu
        self.unpause_game()
        #Puntuacion
        self.score = 0
        #Cantidad de veces que ha matado cierto enemigo
        self.kill_table = {}
        for enemy in self.spawning_enemies:
            name = get_enemy_name(enemy)
            if not self.kill_table.has_key(name):
                self.kill_table[name] = 0

    def pause_game(self):
        if not self.pausemenu:
            size = PAUSE_MENU_SIZE
            rect = ((STANDARD_SIZE[0] / 2) - (size[0] / 2), (STANDARD_SIZE[1] / 2) - (size[1] / 2), size[0], size[1])
            self.pausemenu = MenuGUI(rect, self.screen.set_video, \
                                     self.start_game, self.unpause_game, self.terminate_game, \
                                     self.score, self.game_over, self.spawning_enemies, self.kill_table)
                
    def unpause_game(self):
        if not self.game_over:
            self.pausemenu = None
        else:
            self.start_game()
            
    def terminate_game(self):
        self.running = False

    def create_entity(self, entity, *args, **kwargs):
        class FunctionContainer(object):
            get_terrain = self.map.get_terrain
            set_terrain = self.map.set_terrain
            get_collision = self.get_collision
            set_video = self.map.set_rect_video
            create_entity = self.create_entity
            get_player_entity = self.get_player_entity
            add_score = self.add_score

        args = tuple([FunctionContainer]) + args
        instance = entity(*args, **kwargs)
        self.entities.add(instance)
        if isinstance(instance, GenericEnemy):
            self.enemy_entities = self.enemy_entities + 1
        return instance

    def delete_entity(self, entity, explosion = True):
        entity.active = False
        entity.health = 0
        entity.delete(explosion)
        self.entities.remove(entity)
        if isinstance(entity, GenericEnemy):
            self.enemy_entities = self.enemy_entities - 1
        if entity == self.player:
            self.player = None

    def get_collision(self, pos):
        info = str(type(pos))
        is_vector = "Vector" in info or "vec2" in info
        if is_vector:
            pos = round_vector(pos)

        terrain = self.map.get_terrain(*pos)

        if terrain != CHAR_NAME["AIR"]: #Hay tierra?
            return terrain, True
        else:
            if pos in self.entities_mask: #Hay alguna entidad(es) en la posicion?
                return self.entities_pos[pos], False
            else:
                return None, None
            
    def add_score(self, entity):
        if entity.health <= 0:
            name = get_enemy_name(entity.__class__)
            if name != None:
                self.score = self.score + entity.score
                self.player.health = self.player.health + entity.score
                if self.player.health > self.player.total_health:
                    self.player.health = self.player.total_health
                    
                if entity.parent == None:
                    if self.kill_table.has_key(name):
                        self.kill_table[name] = self.kill_table[name] + 1
                    else:
                        log.warning("Entity [%s] is not listed as enemy" % entity)

    def get_player_entity(self):
        return self.player

    def update_terrain(self):
        displace = TERRAIN_GENERATION_STEP
        self.map.generate_terrain(displace)
        for entity in self.entities:
            entity.displace_update(displace)

    def update_events(self):
        for event in self.screen.eventlistener:
            used = False #Si la tecla fue usada, en caso de que se use no se pasara a los siguientes
            if not used and event == EVENT_QUIT:
                self.terminate_game()
                used = True
            if not used and event == EVENT_MENU_RELEASE and self.pausemenu == None:
                self.pause_game()
                used = True
            if not used and self.pausemenu != None:
                used = self.pausemenu.keypress(event)
            if not used and self.player != None:
                used = self.player.keypress(event)

    def update_entities(self):
        self.entities_pos = {}
        self.entities_mask = set()
        for entity in self.entities:
            moved_positions = entity.movement_processing()
            if not entity.other_ignore_collision:
                for pos in moved_positions:
                    if pos not in self.entities_mask:
                        self.entities_pos[pos] = [entity]
                        self.entities_mask.add(pos)
                    else:
                        self.entities_pos[pos].append(entity)

        for entity in frozenset(self.entities):
            if not inside_rect(entity.pos, self.map.terrain_size) and entity != self.player:
                delete = True
            else:
                delete = entity.update()
            if delete:
                self.delete_entity(entity)
                
        if self.player == None and not self.game_over:
            self.game_over = True

        if self.enemy_spawnrate_count > ENEMIES_SPAWN_RATE:
            self.enemy_spawnrate_count = 0
            log.debug(self.enemy_entities)
            if self.enemy_entities < ENEMIES_MAX:
                pos = Vector(self.map.terrain_size[0] + self.map.terrain_size[2] - 4, randint(self.map.terrain_size[1] + 4, TERRAIN_HEIGHT_MAX))
                if not self.get_collision(pos)[0]:
                    index = randint(0, len(self.spawning_enemies)-1)
                    enemy_cls = self.spawning_enemies[index]
                    if not (enemy_cls == OctopusEnemy and self.score < ENEMIES_OCTOPUS_SPAWNSCORE):
                        self.create_entity(enemy_cls, pos, ENEMIES_SPEED)
        else:
            self.enemy_spawnrate_count = self.enemy_spawnrate_count + 1

    def draw_entities(self):
        for entity in self.entities:
            entity.draw()

    def draw_gui(self):
        if self.statusbar != None:
            self.statusbar.draw()
        if self.pausemenu != None:
            self.pausemenu.draw()

    def update_all(self):
        refresh_frame = self.refresh_control.check_frame()
        update_frame = self.update_control.check_frame()
        ignore_update = self.pausemenu and not self.game_over #Ignorar menu de pausa cuando es game over
            
        if update_frame: #Apartado del juego
            self.update_events()
            if not ignore_update:
                self.update_entities()
                if self.map_gen_control.check_frame(): #Solo actualizar dentro de un update_frame y cuando coincide el map_gen_control
                    self.update_terrain()
                    
            if self.game_over:
                if self.game_over_wait <= 0:
                    self.pause_game()
                else:
                    self.game_over_wait = self.game_over_wait - 1
        
        if refresh_frame: #Apartado del video
            self.map.push_terrain_to_video()
            self.draw_entities()
            self.draw_gui()        
            self.screen.refresh()
                    

#Variables globales
alive_list = []     #Process activos que necesiten ser detenidos
log = FileLog()
crash_flag = Event()

#Funcion y loop principal
def main(screen):
    log.debug("main() called")
    game = Game(screen)
    game.start_game()
    while game.running:
        for alive in alive_list:
            if not alive.is_alive():
                log.error("%s: %s stopped without removing himself from alive_list, something went wrong " % (alive.type, alive.name))
                crash_flag.set()
        if crash_flag.is_set():
            log.error("Crash flag was raised. Terminating")
            break
        game.update_all()

#Inicializacion de main
def main_initializer():
    log.info("Opening Log")
    if not pygame:
        log.error("Pygame is missing, please install Pygame library")
    else:
        log.info("Pygame is present!")
        status = pygame.init()
        log.debug("Pygame init: ", status)
        def pygcurses_main():
            screen = PygcursesScreen()
            main(screen)
        controlled_exception(pygcurses_main)
    for alive in alive_list:
        log.debug("Closing %s: %s" % (alive.type, alive.name))
        alive.stop()
    log.info("Closing Log")
    log.close()

#Solo se ejecuta si no somos importados
if __name__ == "__main__":
    main_initializer()
#EOF