Cap. 3 - Dibujando con Tortugas
Hay muchos módulos en Python que nos permiten hacer cosas muy variadas, desde enviar emails a públicar páginas webs. El que utilizaremos en este capítulo permite crear tortugas con las que podremos dibujar formas y patrones.
Dibujar con tortugas es entretenido, pero nuestro objetivo en este capítulo es ir aprendiendo a pensar computacionalmente, como haría un programador. Muchos temas que sólo mencionaremos en este capítulo serán cubiertos con detalle en capítulos siguientes.
3.1) Nuestro primer programa con la tortuga
Escribamos un primer programa, con una tortuga a la que llamaremos "alex" (L3_tortuga)
import turtle # Allows us to use turtles wn = turtle.Screen() # Creates a playground for turtles alex = turtle.Turtle() # Create a turtle, assign to alex alex.forward(50) # Tell alex to move forward by 50 units alex.left(90) # Tell alex to turn by 90 degrees alex.forward(30) # Complete the second side of a rectangle wn.mainloop() # Wait for user to close window
El programa muestra una ventana en la que ha dibujado medio rectángulo (lado inferior y lado derecho)
- la primera línea dice a Python que usaremos un módulo llamado turtle. Éste nos provee de los dos tipos Turtle y Screen.
- la notación turtle.Turtle significa "el tipo Turtle definido en el módulo turtle"
- creamos entonces un objeto Screen que llamamos "wn" (por "windows") - toda ventana contiene un canvas que es donde se puede dibujar
- la tercera línea crea la tortuga y la llama alex
- en las líneas 5-7 le decimos a alex que se mueva y dibuje. Lo hacemos llamando a los métodos de alex
- la línea wn.mainloop() le indica a la ventana "wn" que quede a la espera de acciones por parte del usuario (acciones de teclado, mouse, etc.)
Un objeto puede tener varios métodos (cosas que puede hacer) y también varios atributos (o propiedades). Por ejemplo, cada turtle tiene un atributo color
- un llamado al método color permite setear el atributo color. Ejemplo: alex.color("red")
- el color de la tortuga, el ancho de línea, su posición en la ventana, la dirección en que mira, etc. son parte de su estado actual
- de modo similar, el objeto window tiene un color de fondo, cierto texto en la barra del título, un tamaño y una posición en pantalla (todos parte del estado del objeto Screen)
Existen varios métodos que nos permiten modificar el estado de la tortuga y la ventana. Veremos sólo algunos.
El siguiente programa es el mismo que teníamos antes con algunas líneas nuevas intercaladas: (L3_tortuga (2))
import turtle wn = turtle.Screen() wn.bgcolor("lightgreen") # Set the window background color wn.title("Hello, Tess!") # Set the window title tess = turtle.Turtle() tess.color("blue") # Tell tess to change her color tess.pensize(3) # Tell tess to set her pen width tess.forward(50) tess.left(120) tess.forward(50) wn.mainloop()
Lo que da una nueva ventana, de fondo verde, con la tortuga de tinta azul, y el dibujo de un ángulo en vez de un semirrectángulo.
Tarea: extender este programa:
- Modificar el programa para que, antes de crear la ventana, pida al usuario que ingrese el color de fondo deseado
- debería guardar la respuesta del usuario en una variable, y modificar el color de la ventana según lo pedido por el usuario
- una lista de color permitidos se puede buscar en: http://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm
- Hacer cambios similares para permitir al usuario elegir el color de la tortuga
- Hacer lo mismo para el ancho del lápiz de la tortuga
- pista: el diálogo retorna un string, pero la tortuga espera un entero, por lo cual habrá que hacer una conversión de string a int
3.2) Instancias - un rebaño de tortugas
Así como podemos tener muchos enteros en un programa, podemos tener muchas tortugas. Cada una se llama instancia y cada instancia tiene sus propios atributos y métodos.
- de este modo, podemos dibujar con alex con un color en cierta posición, y con tess con otro color y en otra posición
Ejemplo: (L3_tortuga (3))
import turtle wn = turtle.Screen() # Set up the window and its attributes wn.bgcolor("lightgreen") wn.title("Tess & Alex") tess = turtle.Turtle() # Create tess and set some attributes tess.color("hotpink") tess.pensize(5) alex = turtle.Turtle() # Create alex tess.forward(80) # Make tess draw equilateral triangle tess.left(120) tess.forward(80) tess.left(120) tess.forward(80) tess.left(120) # Complete the triangle tess.right(180) # Turn tess around tess.forward(80) # Move her away from the origin alex.forward(50) # Make alex draw a square alex.left(90) alex.forward(50) alex.left(90) alex.forward(50) alex.left(90) alex.forward(50) alex.left(90) wn.mainloop()
Al final alex completa un rectángulo negro y tess un triángulo rosa.
Aquí hay algunas observaciones del tipo cómo pensar como un programador:
- Hay 360 grados en un círculo. Si los ángulos que gira una tortuga suman 360 (o un múltiplo de 360), ésta terminará mirando hacia el mismo lugar hacia el que miraba en un principio
- Podríamos haber no incluido el último giro de alex, pero hubiera sido menos correcto, porque hubiéramos dejado a alex mirando en una dirección distinta que la que tenía al principio
- restaurar a alex a su posición inicial y con su dirección original es una buena práctica para cuando utilicemos estas piezas de código como ladrillos para proyectos más grandes
- Hicimos lo mismo con tess: primero dibujó un triángulo volviendo a su posición original, y luego dio media vuelta y se alejó del origen hacia la izquierda
- la línea en blanco entre las dos partes de las acciones de tess es una buena práctica para separar acciones conceptualmente distintas
- Una de las aplicaciones más importantes de los comentarios es para recordarnos las razones por las que fuimos tomando decisiones al programar
- Obviamente podríamos agregar más tortugas al "rebaño", pero con dos alcanzaba y sobraba para ilustrar que podemos tener varias actuando de forma independiente entre sí
3.3) El loop FOR
Para dibujar un cuadrado debimos repetir 4 veces la misma instrucción. De haber sido un hexágono u octógono habría que haber hecho más repeticiones.
Para evitar esta clase de repeticiones es bueno utilizar un bucle como for, que permite repetir el mismo código con ligeras modificaciones.
Por ejemplo, podemos usar un bucle for para enviar un email a cada uno de los amigos (como aun no sabemos enviar emails, lo haremos sólo imprimiendo un mensaje para cada uno)
for f in ["Joe","Zoe","Brad","Angelina","Zuki","Thandi","Paris"]: invite = "Hi " + f + ". Please come to my party on Saturday!" print(invite) # more code can follow here ...
El output se verá así:
Hi Joe. Please come to my party on Saturday! Hi Zoe. Please come to my party on Saturday! Hi Brad. Please come to my party on Saturday! Hi Angelina. Please come to my party on Saturday! Hi Zuki. Please come to my party on Saturday! Hi Thandi. Please come to my party on Saturday! Hi Paris. Please come to my party on Saturday!
Algunas observaciones:
- La variable f en la instrucción for se llama variable del loop. Podríamos haber elegido cualquier otro nombre.
- Las líneas 2 y 3 son el cuerpo del loop. Siempre se indenta (la indentación determina cuáles son las sentencias que están en el cuerpo del loop)
- En cada iteración o paso del loop, primero se chequea si quedan items para ser procesados
- si no quedan (esto se llama condición de terminación del loop), el bucle termina y el programa continúa en la línea que sigue al body del loop
- si quedan items, la variable del loop se actualiza para referirse al siguiente elemento de la lista (en este ejemplo, por lo tanto, el loop body se ejecutará 7 veces)
- Al final de cada ejecución del cuerpo del loop Python vuelve a la línea inicial del for para ver si aun quedan items, etc.
3.4) Flujo de ejecución del bucle FOR
A medida que avanza un programa, el intérprete lleva la cuenta de cuál es la siguiente sentencia a ser ejecutada. Esto se llama control de flujo o flujo de ejecución del programa.
- los humanos "apuntan con el dedo" a la siguiente línea a ejecutarse, así que podríamos decir que el flujo de ejecución es el "dedo de Python" apuntando a la siguiente línea a ejecutar
Hasta ahora el flujo de ejecución había sido vertical ("de arriba abajo"), pero con el for loop esto cambia.
Es fácil de visualizarlo con un diagrama de flujo (flow chart).
3.5) El bucle simplifica nuestro programa con la tortuga
Para dibujar un cuadrado podemos reducir los 4 pares de líneas que ejecutaba alex por sólo un par, así: (L3_tortuga (for))
for i in [0,1,2,3]: alex.forward(50) alex.left(90)
Algunas observaciones:
- No hacemos esto para "ahorrar líneas de código", sino por algo mucho más importante la programar: reconocer patrones de código y evitar trabajo repetitivo
- Encontrar esos patrones y organizar nuestro código para que los aproveche es una habilidad esencial de todo programador
- Los valores [0, 1, 2, 3] fueron utilizados para repetir el cuerpo del bucle 4 veces (se podrían haber utilizado cualquier otras cuatro, pero éstas son las convencionales por defecto)
- Son tan convencionales que Python provee de un objeto especial (el objeto range) para referirse a ellas:
for i in range(4): # Executes the body with i = 0, then 1, then 2, then 3 for x in range(10): # Sets x to each of ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Algunas observaciones:
- A los programadores y científicos de la computación les gusta contar desde cero
- range(4) no incluye al 4 (va hasta el 3), y range(10) no incluye el 10 (va hasta el 9)
En suma, para repetir una acción 4 veces, un buen programador Python haría algo así: (L3_tortuga (for))
for i in range(4): alex.forward(50) alex.left(90)
A estas alturas deberías poder hacer que tess dibuje su triángulo equilátero también mediante un for loop. Sería algo así: (L3_tortuga (for))
for i in range(3): # Make tess draw equilateral triangle tess.forward(80) tess.left(120)
Qué pasaría si hiciéramos esta versión del loop de alex? (L3_tortuga (for))
for c in ["yellow", "red", "purple", "blue"]: alex.color(c) alex.forward(50) alex.left(90)
- Respuesta: se dibujaría cada lado del cuadrado de alex de un color distinto
A una variable se le puede asignar un valor que sea una lista. Luego esa variable se puede utilizar en el for loop, como en el siguiente ejemplo: (L3_tortuga (for))
# Assign a list to a variable clrs = ["yellow", "red", "purple", "blue"] for c in clrs: alex.color(c) alex.forward(50) alex.left(90)
3.6) Algunos métodos y trucos más con la tortuga
Los métodos de turtle pueden usar ángulos o distancias negativas. Así, tess.forward(-100) moverá a tess 100 hacia atrás. Y tess.left(-30) le hará rotar 30° hacia la derecha.
Por otro lado, como hay 360° en un círculo, rotar 30° hacia la izquierda tiene le mismo efecto que rotar 330° hacia la derecha (la única diferencia estará en la animación del movimiento, que nos permite distinguir entre ambos casos).
Esto sugiere que no necesitamos un método "left" y un método "right" de rotación: podríamos usar uno solo. Del mismo modo, existe un método backward que no es estrictamente necesario usar.
- Observación: si hiciéramos alex.backward(-100) lo moveríamos 100 hacia adelante
Parte de pensar como un programador requiere comprender la estructura y ricas relaciones que ocurren en nuestro campo. En este caso, revisando algunos hechos básicos sobre la geometría y comprendiendo las relaciones entre left/right, backward/forward, distancias positivas/negativas y ángulos positivos/negativos mientras jugamos con tortuas es un buen inicio para ese camino.
El lápiz de una tortuga puede ponerse "up" o "down", indicando en el primer caso que queremos movernos sin dibujar.
Las tortugas pueden tener distintas formas. Las formas disponibles por defecto son arrow, blank, circle, classic, square, triangle, turtle
- Ejemplo: alex.shape("turtle") (L3_tortuga (for + shape))
Se puede regular la velocidad con que se mueve la tortuga, desde 1 (lento) hasta 10 (rápido). Si se elige 0, se interpreta como "tan rápido como sea posible" (L3_tortuga (for + shape))
Una tortuga puede "estampar" su huella en el canvas, y ésta quedará aun cuando la tortuga se haya movido. El estampado funciona incluso cuando el pen está "up".
Veamos un ejemplo que aplica todas estas funcionalidades: (L3_tortuga (espiral))
import turtle wn = turtle.Screen() wn.bgcolor("lightgreen") tess = turtle.Turtle() tess.shape("turtle") tess.color("blue") tess.penup() # This is new size = 20 for i in range(30): tess.stamp() # Leave an impression on the canvas size = size + 3 # Increase the size on every iteration tess.forward(size) # Move tess along tess.right(24) # ... and turn her tess.color("orange") wn.mainloop()
Se obtiene lo siguiente:
Cuántas veces se ejecutó el cuerpo del loop? (30) Cuántas tortugas se ven en pantalla? (31) Todas salvo la última son huellas creadas con el llamado a stamp. La última es en realidad la única instancia de la tortuga, es decir la tortuga en su estado actual (la cual todavía no ha impreso ninguna huella en esa posición). Para distinguirla de las demás, agregamos la línea que le asigna un color naranja, lo cual la hace bien distinta de las que sólo son huellas.
3.7) Glosario
- atributo, canvas, control de flujo, loop FOR, cuerpo del loop, variable del loop, instancia
- método, invocar (o llamar) un método, módulo, objeto, range, condición de terminación
3.8) Ejercicios
1) Escribir un programa que escriba 1000 veces "Dibujamos en Python con tortugas".
2) Imagina un objeto celular y designa tres posibles atributos y 3 posibles métodos.
3) Escribe un programa que use un loop para imprimir:
Uno de los meses del año es enero Uno de los meses del año es febrero ...
4) Supón que nuestra tortuga tess está con heading 0 (mirando hacia el este). Si ejecutamos tess.left(3645), ¿qué hace tess, y cuál es su heading final?
- (respuesta: el heading final es 45° sobre la horizontal, y rota 10 veces antes de alcanzar dicha posición)
5) Assume you have the assignment xs = [12, 10, 32, 3, 66, 17, 42, 99, 20]
- Write a loop that prints each of the numbers on a new line.
- Write a loop that prints each number and its square on a new line.
- Write a loop that adds all the numbers from the list into a variable called total. You should set the total variable to have the value 0 before you start adding them up, and print the value in total after the loop has completed.
- Print the product of all the numbers in the list. (product means all multiplied together)
6) Utiliza loops for para hacer que la tortuga dibuje los siguientes polígonos regulares (regular significa que todos los lados y ángulo son iguales):
- Triángulo equilátero
- Cuadrado
- Hexágono (6 lados)
- Octógono (8 lados)
7) Un pirata borracho hace un giro aleatorio y luego avanza 100 pasos hacia adelante, luego hace otro giro aleatorio y da otros 100 pasos, hace un nuevo giro, etc. Una estudiante de ciencias sociales registra el ángulo de cada giro antes de que el pirata dé los 100 pasos siguientes. Sus datos experimentales son [160, -43, 270, -97, -43, 200, -940, 17, -86]. (Los ángulos positivos son antihorarios.) Usa una tortuga para dibujar el camino que ha seguido el pirata.
import turtle # Allows us to use turtles wn = turtle.Screen() # Creates a playground for turtles pirata = turtle.Turtle() # Creo la tortuga y la llamo pirata angulos = [160, -43, 270, -97, -43, 200, -940, 17, -86] for angulo in angulos: pirata.left(angulo) #positivos antihorarios (hacia la "izquierda") pirata.forward(100) wn.mainloop() # Wait for user to close window
8) Mejorar el programa anterior para que reporte el heading del pirata al terminar su recorrido (asumir que el heading inicial es cero).
import turtle # Allows us to use turtles wn = turtle.Screen() # Creates a playground for turtles pirata = turtle.Turtle() # Creo la tortuga y la llamo pirata angulos = [160, -43, 270, -97, -43, 200, -940, 17, -86] heading = 0 for angulo in angulos: pirata.left(angulo) #positivos antihorarios (hacia la "izquierda") pirata.forward(100) heading = heading + angulo heading = heading % 360 print("El heading final del pirata es: " + str(heading)); wn.mainloop() # Wait for user to close window
9) Si fueras a dibujar un polígono regular de 18 lados, cuál sería el ángulo de giro de la tortuga en cada esquina?
- (respuesta: 180*(18 - 2)/18 = 10*16 = 160°)
10) En el prompt interactivo, anticipa qué hará cada una de las siguientes líneas y luego verifica que así ocurra. Ganas un punto por cada vez que aciertes.
>>> import turtle >>> wn = turtle.Screen() >>> tess = turtle.Turtle() >>> tess.right(90) >>> tess.left(3600) >>> tess.right(-90) >>> tess.speed(10) >>> tess.left(3600) >>> tess.speed(0) >>> tess.left(3645) >>> tess.forward(-100)
11) Escribe un programa para dibujar una estrella pentagonal regular
- Pista 1: prueba primero sobre una hoja de papel con un objeto que puedas mover (un lápiz, un celular) - piensa cuántas líneas tienes que dibujar y cuántas veces tienes que rotar
- Si consideras cuántas vueltas completas dio el objeto, y que cada vuelta completa son 360°, tendrás la cantidad total de grados a dividir por 5 para obtener el ángulo de cada giro
- (respuestas: 2 giros completos = 720°, dividido 5 da 144° por giro)
- Pista 2: puedes ocultar la tortuga cuando dibujas, mediante el método tess.hideturtle(). Para hacerla visible otra vez, utiliza tess.showturtle()
12) Escribe un programa que dibuje un reloj similar al de la siguiente figura
13) Crear una tortuga y asignarla a una variable. Si se pregunta por su tipo, cuál es el resultado?
- Respuesta: <class 'turtle.Turtle'> (es decir, el módulo y la clase en particular dentro de ese módulo)