Archivo de la etiqueta: Cuatris

¡Cuatris terminado!: refactorizando el clon de Tetris

Refactorizar es un término obviamente importado del inglés (refactor) que se suele usar para referirse a la actividad de mejorar la calidad del código de un programa, reorganizándolo para que quede más organizado, con menos “números mágicos”, uniformizando la calidad y haciéndolo más fácil de mantener. Se trata de alterar la estructura del código sin, necesariamente, cambiar su comportamiento.

Para terminar nuestro clon de Tetris os voy a proponer una siguiente versión del programa en el que:

  1. Mejoramos la gestión de textos en la pantalla
  2. Generalizamos comportamiento repetido en el tratamiento de eventos, creando nuevos métodos
  3. Queda totalmente orientado a objetos, pudiendo ser ejecutado con las siguientes líneas:
jugar = True
while jugar == True:
    juego = Cuatris(30) # 30: lado del cuadrado
    jugar = juego.ejecutar_juego()

No voy a entrar en detalles acerca de las transformaciones del código, sino que lo que haré será, directamente, dejaros el código en el recuadro inferior (que tendréis que desplegar) y desearos un buen verano:

import pygame
import random

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, pantalla, lado_cuadrado):
        self.matriz = matriz
        # self.color = color
        # self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, Cuatris.colores[0], rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, Cuatris.colores[self.matriz[i][j]])
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, Cuatris.colores[0])

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    if resto.matriz[self.y + i][self.x + j] != 0:
                        return True
        return False

class Recipiente(Pieza):
    
    def __quitar_fila(self, linea):
        'retira la fila indicada, poniendo una nueva fila en la parte de arriba de la pantalla'
        for fila in range(linea, 0, -1):
            self.matriz[fila] = self.matriz[fila - 1]
        self.matriz[0] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
        
    def comprobar_lineas(self, otra_pieza):
        'comprueba las líneas formadas por la nueva pieza'
        lineas = 0
        for fila in range(otra_pieza.y, otra_pieza.y + otra_pieza.filas):
            producto = 1;
            for valor in self.matriz[fila]:
                producto = producto * valor
                if producto == 0:
                    break
            if producto > 0:
                lineas = lineas +1
                self.__quitar_fila(fila)
        return lineas
        
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo, devolviendo el nº de líneas que se han hecho'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] != 0:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]
        return self.comprobar_lineas(otra_pieza)      

class Cuatris:
    'Objeto Cuatris: implementa la lógica del videojuego'

    # Constantes de los colores
    BLANCO = (255,255,255)
    GRIS = (127,127,127)
    NEGRO = (0, 0, 0)
    MAGENTA = (200,0,200)
    VERDE = (0, 200, 0)
    ROJO = (200, 0, 0)
    AZUL = (0, 0, 200)
    AMARILLO = (200, 200, 0)
    MARRON = (0, 200, 200)
    BLANCO = (200, 200, 200)
    colores = [NEGRO, GRIS, MARRON, AZUL, BLANCO, VERDE, ROJO, MAGENTA, AMARILLO]

    

    matriz_L = [(2, 0),
                (2, 0),
                (2, 2)]

    matriz_J = [(0, 3),
                (0, 3),
                (3, 3)]

    matriz_I = [(4, 0),
                (4, 0),
                (4, 0),
                (4, 0)]

    matriz_S = [(0, 5, 5),
                (5, 5, 0)]

    matriz_Z = [(6, 6, 0),
                (0, 6, 6)]

    matriz_T = [(7, 7, 7),
                (0, 7, 0)]

    matriz_O = [(8, 8),
                (8, 8)]

    matrices = [0, 0, matriz_L, matriz_J, matriz_I, matriz_S, matriz_Z, matriz_T, matriz_O]

    matriz_siguiente = [[1, 1, 1, 1, 1, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 1, 1, 1, 1, 1]]

    
    
    # Nº de cuadros por segundo
    FPS = 60

    # Lado del cuadrado por defecto
    SLOT = 40

    # Evento de Gravedad
    GRAVEDAD = pygame.USEREVENT + 1

    # Coordenadas iniciales de las piezas
    x_ini = 4
    y_ini = 0
    
    def __init__(self, lado_cuadrado):
        try:
            self.slot = int(lado_cuadrado)
        except ValueError:
            # Valor por defecto
            self.slot = SLOT
        self.lineas = 0
        self.lineas_siguiente_nivel = 20
        self.nivel = 0
        self.ancho = 21*self.slot
        self.alto = 22*self.slot
        # inicializamos pygame
        pygame.init()

        # definición de la pantalla
        self.pantalla = pygame.display.set_mode((self.ancho, self.alto))
        self.pantalla.fill(Cuatris.NEGRO)
        matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
        self.siguiente = Recipiente (Cuatris.matriz_siguiente, self.pantalla, self.slot)
        self.siguiente.pinta(13, 0)

        self.recipiente = Recipiente (matriz_Recipiente, self.pantalla, self.slot)
        self.recipiente.pinta(0, 0)
        
        self.pieza = self.siguiente_pieza()
        self.pieza.pinta(4,0)
        self.pieza_siguiente = self.siguiente_pieza()
        self.pieza_siguiente.pinta(15,1)

        pygame.display.update()

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

        # Evento de gravedad
        
        self.t_CAIDA = 70
        self.t_GRAVEDAD = 2000 #milisegundos
        pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_GRAVEDAD)
        
        
    def siguiente_pieza(self):
        n_pieza = random.randint(2, 8)
        pieza = Pieza (self.matrices[n_pieza], self.pantalla, self.slot)
        return pieza

    def _imprime(self, font, cadena, left, top, color, fondo):
        text = font.render(cadena, True, color, fondo)
        textrect = text.get_rect()
        textrect.left= left
        textrect.top= top
        self.pantalla.blit(text, textrect)

    def marcador(self):
        basicfont = pygame.font.SysFont(None, self.slot)
        self._imprime(basicfont,'Líneas: '+str(self.lineas), 13*self.slot, 8*self.slot, Cuatris.BLANCO, Cuatris.NEGRO)
        self._imprime(basicfont,'Nivel: '+str(self.nivel), 13*self.slot, 9*self.slot, Cuatris.GRIS, Cuatris.NEGRO) 
        self._imprime(basicfont,'Siguiente: ' + str(self.lineas_siguiente_nivel), 13*self.slot, 10*self.slot, Cuatris.GRIS, Cuatris.NEGRO)
        

    def juego_terminado(self):
        basicfont = pygame.font.SysFont(None, self.slot) # a refactorizar
        left = self.pantalla.get_rect().centerx - len('JUEGO T')*self.slot
        top = self.pantalla.get_rect().centery
        self._imprime(basicfont,'JUEGO TERMINADO', left, top, Cuatris.ROJO, Cuatris.GRIS)
        self._imprime(basicfont,'INTRO PARA REPETIR', left, top+self.slot, Cuatris.ROJO, Cuatris.GRIS)

    def giro(self, antihorario, pieza, recipiente):
        pieza.auto_borra()
        if antihorario:
            pieza.rotarM90()
        else:
            pieza.rotar90()
        if pieza.colisiona_con(recipiente):
            pieza.mover(-1,0)
        pieza.auto_pinta()

    def mover_derecha(self, unidades, pieza, recipiente):
        pieza.auto_borra()
        pieza.mover(unidades,0)
        if pieza.colisiona_con(recipiente):
            pieza.mover(-unidades, 0)
        pieza.auto_pinta()

    def ejecutar_juego(self):
        
        continuar = True

        self.marcador()

        while continuar: 
            # pausa hasta el siguiente "tick" de reloj
            self.clock.tick(Cuatris.FPS)

            # detección de evento QUIT (aspa)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    continuar = False
                else:
                    # Procesar la gravedad
                    if event.type == Cuatris.GRAVEDAD:
                        self.pieza.auto_borra()
                        self.pieza.mover(0, 1)
                        if self.pieza.colisiona_con(self.recipiente):
                            pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_GRAVEDAD)
                            self.pieza.mover(0,-1)
                            # L.auto_pinta()
                            # Incorporar pieza a "P"
                            self.recipiente.auto_borra()
                            self.lineas = self.lineas + self.recipiente.incorporar(self.pieza)
                            # niveles
                            if self.lineas >= self.lineas_siguiente_nivel:
                                self.nivel = self.nivel +1
                                self.lineas_siguiente_nivel = self.lineas_siguiente_nivel + 20
                                if self.t_GRAVEDAD > 100:
                                    self.t_GRAVEDAD = self.t_GRAVEDAD - 1000
                            self.marcador()
                            self.recipiente.auto_pinta()
                            self.pieza_siguiente.auto_borra()
                            self.pieza = self.pieza_siguiente
                            self.pieza.pinta(4,0)
                            self.pieza_siguiente = self.siguiente_pieza()
                            self.pieza_siguiente.pinta(15,1)
                            if self.pieza.colisiona_con(self.recipiente):
                                continuar = False
                                self.juego_terminado()
                        else:
                            self.pieza.auto_pinta()
                
                    if event.type == pygame.KEYDOWN:

                        # El giro de las piezas puede provocar colisiones por el lado derecho
                        if event.key == pygame.K_z:
                            self.giro(True, self.pieza, self.recipiente)

                        if event.key == pygame.K_x:
                            self.giro(False, self.pieza, self.recipiente)

                        if event.key == pygame.K_LEFT:
                            self.mover_derecha(-1,self.pieza, self.recipiente)

                        if event.key == pygame.K_RIGHT:
                            self.mover_derecha(1,self.pieza, self.recipiente)

                        # acelerar gravedad
                        if event.key == pygame.K_DOWN:
                            pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_CAIDA)
                        else:
                            pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_GRAVEDAD)

                    pygame.display.update()
        while True:
            # pausa hasta el siguiente "tick" de reloj
            self.clock.tick(Cuatris.FPS)
            for event in pygame.event.get():
                if event.type==pygame.KEYDOWN and event.key == pygame.K_RETURN:
                    return True
                else:
                    return False
            
jugar = True
while jugar == True:
    juego = Cuatris(30)
    jugar= juego.ejecutar_juego()

print ("Fin del juego")
pygame.quit()

Recuerda que, como siempre, el código que os proporciono es una propuesta y no la única solución posible; de hecho, el código sigue siendo patentemente mejorable: faltan comentarios, ha quedado ligeramente desorganizado y hay métodos de los objetos que son excesivamente largos. Así pues, siéntete libre de dedicar tus ratos libres vacacionales en mejorarlo y, si tienes dudas o cualquier consulta que quieras hacer, no dudes en ponerte en contacto mediante los comentarios o el formulario de contacto.

Cuatris v0.9: nuestro clon de Tetris está casi listo

Poco nos queda ya para tener un clon de Tetris con el que poder jugar con nuestros amigos y familiares. Lo que vamos a hacer en este artículo, quizá cambiando un poco la estructura de la serie, es mostrar un recuadro con la siguiente pieza que saldrá, la puntuación del jugador (en nº de líneas), el nivel en el que está y cuántas líneas le quedan para pasar al siguiente nivel.

El Cuatris: casi, casi terminado
El Cuatris: casi, casi terminado

Recapitulando, hasta ahora hemos hecho los siguientes avances:

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

Para el siguiente capítulo lo que haremos será aplicar una actividad llamada refactorización de forma que tengamos el videojuego 100 % orientado a objetos y sea muy fácil implementar una opción para repetir la partida si el jugador pierde, dándolo así por terminado y dejándoos a vosotros el resto de mejoras que se os ocurran.

Os recomiendo qué debéis leer si llegáis a PItando en este momento, para poder aprovechar este artículo:

  1. La serie de Python, en general.
  2. La serie de Pygame, en particular.

Sigue leyendo Cuatris v0.9: nuestro clon de Tetris está casi listo

Líneas en el clon de Tetris

Estamos muy cerca ya de tener un clon de Tetris funcional, es decir, “algo” con lo que poder hacer líneas y que por lo tanto reproduzca la mecánica del juego. Recapitulando, hasta ahora hemos hecho los siguientes avances:

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

El artículo de hoy contempla muchas modificaciones al programa que tenemos construido, a saber:

  • Detectar que se ha producido una línea
  • Retirar dicha línea del área de juego
  • Generar piezas aleatoriamente, de diferentes colores.
  • Retoques gráficos al área de juego

Con esto podremos estar jugando indefinidamente y tendremos el juego listo para crear puntuaciones, niveles, y representar en la pantalla tanto la puntuación como la siguiente pieza, por ejemplo.

Os recomiendo qué debéis leer si llegáis a PItando en este momento, para poder aprovechar este artículo:

  1. La serie de Python, en general.
  2. La serie de Pygame, en particular.

Sigue leyendo Líneas en el clon de Tetris

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!