domingo, 25 de septiembre de 2022

GIT - 1 - Borrar y Renombrar archivos - Revertir Cambios.

 

Borrar un archivo.


Para borrar un archivo del árbol de trabajo podríamos usar el comando "rm" sobre el mismo. Sin embargo aún tendríamos que usar git add [archivo_borrado] para que está modificación quedara registrada y luego ya podríamos hacer el commit.


chema@lenovo:~/proyecto$ rm main.py
chema@lenovo:~/proyecto$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	deleted:    main.py

no changes added to commit (use "git add" and/or "git commit -a")
chema@lenovo:~/proyecto$ git add main.py
chema@lenovo:~/proyecto$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    main.py

chema@lenovo:~/proyecto$ git commit -m "Pasos para borrar un archivo."
[master 5a2eea1] Pasos para borrar un archivo.
 1 file changed, 2 deletions(-)
 delete mode 100644 main.py

Sin embargo con;

$ git rm [archivos]

No solo borramos los archivos del árbol de trabajo sino que también lo preparamos para hacer el commit.

chema@lenovo:~/proyecto$ git rm main.py
rm 'main.py'
chema@lenovo:~/proyecto$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    main.py

chema@lenovo:~/proyecto$ git commit -m "borrado de un archivo."
[master e86a317] borrado de un archivo.
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 main.py

Mover o Renombrar un Archivo.

Lo mismo ocurre si queremos mover un archivo de un directorio a otro dentro del árbol de trabajo. Para ahorrar tener que confirmar la modificación con git add, directamente tecleamos:

$ git mv [archivo] [nuevo_nombre]

y así podemos mover los archivo entre directorios o bien también lo podemos usar para cambiar el nombre de los archivos y tener estas modificaciones registradas y listas para hacer el commit.

mover un archivo con git

chema@lenovo:~/proyecto$ git status
On branch master
nothing to commit, working tree clean
chema@lenovo:~/proyecto$ git mv main.py ./MOVER/
chema@lenovo:~/proyecto$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    main.py -> MOVER/main.py

Ignorar Cambios.


Git ve cada archivo en el árbol de trabajo como una de estas tres cosas:

- Rastreado. Un archivo que se ha preparado o confirmado previamente.
- Sin seguimiento. Un archivo que no se ha preparado ni confirmado.
- Ignorado. Un archivo que se le ha dicho explícitamente a git que ignore cualquier cambio que se produzca.

Esos archivos o directorios son aquellos que no suelen formar parte del proyecto tales como archivos de compilación o archivos temporales generados por el ordenador. 

Los archivos a ignorar se buscan en un archivo especial llamado .gitignore, un archivo oculto que hay que crear en la raíz del repositorio. Este archivo debe crearse y editarse a mano cuando tengas nuevos archivos que quieras ignorar. Los archivos .gitignore contienen patrones que se comparan con los nombres de los archivos de los repositorios para determinar si deben ignorarse o no.

Se pueden usar patrones globales dentro del archivo para ampliar los casos en los que determinados archivos no deban ser incluidos. Puedes ver una buena explicación del uso de los mismos en esta página.

Ejemplo de archivo .gitignore

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

El propio archivo .gitignore necesita ser comprometido "commit" al igual que el resto.


¿Cómo evitar el rastreo de archivos a los que previamente les has hecho "commit" ?


$ git rm --cached nombre-del-archivo

Establece un archivo como "untracked file", sin seguimiento.

Saca los archivos de nuestro repositorio local y del área de staging, pero los mantiene en el disco duro, no los borra. Básicamente le dice a Git que deje de trackear el historial de cambios de estos archivos, por lo que pasaran a un estado untracked o Sin seguimiento. Si no queremos que git les vuelva a hacer seguimiento los añadiríamos al archivo .gitignore.


Revertir Cambios.

Tenemos que distinguir entre dos posibles escenarios.

A) Revertir cambios antes de que los archivos pasen al Stage.

B) Revertir cambios cuando los archivos ya están en el Stage.


- Revertir cambios antes del Stage.

$ git checkout [archivos]

Revierte cambios en archivos antes de que sean confirmados, antes de que se haga un commit nuevo y  siempre que no los hayamos añadido al Stage, usando un git add. 

Se vuelve a la versión del último commit o confirmación realizada. Es decir, descartamos las modificaciones que hayamos hecho en el archivo y lo devolvemos a como estaba anteriormente, en la confirmación o commit previo.

Veámoslo con un ejemplo.

Imaginemos que iniciamos un repositorio, creamos un archivo y dentro escribimos un comentario. Después lo pasamos al stage y realizamos un commit.

chema@lenovo:~/proyecto$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint: 	git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint: 	git branch -m <name>
Initialized empty Git repository in /home/chema/proyecto/.git/
chema@lenovo:~/proyecto$ echo "# Primera linea del archivo" > main.py
chema@lenovo:~/proyecto$ git add main.py 
chema@lenovo:~/proyecto$ git commit -m "primer commit"
[master (root-commit) ef8ab09] primer commit
 1 file changed, 1 insertion(+)
 create mode 100644 main.py

Ahora, imaginemos que añadimos más código al archivo. Como es un ejemplo para mostrar como funciona el comando, solamente voy a añadir dos líneas más, pero funciona igual si has tecleado miles de líneas de código.

chema@lenovo:~/proyecto$ echo "#Segunda linea añadida" >> main.py 
chema@lenovo:~/proyecto$ echo "#Tercera linea añadida" >> main.py 
chema@lenovo:~/proyecto$ cat main.py 
# Primera linea del archivo
#Segunda linea añadida
#Tercera linea añadida

Ahora me doy cuenta de que en el archivo hay un error y no recuerdo como estaba originalmente cuando funcionaba. Lo que necesito es deshacer lo que he hecho desde la última vez que guarde los cambios. 

Lo único que hay que hacer es usar, el comando $ git checkout main.py

chema@lenovo:~/proyecto$ git checkout main.py
Updated 1 path from the index
chema@lenovo:~/proyecto$ cat main.py
# Primera linea del archivo
Y volvemos al estado original.

Otro ejemplo. Supongamos que tenemos un programa con un montón de archivos y directorios. Como todo funciona bien hacemos un commit del repositorio y seguimos programando. Cuando llevamos algunas líneas de código lo volvemos a probar y algo no va. No sabemos donde puede estar el bug pero si sabemos que la ultima versión que teníamos funcionaba. Decidimos empezar otra vez y volvemos a la anterior versión que era estable. Para ello usamos:

$ git checkout .
(no se si se ve pero al final hay un punto.)

Al escribir este comando de esta forma le decimos que vuelva a la última versión, a la versión previa.
En git esto es volver al commit previo.

- Revertir los cambios cuando ya hemos añadido esos archivos al Stage.


$ git reset HEAD <archivos>

Así como el comando anterior nos permitía revertir cambios a archivos modificados antes de que pasaran al Stage, este comando nos permite deshacer cambios que ya estén en el Stage bien porque ya hayamos hecho, por ejemplo un git add . o un git add *.

Es decir lo que nos permite es pasar archivos, que ya estaban en el Stage preparados para hacer un commit, al árbol de trabajo de nuevo. En definitiva sacar esos archivos del Stage. 

Veámoslo con un ejemplo.

Partimos del ejemplo anterior y le añadimos un cuarto comentario al archivo. Después, aunque solamente tenemos un archivo, lo añadimos al stage. Entonces nos damos cuenta de que queremos cambiar algo antes de hacer el commit con lo que usamos este comando para devolver el archivo, al árbol de trabajo.

chema@lenovo:~/proyecto$ echo "Cuarta linea añadida" >> main.py
chema@lenovo:~/proyecto$ git add *
chema@lenovo:~/proyecto$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   main.py

chema@lenovo:~/proyecto$ git reset HEAD main.py
Unstaged changes after reset:
M	main.py
chema@lenovo:~/proyecto$ cat main.py
# Primera linea del archivo
Cuarta linea añadida
chema@lenovo:~/proyecto$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   main.py

no changes added to commit (use "git add" and/or "git commit -a")

Que quede claro que no se modifica el archivo, simplemente lo saca del Stage y lo devuelve al árbol de trabajo para que podamos trabajar con el. (Aunque si coges ese archivo y lo modificas antes de hacer un commit también lo sacarás del Stage porque habrás hecho una modificación que no has confirmado)

Comandos equivalentes son:

$ git restore --staged <archivos>

$ git reset -p      En este caso nos pedirá confirmación paso a paso de las acciones a realizar.


- Revertir los cambios si ya hemos hecho un commit. 


Si ya hemos realizado una instantánea del repositorio al realizar un commit y queremos anularlo, podemos hacerlo de diversas formas dependiendo de lo que queramos conseguir.

A) Cambiando el último commit:


$ git commit --amend 

Es la forma conveniente de modificar la confirmación más reciente. Sobrescribe el commit previo, es decir, se añade lo que ya tengamos en esa instantánea con lo que tengamos actualmente en el Stage. Si no hemos añadido o modificado nada, se puede utilizar simplemente para editar el mensaje de confirmación anterior sin cambiar su instantánea.

SOLAMENTE USALO EN COMMITS LOCALES porque borra el historial del último commit que en proyectos en grupo podría haber realizado otra persona. Las confirmaciones modificadas son en realidad confirmaciones completamente nuevas y la confirmación anterior ya no aparecerá.

Por ejemplo. Digamos que acabamos de realizar una confirmación (commit) y cometimos un error en el mensaje de confirmación. Ejecutar este comando cuando no hay nada todavía nuevo preparado, que esté en el Stage, nos permite modificar el anterior mensaje de confirmación anterior sin alterar su instantánea.

chema@lenovo:~/proyecto$ git log
commit 2f08687ba8e43dbd7d40d0405108a841c97c69a9 (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Thu Sep 22 19:33:06 2022 +0200

    primer commit. Contiene un error.
chema@lenovo:~/proyecto$ git commit --amend -m "primer commit. Error Subsanado."
[master c5ecfed] primer commit. Error Subsanado.
 Date: Thu Sep 22 19:33:06 2022 +0200
 1 file changed, 1 insertion(+)
 create mode 100644 main.py
chema@lenovo:~/proyecto$ git log
commit c5ecfed30cff8625efd54e1ea136906ef2cca50d (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Thu Sep 22 19:33:06 2022 +0200

    primer commit. Error Subsanado.

Si te fijas es cierto que hemos modificado el mensaje de la confirmación, pero si miras bien verás que el número del commit (en negrita) también es distinto.


Otro ejemplo habitual. Digamos que hemos editado algunos archivos que nos gustaría confirmar en una sola instantánea, pero luego nos damos cuenta de que se nos ha olvidado añadir uno de los archivos la primera vez. Bastará con prepara el nuevo archivo, añadirlo al Stage y usar este comando.

chema@lenovo:~/proyecto$ git log
commit 2ebf529db3565c2c9a26f9fa65ef5ffcc6fc3d5d (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 23 17:33:07 2022 +0200

    primer commit
chema@lenovo:~/proyecto$ > archivo_añadir
chema@lenovo:~/proyecto$ git add archivo_añadir 
chema@lenovo:~/proyecto$ git commit --amend -m "Primer commit con archivo olvidado añadido"
chema@lenovo:~/proyecto$ git show
commit 1b9aa03962422e4cf73c5496ec66557901c81e8f (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 23 17:33:07 2022 +0200

    primer commit. Con archivo olvidado añadido


B) Revertir un commit por completo. (ROLL BACK)


$ git revert HEAD

Crea un nuevo commit que es justo la inversa del último, con lo que volvemos al estado del anterior. Sin embargo esta acción no sobrescribe el commit previo ni lo elimina del historial

* Estado del archivo en la última instantánea.
chema@lenovo:~/proyecto$ cat main.py 
# Primera linea del archivo
#!/usr/bin/env python3
import math

* Deshacemos los cambios
chema@lenovo:~/proyecto$ git revert HEAD
[master 7d7e430] Revert "muevo commit"
 1 file changed, 2 deletions(-)

chema@lenovo:~/proyecto$ cat main.py 
# Primera linea del archivo

* El último commit es el inverso del anterior.
chema@lenovo:~/proyecto$ git log
commit 7d7e43071d1b48ea76ba6f0ab99e0e1732e688ea (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 23 19:53:05 2022 +0200

    Revert "nuevo commit"
    
    This reverts commit 7b0418f9029abea87808f019f0683bc6b70ec709.

commit 7b0418f9029abea87808f019f0683bc6b70ec709
Author: usuario <usuario@correo.es>
Date:   Fri Sep 23 19:51:59 2022 +0200

    muevo commit

C) Revertir un commit por completo que no sea el último.


El Id es el código alfanumérico muy largo que aparece al lado de la palabra commit. Lo calcula el programa con el algoritmo SHA1. Sirve para garantizar que la integridad del commit y que ese commit es único.

En el ejemplo anterior el ID del primer commit es 7b0418f9029abea87808f019f0683bc6b70ec709

$ git revert <ID del commit>

Este comando desharía los cambios de esa instantánea en concreto, pero ojo que esto nos puede dar dolores de cabeza el revertir cambios anteriores que ya estaban consolidados.

Borrar todos los commits posteriores a uno previo.


$ git reset --hard <id o sha del commit>

Se BORRAN PARA SIEMPRE todos los commits más nuevos y se recupera el repositorio al estado del que hemos ido.

Movernos entre distintos commits.

En ocasiones nos interesa movernos atrás en el tiempo para ver como estaba un repositorio en un momento concreto. 

Considera este repositorio con 4 confirmaciones.

chema@lenovo:~/Prueba$ git log --oneline
0face9c (HEAD -> main) Añadido archivo de instrucciones
e624744 añadido nuevo comando a main.py
ad3b242 Segundo commit.
5011475 Primer commit del repositorio.

Si quisiera volver, a ver como estaba el proyecto en el segundo repositorio (ad3b242) se puede usar:

$ git checkout <id del commit>

git checkout ad3b242
Note: switching to 'ad3b242'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at ad3b242 Segundo commit.
chema@lenovo:~/Prueba$ ls
main.py

Pero OJOOOO, que si realizas alguna modificación y la confirmas no volverás a la rama main o master que es en la que estas trabajando, si no que crearemos una nueva rama. Aunque el concepto de ramas lo veremos en los siguientes capítulos voy a hacer una modificación al repositorio y un commit para ver lo que ocurre.

Luego, si le pregunto a git cuantas ramas tengo y donde estoy, veo lo siguiente:

chema@lenovo:~/Prueba$ git branch
* (HEAD detached from ad3b242)
  main
 
El asterisco indica en donde estoy. Para volver a donde estaba trabajando, tengo que desplazarme al último commit de mi rama principal que en mi caso se llama main (también puede aparecerte como master)

$ git checkout <rama a donde nos queremos mover>

chema@lenovo:~/Prueba$ git checkout main
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  327aad6 prueba de checkout

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 327aad6

Switched to branch 'main'
chema@lenovo:~/Prueba$ git log --oneline 
* 0face9c (HEAD -> main) Añadido archivo de instrucciones
* e624744 añadido nuevo comando a main.py
* ad3b242 Segundo commit.
* 5011475 Primer commit del repositorio.
 

y volvemos a donde estabamos.

miércoles, 14 de septiembre de 2022

Curso Esquemático de Git y Github

imagen de entrada de git







Curso Esquemático de Git y Github.


Cuando pasas de un proyecto sencillo a otro más complicado, estas trabajando con más personas en un mismo programa o si simplemente quieres tener un registro de como está evolucionando el código que estas desarrollando para que un tú del futuro sepa lo que has hecho, tenemos que utilizar si o si un administrador de versiones. El más conocido en términos de rendimiento, seguridad y flexibilidad es Git y su plataforma GitHub.

Antes de meternos en materia de como funciona, es necesario entender como se pueden controlar las diferentes versiones de un programa. Si tuviéramos que hacer nosotros manualmente un control de como evoluciona un programa podríamos usar el comando "diff" de Linux de la siguiente forma.

Imaginemos un programa como el siguiente:

# version 0.1 
print("Hola Mundo")

Vamos a ponerle un nombre y guardarlo. Yo lo llamaré archivo_1.py. Ahora vamos a añadir una nueva línea:

# version 0.2 
print("Hola Mundo")
print("Esta es la segunda versión")

Lo vamos a guardar con un nombre distinto para poder comparar ambas versiones. Este se llamará archivo_2.py

Si en el shell de Linux tecleamos:

$ diff archivo_1.py archivo2_py

Veremos lo siguiente:

1c1
< # version 0.1 
---
> # version 0.2 
2a3
> print("Esta es la segunda versión")
Aunque se pueden intuir las diferencias existentes entre los archivos, el resultado no es fácilmente comprensible. Esto es debido a que fue diseñado para ser usado por computadoras y no por humanos. Pero a groso modo lo que viene a decir es que la primera línea del primer archivo debe cambiarse por la primera del segundo para que estén sincronizados. El símbolo "<" es el texto que sale y el ">" el que entra
Y que a la segunda línea del primer archivo que no cambia, hay que añadirle la tercera del segundo archivo.

Otras formas de hacer los mismo es usando el parámetro -u que compara ambos programas línea por línea.

$ diff -u archivo_1.py archivo2_py
--- archivo_1.py	2022-08-15 18:15:18.939411615 +0200
+++ archivo_2.py	2022-08-15 18:15:31.091518676 +0200
@@ -1,2 +1,3 @@
-# version 0.1 
+# version 0.2 
 print("Hola Mundo")
+print("Esta es la segunda versión")

Y quizá la más intuitiva es la que lo compara a dos columnas:

$ diff -y archivo_1.py archivo2_py
# version 0.1 			|# version 0.2 
print("Hola Mundo")		   print("Hola Mundo")
				>  print("Esta es la segunda versión")

Aplicando Cambios.

Para guardar los cambios que ha habido de la versión 0.1 del programa a la 0.2 usaríamos la siguiente instrucción:

$ diff -u archivo_1.py archivo2_py > cambios.diff

Si ahora, por ejemplo, se nos borra el archivo_2.py pero conservamos el archivo con las diferencias podemos volver a generarlo con las siguiente instrucción.

$ patch archivo_1.py < cambios.diff

Esto nos dará el archivo_2.py que se nos había borrado.

Esto es especialmente útil cuando tienes un programa que contiene un error y que tu no localizas. Imagina que se lo pasas a un amigo que te lo soluciona. Este amigo genera un nuevo archivo con las diferencias "cambios.diff" y te lo reenvía. Tu aplicas la solución ejecutando el comando "patch" tal como vimos en el ejemplo anterior sobre tu archivo.

IMPORTANTE: estos comandos se pueden aplicar también sobre directorios.

No obstante imagínate el lio que supondría aplicar estos métodos cada vez que hayas realizado modificaciones significativas en tus programas. Para hacerlo de forma automática por ti están los controladores de versiones como es el caso de Git.


Instalando GIT, configuración y uso.


1.- Instalando GIT.

Primeramente debemos comprobar si tenemos Git instalado. Para ello podemos usar:

$ git --version
git version 2.34.1

Esto nos dice que tenemos la versión 2.34.1 instalada. 

Si no lo tuviésemos instalado, en Linux una de las formas de hacerlo es con:

$ sudo apt-get install git

Si no usas un gestor de paquetes entonces hay que ir a su página web y seguir las instrucciones.

En Ubuntu además puedes considerar instalar estos dos paquetes que te pueden ayudar aunques son opcionales:

- Gitk 

Es un navegador gráfico que se usa para ver de manera gráfica el historial de un repositorio git.

Se instala con:

$ sudo apt-get install gitk

Ejemplo con $ gitk -all sobre un repositorio.

gtik ejemplo

- Git-gui

Es un entorno gráfico para usar los comandos de git sobre los repositorios.

Se instala con:

$ sudo apt-get install git-gui

Ejemplo de uso de $git gui

git gui



2.- CONFIGURANDO GIT.

Una vez instalado lo primero que tenemos que hacer es configurarlo con nuestro nombre y correo electrónico:

$ git config --global user.email  micorreo@ejemplo.com

$ git config --global user.name  mi_nombre

Usamos el parámetro "--global" porque queremos usar estos valores para todos los repositorios que creemos. Si queremos usar un correo y nombre especifico para un proyecto concreto, dentro de ese proyecto usaremos la mima instrucción pero con el parámetro "--local".

Si quieres ver como está la configuración actual de Git puedes usar el siguiente comando:

$ git config -l  o $ git config --list


3.- INICIANDO EL REPOSITORIO.

Dentro de la carpeta del proyecto hay dos formas de iniciar git.

Si empezamos el repositorio desde cero:

$ git init

Sin argumentos nos crea una carpeta oculta .git en la carpeta de trabajo donde iran todos los archivos que git necesita para funcionar. Si le pasamos como argumento un nombre entonces nos crea una carpeta con ese nombre y dentro crea la carpeta oculta .git.

Si lo que queremos es hacer una copia de un repositorio ya existente en otro lugar, entonces usaremos:

$ git clone dirección@delrepositorio


Antes de continuar hay que aclarar estos tres conceptos:

escenarios en git

El directorio
. Ahí están todos los archivos del proyecto tanto los que queramos realizar un seguimiento como los que no. Es el directorio de trabajo con todos sus archivos y directorios.

Working Tree. En este mismo directorio, a todo lo que queremos realizar un seguimiento para controlar sus cambios, tanto si son archivos concretos como directorios, es lo que va a formar el llamado árbol de trabajo. Esta lleno de archivo que editas, donde se agregan nuevos archivos y de los que eliminamos archivos innecesarios. Cualquier cambio en el árbol de trabajo, queda registrado y se muestra como archivos o directorios modificados.

Stage. Es el lugar donde se guardan los cambios, para luego ser llevados definitivamente al repositorio cuando hagamos instantáneas del proyecto llamadas confirmaciones o commits en inglés.

Vamos a ver un pequeño ejemplo de como crear un repositorio desde cero. Primero creamos el directorio de trabajo (proyecto), entramos en él y posteriormente iniciamos el repositorio.

creación de un repositorio desde cero


4.- Añadir archivos al árbol de trabajo o rastrearlos.

Si queremos ir añadiendo archivos uno a uno.

$ git add [nombre_del_archivo]

Pasa los archivos del directorio al staging area.

Si queremos añadir todo el directorio. (el punto al final aquí es importante, no es una errata)

$ git add .


primer archivo añadido



En el ejemplo anterior creamos un archivo nuevo llamado main.py, que antes no estaba. Quise ver en que estado estaba para git. 

Lo realizamos con la sentencia:

$ git status

Git nos dijo como estaba el árbol de trabajo y la situación actual del repositorio. También que estamos en la rama "master", que aún no hemos hecho ningún "commit" y que tenemos un archivo sin seguimiento, que es el main.py vacío que hemos creado.

Lo añadimos al seguimiento con "git add" y como vemos después, otra vez con git status, el archivo aparece ahora en verde y está listo para acciones posteriores.


5.- Pasar los cambios del Working Tree al Stage.


Ahora que ya tenemos un repositorio git, lo hemos configurado y hemos creado un archivo. El siguiente paso es realizar algunos cambios en el mismo y confirmar instantáneas de esos cambios en el repositorio cada vez que el proyecto alcance un estado que quieras conservar.  Tendremos que poner todo lo que queramos que se conserve en el Stage Area "Escenario" para poder realizarle una instantánea.

Abre el archivo main.py e incluye algún comentario o algo de texto. 

Ahora queremos conservar nuestro proyecto tal como esta en este momento, con el comentario o texto que acabamos de añadir. Como hemos modificado el archivo que ya teníamos rastreado, si hacemos un "git status"  el programa nos avisa que tenemos un archivo que ya teníamos rastreado pero que hemos hecho cambios. Como queremos hacer la instantánea del archivo con el comentario nuevo introducido tenemos nuevamente que confirmar ese cambio para que el archivo pase del área de trabajo al Escenario (Stage).

Hacemos nuevamente:

$ git add main.py

Ahora si que podemos tomar la instantánea del proyecto ya que el archivo está en el staging area. Para conservar el repositorio tal como está en este momento y hacer la instantánea o foto del proyecto en su estado actual.

El comando a utilizar es:

$ git commit

Como ves este comando toma los archivo, directorios etc que están en el área de trabajo y crea el repositorio.

Al hacerlo se abrirá tu editor de texto por defecto, el mío en Ubuntu es nano y podrás escribir de forma detallada en que consisten las modificaciones, para que tu yo del futuro o cualquiera que lo lea pueda saber lo que se ha hecho.

El editor por defecto se puede modificar con:

$ git config --global core.editor <nombre_editor>                   (nano, vim, emac etc)

Existen unas reglas de estilo a seguir que brevemente son:

- La primera línea se pondrá una breve descripción de no más de 50 caracteres de lo que se ha hecho desde el último commit.

- Luego añadimos una descripción ya más detallada del mismo.

primer commit

Guardamos y ya está. 

Nota: Si no escribes nada o sales se aborta el commit.

Si el repositorio es para tí y no estás muy interesado en realizar una descripción detallada de los cambios puedes utilizar el comando:

$ git commit -m "texto con la descripción abreviada del cambio"

Por ejemplo:

$ git commit -m "Primer commit"

No te saldrá el editor de texto y el commit se guardará con el texto introducido.


Otro atajo para realizar los commits, puede ser también:

$ git commit -a 

Es un atajo para confirmar cualquier cambio en los archivos que tengamos rastreados y confirmarlos en un solo paso. Pero ¡Ojo! no realiza commit de los archivos nuevos, solamente de los que estén en seguimiento, es decir de aquellos que hayamos hecho un "git add [archivo]" primero.

Es una mezcla de "git add . + git commit"

Luego, para ver los comentarios de los commits usaremos el argumento "log". Hay varios parámetros que podemos usar.

El más sencillo es:

$ git log

chema@lenovo:~/proyecto$ git log
commit 168d6f4f8baa5c29310309b82c5e7b9232ff877d (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 9 19:33:22 2022 +0200

    Proyecto inicial para aprender git básico
    
    Este es el commit o primera instantanea del proyecto. Hemos iniciado un
    repositorio, creado un archivo. Luego hemos añadido ese archivo al segui-
    miento de git. Añadimos un comentario volvimos a confirmar los cambios y
    realizamos el primer commit.

$ git log --stat

Muestra estadísticas de los commits.

chema@lenovo:~/proyecto$ git log --stat
commit 168d6f4f8baa5c29310309b82c5e7b9232ff877d (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 9 19:33:22 2022 +0200

    Proyecto inicial para aprender git básico
    
    Este es el commit o primera instantánea del proyecto. Hemos iniciado un
    repositorio, creado un archivo. Luego hemos añadido ese archivo al segui-
    miento de git. Añadimos un comentario volvimos a confirmar los cambios y
    realizamos el primer commit.

 main.py | 1 +
 1 file changed, 1 insertion(+)

$ git log -p 

Nos indica los cambios entre commits, equivalente a la salida de la instrucción "diff -u" que vimos al principio del capitulo. Página la salida.

chema@lenovo:~/proyecto$ git log -p
commit 168d6f4f8baa5c29310309b82c5e7b9232ff877d (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 9 19:33:22 2022 +0200

    Proyecto inicial para aprender git básico
    
    Este es el commit o primera instantánea del proyecto. Hemos iniciado un
    repositorio, creado un archivo. Luego hemos añadido ese archivo al segui-
    miento de git. Añadimos un comentario volvimos a confirmar los cambios y
    realizamos el primer commit.

diff --git a/main.py b/main.py
new file mode 100644
index 0000000..1274ace
--- /dev/null
+++ b/main.py
@@ -0,0 +1 @@
+# comentario

$ git log --oneline

Muestra cada commit que hayamos hecho en una sola línea, con lo que es más fácil de ver.


$ git show [id]

Lo mismo que el comando anterior pero mostrando solamente el último commit realizado. Si se introduce un ID concreto nos muestra solamente ese. El ID es el número largo que aparece después del commit, (en el siguiente ejemplo el ID es 168d6f4f8baa5c29310309b82c5e7b9232ff877d. Se puede usar abreviadamente los 6 primeros caracteres, en vez de teclear el número entero.)

chema@lenovo:~/proyecto$ git show
commit 168d6f4f8baa5c29310309b82c5e7b9232ff877d (HEAD -> master)
Author: usuario <usuario@correo.es>
Date:   Fri Sep 9 19:33:22 2022 +0200

    Proyecto inicial para aprender git básico
    
    Este es el commit o primera instantanea del proyecto. Hemos iniciado un
    repositorio, creado un archivo. Luego hemos añadido ese archivo al segui-
    miento de git. Añadimos un comentario volvimos a confirmar los cambios y
    realizamos el primer commit.

diff --git a/main.py b/main.py
new file mode 100644
index 0000000..1274ace
--- /dev/null
+++ b/main.py
@@ -0,0 +1 @@
+# comentario

$ git diff (archivo) 

Equivale al comando "diff -u" que vimos al principio, para comparar las diferencias entre dos versiones del mismo archivo. Nos muestra los cambios en los archivos en seguimiento PERO teniendo en cuenta el último commit y antes de realizar uno nuevo. Se le puede especificar tambien el nombre de un archivo en concreto.

$ git add -p  

Para confirmar las modificaciones realizadas a los archivos, antes de hacer el commit. En vez de realizar las confirmaciones de los cambios en los archivos de forma automática nos muestra los cambios y nos permite confirmarlos o no. Es como "git add" pero nos permite ver antes los cambios y confírmalos para añadirlos al stage.

chema@lenovo:~/proyecto$ git add -p
diff --git a/main.py b/main.py
index 3808744..91479a0 100644
--- a/main.py
+++ b/main.py
@@ -1,2 +1,2 @@
 # comentario

(1/1) Stage this hunk [y,n,q,a,d,e,?]? 

$ git diff --staged

Para ver los cambios en el stage pero que aún no tienen commit.


 

domingo, 7 de agosto de 2022

¿Cómo ejecutar funciones escritas en C desde Python?


Imagen de entrada simbolos c y python

Llamando a una función creada en C desde Python.

Existen ocasiones en que nos vendría bien ejecutar funciones creadas en C desde Python. una de las razones puede ser, por ejemplo, ganar en rapidez en la ejecución de una determinada función, ya que al estar compilada, la función en C es bastante más rápida que su homóloga en Python.

¿Y como conseguimos esto? Pues usando el módulo de Python Ctypes.

Para conseguir nuestro objetivo vamos a seguir estos pasos:

  1. Crear un archivo en C con la función que queramos utilizar.
  2. Crear un archivo de biblioteca compartida (extensión .so) usando el compilador C.
  3. En nuestro programa en Python, creamos una instancia de ctypes.CDLL a partir del archivo compartido anterior.
  4. Finalmente, llamar a la función C usando el formato {CDLL_instance}.{nombre_funcion}({parametros_función}).


Paso 1: crear un archivo C con algunas funciones

#include <stdio.h>

int cuadrado(int i) 
{
    return i * i;
}


Hemos creado una función en C muy simple que devuelve el cuadrado de un número entero que le pasaremos como parámetro. El nombre de la función es "cuadrado". Guardamos el archivo con el nombre que queramos, en mi caso mi_funcion.c

Paso 2: crear el archivo de la biblioteca compartida

Podemos usar el siguiente comando para crear el archivo de biblioteca compartida desde el archivo fuente C que es mi_funcion.c

$ cc -fPIC -shared -o mi_funcion.so mi_funcion.c

El archivo con la biblioteca compartida se crea con el nombre de mi_funcion.so

Paso 3 y 4: Llamar a la función C desde el programa de Python


Creamos el programa en Python que utilizará la función creada en C.
from ctypes import *
# Ruta absoluta de la biblioteca compartida que contiene 
# la función.
so_file = "/home/chema/Cursos/mi_funcion.so"
# Creamos una instancia de CDLL pasándole como argumento
# la ruta de la biblioteca compartida.
mi_funcion = CDLL(so_file)
# Mostrará que el objeto creado es una instancia de la clase
# ctypes.CDLL
print(type(mi_funcion))
# Ejecutamos la función creada (paso 4).
print(mi_funcion.cuadrado(5))
print(mi_funcion.cuadrado(56))
Salida:
<class 'ctypes.CDLL'>
25
3136
Si cambias el archivo del programa en C, tendrás que volver a generar el archivo de la biblioteca compartida.

Conclusión

La implementación predeterminada de Python está escrita en lenguaje C y se llama CPython. Por lo tanto, no es muy raro usar funciones de C en un programa de Python.

En este pequeño tutorial, aprendimos cómo llamar fácilmente a funciones C en un programa de Python.



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.