Cap. 10 - Eventos

Muchos programas y dispositivos como los teléfonos celulares responden a eventos - cosas que ocurren.

En este capítulo veremos muy brevemente cómo funciona la programación orientada a eventos.

10.1) Eventos de teclado (keypress)

Aquí hay un programa con ciertas funcionalidades. Cópialo en tu espacio de trabajo y ejecútalo. Cuando se abra la ventana, presiona las teclas y la tortuga tess se moverá!

import turtle

turtle.setup(400,500)                # Determino el tamaño de la ventana
wn = turtle.Screen()                 # Obtengo una referencia a la ventana
wn.title("Detectando acciones del teclado!")     # Modifico el título de la ventana
wn.bgcolor("lightgreen")             # Color de fondo
tess = turtle.Turtle()               # Creamos una tortuga

# Las próximas 4 funciones son nuestros "manejadores de eventos"
def h1():
   tess.forward(30)

def h2():
   tess.left(45)

def h3():
   tess.right(45)

def h4():
    wn.bye()                        # Cerramos la ventana

# Estas líneas conectan las teclas concretas presionadas con los manejadores de eventos que definimos antes
wn.onkey(h1, "Up")
wn.onkey(h2, "Left")
wn.onkey(h3, "Right")
wn.onkey(h4, "q")

# Ahora le decimos a la ventana comience a oír eventos,
# Si cualquiera de las teclas anteriores son presionadas,
# el evento será "oído" y su correspondiente manejador será llamado
wn.listen()
wn.mainloop()

Algunas observaciones:

10.2) Eventos del mouse

Un evento del mouse es un poco distinto de uno de teclado porque su manejador necesita dos parámetros para recibir las coordenadas x, y indicando dónde estaba el mouse cuando ocurrió el evento.

import turtle

turtle.setup(400,500)
wn = turtle.Screen()
wn.title("Cómo manejar clicks del mouse en la ventana!")
wn.bgcolor("lightgreen")

tess = turtle.Turtle()
tess.color("purple")
tess.pensize(3)
tess.shape("circle")

def h1(x, y):
   tess.goto(x, y)

wn.onclick(h1)  # Responder a un click en la ventana.
wn.mainloop()

Aquí usamos por primera vez el método goto de la tortuga, que permite moverla hacia una posición de coordenadas absolutas (en casi todos los ejemplos anteriores móviamos la tortuga relativamente a su posición actual). Entonces lo que este programa hace es mover la tortuga (y dibujar una línea al hacerlo) hacia donde se haga click con el mouse. Si no lo has probado es tiempo de hacerlo!

Si agregamos esta línea como la primera del cuerpo de h1, aprenderemos un útil truco de debugging:

		   wn.title("Hubo un click en las coordenadas {0}, {1}".format(x, y))

Dado que es fácil cambiar el texto en la barra de título (title bar) de la ventana, éste resulta un lugar práctico para mostrar información de debugging o del status actual del programa. (Por supuesto, éste no es el propósito de la barra del título) (y usarla de esta forma es sólo un truco recomendable para la fase de desarrollo, no para el momento en que uno llegó a la versión final)

Pero hay más!

No sólo la ventana puede recibir eventos del mouse, también cada tortuga puede tener manejadores propios de eventos del mouse. Así que crearemos dos tortugas, cada una con un manejador ligado a su propio evento onclick. Y los dos manejadores pueden hacer cosas distintas para sus respectivas tortugas:

import turtle

turtle.setup(400,500)              # Determinar el tamaño de ventana
wn = turtle.Screen()               # Obtener una referencia a la ventana
wn.title("Evento click con 2 tortugas!")  # Cambiar el título de la ventana
wn.bgcolor("lightgreen")           # Establecer el color de fondo
tess = turtle.Turtle()             # Crear dos tortugas
tess.color("purple")
alex = turtle.Turtle()
alex.color("blue")
alex.forward(100)                  # Separarlas entre sí

def handler_para_tess(x, y):
    wn.title("Tess responde al click en {0}, {1}".format(x, y))
    tess.left(42)
    tess.forward(30)

def handler_para_alex(x, y):
    wn.title("Alex responde al click en {0}, {1}".format(x, y))
    alex.right(84)
    alex.forward(50)

tess.onclick(handler_para_tess)
alex.onclick(handler_para_alex)

wn.mainloop()

Ejecútalo y fíjate qué sucede si haces click sobre las tortugas!

10.3) Eventos automáticos desde un timer

Los relojes despertadores, temporizadores de cocina, y contadores de cuentas regresivas para bombas y explosivos en algunas películas están diseñados para crear un evento "automático" tras cierto intervalo de tiempo. El módulo turtle de Python tiene un timer que puede causar un evento cuando pasó cierta cantidad de tiempo.

import turtle

turtle.setup(400,500)
wn = turtle.Screen()
wn.title("Probando un timer")
wn.bgcolor("lightgreen")

tess = turtle.Turtle()
tess.color("purple")
tess.pensize(3)

def h1():
    tess.forward(100)
    tess.left(56)

wn.ontimer(h1, 2000)
wn.mainloop()

El timer es inicializado en el llamado al método ontimer de la ventana, seteado para responder en 2 segundos (2000 milisegundos). Cuando ocurre el evento, el manejador h1 es llamado y tess se mueve 100 pasos hacia la izquierda (tras lo cual rota 56°).

Cuando se setea un timer éste sólo se ejecuta una vez. Así que es habitual reiniciar el timer desde el propio handler, para habilitar así a que se disparen nuevos eventos. Esto se ilustra en el siguiente programa:

import turtle

turtle.setup(400,500)
wn = turtle.Screen()
wn.title("Probando un timer")
wn.bgcolor("lightgreen")

tess = turtle.Turtle()
tess.color("purple")
tess.pensize(3)

def h1():
    tess.forward(100)
    tess.left(56)
    wn.ontimer(h1, 60)

h1()
wn.mainloop()

10.4) Un ejemplo: máquinas de estado

Una máquina de estado es un sistema que puede estar en uno de un pequeño conjunto de estados. Dibujamos un diagrama de estados para representar la máquina, en que cada estado es dibujado en un círculo o elipse. Ciertos eventos ocurren que hace que el sistema deje un estado y transite hacia uno diferente. Estas transiciones de estados se dibujan habitualmente como flechas en el diagrama.

Esta idea no es nueva. Cuando prendemos un celular por primera vez, va hacia un estado que podríamos llamar "esperando PIN". Cuando se ingresa el PIN correcto, va hacia un estado diferente (digamos: "Ready"). Luego podemos bloquear el teléfono, y entraría en un estado "Locked", etc.

Una máquina de estado simple que encontramos en la vida cotidiana es un semáforo (luces de tráfico). Aquí hay un diagrama de estados que muestra que la máquina circula continuamente a través de tres diferentes estados, que enumeramos como 0, 1 y 2.

Construiremos un programa que usa la tortuga para simular las luces de tráfico. Esta tarea nos dará 3 lecciones importantes:

Copia y ejecuta este programa. Asegúrate de comprender lo que hace cada línea, consultando la documentación en caso de ser necesario.

import turtle           # La tortuga tess se convierte en unas luces de tráfico

turtle.setup(400,500)
wn = turtle.Screen()
wn.title("Tess se convierte en un semáforo!")
wn.bgcolor("lightgreen")
tess = turtle.Turtle()


def dibujar_semaforo():
    """ Dibujar un recuadro que haga del cuerpo del semáforo """
    tess.pensize(3)
    tess.color("black", "darkgrey")
    tess.begin_fill()
    tess.forward(80)
    tess.left(90)
    tess.forward(200)
    tess.circle(40, 180)
    tess.forward(200)
    tess.left(90)
    tess.end_fill()


dibujar_semaforo()

tess.penup()
# Ubicar a tess en el lugar en que debería ir la luz verde
tess.forward(40)
tess.left(90)
tess.forward(50)
# Convertir a tess en un círculo verde grande
tess.shape("circle")
tess.shapesize(3)
tess.fillcolor("green")

# Las luces de tráfico son un tipo de máquina de estado con tres estados,
# Verde, Amarillo y Rojo. Nombramos a estos estados 0, 1 y 2
# Cuando la máquina cambia de estado, cambiamos la posición de tess
# y su color.

# Esta variable lleva la cuenta del estado actual de la máquina
state_num = 0


def avanzar_estado_de_la_maquina():
    global state_num
    if state_num == 0:       # Transición del estado 0 al estado 1
        tess.forward(70)
        tess.fillcolor("orange")
        state_num = 1
    elif state_num == 1:     # Transición del estado 1 al estado 2
        tess.forward(70)
        tess.fillcolor("red")
        state_num = 2
    else:                    # Transición del estado 2 al estado 0
        tess.back(140)
        tess.fillcolor("green")
        state_num = 0

# Asociar el manejador de estados a la tecla barra espaciadora
wn.onkey(avanzar_estado_de_la_maquina, "space")

wn.listen()                      # Escuchar eventos
wn.mainloop()

La novedad en cuanto a instrucciones de Python está en la primera línea de la función avanzar_estado_de_la_maquina, en la que utilizamos la palabra clave global. Ésta indica a Python que la variable estado_actual no debe ser local (a pesar de que la función la utilizará para hacer chequeos y asignaciones en varios puntos del código), sino que siempre ha de ser considerada como una referencia a la variable de ese mismo nombre que había sido definida antes de la definición de avanzar_maquina_de_estado.

Lo que hace el código de esta función es avanzar desde del estado actual hacia el próximo. Al cambiar de estado se mueve a tess hacia su nueva posición, se le cambia el color y se le asigna a estado_actual el número del nuevo estado que acabamos de ingresar.

Cada vez que el usuario presione la barra espaciadora, el manejador de eventos hará que la máquina de estados de tres luces pase a su nuevo estado.

10.5) Glosario

10.6) Ejercicios

1) Agrega algunos manejadores de teclado al programa de la sección 10.1: (hacerlo)

 

2) Cambiar el programa de las luces de tráfico para que el cambio ocurra automáticamente, según vaya pasando el tiempo. (hacerlo)

 

3) En un capítulo anterior vimos dos métodos, hideturtle y showturtle que permiten ocultar o hacer visible a una tortuga. Esto sugiere que podríamos implementar las luces de tráfico de un modo distinto. Modifica el programa para que cree 3 tortugas distintas para cada una de las luces, y en vez de mover a tess a diferentes posiciones, cambiando su color a cada paso, simplemente haga que una de las 3 tortugas sea visible cada vez, y haga invisibles a las otras dos. Una vez que hayas hecho los cambios, detente un momento y medita sobre ambos programas: si bien ambos parecen hacer lo mismo, ¿uno de los dos métodos es preferible al otro? ¿cuál de los dos se parece más a lo que ocurre en un semáforo real? (hacerlo)

 

4) Ahora que ya tenemos una tortuga para cada luz de tráfico, cabe pensar que tal vez no fue una buena idea lo de la visibilidad/invisibilidad. Si miramos las luces de tráfico reales, se prenden y apagan - pero cuando se apagan siguen ahí, sólo que con un color oscuro. Modifica el programa para que las luces no desaparezcan, sino que simplemente estén prendidas o apagadas (siguen visibles cuando están apagadas) (hacerlo)

 

5) Tu programa controlador de luces de tráfico fue patentado, y estás a punto de hacerte rico. Pero tu nuevo cliente necesita un ajuste. Quieren 4 estados en su máquina de estado. Verde, luego Verde y Amarillo juntos, luego Amarillo y luego Rojo. Adicionalmente, quiere que se pase una distinta cantidad de tiempo en cada estado. La máquina debería estar 3 segundos en el estado Verde, luego 1 segundo en el estado Verde+Amarillo, luego 1 segundo en el estado Amarillo y por último 2 segundos en el estado Rojo. Modifica la lógica de tu máquina de estado para reflejarlo. (hacerlo)

 

6) Si no sabes cómo se computan los puntos en un partido de tenis, pregúntale a un amigo o revisa Wikipedia. Un game de tenis entre un jugador A y uno B siempre tiene un score. Queremos pensar en el "estado del score" como una máquina de estados. El juego empieza en el estado (0, 0), significando que ninguno de los jugadores tiene ningún puntaje todavía. Asumiremos que el primer elemento en este par es el puntaje del jugador A. Si el jugador A gana el primer punto, el score pasa a ser (15, 0). Si en cambio B gana el primer punto, el score pasa a ser (0, 15). En lo que sigue hay unos pocos estados y transiciones para un diagrama de estados. En este diagrama, cada estado tiene dos posibles salidas (según que sea A o B quien gane el siguiente punto), y la flecha de más arriba es siempre la transición que ocurre cuando A gana el punto. Completar el diagrama, mostrando todas las transiciones y todos los estados. (hacerlo