Hoy te mostraré un juego que recién he programado usando el lenguaje de programación Python y la librería PyGame.
Se trata del videojuego Memorama, también conocido como Memoria. Es un juego en donde hay varias tarjetas en donde hay que voltearlas y encontrar el par de cada una. El jugador gana cuando encuentra todos los pares de tarjetas.
El juego que he desarrollado en PyGame cuenta con imágenes, música de fondo y sonidos, además de que este Memorama o juego de memoria es totalmente jugable.
Obviamente el código es open source y gratuito, así que puedes descargarlo, probarlo, usarlo y modificarlo a tu gusto.
Características del juego memoria en Python
Como lo mencioné anteriormente, este juego está desarrollado con PyGame. Utiliza imágenes tomadas de flaticon y sonidos de freesound. Muchas gracias a ambas páginas por poner a la disposición del público tan buenas creaciones.
En el inicio del juego se muestran todas las imágenes originales y sin ser ocultadas. En cuanto se hace clic en el botón de “Iniciar juego” las imágenes son mezcladas y ahora se muestra el lado oculto de cada tarjeta o carta.
Al hacer clic en cada imagen, la misma gira o “se voltea” para mostrar la imagen real. Y después se puede buscar su pareja.
El jugador gana cuando ya no hay tarjetas ocultas; y puede volver a iniciar el juego. Cuando el juego de memoria es reiniciado, se mezclan y ocultan las imágenes. Así de manera infinita.
A través del post te explicaré los fragmentos del código más importantes de este juego de Memorama open source y gratuito programado con Python y PyGame.
Recuerda que para probarlo debes contar con Python y PIP. Además, debes instalar pygame con:
pip install pygame
Algoritmo general
Lo que hacemos es tener una matriz que representa al juego en sí. La matriz, que no es otra cosa más que un arreglo de arreglos, tiene filas. Cada fila tiene un cuadro. En esos cuadros guardamos toda la información relacionada a la tarjeta.
Por ejemplo, cada cuadro tiene propiedades como descubierto
que indica si se debería mostrar porque el jugador lo descubrió antes, así como la fuente de la imagen o la propiedad mostrar
que indica si la imagen original se debería mostrar porque se está buscando su par.
Luego, pintamos todos esos cuadros en la pantalla usando PyGame, escuchamos el clic del usuario y verificamos cuál clic es.
En caso de que sea el primer clic, entonces modificamos algunas variables para esperar el segundo clic (que será cuando el jugador busque la pareja de la tarjeta anteriormente seleccionada).
Cuando escuchamos el segundo clic verificamos a través de la ruta de la imagen si las imágenes son iguales, en ese caso reproducimos el sonido de éxito y ponemos ambos cuadros como descubiertos.
Si el jugador se equivoca en la tarjeta, entonces establecemos una marca de tiempo para indicarle al programa que las tarjetas deben ocultarse dentro de determinados segundos.
El jugador gana el juego de Memorama cuando todos los cuadros tienen la propiedad descubierto en True
.
Recuerda que todo el proceso del juego se estará realizando en un ciclo infinito.
Por cierto, ya he hecho este juego anteriormente en JavaScript.
Clase Cuadro
Como lo dije, todo el juego es una matriz de instancias de la clase Cuadro
que se ve así:
"""
Una clase que representa el cuadro. El mismo tiene una imagen y puede estar
descubierto (cuando ya lo han descubierto anteriormente y no es la tarjeta buscada actualmente)
o puede estar mostrado (cuando se voltea la imagen)
También tiene una fuente o nombre de imagen que servirá para compararlo más tarde
"""
class Cuadro:
def __init__(self, fuente_imagen):
self.mostrar = True
self.descubierto = False
"""
Una cosa es la fuente de la imagen (es decir, el nombre del archivo) y otra
la imagen lista para ser pintada por PyGame
La fuente la necesitamos para más tarde, comparar las tarjetas
"""
self.fuente_imagen = fuente_imagen
self.imagen_real = pygame.image.load(fuente_imagen)
En la línea 19 estamos cargando la imagen, de este modo cada instancia tendrá su propia imagen. Recuerda que también guardamos la fuente de la imagen (la cadena) para más tarde comparar si dos imágenes son iguales.
Una vez definida la clase, veamos también el tablero del juego:
cuadros = [
[Cuadro("assets/coco.png"), Cuadro("assets/coco.png"),
Cuadro("assets/manzana.png"), Cuadro("assets/manzana.png")],
[Cuadro("assets/limón.png"), Cuadro("assets/limón.png"),
Cuadro("assets/naranja.png"), Cuadro("assets/naranja.png")],
[Cuadro("assets/pera.png"), Cuadro("assets/pera.png"),
Cuadro("assets/piña.png"), Cuadro("assets/piña.png")],
[Cuadro("assets/plátano.png"), Cuadro("assets/plátano.png"),
Cuadro("assets/sandía.png"), Cuadro("assets/sandía.png")],
]
El constructor de Cuadro
recibe únicamente una cosa: la ruta de la imagen. En este caso definimos dos cuadros por cada imagen, pues recuerda que debe buscarse el par de cada una. Más tarde veremos cómo mezclarlas.
Fíjate bien en la matriz, pues es un arreglo que tiene arreglos. Cada arreglo interno tiene 4 imágenes o mejor dicho, 4 instancias de Cuadro
.
En total hay 16 imágenes en este juego de Memoria en Python, aunque recordemos que se repiten, así que solo hay 8 imágenes únicas.
Colores y sonidos
Definimos algunos colores del juego (para el color del botón, color de fondo, etcétera) y también los sonidos que vamos a reproducir a lo largo del mismo:
# Colores
color_blanco = (255, 255, 255)
color_negro = (0, 0, 0)
color_gris = (206, 206, 206)
color_azul = (30, 136, 229)
# Los sonidos
sonido_fondo = pygame.mixer.Sound("assets/fondo.wav")
sonido_clic = pygame.mixer.Sound("assets/clic.wav")
sonido_exito = pygame.mixer.Sound("assets/ganador.wav")
sonido_fracaso = pygame.mixer.Sound("assets/equivocado.wav")
sonido_voltear = pygame.mixer.Sound("assets/voltear.wav")
Botón para iniciar juego
El usuario debe hacer clic en un botón para iniciar el juego. En este caso el botón no es más que un rectángulo. Primero definimos la fuente que vamos a usar para escribir texto dentro del botón, y luego el botón:
# La fuente que estará sobre el botón
tamanio_fuente = 20
fuente = pygame.font.SysFont("Arial", tamanio_fuente)
xFuente = int((anchura_boton / 2) - (tamanio_fuente / 2))
yFuente = int(altura_pantalla - altura_boton)
# El botón, que al final es un rectángulo
boton = pygame.Rect(0, altura_pantalla - altura_boton,
anchura_boton, altura_pantalla)
Los cálculos que ves son para posicionar el botón en la parte inferior. Al analizar el código fuente completo verás a lo que me refiero.
Para dibujar el botón hacemos lo siguiente, dependiendo de si el juego está o no iniciado:
# También dibujamos el botón
if juego_iniciado:
# Si está iniciado, entonces botón blanco con fuente gris para que parezca deshabilitado
pygame.draw.rect(pantalla_juego, color_blanco, boton)
pantalla_juego.blit(fuente.render(
"Iniciar juego", True, color_gris), (xFuente, yFuente))
else:
pygame.draw.rect(pantalla_juego, color_azul, boton)
pantalla_juego.blit(fuente.render(
"Iniciar juego", True, color_blanco), (xFuente, yFuente))
Funciones útiles
Aunque el juego no reside en una clase separada, y todo está de manera global, tenemos algunas funciones útiles que nos permiten separar el código.
# Ocultar todos los cuadros
def ocultar_todos_los_cuadros():
for fila in cuadros:
for cuadro in fila:
cuadro.mostrar = False
cuadro.descubierto = False
def aleatorizar_cuadros():
# Elegir X e Y aleatorios, intercambiar
cantidad_filas = len(cuadros)
cantidad_columnas = len(cuadros[0])
for y in range(cantidad_filas):
for x in range(cantidad_columnas):
x_aleatorio = random.randint(0, cantidad_columnas - 1)
y_aleatorio = random.randint(0, cantidad_filas - 1)
cuadro_temporal = cuadros[y][x]
cuadros[y][x] = cuadros[y_aleatorio][x_aleatorio]
cuadros[y_aleatorio][x_aleatorio] = cuadro_temporal
def comprobar_si_gana():
if gana():
pygame.mixer.Sound.play(sonido_exito)
reiniciar_juego()
# Regresa False si al menos un cuadro NO está descubierto. True en caso de que absolutamente todos estén descubiertos
def gana():
for fila in cuadros:
for cuadro in fila:
if not cuadro.descubierto:
return False
return True
def reiniciar_juego():
global juego_iniciado
juego_iniciado = False
def iniciar_juego():
pygame.mixer.Sound.play(sonido_clic)
global juego_iniciado
# Aleatorizar 3 veces
for i in range(3):
aleatorizar_cuadros()
ocultar_todos_los_cuadros()
juego_iniciado = True
Tenemos varias funciones que, espero, se expliquen por sí mismas. Por ejemplo, una función oculta todos los cuadros del arreglo. Otra función verifica si el jugador ha ganado, existe otra que mezcla las tarjetas, etcétera.
Recuerda que usamos la palabra reservada global
para modificar una variable global, pues en Python podemos leer pero no modificar las variables globales, al menos que usemos esta palabra reservada.
Eventos del juego
Ahora vamos a ver cómo detectar el clic de las imágenes o el botón, además del evento para cerrar el juego. Para cerrar el juego en la señal de Python, invocamos a sys.exit
:
# Si quitan el juego, salimos
if event.type == pygame.QUIT:
sys.exit()
Existe otro evento, que es el clic del botón. Debido a que es un rectángulo, podemos usar los métodos de PyGame para saber si el clic colisiona con el botón:
# Si hicieron clic y el usuario puede jugar...
elif event.type == pygame.MOUSEBUTTONDOWN and puede_jugar:
"""
xAbsoluto e yAbsoluto son las coordenadas de la pantalla en donde se hizo
clic. PyGame no ofrece detección de clic en imagen, por ejemplo. Así que
se deben hacer ciertos trucos
"""
# Si el click fue sobre el botón y el juego no se ha iniciado, entonces iniciamos el juego
xAbsoluto, yAbsoluto = event.pos
if boton.collidepoint(event.pos):
if not juego_iniciado:
iniciar_juego()
En este caso collidepoint
recibe x
e y
, mismos que ya están dentro de event.pos
.
Finalmente veamos cómo detectar si el usuario hizo clic en una imagen, pues en este método es en donde se lleva la mayor parte del funcionamiento del juego memorama en Python:
# Si no hay juego iniciado, ignoramos el clic
if not juego_iniciado:
continue
"""
Ahora necesitamos a X e Y como índices del arreglo. Los índices no
son lo mismo que los pixeles, pero sabemos que las imágenes están en un arreglo
y por lo tanto podemos dividir las coordenadas entre la medida de cada cuadro, redondeando
hacia abajo, para obtener el índice.
Por ejemplo, si la medida del cuadro es 100, y el clic es en 140 entonces sabemos que le dieron
a la segunda imagen porque 140 / 100 es 1.4 y redondeado hacia abajo es 1 (la segunda posición del
arreglo) lo cual es correcto. Por poner otro ejemplo, si el clic fue en la X 50, al dividir da 0.5 y
resulta en el índice 0
"""
x = math.floor(xAbsoluto / medida_cuadro)
y = math.floor(yAbsoluto / medida_cuadro)
# Primero lo primero. Si ya está mostrada o descubierta, no hacemos nada
cuadro = cuadros[y][x]
if cuadro.mostrar or cuadro.descubierto:
# continue ignora lo de abajo y deja que el ciclo siga
continue
# Si es la primera vez que tocan la imagen (es decir, no están buscando el par de otra, sino apenas
# están descubriendo la primera)
if x1 is None and y1 is None:
# Entonces la actual es en la que acaban de dar clic, la mostramos
x1 = x
y1 = y
cuadros[y1][x1].mostrar = True
pygame.mixer.Sound.play(sonido_voltear)
else:
# En caso de que ya hubiera una clickeada anteriormente y estemos buscando el par, comparamos...
x2 = x
y2 = y
cuadros[y2][x2].mostrar = True
cuadro1 = cuadros[y1][x1]
cuadro2 = cuadros[y2][x2]
# Si coinciden, entonces a ambas las ponemos en descubiertas:
if cuadro1.fuente_imagen == cuadro2.fuente_imagen:
cuadros[y1][x1].descubierto = True
cuadros[y2][x2].descubierto = True
x1 = None
x2 = None
y1 = None
y2 = None
pygame.mixer.Sound.play(sonido_clic)
else:
pygame.mixer.Sound.play(sonido_fracaso)
# Si no coinciden, tenemos que ocultarlas en el plazo de [segundos_mostrar_pieza] segundo(s). Así que establecemos
# la bandera. Como esto es un ciclo infinito y asíncrono, podemos usar el tiempo para saber
# cuándo fue el tiempo en el que se empezó a ocultar
ultimos_segundos = int(time.time())
# Hasta que el tiempo se cumpla, el usuario no puede jugar
puede_jugar = False
comprobar_si_gana()
Los comentarios son los que explican todo el código que ya expliqué anteriormente en el algoritmo. Solo fíjate en el truco que hace que sepamos en cuál imagen hicieron clic, pues debido a que todo es una cuadrícula, podemos obtener los índices a partir de las coordenadas.
Presta atención a la variable ultimos_segundos
, esa variable indica si se debe ocultar alguna imagen dentro de determinados segundos. Se usa la misma lógica que el ejercicio de Semáforo en arduino sin delay.
También fíjate que en cada clic de la imagen comprobamos si el usuario gana, invocando en la línea 53 a la función que previamente mostré.
Ocultar imágenes si no se encuentra pareja
Cuando se hace clic en la segunda imagen, en caso de que esta no sea el par de la anteriormente seleccionada, se ocultan de nuevo. El código que hace eso posible es:
ahora = int(time.time())
# Y aquí usamos la bandera del tiempo, de nuevo. Si los segundos actuales menos los segundos
# en los que se empezó el ocultamiento son mayores a los segundos en los que se muestra la pieza, entonces
# se ocultan las dos tarjetas y se reinician las banderas
if ultimos_segundos is not None and ahora - ultimos_segundos >= segundos_mostrar_pieza:
cuadros[y1][x1].mostrar = False
cuadros[y2][x2].mostrar = False
x1 = None
y1 = None
x2 = None
y2 = None
ultimos_segundos = None
# En este momento el usuario ya puede hacer clic de nuevo pues las imágenes ya estarán ocultas
puede_jugar = True
Dibujar memorama
Ya vimos todos los eventos, ahora veamos cómo dibujar el juego de memoria, mostrar las imágenes ya sea ocultas o volteadas, etcétera. El dibujo de la pantalla queda así:
# Hacer toda la pantalla blanca
pantalla_juego.fill(color_blanco)
# Banderas para saber en dónde dibujar las imágenes, pues al final
# la pantalla de PyGame son solo un montón de pixeles
x = 0
y = 0
# Recorrer los cuadros
for fila in cuadros:
x = 0
for cuadro in fila:
"""
Si está descubierto o se debe mostrar, dibujamos la imagen real. Si no,
dibujamos la imagen oculta
"""
if cuadro.descubierto or cuadro.mostrar:
pantalla_juego.blit(cuadro.imagen_real, (x, y))
else:
pantalla_juego.blit(imagen_oculta, (x, y))
x += medida_cuadro
y += medida_cuadro
# También dibujamos el botón
if juego_iniciado:
# Si está iniciado, entonces botón blanco con fuente gris para que parezca deshabilitado
pygame.draw.rect(pantalla_juego, color_blanco, boton)
pantalla_juego.blit(fuente.render(
"Iniciar juego", True, color_gris), (xFuente, yFuente))
else:
pygame.draw.rect(pantalla_juego, color_azul, boton)
pantalla_juego.blit(fuente.render(
"Iniciar juego", True, color_blanco), (xFuente, yFuente))
# Actualizamos la pantalla
pygame.display.update()
Para saber si se dibuja la imagen oculta o la real, accedemos a la propiedad mostrar
y descubierto
de cada cuadro. En caso de que alguna de estas esté en True
, mostramos la imagen del cuadro.
Caso contrario, dibujamos la imagen oculta. Recuerda que todo esto se estará repitiendo varias veces por segundo.
Poniendo todo junto
Como lo dije anteriormente, solo expuse el código más importante. El código completo lo encuentras en mi GitHub.
Al final, solo debes instalar Python y Pip, instalar la dependencia de PyGame y ejecutar el script de memoria.py con:
python memoria.py
Acá una captura de mí jugando:
Te invito a leer más sobre programación usando Python en mi blog. También te dejo el código de un memorama para la web usando JavaScript.
Tienes algun ejemplo para crear una mazmorra en Python, con matriz de 5 x 5 con una entrada, una salida, una trampilla, el aventurero solo tiene 4 movimientos (norte,sur,este y oeste) con el objetivo de salir de la mazmorra sin caer en la trampilla porque sino muere, puede ser un esqueleto o una trampilla.
Es para un ejercicio que estoy haciendo con un curso de la Once. Solo me falta eso para terminarlo y no logro hacerlo. Me puede ayudar por favor
Hola. Todos los ejemplos que tengo están publicados en mi blog, usted puede buscarlos.
Por si necesita ayuda le atiendo en https://parzibyte.me/#contacto
Dejaste los enlaces para descargar los sonidos del juego en alguna parte?, no los encontré, gracias me sirvió de mucho este tutorial
Hola. Gracias por sus comentarios.
En cuanto a los sonidos, claro, están en el repositorio.
Saludos!
Hola que tal !, se me hace super interesante tu implementación del memorana usando pygame. Pero en caso de que quisiéramos usar imágenes diferentes para el memorama, es decir, en una carta usar la imagen y en otra carta que sea texto, ya no se podría usar la fuente para encontrar el par … ahi cómo se podría hacer??? ,… le agradecería me pudiese apoyar con una idea, estoy trabajando con algo similar en python.
Hola. Con gusto le ayudo en https://parzibyte.me#contacto
Saludos!