hugoruscitti/pilas

View on GitHub
pilas/utils.py

Summary

Maintainability
D
2 days
Test Coverage
# -*- encoding: utf-8 -*-
# Pilas engine - A video game framework.
#
# Copyright 2010 - Hugo Ruscitti
# License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html)
#
# Website - http://www.pilas-engine.com.ar

from __future__ import print_function
import os
from . import interpolaciones
import sys
import subprocess
import math
import uuid
import pilas
import mimetypes

if sys.version_info.major == 3:
    def execfile(f):
        exec(compile(open(f).read(), f, 'exec'))

PATH = os.path.dirname(os.path.abspath(__file__))
INTERPRETE_PATH = os.path.dirname(sys.argv[0])


def es_interpolacion(an_object):
    """Indica si un objeto se comportará como una interpolación.

    :param an_object: El objeto a consultar.
    """
    return isinstance(an_object, interpolaciones.Interpolacion)


def obtener_ruta_al_recurso(ruta):
    """Busca la ruta a un archivo de recursos.

    Los archivos de recursos (como las imagenes) se buscan en varios
    directorios (ver docstring de image.load), así que esta
    función intentará dar con el archivo en cuestión.

    :param ruta: Ruta al archivo (recurso) a inspeccionar.
    """

    dirs = ['./', INTERPRETE_PATH, INTERPRETE_PATH + '/../data', '../data', PATH, PATH + '/../data']


    for x in dirs:
        full_path = os.path.join(x, ruta)

        if os.path.exists(full_path):
            return full_path

    # Si no ha encontrado el archivo lo reporta.
    raise IOError("El archivo '%s' no existe." % (ruta))


def esta_en_sesion_interactiva():
    """Indica si pilas se ha ejecutado desde una consola interactiva de python."""
    import sys
    try:
        cursor = sys.ps1
        return True
    except AttributeError:
        try:
            in_ipython = sys.ipcompleter
            return True
        except AttributeError:
            if sys.stdin.__class__.__module__.startswith("idle"):
                return True

    return False


def distancia(a, b):
    """Retorna la distancia entre dos numeros.

        >>> distancia(30, 20)
        10

    :param a: Un valor numérico.
    :param b: Un valor numérico.
    """
    return abs(b - a)


def distancia_entre_dos_puntos(coords1, coords2):
    """Retorna la distancia entre dos puntos en dos dimensiones.

    :param coords1: Tupla de coordenadas (x, y) del primer punto.
    :param coords2: Tupla de coordenadas (x, y) del segundo punto.
    """
    (x1, y1) = coords1
    (x2, y2) = coords2
    return math.sqrt(distancia(x1, x2) ** 2 + distancia(y1, y2) ** 2)


def distancia_entre_dos_actores(a, b):
    """Retorna la distancia en pixels entre dos actores.

    :param a: El primer actor.
    :param b: El segundo actor.
    """

    dis = distancia_entre_dos_puntos((a.x, a.y), (b.x, b.y)) - a.radio_de_colision - b.radio_de_colision

    if (dis < 0):
        return 0
    else:
        return dis



def actor_mas_cercano_al_actor(actor):
    """Retorna el actor de la escena que esté mas cercano a otro indicado por parámetro.

    :param actor: El actor tomado como referencia.
    """
    actor_mas_cercano = None
    distancia = 999999
    for actor_cercano in pilas.escena_actual().actores:
        if id(actor_cercano) != id(actor):
            if distancia_entre_dos_actores(actor, actor_cercano) < distancia:
                actor_mas_cercano = actor_cercano

    return actor_mas_cercano


def colisionan(a, b):
    """Retorna True si dos actores estan en contacto.

    :param a: Un actor.
    :param b: El segundo actor a verificar.
    """
    return distancia_entre_dos_actores(a, b) < a.radio_de_colision + b.radio_de_colision


def interpolable(f):
    """Decorador que se aplica a un metodo para que permita animaciones de interpolaciones.

    Ejemplo::

        @interpolable
        def set_x(self, valor_x):
            [...]

    :param f: Método sobre el que va a trabajar el interpolador.
    """

    def inner(*args, **kwargs):
        value = args[1]

        # Si le indican dos argumentos, el primer sera
        # el valor de la interpolacion y el segundo la
        # velocidad.
        if isinstance(value, tuple) and len(value) == 2:
            duracion = value[1]
            value = value[0]
        else:
            duracion = 1

        if isinstance(value, list):
            value = interpolar(value, duracion=duracion, tipo='lineal')
        elif isinstance(value, xrange):
            value = interpolar(list(value), duracion=duracion, tipo='lineal')

        if es_interpolacion(value):
            value.apply(args[0], function=f.__name__)
        else:
            f(args[0], value, **kwargs)

    return inner


def hacer_coordenada_mundo(x, y):
    """Convierte una coordenada de pantalla a una coordenada dentro del motor de física.

    :param x: Coordenada horizontal.
    :param y: Coordenada vertical.
    """
    dx, dy = pilas.mundo.motor.centro_fisico()
    return (x + dx, dy - y)


def hacer_coordenada_pantalla_absoluta(x, y):
    # TODO: Duplicado del codigo anterior?
    dx, dy = pilas.mundo.motor.centro_fisico()
    return (x + dx, dy - y)


def listar_actores_en_consola():
    """Imprime una lista de todos los actores en la escena sobre la consola."""
    todos = pilas.escena_actual().actores

    print("Hay %d actores en la escena:" % (len(todos)))
    print("")

    for s in todos:
        print("\t", s)

    print("")


def obtener_angulo_entre(punto_a, punto_b):
    """Retorna el ángulo entro dos puntos de la pantalla.

    :param punto_a: Una tupla con la coordenada del primer punto.
    :param punto_b: Una tupla con la coordenada del segundo punto.
    """
    (x, y) = punto_a
    (x1, y1) = punto_b
    return math.degrees(math.atan2(y1 - y, x1 - x))


def convertir_de_posicion_relativa_a_fisica(x, y):
    dx, dy = pilas.mundo.motor.centro_fisico()
    return (x + dx, dy - y)


def convertir_de_posicion_fisica_relativa(x, y):
    dx, dy = pilas.mundo.motor.centro_fisico()
    return (x - dx, dy - y)


def calcular_tiempo_en_recorrer(distancia_en_pixeles, velocidad):
    """Calcula el tiempo que se tardará en recorrer una distancia en
    pixeles con una velocidad constante.

    :param distancia_en_pixeles: La longitud a recorrer medida en pixels.
    :param velocidad: La velocida medida en pixels.
    """
    if (pilas.mundo.motor.canvas.fps.cuadros_por_segundo_numerico > 0):
        return (distancia_en_pixeles / (pilas.mundo.motor.canvas.fps.cuadros_por_segundo_numerico * velocidad))
    else:
        return 0


def interpolar(valor_o_valores, duracion=1, demora=0, tipo='lineal'):
    """Retorna un objeto que representa cambios de atributos progresivos.

    El resultado de esta función se puede aplicar a varios atributos
    de los actores, por ejemplo:

        >>> bomba = pilas.actores.Bomba()
        >>> bomba.escala = pilas.interpolar(3)

    Esta función también admite otros parámetros cómo:

    :param duracion: es la cantidad de segundos que demorará toda la interpolación.
    :param demora: cuantos segundos se deben esperar antes de iniciar.
    :param tipo: es el algoritmo de la interpolación, puede ser 'lineal'.
    """

    from . import interpolaciones

    algoritmos = {
        'lineal': interpolaciones.Lineal,
        'aceleracion_gradual': interpolaciones.AceleracionGradual,
        'desaceleracion_gradual': interpolaciones.DesaceleracionGradual,
        'rebote_inicial': interpolaciones.ReboteInicial,
        'rebote_final': interpolaciones.ReboteFinal,
        'elastico_inicial': interpolaciones.ElasticoInicial,
        'elastico_final': interpolaciones.ElasticoFinal
    }

    if tipo in algoritmos:
        clase = algoritmos[tipo]
    else:
        raise ValueError("El tipo de interpolacion %s es invalido" % (tipo))

    # Permite que los valores de interpolacion sean un numero o una lista.
    if not isinstance(valor_o_valores, list):
        valor_o_valores = [valor_o_valores]

    return clase(valor_o_valores, duracion, demora)


def detener_interpolacion(objeto, propiedad):
    """Deteiene una interpolación iniciada en un campo de un objeto.

       >>> pilas.utils.detener_interpolacion(actor, 'y')

    :param objeto: Actor del que se desea detener al interpolacion.
    :para propiedad: Cadena de texto que indica la propiedad del objeto cuya interpolación se desea terminar.
    """
    setter = 'set_' + propiedad
    try:
        getattr(objeto, setter)
        pilas.escena_actual().tweener.removeTweeningFromObjectField(objeto, setter)
    except:
        print("El obejto %s no tiene esa propiedad %s" % (objeto.__class__.__name__, setter))


def obtener_area():
    """Retorna el area que ocupa la ventana"""
    return pilas.mundo.obtener_area()


def obtener_bordes():
    """Retorna los bordes de la pantalla en forma de tupla."""
    ancho, alto = pilas.mundo.obtener_area()
    return -ancho/2, ancho/2, alto/2, -alto/2


def obtener_area_de_texto(texto):
    """Informa el ancho y alto que necesitara un texto para imprimirse.

    :param texto: La cadena de texto que se quiere imprimir.
    """
    return pilas.mundo.motor.obtener_area_de_texto(texto)


def realizar_pruebas():
    """Imprime pruebas en pantalla para detectar si pilas tiene todas las dependencias instaladas."""
    print("Realizando pruebas de dependencias:")
    print("")

    print("Box 2D:", end=' ')
    ver = pilas.fisica.obtener_version_en_tupla()
    if ver[0] == 2 and ver[1] >= 1:
        print("OK, versión", pilas.fisica.obtener_version())
    else:
        print("Error -> la versión está obsoleta, instale una versión de la serie 2.1")

    print("pyqt:", end=' ')

    try:
        from PyQt4 import Qt
        print("OK, versión", Qt.PYQT_VERSION_STR)
    except ImportError:
        print("Error -> no se encuentra pyqt.")

    print("pyqt con aceleracion:", end=' ')

    try:
        from PyQt4 import QtOpenGL
        from PyQt4.QtOpenGL import QGLWidget
        print("OK")
    except ImportError:
        print("Error -> no se encuentra pyqt4gl.")

    print("PIL para soporte de jpeg (opcional):", end=' ')

    try:
        from PIL import Image
        print("OK")
    except ImportError:
        print("Cuidado -> no se encuentra PIL.")


def ver_codigo(objeto, imprimir, retornar):
    """Imprime en pantalla el codigo fuente asociado a un objeto.

    :param objeto: El objeto que se quiere inspeccionar.
    :param imprimir: Un valor True o False indicando si se quiere imprimir directamente sobre la pantalla.
    :param retornar: Un valor True o False indicando si se quiere obtener el código como un string.
    """
    import inspect

    try:
        codigo = inspect.getsource(objeto.__class__)
    except TypeError:
        try:
            codigo = inspect.getsource(objeto)
        except TypeError:
            codigo = "<< imposible inspeccionar código para mostrar >>"

    if imprimir:
        print(codigo)

    if retornar:
        return codigo


def obtener_uuid():
    """Genera un identificador único."""
    return str(uuid.uuid4())


def abrir_archivo_con_aplicacion_predeterminada(ruta_al_archivo):
    """Intenta abrir un archivo con la herramienta recomenda por el sistema operativo.

    :param ruta_al_archivo: La ruta al archivo que se quiere abrir.
    """
    if sys.platform.startswith('darwin'):
        subprocess.call(('open', ruta_al_archivo))
    elif os.name == 'nt':
        os.startfile(ruta_al_archivo)
    elif os.name == 'posix':
        subprocess.call(('xdg-open', ruta_al_archivo))


def centrar_ventana(widget):
    """Coloca la ventana o widget directamente en el centro del escritorio.

    :param widget: Widget que representa la ventana.
    """
    from PyQt4 import QtGui
    desktop = QtGui.QApplication.desktop()
    widget.move(desktop.screen().rect().center() - widget.rect().center())


def descargar_archivo_desde_internet(parent, url, archivo_destino):
    """Inicia la descarga de una archivo desde Internet.

    :param parent: El widget que será padre de la ventana.
    :param url: La URL desde donde se descargará el archivo.
    :param archivo_destino: La ruta en donde se guardará el archivo.
    """
    from . import descargar
    ventana = descargar.Descargar(parent, url, archivo_destino)
    ventana.show()


def imprimir_todos_los_eventos():
    """Muestra en consola los eventos activos y a quienes invocan"""
    import pilas

    for x in dir(pilas.escena_actual()):
        attributo = getattr(pilas.escena_actual(), x)

        if isinstance(attributo, pilas.evento.Evento):
            print("Evento:", attributo.nombre)
            attributo.imprimir_funciones_conectadas()
            print("")


def habilitar_depuracion():
    """Permite habilitar un breakpoint para depuracion una vez inicializado pilas."""
    from PyQt4.QtCore import pyqtRemoveInputHook
    from pdb import set_trace
    pyqtRemoveInputHook()
    set_trace()


def mostrar_mensaje_de_error_y_salir(motivo):
    """Muestra un mensaje de error y termina con la ejecución de pilas.

    :param motivo: Un mensaje que explica el problema o la razón del cierre de pilas.
    """
    from PyQt4 import QtGui
    app = QtGui.QApplication(sys.argv[:1])
    app.setApplicationName("pilas-engine error")
    main_window = QtGui.QMainWindow()
    main_window.show()
    main_window.raise_()
    QtGui.QMessageBox.critical(main_window, "Error", motivo)
    app.exit()
    sys.exit(1)


def obtener_archivo_a_ejecutar_desde_argv():
    """Obtiene la ruta del archivo a ejecutar desde la linea de argumentos del programa."""
    import sys

    argv = sys.argv[:]

    if '-i' in argv:
        argv.remove('-i')

    return " ".join(argv[1:])


def procesar_argumentos_desde_command_line():
    from optparse import OptionParser


    analizador = OptionParser()

    analizador.add_option("-t", "--test", dest="test",
                          action="store_true", default=False,
                          help="Invoca varias pruebas verificar el funcionamiento de pilas")

    analizador.add_option("-v", "--version", dest="version",
                          action="store_true", default=False,
                          help="Consulta la version instalada")

    analizador.add_option("-i", "--interprete", dest="interprete",
                          action="store_true", default=False,
                          help="Abre el interprete interactivo")

    (opciones, argumentos) = analizador.parse_args()
    return (opciones, argumentos)


def iniciar_asistente_desde_argumentos():
    if sys.platform == 'darwin':
        import pilas
        pilas.abrir_asistente()
    else:
        (opciones, argumentos) = procesar_argumentos_desde_command_line()

        if argumentos:
            archivo_a_ejecutar = obtener_archivo_a_ejecutar_desde_argv()

            if not os.path.exists(archivo_a_ejecutar):
                mostrar_mensaje_de_error_y_salir("El archivo '%s' no existe o no se puede leer." % (archivo_a_ejecutar))

            if not 'text/x-python' in mimetypes.guess_type(archivo_a_ejecutar):
                mostrar_mensaje_de_error_y_salir("El archivo '%s' no parece un script python. Intenta con un archivo .py" % (archivo_a_ejecutar))

            ## Intenta ejecutar el script como un programa de pilas.
            try:
                directorio_juego = os.path.dirname(archivo_a_ejecutar)

                if directorio_juego:
                    os.chdir(directorio_juego)

                sys.exit(execfile(archivo_a_ejecutar))
            except Exception as e:
                mostrar_mensaje_de_error_y_salir(e.__class__.name + ": " + e.message)
                return

        if opciones.interprete:
            import pilas
            app = pilas.abrir_interprete(do_raise=True, con_aplicacion=True)
            app.exec_()
            return
        elif opciones.test:
            realizar_pruebas()
        elif opciones.interprete:
            import pilas
            pilas.abrir_interprete(do_raise=True, con_aplicacion=True)
        elif opciones.version:
            from pilas import pilasversion
            print(pilasversion.VERSION)
        else:
            import pilas
            pilas.abrir_asistente()

def distancia_entre_radios_de_colision_de_dos_actores(a, b):
    """Retorna la distancia entre dos actores tenieno en cuenta su radio de colisión

    :param a: Un actor.
    :param b: El segundo actor a verificar.
    """
    return distancia_entre_dos_actores(a, b) - (a.radio_de_colision + b.radio_de_colision)

def beep(freq, seconds):
    import pygame
    import math
    import numpy

    size = (1366, 720)
    bits = 16


    #pyg ame.mixer.pre_init(44100, -bits, 1)#esto hace q se configure en mono, falta ver que el array buf sea unidimensional
    pygame.mixer.pre_init(44100, -bits, 2)#esto hace q se configure en mono, falta ver que el array buf sea unidimensional
    pygame.init()
    #_display_surf = pygame.display.set_mode(size, pygame.HWSURFACE | pygame.DOUBLEBUF)

    #this sounds totally different coming out of a laptop versus coming out of headphones

    sample_rate = 44100
    n_samples = int(round(seconds*sample_rate))

    #setup our numpy array to handle 16 bit ints, which is what we set our mixer to expect with "bits" up above
    buf = numpy.zeros((n_samples, 2), dtype = numpy.int16)
    max_sample = 2**(bits - 1) - 1

    for s in range(n_samples):
        t = float(s)/sample_rate    # time in seconds

        #grab the x-coordinate of the sine wave at a given time, while constraining the sample to what our mixer is set to with "bits"
        buf[s][0] = int(round(max_sample*math.sin(2*math.pi*freq*t)))        # left
        buf[s][1] = int(round(max_sample*math.sin(2*math.pi*freq*t)))        # left

    sound = pygame.sndarray.make_sound(buf)
    #play once, then loop forever
    sound.play()

    def hacerEvent():
        for event in pygame.event.get():
            pass

    pilas.escena_actual().tareas.una_vez(0.1, hacerEvent)