Mostrando entradas con la etiqueta pop(). Mostrar todas las entradas
Mostrando entradas con la etiqueta pop(). Mostrar todas las entradas

sábado, 6 de agosto de 2022

Curso Guizero 6. Construyendo una aplicacion más compleja. Emoji Game GUI.

Anteriormente Guizero 5.- Controlando la GUI.

En este capítulo vamos a utilizar Guizero para crear un juego. Como el programa que vamos a construir va a ser un poco "complejo" lo vamos a descomponer en varios pasos para crearlo. 

El Juego de los Emojis.


En este juego vamos a ver unos emojis en la parte superior y otros en la parte inferior. Tendremos que buscar cual de los emojis de la parte superior, solamente uno, se repite en la parte inferior. Tendremos que poner en juego nuestra memoria visual.

juego de las parejas


Por ejemplo, en la imagen de arriba el único emoji que está tanto en el cuadro superior como en el cuadro inferior es el emoji amarillo con una mascarilla.

El juego incorpora muchos de los elementos que hemos visto en las últimas semanas, como diferentes tipos de widgets, elementos de diseño, diferentes tipos de comandos y eventos.

Descomponiendo el programa en Tareas más Sencillas.


Puesto que este programa va a ser más elaborado que los de costumbre e involucra múltiples componentes, vamos a descomponerlo en partes más sencillas.

En informática, el proceso de dividir una tarea en etapas más simples que se pueden volver a ensamblar en el programa completo se denomina descomposición.

Para descomponer el programa en partes más pequeñas y sencillas, un programador podría empezar escribiendo los objetivos del programa. Luego podría hacer una lista de todos los recursos que tiene para alcanzar ese objetivo. A continuación, debe resolver todas las etapas que deben completarse para alcanzar el objetivo. Luego debe averiguar como completar cada etapa. Pues eso es lo que vamos a hacer nosotros.

Empecemos pensando lo que el juego necesita:

- Mostrar dos conjuntos de imágenes (emojis) elegidos al azar, y que solo uno esté representado una única vez en cada grupo.
- Permitir al jugador elegir una imagen.
- Mostrar un advertencia que le diga al jugador si la imagen elegida es correcta o incorrecta.
- Mantener la puntuación del jugador.
- Mostrar el tiempo de juego.

El juego lo haremos usando los elementos que hemos visto de Guizero y usando una biblioteca de emojis ya creada que veremos como descargar en un paso posterior.


Diseños en Cuadricula.

Para esta aplicación vamos a usar el Gestor de Geometría GRID. Repasemos en que consiste. En el gestor de geometría GRID los widgets se organizan siguiendo una cuadrícula. Para cada widget que se agregue al contenedor hay que especificar sus coordenadas de cuadrícula, para que GUIZERO sepa donde colocarlo. Esto significa que, al especificar el diseño de la cuadrícula para un contenedor, las coordenadas de la cuadrícula se convierten en un parámetro necesario para cualquier widget alojado en el mismo.

Cuadricula

Las coordenadas de la cuadrícula se proporcionan como un parámetro inicial en la forma [x,y], donde x e y son números enteros que comienzan desde el 0. El valor de x representa la columna de la cuadrícula y el valor de y representa la fila. Para familiarizarse con estos parámetros, intenta cambiar las posiciones de los cuadros de colores en el siguiente ejemplo. ¿Puedes predecir la salida antes de ejecutar el código?

from guizero import App, Text, Box

app = App()

box = Box(app, layout="grid", width="fill", height="fill")

rojo = Text(box, bg="red", grid=[0,0])
azul = Text(box, bg="blue", grid=[0,1])
verde = Text(box, bg="green", grid=[1,0])
blanco = Text(box, bg="white", grid=[1,1])

app.display()
Se puede usar un diseño de cuadricula en cualquier contenedor configurando el parámetro "layout" del mismo. No se necesita especificar el tamaño de la cuadrícula, ya que se expandirá ajustándose al tamaño de las coordenadas mayores dadas a un widget en ese contenedor. Cualquier contendor (App, Box o Window) se puede configurar para que usen el gestor de geometría GRID lo que significa que los diseños de cuadrícula se pueden usar en combinación con el diseño estándar de GUIZERO en la misma aplicación.

El siguiente código usa el diseño de cuadrícula para crear algo que debería ser familiar. ¿Puedes adivinar qué es antes de ejecutar el código?

from guizero import App, PushButton

app = App(layout="grid")

button1 = PushButton(app, text="1", grid=[0,0])
button2 = PushButton(app, text="2", grid=[1,0])
button3 = PushButton(app, text="3", grid=[2,0])
button4 = PushButton(app, text="4", grid=[0,1])
button5 = PushButton(app, text="5", grid=[1,1])
button6 = PushButton(app, text="6", grid=[2,1])
button7 = PushButton(app, text="7", grid=[0,2])
button8 = PushButton(app, text="8", grid=[1,2])
button9 = PushButton(app, text="9", grid=[2,2])
button0 = PushButton(app, text="0", grid=[1,3])

app.display()

Aplicaciones del diseño en cuadricula.


El diseño de cuadrícula tiene muchas aplicaciones y es especialmente útil si necesitas colocar objetos en filas o columnas.

Veamos cómo crear un formulario de entrada de datos utilizando diseños automáticos y de cuadrícula.





La creación de este formulario con el diseño automático (usando el gestor de geometría pack) requeriría:
- crear un cuadro para cada widget de texto y cuadro de texto 
- alinear los widgets para que aparezcan a la izquierda 
- configurar el ancho del widget de texto para que todos se alineen

from guizero import App, Box, Text, TextBox

app = App()

box_fname = Box(app, width="fill")
lbl_fname = Text(box_fname, text="Nombre", align="left", width=20)
txt_fname = TextBox(box_fname, align="left")

box_sname = Box(app, width="fill")
lbl_sname = Text(box_sname, text="Apellido", align="left", width=20)
txt_sname = TextBox(box_sname, align="left")

box_dob = Box(app, width="fill")
lbl_dob = Text(box_dob, text="Fecha de Nacimiento", align="left", width=20)
txt_dob = TextBox(box_dob, align="left")

app.display()

Al usar el diseño en cuadrícula, el código se vuelve más simple y fácil. No hay que calcular los anchos de los elementos para que todo cuadre.

from guizero import App, Text, TextBox

app = App(layout="grid")

lbl_fname = Text(app, text="Nombre", grid=[0,0])
txt_fname = TextBox(app, grid=[1,0])

lbl_sname = Text(app, text="Apellido", grid=[0,1])
txt_sname = TextBox(app, grid=[1,1])

lbl_dob = Text(app, text="Fecha de Nacimiento", grid=[0,2])
txt_dob = TextBox(app, grid=[1,2])

app.display()

¿Puedes ver las diferencias entre los dos códigos?


Comenzando el Juego.

Ahora es el momento de comenzar a armar el Juego de los Emojis.

Descomposición:

Comenzaremos por descomponer el Juego Emoji en varias etapas, cada una de las cuales aún requerirá una mayor descomposición. Para construir el juego, crearemos:

- Una cuadrícula con Emojis seleccionados al azar.
- Una segunda cuadrícula con botones en los cuales insertaremos un emoji de la primera cuadricula y otros elegidos al azar pero que no se encuentren en la de arriba.
- Una función que sea llamada al presionar el botón y que verifique si se este mostró el emoji correspondiente.
- Un cronómetro de cuenta atrás que le muestre al jugador el tiempo disponible.
- un widget que haga de marcador para conservar la puntuación del juego.

Construyendo una cuadricula de Emojis.

La primera etapa de la descomposición anterior, que es crear la primera cuadrícula que contendrá los emojis elegidos al azar, implica a su vez varias tareas distintas. Por tanto, también deberemos descomponerla en otras tareas más sencillas:

- Crear un directorio donde descargaremos las imágenes de los emojis.
- Poner esos emojis en una lista, mezclarlos aleatoriamente y elegir nueve de ellos.
- mostrar los emojis elegidos en la cuadrícula.

Conseguir y cargar los Emojis en la lista.

Para crear el juego necesitamos unos Emojis. Usaremos los Emojis que se usaron para Twitter. Para ello descarga el archivo emojis.zip desde el enlace https://rpf.io/fl-guis-emojis, abre el archivo zip y copia la carpeta con los emojis en el mismo directorio donde tengas el código del programa.

El juego deberá elegir nueve emojis al azar y organizarlos en una cuadrícula. Una forma sencilla de hacer esto es poner todos los emojis en una lista y mezclarlos aleatoriamente.

El siguiente código crea una lista aleatoria de elementos, cada uno con el formato ruta/nombre_emoji. La ruta es la ubicación de los emojis en tu computadora; le dirá al código que carga los emojis dónde encontrarlos.

import os
from random import shuffle

# Establece la ruta a la carpeta del emoji en tu computadora.
emojis_dir = "emojis"
# Crea una lista de las ubicaciones de las imágenes de los emojis.
emojis = [os.path.join(emojis_dir, f) for f in os.listdir(
    emojis_dir) if os.path.isfile(os.path.join(emojis_dir, f))]
# Baraja los emojis
shuffle(emojis)

print(emojis)
Empezamos diciendo al ordenador que importe las librerías necesarias. Después establecemos en la variable emojis_dir el nombre del directorio donde están los emojis. (esta carpeta está en el mismo directorio que la aplicación principal). Luego usando compresión de listas vamos a crear una nueva lista con la referencia a cada uno de los emojis.

Con os.listdir(emojis_dir) obtenemos el nombre de todos los archivos y directorios (que son los nombres de los emojis) que están presentes en la carpeta emojis_dir. Ahora bien solamente nos interesan aquellos que efectivamente sean archivos, con lo que con os.path.isfile() comprobamos si la ruta especificada es un archivo regular y si es el caso entonces, lo agregamos finalmente a la lista.


listado de emojis



Una vez tenemos la lista con todos los emojis, para que no estén siempre en el mismo orden, la barajamos pasándosela como parámetro al método shuffle().

Seleccionamos nueve emojis y los mostramos.

A continuación, el código debe elegir nueve emojis y organizarlos en una cuadrícula. Para esta primera etapa, el juego solo necesita mostrar una imagen de cada emoji.

Nota: Intenta crear una cuadrícula de emojis tu mismo, antes de mirar el código a continuación.

El siguiente código crea una lista de widgets de imágenes, cada uno con diferentes coordenadas de cuadrícula dentro de la aplicación. Finalmente, asigna un emoji de la lista de emojis a cada imagen en la cuadrícula.


eligiendo 9 emojis al azar



import os
from random import shuffle
from guizero import App, Box, Picture

# Establece la ruta a la carpeta del emoji en tu computadora.
emojis_dir = "emojis"
# Crea una lista de las ubicaciones de las imagenes de los emojis.
emojis = [os.path.join(emojis_dir, f) for f in os.listdir(
    emojis_dir) if os.path.isfile(os.path.join(emojis_dir, f))]
# Baraja los emojis
shuffle(emojis)

raiz = App("Juego de emojis")
# Crea una caja que será el contenedor de las cuadrículas
pictures_box = Box(raiz, layout="grid")

# Crea una lista vacía a la cual se añadirán las imágenes de los emojis.
imagenes = []
for x in range(0, 3):
    for y in range(0, 3):
        # Pone las imágenes en la lista.
        imagen = Picture(pictures_box, grid=[x, y])
        imagenes.append(imagen)

# Por cada una de las imagenes en la lista.
for imagen in imagenes:
        # hace que la imagen sea un emoji aleatorio.
        imagen.image = emojis.pop()    

raiz.display()

Para asignar las coordenadas a cada widget imagen utilizamos dos bucles "for". Ambos recorren el rango de 0 a 2. Uno asigna su valor a la variable "x" y el otro a la variable "y". 

Para asignar a cada widget Picture una imagen, primero introducimos todos los widget creados (ya con sus coordenadas) en una lista. Posteriormente asignamos la imagen a cada uno mediante la función integrada de Python, .pop(), la cual si no se le asigna ningún argumento selecciona el último elemento de la lista y luego lo elimina de la misma. 

Si no te ha quedado claro, como funciona el método .pop() mira el siguiente código para aclararlo un poco.

test = [1,2,3,4,5]

print(test.pop())

# Imprime la lista "test" después de haber eliminado el último elemento.
print("La nueva lista después de haber usado pop() : ", test, "\n")

Desafios.

  • Intenta adaptar el código para que se muestre una cuadrícula de 4 x 4 en la que se visualicen 16 emojis.
  • Intenta volver a escribir las dos últimas líneas del código que asigna las imágenes a cada widget sin utilizar el método .pop(). Compara ambos códigos el original con .pop() y el que hayas escrito. ¿Es más largo o más complicado?

Añadiendo los botones con las Imágenes.

En esta parte vamos a completar la segunda tarea que nos marcamos al descomponer el problema original, que es añadir los botones que contendrán las imágenes.

En el juego de los Emojis hay dos cuadrículas. Uno superior, que contiene 9 imágenes de Emojis y otro inferior que contiene 9 botones, que se pueden pulsar, con otro tantos Emojis. Solamente un Emoji se repite en ambas cuadrículas y el objetivo del juego es adivinar cual es. (Con 9 emojis no es tarea difícil pero podemos complicarlo mucho si ponemos una cuadricula de 6 x 6 con 36 imágenes distintas y ponemos un tiempo finito para encontrarlo)

Una forma de asegurarnos que haya uno y solamente un Emoji coincidente, es dibujar ambas cuadrículas primero y luego insertar el mismo Emoji en ambas. Para hacer esto primero crearemos una cuadrícula con los botones de los Emojis y luego modificaremos tanto esta cuadrícula como la que creamos antes para agregar el Emoji correspondiente.

Creando una cuadrícula con botones que contengan los Emojis.

Puedes crear una cuadrícula de botones emoji usando el mismo método que en el paso anterior para agregar las imágenes, pero esta vez usando el widget PushButton en lugar de Picture.


Imágenes y botones con emojis juntos



Tendrás que añadir las siguientes líneas de código en el orden correcto para que el programa funcione.

from guizero import PushButton

buttons_box = Box(game_box, layout="grid")

boton = PushButton(buttons_box, grid=[x,y])

botones.append(button)

for boton in botones:
	  boton.image = emojis.pop()

Intenta adaptar el código tu mismo antes de mirar su implementación en el código inferior. 

import os
from random import shuffle
from guizero import App, Box, Picture, PushButton

# Establece la ruta a la carpeta del emoji en tu computadora.
emojis_dir = "emojis"
# Crea una lista de las ubicaciones de las imagenes de los emojis.
emojis = [os.path.join(emojis_dir, f) for f in os.listdir(
    emojis_dir) if os.path.isfile(os.path.join(emojis_dir, f))]
# Baraja los emojis
shuffle(emojis)

raiz = App("Juego de emojis")
# Crea una caja que será el contenedor de las cuadrículas
pictures_box = Box(raiz, layout="grid")
# Crea una caja que será el contendor de los botones
buttons_box = Box(raiz, layout="grid")

# Crea una lista vacia a la cual se añadirán las imagenes de los emojis.
# Primero crearemos los objetos, los métemos en la lista y luego le añadimos
# las imagenes.
imagenes = []
botones = []
for x in range(0, 3):
    for y in range(0, 3):
        # Pone las imagenes en la lista.
        imagen = Picture(pictures_box, grid=[x, y])
        imagenes.append(imagen)
        # Pone los botones en la lista.
        boton = PushButton(buttons_box, grid=[x,y])
        botones.append(boton)

# Por cada una de las imagenes en la lista.
for imagen in imagenes:
        # hace que la imagen sea un emoji aleatorio.
        imagen.image = emojis.pop()

for boton in botones:
        # hace que la imagen dentro del botón sea un emoji aleatorio.
        boton.image = emojis.pop()

raiz.display()
Puesto que las dos cuadrículas son muy similares, es importante asegurarse de que las variables se nombren de forma clara. En el ejemplo hemos separado en una cuadrícula los botones con emojis de la cuadrícula con las imágenes de emojis. Esta cuadricula se llama buttons_box y la cuadricula de las imágenes picture_box. De manera similar, la lista que contiene los PushButton se llama "botones" y la lista de que contiene los Picture se llama "imagenes". Esto hará que el código sea más intuitivo de leer y reducirá el riesgo de que se confundan las listas u otros objetos.

Para hacer que el código sea más eficiente, se asignan coordenadas a los PushButtons utilizando el mismo conjunto de bucles for que antes.


Agregar el Único Emoji Coincidente en ambas cuadrículas.

A continuación debemos asegurarnos de que ambas cuadrículas tenga un Emoji coincidente, es decir que sea el mismo en ambas. Para hacer esto tenemos que cambiar una de las imágenes y uno de los botones por un Emoji que sea el mismo en las dos cuadrículas. Este Emoji se llama emoji_coincidente y se selecciona de nuevo de la lista de Emojis.

Dedica algún tiempo a revisar el código a continuación, que debe agregarse al final del programa. ¿Puedes averiguar qué está haciendo?

# importa la función randint
from random import randint

# escoge un nuevo emoji
emoji_coincidente = emojis.pop()

# selecciona un número al azar
imagen_aleatoria = randint(0,8)
# Cambia una de las imagenes de forma aleatoria por el emoji seleccionado.
imagenes[imagen_aleatoria].image = emoji_coincidente

boton_aleatorio = randint(0,8)
# Cambia uno de los botones de forma aleatoria por el mismo emoji que seleccionamos anteriormente.
botones[boton_aleatorio].image = emoji_coincidente
Este código utiliza una función aleatoria de la biblioteca de Python llamada randint, que selecciona aleatoriamente un número entero dentro de un rango dado; en este caso, el rango es 0–8.

Luego, el programa elige una imagen y un botón y les da a ambos el mismo emoji para mostrar. Esto garantiza que las cuadrículas tengan un emoji en común.

¿Puedes identificar qué emoji es el que se repite en ambas cuadrículas?


Emoji coincidente



Eventos basados en el tiempo.


Descubre como Guizero trata los eventos basados en el tiempo y como añadir un cronómetro regresivo al juego.

La siguiente etapa en el proyecto del juego es añadir un temporizador. Es este punto veremos como funciona Guizero y por qué configurar un temporizador no es sencillo.

Dentro del juego queremos establecer una función que cuente hacia atrás un número preestablecido de segundos y le diga al jugador cuando se le acabe el tiempo de juego. Antes de agregar esto a la aplicación, trata de intentar crear un script en python normal que cuente 10 segundos sin usar ningún elemento de Guizero. Si no estás familiarizado con la función "sleep" de python, es posible que quieras leer sobre ella antes de intentar esta tarea. 

Una forma de realizar lo anterior sería:
from time import sleep

def cuenta_atras(t):
	while t > 0:
		print(t)
		t -= 1
		sleep(1)

cuenta_atras(10)
Utilizamos un bucle while para realizar la misma operación hasta que la variable de tiempo t llega a cero. Esa operación repetida imprime el valor de t, reduce el valor en 1 y luego 'duerme' el programa durante 1 segundo.

Añadiendo el contador a Guizero.

Ahora vamos a intentar añadir este contador regresivo a una aplicación de Guizero.

from guizero import App, Text
from time import sleep

def cuenta_atras(t):
	while t > 0:
		text.value = int(text.value) - 1
		sleep(1)

app = App()
text = Text(app, text=10)
cuenta_atras(10)

app.display()

Cuando intentes ejecutar este código, es posible que observes que tu código parece haberse congelado y la aplicación no funciona; el valor en el cuadro de texto no se actualiza. Intente agregar un PushButton a tu aplicación y vuelva a ejecutar la función de cuenta regresiva. Cuando hagas esto, deberías notar que ya no puede interactuar con el botón.

¿Por qué no puedes utilizar bucles while en Guizero?

La razón por la que la función de cuenta_atras hace que tu aplicación deje de funcionar está relacionada con la forma en que guizero le indica a Python que ejecute la aplicación. Cada aplicación que ha creado hasta ahora en este curso siempre ha incluido la línea
app.display()
Esta línea crea un bucle infinito que espera que ocurran eventos (como un usuario que presiona un botón o escribe en un cuadro de texto). Esto es lo que permite que tu aplicación permanezca abierta y permite que un usuario interactúe con ella tantas veces como quiera.

Si interrumpes tu programa sin cerrar tu aplicación (por ejemplo, presionando ctrl+c), puede ver que el valor en el cuadro de texto habrá cambiado. Esto muestra que la función de cuenta regresiva ha estado funcionando pero la propiedad de valor del texto no se ha actualizado.

Esto se debe a que cuando agregas bucles while a tu código, evitas que el bucle .display() actualice tu aplicación. Es por eso que parece congelado o incluso podría bloquearse. Del mismo modo, tu aplicación no puede actualizarse mientras el programa está inactivo, por lo que usar sleep() evita que su aplicación se actualice.

Funciones que gestionan el tiempo en Guizero.

Afortunadamente, guizero tiene sus propias funciones integradas que se pueden usar para crear contadores y temporizadores que no dependan de bucles while.

Por ejemplo, .repeat() le indica a un widget que llame repetidamente al mismo comando. Este método requiere que especifique el tiempo de retraso entre las repeticiones y el comando que se está llamando.

El siguiente código seguirá aumentando el tamaño de fuente de un texto cada medio segundo llamando repetidamente a la función de aumento, que le indica al widget de texto que incremente el tamaño de fuente.
from guizero import App, Text

def aumentar():
	text.text_size = int(text.text_size) + 1

app = App("textsize")

text = Text(app, text="bigger")
text.repeat(500, aumentar)

Añadiendo la Cuenta Atrás.

En este paso vamos a añadir un contador a nuestro juego usando un método de Guizero.

En el juego de los Emojis que estamos creando, los jugadores tienen 30 segundos para ver cuantas coincidencias pueden obtener. Para poder habilitar esta función crearemos un widget que cuente de forma regresiva desde el 30 y luego reinicie el juego.

Creando el temporizador.

Existen varias formas de crear un temporizador sin usar un bucle "while". Si quieres intentar un desafío, prueba a diseñar el tuyo propio antes de seguir leyendo. Puedes encontrar útil la información de Guizero

Crearemos el temporizador usando el método .repeat(). Este método te permite llamar repetidamente a un comando y especificar un tiempo de retraso entre cada llamada.

Para empezar, crearemos el temporizador usando un widget de texto, configurando la propiedad .value en "Preparado" cuando comience el juego. Puedes cambiar este mensaje por el que a ti te parezca mejor. Esto debe hacerse una vez al comienzo del juego, por lo que el siguiente código lo podemos insertar después de crear las dos cuadriculas, pero antes de llamar a la función principal "iniciar_ronda"

# Añadir en las caracteristicas_adicionales
caracteristicas_adicionales = Box(app)
temporizador = Text(caracteristicas_adicionales, text="Preparado.")

A continuación debe iniciarse el temporizador. Para hacer esto, establecemos el valor inicial y le ordenamos al temporizador que cuente hacia atrás. El siguiente código le dice al temporizador que realice una cuentea regresiva diciéndole que llame repetidamente una función, que llamaremos contador, cada 1.000 milisegundos. El temporizador debe comenzar después de dibujar las cuadriculas y preparar la ronda por lo que el código también debe insertarse después de la función iniciar_ronda() y antes de display().

# inicia el temporizador
temporizador.value = 30
temporizador.repeat(1000, contador)
El temporizador está llamando repetidamente a la función contador, por lo que esta función debe definirse. Mire el siguiente código y mira si puedes averiguar cómo funciona la función.

def contador():
    temporizador.value = int(temporizador.value) - 1
    if int(temporizador.value) == 0:
        # resetea el temporizador
        temporizador.value = 30
Cada vez que se llama a esta función (en este caso, cada segundo), reduce el valor del temporizador en 1 y luego verifica si es igual a 0. Si es así, establece el valor del temporizador en 30 para comenzar de nuevo.

Reorganizando el código e incluyendo lo que hemos visto el programa se ve de esta forma. El tuyo debería ser parecido.


emoji gui con temporizador



¿Y que sucede si se acaba el tiempo?

Hasta ahora cuando se acaba el tiempo el temporizador se reinicia. Estaría bien que informáramos al jugador de que se ha acabado el tiempo en vez de simplemente reiniciarlo. Podríamos establecer varias opciones: que se acabe el juego o que empiece una nueva partida etc. Tomate un tiempo para decidir que quieres que haga el juego cuando el tiempo termine.

guizero - tiempo agotado



from guizero import warn

def contador():
    temporizador.value = int(temporizador.value) - 1
    if int(temporizador.value) == 0:
        temporizador.cancel(contador)
        # resetea el temporizador
        resultado.value = "JUEGO TERMINADO"
        warn("GAME OVER", "El tiempo se ha agotado")
        temporizador.value = 30
        # resetea el resultado
        resultado.value = ""
        # Comienza una nueva ronda
        iniciar_ronda()
        #resetea el contador
        temporizador.repeat(1000, contador)


Cuando el tiempo se agota una ventana emergente avisa al jugador. El temporizador se cancela y luego se reinicia y comienza una nueva ronda del juego. Todo esto se inicia cuando dentro de la función contador() el valor del temporizador es igual a cero. Ten en cuenta que primero hay que cancelar el temporizador, para evitar tener dos temporizadores funcionando al mismo tiempo.

La ventana emergente se creó utilizando Guizero Alerts. Estos te permiten crear ventanas emergentes que se activan cuando ocurre un evento en una aplicación. Incluso se pueden crear ventanas emergentes que hagan una pregunta al usuario y devuelvan diferentes valores según su respuesta.


Manteniendo el Marcador. 

El último paso que vamos a añadir a el juego es un marcador. Vamos a ver como crear un widget que mantenga la puntuación que alcance el jugador.

Un marcador debe mostrar la cantidad de coincidencias correctas que encuentra el jugador, mostrar este valor y reiniciarse cuando se agota el tiempo. Comenzaremos agregando un widget para mostrar un marcador, luego identificaremos dónde necesitamos cambiar el código para indicarle al juego que actualice el marcador.

Añadiendo el Marcador.

Colocaremos el marcador en la parte inferior del juego, debajo del cronómetro. Tu puedes colocar el marcador donde quieras. Para crear el marcador, utilizaremos un widget Text y actualizaremos su valor cada vez que el jugador obtenga un punto al descubrir el Emoji coincidente.

from guizero import Box, Text

marcador = Box(raiz)
etiqueta = Text(marcador, text="Puntuación", align="left")
puntuacion = Text(marcador, text="0", align="left")

app.display()
El lugar donde insertes este código depende de dónde desees que se muestre tu marcador. Para ponerlo en la parte inferior, agregaré este código en la parte final; Sin embargo, ten cuidado: si haces esto, tu marcador puede estar debajo de la parte visible de la aplicación. Para aumentar un poco el tamaño y que se muestre correctamente añadiremos el argumento height=550 al crear la aplicación.

# Inicia la aplicación
raiz = App("Juego de emojis", height=550)

Actualizando el Marcador.

Vamos a actualizar el marcador cada vez que el jugador acierte. Esto significa modificar la función coincide_Emoji() agregando una nueva línea que le diga al programa que sume uno a la puntuación cada vez que se pulse el Pushbutton del Emoji coincidente.

def coincide_emoji(coincide):
    if coincide:
        resultado.value = "Correcto"
        puntuacion.value = int(puntuacion.value) + 1
    else:
        resultado.value = "Falso"
    
    iniciar_ronda()

Reiniciando la puntuación

Finalmente necesitamos restablecer la puntuación cuando se agote el tiempo y termine la ronda. Para restablecer el marcador tenemos que establecer el valor de la puntuación de nuevo a cero.

puntuacion.value = "0"
Lee tu código para tratar de identificar dónde agregarías esta línea antes de seguir leyendo.

Esta línea se puede incluir en la función contador(). Esto se debe a que esta función realiza un seguimiento de cuánto tiempo queda y le indica al juego que se reinicie cuando se agote el mismo.

¡Enhorabuena! Con esto hemos terminado la construcción del juego y también el curso de Guizero. 

Puedes encontrar tanto el código de esta aplicación, como una versión modificada para dos jugadores con un marcador que además conserva la puntuación más alta conseguida en sus correspondientes enlaces.


martes, 23 de marzo de 2021

Las listas en Python



una_lista = ['peras', 'manzanas', 'zanahorias']

Las Listas. 


Usamos las listas para guardar múltiples elementos en una sola variable.

Las listas son uno de los cuatro elementos que se usan para almacenar datos, los otros son las Tuplas, los Conjuntos y los Diccionarios.

Las listas se crean usando corchetes.

nombre_lista = [elemento1, elemento2,....., elemento_n]

Los elementos de una lista están ordenados, se pueden modificar y permiten valores duplicados.

Al estar ordenados podemos acceder a cualquier elemento de la lista indicando su posición en la misma. Asi empezando por la izquierda, el primer elemento tiene el indice [0], el segundo [1] y así sucesivamente. Este orden no cambia de forma que si añadimos nuevos elementos a la lista estos se situarán al final de la misma.

Veamos algunos ejemplos de como acceder a una lista usando una lista con números.

>>> lista = [1,2,3,4,5,6,7,8,9,10]
>>> # Si queremos por ejemplo imprimir el numero 4 (cuyo índice es el 3)
>>> print(lista[3])
4

Si quisiéramos imprimir toda la lista podemos usar:

>>> print(lista)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> # o también [:] lo que quiere decir que imprima desde el primer al ultimo elemento de la lista >>> print(lista[:]) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Si quisiéramos imprimir o usar solo una parte o porción de la lista (Slicing o rebanadas en python), usamos la expresión:

lista[desde_posicion, hasta posición sin incluir ese elemento, salto]

>>> # Imprimir los 5 primeros números
>>> print(lista[0:5])
[1, 2, 3, 4, 5]
>>> # Imprimir desde el 6 al final.
>>> print(lista[5:10])
[6, 7, 8, 9]
>>> print(lista[5:])
[6, 7, 8, 9, 10]
>>> # Imprimir cada dos números desde el primer al ultimo elemento
>>> print(lista[::2])
[1, 3, 5, 7, 9]


También podemos indicar el índice del elemento empezando a contar desde la izquierda, en ese caso utilizaremos el índice con el signo menos y empezando por el -1 y no por el cero como hicimos antes. Podemos combinar ambas formas de indicar el índice de los elementos.

>>> # Imprimir los 5 primeros números
>>> print(lista[-10:-5])
[1, 2, 3, 4, 5]
>>> # Imprimir desde el 6 al final.
>>> print(lista[-5:10])
[6, 7, 8, 9]
>>> print(lista[-5:])
[6, 7, 8, 9, 10]
>>> # Imprimir cada dos números desde el último al primero
>>> print(lista[::-2])
[10, 8, 6, 4, 2]
>>> # Invertir el orden de los elementos de una lista.
>>> print(lista[::-1])
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]


Longitud de una lista.


Para saber cuantos elementos tiene una lista usaremos la función len()

Ejemplo

Imprimimos el tamaño o número de elementos de una lista:

>>> lista = ["manzana""platano""pera"]
>>> print(len(lista))
3


Tipos de datos que pueden formar una lista.


Los elementos que se pueden almacenar en una lista pueden ser.....CUALQUIERA. Pueden ser números, strings o cadenas de texto, booleanos o incluso una combinación de todos ellos.

lista_string = ["manzana""platano""pera"]
lista_numeros = [15793]
lista_booleanos = [TrueFalseFalse]
lista_combinada = [True, 5, "manzana"]

También puede haber una lista con otra u otras listas dentro:

>>> lista = ["manzana", "platano", "pera", [1, 2, 3, 4]]
>>> print(lista[len(lista)])
4

Como ves la lista tiene 4 elementos, los tres primeros son frutas y el cuarto es otra lista. Si queremos acceder a un elemento de esta última lista es muy sencillo. Primero indicamos su índice en la lista principal y luego otra vez el elemento al que queremos acceder dentro de la sub-lista de números. Por ejemplo para imprimir el nº 3 de la lista de números:

>>> lista = ["manzana", "platano", "pera", [1, 2, 3, 4]]
>>> print(lista[3][2])
3
# [Elemento con indice 3 de la lista principal] y [2 de la sublista]


Desde la perspectiva de Python las listas son definidas como objetos cuyo tipo de datos es "list"

>>> lista = ["manzana", "platano", "pera", [1, 2, 3, 4]]
>>> print(type(lista))
<class 'list'>


y también es posible usar el constructor "list()" para crear una lista a partir de otro tipo de objeto:

>>> palabra ="Bienvenido"
>>> print(list(palabra))
['b', 'i', 'e', 'n', 'v', 'e', 'n', 'i', 'd', 'o']


Métodos que podemos usar para trabajar con listas.


*)     nombre_lista.append(elemento)        Añade un elemento al final de la lista.

>>> lista = ['uno','dos','tres']
>>> item = 'cuatro'
>>> lista.append(item)
>>> print(lista)
['uno', 'dos', 'tres', 'cuatro']
Otra forma usando slicing de listas es la siguiente:

>>> lista = ['uno','dos','tres']
>>> item = ['cuatro']
>>> lista[len(lista):]=item
>>> print(lista)
['uno', 'dos', 'tres', 'cuatro']

Aunque quizá la forma más fácil de fusionar o añadir una lista a continuación de otra es usar las suma "+", vamos sumando ambas listas:

>>> lista = ['uno','dos','tres']
>>> item = ['cuatro']
>>> lista = lista + item
>>> print(lista)
['uno', 'dos', 'tres', 'cuatro']

y ya que estamos con los signos matemáticos si usas el signo de multiplicar con una lista la multiplicas n veces:

>>> lista = ['uno','dos'] * 3
>>> print(lista)
['uno', 'dos', 'uno', 'dos', 'uno', 'dos']


*)     nombre_lista.insert(i, item)                 Añade el item en la posición i desplazando al resto hacia la derecha.

>>> # añadimos el cuatro donde estaba el dos, es decir en la posición 1.
>>> lista = ['uno','dos','tres']
>>> item = 'cuatro'
>>> lista.insert(1,item)
>>> print(lista)
['uno', 'cuatro', 'dos', 'tres']


*)     nombre_lista.extend(otra_lista)            Fusiona o añade una lista a la anterior.

>>> lista = ['uno','dos','tres']
>>> items = ['cuatro','cinco']
>>> lista.extend(items)
>>> print(lista)
['uno', 'dos', 'tres', 'cuatro', 'cinco']

otro ejemplo:

>>> lista = [1,2,3,4]
>>> lista.extend(range(5,11))
>>> print(lista)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


En el ejemplo he utilizado la función range() que lo que hace es crear una lista de números secuenciales.

Su estructura es:

 range(nº inicio, nº final sin incluir, salto)


*)       nombre_lista.index(elemento)                 Nos devuelve el índice o posición del primer elemento de la lista que coincida con el elemento dado.

>>> lista = ["manzana", "platano", "pera", "platano"]
>>> print(lista.index("manzana"))
0
>>> print(lista.index("platano"))
1
>>> print(lista.index("pera"))
2


*)     nombre_lista.pop(<indice_elemento>)       Modifica la lista original, eliminando el elemento de la misma que esté en la posición indicada y lo asigna a una variable si es el caso. El argumento del método es opcional y si no pones nada se entiende que es el último elemento de la lista.

>>> lista_original = ["manzana", "platano", "pera", "platano"]
>>> lista_original.pop() # quita el ultimo elemento de la lista modificándola.
>>> print(lista_original)
['manzana', 'platano', 'pera']
x= lista_original.pop(1) # quitamos el elemento de la posición 1 y lo asignamos a x.
>>> print(x)
platano
>>> print(lista_original) # Como queda la lista después de las modificaciones.
['manzana', 'pera']


Con esto podemos utilizar cosas que se encuentran en otros lenguajes de programación como son las pilas y colas de datos. 

En las pilas se sigue el método LIFO (Last In First Out), es decir, los últimos datos que entran en las listas son los primeros en salir. Utilizaremos los métodos append() y pop(). Veámoslo con este ejemplo. Tenemos un almacén  nos traen la mercancía en cajas. Y la vamos vendiendo primero las últimas que nos traen.

>>> lista_cajas = [5,2,6,7]
>>> # Nos traen otras dos cajas y las metemos en el stock
>>> lista_cajas.append(2)
>>> print(lista_cajas)
[5, 2, 6, 7, 2]
>>> # Ahora sacamos 9 del almacén utilizando el método de la pila. (7, 2)
>>> lista_cajas.pop()
>>> list_cajas.pop()
>>> print(lista_cajas)
[5, 2, 6]


En las colas pasa lo contrario. Va saliendo de la variable primero lo que entro antes. Utilizaremos los métodos insert(0, item) y pop(). Veámoslo con un ejemplo.

>>> lista_cajas = [5,2,6,7]
>>> # Nos traen otras dos cajas y las metemos en el stock
>>> lista_cajas.insert(0, 2)
>>> print(lista_cajas)
[2, 5, 2, 6, 7]
>>> # Ahora sacamos 7 del almacén utilizando el método de la pila. (7)
>>> lista_cajas.pop()
>>> print(lista_cajas)
[2, 5, 2, 6]


*)   nombre_lista.count(item)           Cuenta las veces que un objeto está en una lista.

>>> lista=[2, 5, 2, 6, 7, 2]
>>> print(lista.count(2))
3


*) nombre_lista.remove(item)            Elimina el primer elemento que coincide de una lista.     

>>> lista=[2, 5, 2, 6, 7, 2]
>>> lista.remove(2)
>>> print(lista)
[5, 2, 6, 7, 2]


Para eliminar un elemento de una lista también podemos usar su posición con la instrucción:

del lista[indice_elemento]

>>> lista=[2, 5, 2, 6, 7, 2]
>>> # Borramos el primer 2 de la lista que ocupa la posición 0.
>>> del lista[0]
>>> print(lista)
[5, 2, 6, 7, 2]


 *)  nombre_lista.clear()            Vacía la lista de elementos.

>>> lista=[2, 5, 2, 6, 7, 2]
>>> # Vaciamos todos los elementos de la lista.
>>> lista.clear()
>>> print(lista)
[]     


Podemos conseguir lo mismo también con las siguientes instrucciones:

>>> lista=[2, 5, 2, 6, 7, 2]
>>> # Vaciamos todos los elementos de la lista.
>>> lista=[]
>>> print(lista)
[]
# O también podemos vaciarla entera o solo una parte usando
# del y usando slicing de listas.
>>> lista=[2, 5, 2, 6, 7, 2]
>>> del lista[:]
>>> print(lista)
[]


*)     nombre_lista.reverse()        Devuelve la misma lista pero al revés.

>>> lista=[2, 5, 2, 6, 7, 2]
>>> lista.reverse()
>>> print(lista)
[2, 7, 6, 2, 5, 2]
# lo mismo lo podríamos conseguir con lista[::-1]


*)     nombre_lista.sort()            Ordena solamente los elementos de una lista. Por defecto modifica la lista orignal y lo hace en orden creciente, a no ser que como argumento usemos reverse=True que lo haría en orden decreciente.

>>> lista=[2, 5, 2, 6, 7, 2]
>>> lista.sort() # Orden creciente
>>> print(lista)
[2, 2, 2, 5, 6, 7]
>>> lista.sort(reverse=True) # Orden decreciente
[7, 6, 5, 2, 2, 2]


Opcionalmente también podríamos usar la función sorted(), que no solo puede ordenar listas sino cualquier otro iterable. Además sorted() no modifica la lista original, sino que devuelve la lista ordenada. 

>>> lista=[2, 5, 2, 6, 7, 2]
>>> x = sorted(lista)
>>> print(x)
[2, 2, 2, 5, 6, 7]
>>> print(lista)
[2, 5, 2, 6, 7, 2]


*)     nombre_lista.copy()         Devuelve una copia de esa lista. Si simplemente asignamos una lista a una variable no estamos copiando la lista en otra independiente sino solo haciendo una referencia a la misma. Por tanto si lo que queremos es copiar una lista tenemos que hacer uso del método .copy()

>>> lista=[2, 5, 2, 6, 7, 2]
>>> x = lista # Esto no copia la lista en una independiente, solo hacemos una referencia
a la misma.
>>> x.append(9)
>>> print(lista)
[2, 5, 2, 6, 7, 2, 9]
>>> z = lista.copy() # Crea una verdadera copia independiente de la lista.
>>> print(z)
[2, 5, 2, 6, 7, 2, 9]


Otras funciones y otras cosas que se pueden aplicar a una lista.


*)     sum(lista)            Devuelve la suma de los elementos de la lista.

>>> lista = [2, 5, 2, 6, 7, 2, 9]
>>> print(sum(lista)) 
33


*)     max(lista)            Devuelve el valor máximo de una lista.

>>> lista = [2, 5, 2, 6, 7, 2, 9]
>>> print(max(lista)) 
9


*)    min(lista)            Devuelve el valor mínimo de una lista.

>>> lista = [2, 5, 2, 6, 7, 2, 9]
>>> print(min(lista)) 
2


Cuando estamos asignando elementos de un iterable a unas variables, si a una de estas variables le anteponemos un *, Python se encarga de asignarle todos los valores del iterable que le dejen la asignación de las otras variables.

>>> lista = [2, 5, 2, 6, 7, 2, 9]
>>> a, *b, c = lista
>>> print(a)
2
>>> print(b)
[5, 2, 6, 7, 2]
>>> print(c)
9


*) Operador especial "in" aplicado a una lista.         Nos dice si un determinado elemento esta en la lista.

Devuelve True si el elemento esta en la lista y False en caso contrario.

>>> lista = ['angel','andres','maria']
>>> print('maria' in lista)
True


Compresión de listas (list comprehensions).

Nos sirve para crear una nueva lista, excluyendo o trasformando sus elementos, pero de una forma más rápida y que se lea mejor que utilizando el código normal y en una sola línea de código.  

Toda compresión de listas puede ser representada con un bucle for, pero no todo bucle for, puede ser representado como una compresión de listas.

La síntesis de una compresión de listas es la siguiente:

[expression for item in lista condicional]

Esto se puede usar con cualquier iterable, no solo con las listas.

Lo mejor es verlo con ejemplos:

Vamos a obtener una nueva lista, de una lista que ya tenemos, con los números que sean mayores que 10.

>>> lista = [4,22,58,4,1,20,8]
>>> lista_nueva = [x for x in lista if x>10]
>>> print(lista_nueva)
[22, 58, 20]

Esto mismo se podría haber hecho de forma, digamos tradicional usando el siguiente código:

>>> lista = [4,22,58,4,1,20,8]
>>> lista_nueva = []
>>> for x in lista:
>>>   if x > 10:
>>>    lista_nueva.append(x)
print(lista_nueva)
[22, 58, 20]


Otro ejemplo. Multiplicar por 2 los elementos de una lista.

>>> lista = [4,22,58,4,1,20,8]
>>> lista_nueva = [x * 2 for x in lista]
>>> print(lista_nueva)
[8, 44, 116, 8, 2, 40, 16]

 

O por ejemplo también podemos usar  bucles anidados para ciertas cosas, por ejemplo crear puntos de coordenadas:

>>> points = [(x, y) for y in range(0, 3) for x in range(0, 5)]
>>> print(points)
[(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), 
 (0, 1), (1, 1), (2, 1), (3, 1), (4, 1), 
 (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
que de forma tradicional llevarían más trabajo:

  1. points = []
  2. for y in range(0, 3):
  3. for x in range(0, 5):
  4. points.append((x, y))
  5. print(points)

e incluso podríamos haber utilizado una condición para mostrar solo los puntos en los que x == y.

  1. points = [(x, y) for x in range(0, 5) for y in range(0, 5)
  2. if x == y]
Para finalizar, si alguna vez necesitamos evaluar una lista, es útil usar los comandos ALL y ANY. (todos o alguno en español)

Por ejemplo si tenemos una serie de números en una lista y queremos saber si son mayores que uno dado podemos usar lo siguiente para abreviar código.

> numeros = [55, 44, 33, 22, 7]
> # ¿Son todos mayores que 5?
> if all([i>5 for i in numeros]):
>	print("Todos los números son mayores que 5.")
Todos los números son mayores que 5.

O si tenemos una lista de números y queremos saber si alguno de ellos es par podemos usar:

> numeros = [55, 44, 33, 22, 7]
> # ¿Alguno de ellos es par?
> if any([i%2==0 for i in numeros]):
     print("al menos un número es par")
> else:
     print("ninguno es par")    

Puedes encontrar más información sobre listas, tuplas y secuencias en la documentación oficial.