<-- Back to list of examples

"""
Programas de muestra con Python/Pygame 
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/

De:
http://programarcadegames.com/python_examples/f.php?file=Plataforma_moving.py

Vídeo explicativo: http://youtu.be/YKdOD5VkY48

Parte de la serie:
http://programarcadegames.com/python_examples/f.php?file=move_with_walls_example.py
http://programarcadegames.com/python_examples/f.php?file=maze_runner.py
http://programarcadegames.com/python_examples/f.php?file=Plataforma_jumper.py
http://programarcadegames.com/python_examples/f.php?file=Plataforma_scroller.py
http://programarcadegames.com/python_examples/f.php?file=Plataforma_moving.py
http://programarcadegames.com/python_examples/sprite_sheets/
"""

import pygame

"""
Constantes globales
"""

# Colores
NEGRO = (0, 0, 0) 
BLANCO = (255, 255, 255) 
AZUL = (0, 0, 255)
ROJO = (255, 0, 0)
VERDE = (0, 255, 0)

# Dimensiones de la pantalla
LARGO_PANTALLA  = 800
ALTO_PANTALLA = 600


class Protagonista(pygame.sprite.Sprite): 
    """ Esta clase representa la barra inferior que controla el protagonista. """
  
    
    # -- Métodos 
    def __init__(self): 
        """ Función Constructor  """ 
        
        #  Llama al constructor padre 
        super().__init__() 
        
        # Crea una imagen del bloque y lo rellena con color rojo.
        # También podríamos usar una imagen guardada en disco	
        largo = 40
        alto = 60
        self.image = pygame.Surface([largo, alto])
        self.image.fill(ROJO)        
  
        # Establecemos una referencia hacia la imagen rectangular
        self.rect = self.image.get_rect()

        # Establecemos el vector velocidad del protagonista
        self.cambio_x = 0
        self.cambio_y = 0

        # Lista de todos los sprites contra los que podemos botar
        self.nivel = None
      
    def update(self): 
        """ Desplazamos al protagonista.  """
        # Gravedad
        self.calc_grav()
        
        # Desplazar izquierda/derecha
        self.rect.x += self.cambio_x
        
        # Comprobamos si hemos chocado contra algo
        lista_impactos_bloques = pygame.sprite.spritecollide(self, self.nivel.listade_plataformas, False)
        for bloque in lista_impactos_bloques:
            # Si nos estamos desplazando hacia la derecha, hacemos que nuestro lado derecho sea el lado 	    
            # izquierdo del objeto que hemos tocado
            if self.cambio_x > 0:
                self.rect.right = bloque.rect.left
            elif self.cambio_x < 0:
                # En caso contrario, si nos desplazamos hacia la izquierda, hacemos lo opuesto.
                self.rect.left = bloque.rect.right

        #  Desplazar arriba/izquierda
        self.rect.y += self.cambio_y
        
        # Comprobamos si hemos chocado contra algo
        lista_impactos_bloques = pygame.sprite.spritecollide(self, self.nivel.listade_plataformas, False) 
        for bloque in lista_impactos_bloques:

            # Restablecemos nuestra posición basándonos en la parte superior/inferior del objeto.
            if self.cambio_y > 0:
                self.rect.bottom = bloque.rect.top 
            elif self.cambio_y < 0:
                self.rect.top = bloque.rect.bottom

            # Detenemos nuestro movimiento vertical
            self.cambio_y = 0
            
            if isinstance(bloque, PlataformaEnMovimiento):
                self.rect.x += bloque.cambio_x

    def calc_grav(self):
        """ Calculamos el efecto de la gravedad.  """ 
        if self.cambio_y == 0:
            self.cambio_y = 1
        else:
            self.cambio_y += .35

        # Observamos si nos encontramos sobre el suelo. 
        if self.rect.y >= ALTO_PANTALLA - self.rect.height and self.cambio_y >= 0:
            self.cambio_y = 0
            self.rect.y = ALTO_PANTALLA - self.rect.height

    def jump(self):
        """ Llamado cuando el usuario pulsa el botón de 'saltar'. """ 
        
        # Descendemos un poco y observamos si hay una plataforma debajo nuestro.
        # Descendemos 2 píxels (con una plataforma que está  descendiendo, no funciona bien 
	# si solo descendemos uno).
        self.rect.y += 2
        lista_impactos_plataforma = pygame.sprite.spritecollide(self, self.nivel.listade_plataformas, False)
        self.rect.y -= 2
        
        # Si está listo para saltar, aumentamos nuestra velocidad hacia arriba
        if len(lista_impactos_plataforma) > 0 or self.rect.bottom >= ALTO_PANTALLA:
            self.cambio_y = -10
            
    # Movimiento controlado por el protagonista:
    def ir_izquierda(self):
        """ Es llamado cuando el usuario pulsa la flecha izquierda  """
        self.cambio_x = -6

    def ir_derecha(self):
        """ Es llamado cuando el usuario pulsa la flecha  derecha """
        self.cambio_x = 6

    def stop(self):
        """ Es llamado cuando el usuario abandona el teclado """
        self.cambio_x = 0
                   
class Plataforma(pygame.sprite.Sprite):
    """ Plataforma sobre la que el usuario puede saltar. """

    def __init__(self, largo, alto ):
        """ Constructor de plataforma. Asume su construcción cuando el usuario le haya pasado 
            un array de 5 números, tal como se ha definido al principio de este código.  """
        super().__init__()
        
        self.image = pygame.Surface([largo, alto])
        self.image.fill(VERDE)    
                
        self.rect = self.image.get_rect()
 

class PlataformaEnMovimiento(Plataforma):
    """ Esta es una Plataforma que podemos realmente mover. """
    cambio_x = 0
    cambio_y = 0
     
    limite_superior = 0
    limite_inferior = 0
    limite_izquierda = 0
    limite_derecha = 0
    
    protagonista = None
    
    Nivel = None
    
    def update(self):
        """ Desplaza la Plataforma.
            Empujará al protagonista fuera de su camino si viene en esta dirección.
            NO gestiona qué sucede si la plataforma empuja al protagonista contra 
	    otro objeto. Asegúrate de que las plataformas tengan espacio para poder 
	    empujar al protagonista. En caso contrario, añade el código necesario
	    para que pueda gestionarlo si esto no sucede. """
        
        # Desplazar izquierda/derecha
        self.rect.x += self.cambio_x
        
        # Comprobamos si hemos chocado contra el protagonista
        impacto = pygame.sprite.collide_rect(self, self.protagonista)
        if impacto:
            # Hemos impactado contra el protagonista. Lo empujamos a un lado
            # y asumimos que no impactará con ninguna otra cosa.
            
            # Si nos estamos desplazando hacia la derecha, establece que nuestro lado
	    # derecho se coloque al lado izquierdo del objeto contra el que hemos 
            # impactado
            if self.cambio_x < 0:
                self.protagonista.rect.right = self.rect.left
            else:
                # En caso contrario (desplazamiento a la izquierda), hacemos lo opuesto
                self.protagonista.rect.left = self.rect.right

        # Desplazar arriba/abajo
        self.rect.y += self.cambio_y
        
        # Comprobamos si hemos impactado con el protagonista
        impacto = pygame.sprite.collide_rect(self, self.protagonista)
        if impacto:
           # Hemos impactado contra el protagonista. Lo empujamos a un lado
           # y asumimos que no impactará con ninguna otra cosa.
            
           # Restablecemos nuestra posición basándonos en la parte superior/inferior
 	   # del objeto
            if self.cambio_y < 0:
                self.protagonista.rect.bottom = self.rect.top 
            else:
                self.protagonista.rect.top = self.rect.bottom

        # Comprobamos los límites y vemos si es necesario invertir el sentido

        if self.rect.bottom > self.limite_inferior or self.rect.top < self.limite_superior:
            self.cambio_y *= -1
            
        cur_pos = self.rect.x - self.nivel.desplazar_escenario
        if cur_pos < self.limite_izquierda or cur_pos > self.limite_derecha:
            self.cambio_x *= -1         

class Nivel():
    """ Esta es una súper clase genérica usada para definir un Nivel.
        Crea una clase hija específica para cada Nivel con una info
        específica.  """
       
    def __init__(self, protagonista):
        """ Constructor. Requerido cuando las Plataformas
            móviles colisionan con el protagonista. """
        self.listade_plataformas = pygame.sprite.Group()
        self.listade_enemigos = pygame.sprite.Group()
        self.protagonista = protagonista
        
        # Imagen de fondo
        self.imagende_fondo = None
        
        # Cuán lejos a la izquierda/derecha se ha desplazado el escenario
        self.desplazar_escenario = 0
        self.limitedel_nivel = -1000
    
    # update todo en este Nivel
    def update(self):
        """ update todo en este Nivel."""
        self.listade_plataformas.update()
        self.listade_enemigos.update()
    
    def draw(self, pantalla):
        """ dibuja todo en este Nivel. """
        
        # dibuja la imagen de fondo
        pantalla.fill(AZUL)
                  
        # dibuja todas las listas de sprites que tengamos
        self.listade_plataformas.draw(pantalla)
        self.listade_enemigos.draw(pantalla)
        
    def escenario_desplazar(self, desplazar_x):
        """ Cuando el usuario se mueve de izquierda/derecha y necesitamos que todo se desplace: """
        
        # Llevamos la cuenta de la cantidad de desplazamiento
        self.desplazar_escenario += desplazar_x
        
        # Iteramos a través de todas las listas de sprites y desplazamos
        for plataforma in self.listade_plataformas:
            plataforma.rect.x += desplazar_x
            
        for enemigo in self.listade_enemigos:
            enemigo.rect.x += desplazar_x
    
# Creamos las Plataformas para el Nivel
class Nivel_01(Nivel):
    """ Definición para el Nivel 1. """

    def __init__(self, protagonista):
        """ Crear Nivel 1. """
        
        # Llamada al constructor padre
        Nivel.__init__(self, protagonista)

        self.limitedel_nivel = -1500
        
        # Array con el largo, alto, x, e y de la Plataforma
        nivel = [ [210, 70, 500, 500],
                  [210, 70, 800, 400],
                  [210, 70, 1000, 500],
                  [210, 70, 1120, 280],
                  ]
        
        
        # Iteramos a través del array anterior y añadimos Plataformas
        for plataforma in nivel:
            bloque = Plataforma(plataforma[0], plataforma[1])
            bloque.rect.x = plataforma[2]
            bloque.rect.y = plataforma[3]
            bloque.protagonista = self.protagonista
            self.listade_plataformas.add(bloque)
        
        # Añadimos una Plataforma móvil personalizada
        bloque = PlataformaEnMovimiento(70, 40) 
        bloque.rect.x = 1350
        bloque.rect.y = 280
        bloque.limite_izquierda = 1350
        bloque.limite_derecha = 1600
        bloque.cambio_x = 1
        bloque.protagonista = self.protagonista
        bloque.nivel = self
        self.listade_plataformas.add(bloque)
                        

# Creamos las Plataformas para el Nivel
class Nivel_02(Nivel):
    """ Definición para el Nivel 2. """

    def __init__(self, protagonista):
        """ Crear Nivel 2. """
        
        # Llamada al constructor padre
        Nivel.__init__(self, protagonista)

        self.limitedel_nivel = -1000
        
        # Array con el largo, alto, x, e y de la Plataforma
        nivel = [ [210, 70, 500, 550],
                  [210, 70, 800, 400],
                  [210, 70, 1000, 500],
                  [210, 70, 1120, 280],
                  ]
        
        
        # Iteramos a través del array anterior y añadimos Plataformas
        for plataforma in nivel:
            bloque = Plataforma(plataforma[0], plataforma[1])
            bloque.rect.x = plataforma[2]
            bloque.rect.y = plataforma[3]
            bloque.protagonista = self.protagonista
            self.listade_plataformas.add(bloque)
            
        # Añadimos una Plataforma móvil personalizada
        bloque = PlataformaEnMovimiento(70, 70) 
        bloque.rect.x = 1500
        bloque.rect.y = 300
        bloque.limite_superior = 100
        bloque.limite_inferior = 550
        bloque.cambio_y = -1
        bloque.protagonista = self.protagonista
        bloque.nivel = self
        self.listade_plataformas.add(bloque)

def main():
    """ Programa Principal """
    pygame.init() 
       
    # Establecemos el alto y largo de la pantalla 
    dimensiones = [LARGO_PANTALLA, ALTO_PANTALLA] 
    pantalla = pygame.display.set_mode(dimensiones) 
      
    pygame.display.set_caption("Plataformer y Plataformas en Movimiento") 
    
    # Creamos al protagonista
    protagonista = Protagonista()

    # Creamos todos los Niveles
    listade_niveles = []
    listade_niveles.append( Nivel_01(protagonista) )
    listade_niveles.append( Nivel_02(protagonista) )
    
    # Establecemos el Nivel actual
    nivel_actual_no = 0
    nivel_actual = listade_niveles[nivel_actual_no]
    
    listade_sprites_activas = pygame.sprite.Group()
    protagonista.nivel = nivel_actual
    
    protagonista.rect.x = 340
    protagonista.rect.y = ALTO_PANTALLA - protagonista.rect.height
    listade_sprites_activas.add(protagonista)
        
    # Iteramos hasta que el usuario hace click sobre el botón de salir.
    hecho = False
      
    # Usado para gestionar cuán rápido se actualiza la pantalla.
    reloj = pygame.time.Clock() 
      
    # -------- Bucle Principal del Programa ----------- 
    while not hecho: 
        for evento in pygame.event.get(): # El usuario realizó alguna acción 
            if evento.type == pygame.QUIT:  # Si el usuario hizo click en salir
                hecho = True # Marcamos como hecho y salimos de este bucle
    
            if evento.type == pygame.KEYDOWN:
                if evento.key == pygame.K_LEFT:
                    protagonista.ir_izquierda()
                if evento.key == pygame.K_RIGHT:
                    protagonista.ir_derecha()
                if evento.key == pygame.K_UP:
                    protagonista.jump()
                    
            if evento.type == pygame.KEYUP:
                if evento.key == pygame.K_LEFT and protagonista.cambio_x < 0: 
                    protagonista.stop()
                if evento.key == pygame.K_RIGHT and protagonista.cambio_x > 0:
                    protagonista.stop()

        # Actualizamos al protagonista. 
        listade_sprites_activas.update()
        
        # Actualizamos los objetos en el Nivel
        nivel_actual.update()
        
        # Si el protagonista se aproxima al borde derecho, desplazamos el escenario a la izquierda(-x)
        if protagonista.rect.x >= 500:
            diff = protagonista.rect.x - 500
            protagonista.rect.x = 500
            nivel_actual.escenario_desplazar(-diff)
    
        # Si el protagonista se aproxima al borde izquierdo, desplazamos el escenario a la derecha(+x)
        if protagonista.rect.x <= 120:
            diff = 120 - protagonista.rect.x
            protagonista.rect.x = 120
            nivel_actual.escenario_desplazar(diff)
 
        # Si el protagonista alcanza el final del Nivel, pasa al siguiente
        posicion_actual = protagonista.rect.x + nivel_actual.desplazar_escenario
        if posicion_actual < nivel_actual.limitedel_nivel:
            if nivel_actual_no < len(listade_niveles)-1:
                protagonista.rect.x = 120
                nivel_actual_no += 1
                nivel_actual = listade_niveles[nivel_actual_no]
                protagonista.nivel = nivel_actual
            else:
                # No hay más Niveles. Esto solo hace que el programa termine.
                # Seguramente quieres que haga algo mejor que esto, no?
                hecho = True
            
        # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR DEBAJO DE ESTE COMENTARIO
        nivel_actual.draw(pantalla)
        listade_sprites_activas.draw(pantalla)
        
        # TODO EL CÓDIGO DE DIBUJO DEBERÍA IR ENCIMA DE ESTE COMENTARIO
          
        # Limitamos a 60 fps
        reloj.tick(60) 
      
        #Avanzamos y actualizamos la pantalla que ya hemos dibujado
        pygame.display.flip() 
          
    # Pórtate bien con el IDLE. Si olvidas esta línea, el programa se 'colgará' 
    # al salir.
    pygame.quit()

if __name__ == "__main__":
    main()