Archivos de la categoría Python

Nuestra pieza L

“Cuatris”, o nuestro clon de Tetris en Python: ¡comenzamos!

Tetris es un juego que marcó una época. Llegó a occidente en 1986 y fue desarrollado en 1984 por Alekséi Pázhitnov (Muscú, 1956), con la ayuda de Dmitri Pavlovski y Vadim Gerasimov mientras trabajaba en la Academia de Ciencias de la URSS.

La mecánica es sencilla: tenemos una serie de piezas llamadas Tretriminos J, L, Z, S, I, O y T, que caen desde la parte de arriba de la pantalla con ayuda de la gravedad.

Los Tetriminos: I, J, L, O, S, T y Z
Los Tetriminos: I, J, L, O, S, T y Z
  • La pantalla visible del Tetris mide 10 cuadros de ancho por 20 de alto.
  • El jugador, con ayuda de los controles, puede desplazarlas en horizontal (izquierda y derecha) y girarlas de 90 en 90º tanto en sentido horario como en sentido antihorario.
  • Cuando la haya colocado correctamente, podrá precipitarla hacia abajo para encajarlas en las piezas ya depositadas, para formar líneas.
  • Cuando se forma una línea horizontal, todos los segmentos de las piezas que forman una línea desaparecen, y todos los elementos que hubiera por encima, caen.
  • Cada cierto número de líneas, que forman un nivel, la velocidad aumenta.

Lo que vamos a hacer es programar poco a poco un juego como éste, con una estructura que a día de hoy seguirá más o menos este guión:

  1. Dibujar una pieza (básica) y programar la rotación: el artículo de hoy
  2. Programar la gravedad y el resto de movimientos
  3. Colisiones de la pieza con otras piezas y el borde de la pantalla.
  4. Haciendo líneas y retocando la mecánica anterior
  5. Puntuaciones y niveles
  6. Borde, marcador, tabla de puntuaciones
  7. Refinando los gráficos de las piezas

Para poder aprovechar estos artículos conviene que hayas hecho los anteriores artículos de la serie de Pygame con Python, y la propia serie de Python.

¡Vamos a ello!

Sigue leyendo “Cuatris”, o nuestro clon de Tetris en Python: ¡comenzamos!

Comparar objetos en Python y otras sutilezas

En esta entrada vamos a continuar aprendiendo técnicas y conceptos relacionados con la programación orientada a objetos, que en la anterior entrega quedaron postergados de forma deliberada porque de por sí daban para bastante discusión.

En concreto, veremos tres cosas muy útiles cuando se trata de hacer programas potentes:

  • Comparación entre objetos de una misma clase: cómo comparar objetos correctamente.
  • Acceso a los métodos y variables de la superclase, o clase de la cual heredamos.
  • Ocultación de la información, o encapsulamiento.

¡Vamos allá!

Sigue leyendo Comparar objetos en Python y otras sutilezas

Eventos en Pygame (1) – Mover un objeto con tu teclado

Si has seguido los dos artículos que han aparecido acerca de Pygame, habrás notado que el programa se articula en torno a un bucle que controla unas variables llamadas genéricamente “eventos”, esto es, cosas que han pasado. Como muchas librerías que proporcionan mecanismos para desarrollar interactividad con el usuario, Pygame proporciona un bucle de eventos, o eventos en un bucle. Esto significa que cada cierto tiempo nos da la oportunidad de saber qué ocurre en los dispositivos de entrada (teclado, ratón, joystick) y nos proporciona esa información en orden, en una estructura de tipo lista ordenada. En general, los eventos se deberían procesar en modo cola: las colas son estructuras de datos en cuya cabecera encontraremos el primer elemento que se ha introducido, y podremos así consultar su contenido en el orden correcto. En Pygame los obtendremos en forma de lista, pudiendo procesarlos en orden, pero también acceder aleatoriamente a los elementos de la lista.

Vamos a ver los tipos de eventos que ofrece Pygame para el teclado y cómo se comportan, reutilizando para ello el programa del artículo anterior.

El bucle de procesado de eventos de Pygame

El bucle de eventos es la pieza central del programa, y en ella podemos comprobar las cosas que han ido ocurriendo y traducirlas en efectos sobre la pantalla. Lo hemos hecho con un fragmento de código que suele tener una estructura como la siguiente

while [condición]:
    for event in pygame.event.get():
        if event.type == [evento]:
            [acción]

Es decir, mientras se cumpla una determinada condición, extraeremos los eventos de la cola y los examinaremos uno a uno, programando acciones en nuestro programa con respecto al tipo (y contenido) del evento. Para eso, obtendremos el contenido de la cola en forma de lista ordenada mediante pygame.event.get()

Visto así, Pygame es una librería orientada a eventos, puesto que la forma de controlar el ritmo del juego que nos propone es precisamente procesar los eventos que se registran entre una pasada y otra del bucle. Pero, la cuestión: ¿cómo de frecuente debe ser el bucle para que el programa funcione de forma fluida, pero sin hacer que la frecuencia del mismo degrade el rendimiento?

En general, desde el punto de vista del programador, no interesa consultar los eventos a más frecuencia que la de refresco en pantalla puesto que la mayoría de periféricos de entrada y de reacciones del usuario son más lentas que la pantalla. En los vídeos de alta resolución, esta frecuencia es de 60 Hz, es decir, 60 cuadros por segundo.

Para realizar este control, Pygame nos ofrece un reloj que permite estructurar el programa en intervalos temporales, de tal forma que podamos esperar a que se cumpla una determinada frecuencia. Lo hemos usado ya, son las líneas resaltadas de este código esquemático:

FPS = [valor] # frecuencia: nº de veces por segundo.
clock = pygame.time.Clock()

while [condición]
    clock.tick(FPS) # pausa hasta el siguiente "tick" de reloj

    for event in pygame.event.get():
        if event.type == [evento]:
            [acción]

clock.tick(FPS)  suspende la ejecución del programa hasta que el tiempo no llega a la siguiente fracción de segundo de la duración que hemos definido (1/FPS segundos).

Procesando teclas: eventos de teclado

Vamos a ver muy brevemente cómo mover el objeto que ya teníamos construido en el artículo anterior. Para ello os presento primero el programa modificado en donde podréis ver dos novedades (resaltadas en el código):

  • He movido la acción de mover el cuadrado por la pantalla a la propia clase Cuadrado , creando un nuevo método en ella, mover_p  a la que le podemos proporcionar la pantalla como parámetro, de forma que se pueda encapsular el borrado y el redibujado de las zonas afectadas por el movimiento.
  • Por otro lado, hay código nuevo en el bucle de eventos, que analizaré a continuación. Puedes ejecutar primero el programa y ver que, ahora, el objeto se puede mover en horizontal con las flechas del cursor. Sin embargo, se mueve “por pasos”, es decir: sólo detecta una pulsación y lo mueve de 20 en 20 píxels.
import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

    def mover_p(self, pantalla, avance_x, avance_y):
        'avance del cuadrado en un cuadro, con refresco de pantalla'
        cuadrado.borra(pantalla)
        rect_inicial = cuadrado.rect.copy()
        cuadrado.mover(avance_x, avance_y)
        rect_final = cuadrado.rect.copy()
        cuadrado.pinta(pantalla)
        pygame.display.update(rect_final.union(rect_inicial))


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
    
        # Movimiento
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                cuadrado.mover_p(pantalla, -20, 0)

            if event.key == pygame.K_RIGHT:
                cuadrado.mover_p(pantalla, 20, 0)

    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()

Vamos a examinar el bucle que procesa los eventos:

  • Ya sabemos que para obtenerlos podemos invocar a pygame.event.get(), el cual los devuelve en una lista sobra la que podemos iterar con cualquier construcción de tipo bucle.
  • Dentro del bucle, comprobaremos para cada evento su tipo mediante el valor event.type . En nuestro programa controlamos los eventos de tipo pygame.QUIT ,  y pygame.KEYDOWN  (“tecla abajo”).
    • Una vez detectado el tipo de evento que nos interesa, y atendiendo a esto, podemos examinar la información que trae dicho evento como contenedor de datos.
    • En este caso, nos interesa el atributo event.key, que nos dice la tecla pulsada.
    • Para este artículo reaccionaremos ante pygame.K_LEFT  y pygame.K_RIGHT , que se corresponden con las flechas izquierda y derecha del cursor.

El funcionamiento de este programa es “paso a paso”, es decir, Pygame sólo va a proporcionar una pulsación de teclas sin importar el tiempo que mantengamos la tecla pulsada. Así pues, no responde a un movimiento fluido.

Para conseguir un movimiento fluido basta con configurar el módulo de teclado con la sentencia que os he resaltado en la siguiente versión del programa (también he modificado las líneas que ejecutan el movimiento para suavizarlo un poco):

import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

    def mover_p(self, pantalla, avance_x, avance_y):
        'avance del cuadrado en un cuadro, con refresco de pantalla'
        cuadrado.borra(pantalla)
        rect_inicial = cuadrado.rect.copy()
        cuadrado.mover(avance_x, avance_y)
        rect_final = cuadrado.rect.copy()
        cuadrado.pinta(pantalla)
        pygame.display.update(rect_final.union(rect_inicial))


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()
pygame.key.set_repeat(True)

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
    
        # Movimiento
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                cuadrado.mover_p(pantalla, -2, 0)

            if event.key == pygame.K_RIGHT:
                cuadrado.mover_p(pantalla, 2, 0)

    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()
    

En el sentido de ampliar esta sencilla técnica con todas sus posibilidades, y ya para terminar este artículo, os dejo en el apartado de referencias una serie de enlaces que tener a mano para dominar los eventos de teclado (están en inglés). Con lo que sabéis ya podéis hacer un pequeño experimento de juego en el que un cuadrado tenga que llegar hasta el otro esquivando objetos que se muevan por la pantalla. ¿Os atrevéis?

No dudéis en dejarme vuestros comentarios en este artículo o a través del formulario de contacto.

Referencias

  • Referencia de los eventos en Pygame: qué tipos de eventos hay tanto de teclado como de otros dispositivos, cuál es su estructura, y cómo gestionarlos o leer su contenido: http://www.pygame.org/docs/ref/event.html
  • Mapa de teclado – teclas que reconoce Pygame: http://www.pygame.org/docs/ref/key.html. En este enlace, además, encontraréis la referencia completa del teclado para poderlo configurar.

Movimiento y colisiones en Pygame

Gran parte de los videojuegos se basan en un funcionamiento básico en el que un objeto interactúa con otro en la pantalla. En esos videojuegos, la gracia está en evitar a otros objetos, o por el contrario en buscar esos otros objetos. Piensa en Pac-Man, o el comecocos de toda la vida: el protagonista debe comer todas las fichas de un laberinto, mientras los fantasmas le persiguen. Las paredes no se pueden atravesar, y si los fantasmas tocan a nuestro héroe, éste muere. Pero sin embargo, si el comecocos se come una ficha especial, la mecánica es la inversa: se puede comer a los fantasmas enviándolos a una especie de cárcel.

De esta forma, tenemos:

  • Varios objetos moviéndose: un comecocos y cuatro fantasmas.
  • Un objeto especial (el comecocos) debe ir colisionando con cuantos objetos inmóviles pueda (fichas), pero no puede atravesar los límites que definen el laberinto (las paredes).
  • Si los fantasmas chocan con el comecocos, en función del estado de este último, ocurren eventos especiales (muerte del comecocos o captura del fantasma).

Todo en este tipo de juegos se basa en dos mecanismos: el movimiento y la detección de colisiones. Vamos a examinar el funcionamiento de estos dos mecanismos con Pygame.

Sigue leyendo Movimiento y colisiones en Pygame

Python y programación orientada a objetos

Nuestra próxima incursión en la programación de videojuegos es una oportunidad fantástica para introducir la programación orientada a objetos en nuestra vida, de una forma sencilla y práctica con Python. Usaremos como ejemplos… galletas y bicicletas 🙂

Bowden Spacelander Bicycle - Museo de Brooklyn (licencia CC 3.0 - http://creativecommons.org/licenses/by/3.0)
Bowden Spacelander Bicycle

(Foto: Museo de Brooklyn – licencia CC 3.0)

La programación orientada a objetos es un paradigma (estilo, marco conceptual, filosofía) de programación que trata de organizar los programas modelando objetos, como si objetos del mundo real se tratasen. Las primeras nociones de objetos en un programa informático se dieron en el Instituto Tecnológico de Massachussets en las décadas de los 1950 y 1960. Por aquel entonces, un objeto era algo tan vago como “un elemento identificable con atributos asociados”. Este paradigma se formalizó en Oslo en el Centro de Cálculo Noruego, en el que intentaban simular movimientos navíos y sus cargas entre diferentes puertos usando Simula I. Como encontraban limitaciones en el lenguaje a la hora de plantear un modelo lo suficientemente completo, crearon una versión del lenguaje llamada Simula 67 que ya introducía muchos de los conceptos de lenguajes modernos orientados a objetos. Con este lenguaje ya podían declarar tipos concretos de barcos con propiedades y comportamientos muy detallados y específicos, creando así simulaciones mucho mejores. Anteriormente a este punto, los conceptos de la programación orientada a objetos eran difusos, no estaban explícitos en los lenguajes y eran más bien una forma de organizar el código que un modelo de programación establecido.

En este artículo vamos a ver los conceptos fundamentales de la programación orientada a objetos, y los aplicaremos a Python.

Sigue leyendo Python y programación orientada a objetos