Senti a necessidade de fazer alguns desenhos para fazer a explicação sobre interseções de círculos (a publicar). Para isso, me lembrei que na resposta original que dei no StackOverflow fiz os desenhos usando o módulo Turtle do Python. Porém, não consegui resgatar os códigos originais.

Então, vamos aprender a mexer com tartarugas em Python?

Meu foco aqui é:

  • desenhar os eixos principais
  • desenhar círculos
  • desenhar segmentos de retas
  • mudar a cor do que está sendo desenhado

No final deste artigo eu terei descrito como fazer essas 4 coisas. Não necessariamente nessa mesma ordem. Outras coisas também serão estudadas, tanto sobre Python quanto sobre seu próprio módulo turtle.

Iniciando a tartaruga

Estou com uma instalação padrão do Python 3.9.7 instalado pelo Windows. Simplesmente digitei python na linha de comando e segui as instruções para instalá-lo.

Para começar e deixar mais simples, escolhi por brincar no REPL do Python. Isso permite que eu teste conforme digito e obtenho o resultado imediatamente.

Ok, importar a tartaruga. O módulo literalmente se chama turtle, então é import turtle.

Eu posso simplesmente iniciar uma tartaruga fazendo t = turtle.Turtle(). Isso criará uma tela padrão com a tartaruga:

Print da tela exibindo a criação da tela através da instanciação da tartaruga

Porém, iniciar desse jeito não tem tanta interação com a tela. Não tenho de imediato a tela para pegar dimensões, setar o título, deixar interativo, coisas assim. Nesse caso, podemos iniciar a tela e, então, criar a tartaruga:

import turtle

sc = turtle.Screen()
t = turtle.Turtle()
sc.title("Uma tortuguita")

Iniciando a tartaruga e setando título na tela

Prendendo a eventos para poder gravar

Tentei desenhar o eixo X:

import turtle

sc = turtle.Screen()
t = turtle.Turtle()
sc.title("Uma tortuguita")
x, y = sc.screensize()

t.fd(x)

E com isso obtive o seguinte resultado:

Tela mostrando um segmento reto do centro até o lado direito

Só que não consegui gravar o processo… O ideal seria que eu conseguisse prender a um evento (um clique, por exemplo), para disparar o evento quando eu estivesse pronto para gravar. Um sleep também poderia fornecer o resultado esperado, me deixar gravar, mas isso significa que eu preciso me preparar antes do começo do processo e esperar ele iniciar; isso significa que tenho um belo ponto de falha (não conseguir me preparar a tempo) e um belo ponto de tédio (me preparo muito rapidamente e preciso ficar esperando).

A classe turtle.Screen fornece um método de interceptar cliques do mouse passando uma função lambda que consuma dois parâmetros x,y. Posso então prender no clique e poder gravar a animação que eu desejo:

import turtle

sc = turtle.Screen()
t = turtle.Turtle()
sc.title("Uma tortuguita")

sc.onscreenclick(lambda x,y: t.fd(100))

Mostrando a tartaruga prosseguindo conforme

A partir de agora, exceto se especificado o contrário, toda a função de desenho será feito dentro da função desenha(t, sc), que recebe uma tartaruga e uma tela. Ela será disparada no clique. Os parâmetros fornecidos pelo lambda são ignorados pois não nos interessa saber nada sobre o clique. O corpo será feito assim:

import turtle

def desenha(t, sc):
    # código do desenho reside aqui
    pass

sc = turtle.Screen()
t = turtle.Turtle()
sc.title("Uma tortuguita")

sc.onscreenclick(lambda x,y: desenha(t, sc))

Eventualmente o título pode ser diferente.

Posteriormente descobri que onclick é um método sinônimo de onscreenclick. Não irei retroadequar o que foi escrito, mas eventualmente algum trecho de código pode ser escrito de maneira distinta, assim como material de apoio também.

Desenhando os eixos principais

Lembrando que resgatamos as dimensões da tela fazendo x, y = sc.screensize(), primeiro experimento é mandar avançar a tartaruga x adiante: t.fd(x)

Ok, agora então para terminar de desenhar o eixo horizontal só mandar a tartaruga voltar 2 vezes x. E para ficar na origem de novo é só avançar x de novo:

t.fd(x)
t.back(2*x)
t.fd(x)

E para o eixo vertical é a mesma coisa, mas antes preuciso rodar a tartaruga 90º (no sentido anti-horário para que ela aponte para cima, por uma questão estética). Mas, já que é exatamente a mesma coisa, por que não extrair isso numa função desenha_eixo(t,s)? Sem problemas, mas e como rodar a tartaruga? E, em cima disso, qual a unidade de rotação que é usada?

Sobre a unidade, existem duas principais candidatas:

  • radianos
  • graus

Como descobrir qual ela estará usando? Uma chance seria lendo a documentação, mas é mais rápido e barato simplesmente testar:

t.left(90)

se estiver usando graus, vai dar um quarto de volta, bem bonitinho. Se for em radianos, vai dar um pouco menos do que 30 voltas, parando em algum estado intermediário da trigésima volta.

Ok, minha tartaruga subiu usando graus. Vamos desenhar os eixos?

def desenha_eixo(t, s):
  t.fd(s)
  t.back(2*s)
  t.fd(s)

x, y = sc.screensize()
desenha_eixo(t, x)
t.left(90)
desenha_eixo(t, y)
t.right(90)

Tá, e se não estiver veloz o suficiente? Posso mexer no t.speed. help(t.speed) vem me ajudar:

>>> help(t.speed)
Help on method speed in module turtle:

speed(speed=None) method of turtle.Turtle instance
    Return or set the turtle's speed.

    Optional argument:
    speed -- an integer in the range 0..10 or a speedstring (see below)

    Set the turtle's speed to an integer value in the range 0 .. 10.
    If no argument is given: return current speed.

    If input is a number greater than 10 or smaller than 0.5,
    speed is set to 0.
    Speedstrings  are mapped to speedvalues in the following way:
        'fastest' :  0
        'fast'    :  10
        'normal'  :  6
        'slow'    :  3
        'slowest' :  1
    speeds from 1 to 10 enforce increasingly faster animation of
    line drawing and turtle turning.

    Attention:
    speed = 0 : *no* animation takes place. forward/back makes turtle jump
    and likewise left/right make the turtle turn instantly.

    Example (for a Turtle instance named turtle):
    >>> turtle.speed(3)

Então posso guardar o valor anterior old_speed = t.speed() e então mandar ir o mais rápido possível não instantâneo t.speed(10):

Por curiosidade, para aproveitar o canvas levantado no REPL, eu não fecho a tela e a recrio, faço apenas o mesmo movimento com a tartaruga pintando de branco (já que esta é a cor de fundo mesmo). Para mudar a cor da caneta da tartaruga, só chamar t.color('white').:

Para voltar pro preto, só depois chamar para a cor desejada t.color('black'). Posso guardar a cor resgatando de t.color(), mas tem uma coisinha que preciso prestar atenção: t.color se refere a ambos pencolor (cor da caneta) como a fillcolor (cor do preenchimento), nessa ordem.

Então, se eu quiser um controle mais específico do que estou lidando, posso chamar t.pencolor.

Desenhando círculos

A primeira coisa que me veio à cabeça para desenhar círculos foi fazer uns dry runs. Mas, como fazer isso com tartarugas?

Bem, a tartaruga desenha porque ela tem uma “caneta” em sua barriga que está para baixo, rumo ao “chão”, portato o movimento da tartaruga faz com que a caneta risque o chão. E… e se… eu “levantar” a caneta?

t.penup()
t.circle(10)

Ótimo. Temos o experimento feito. A tartaruga irá percorrer um círculo de raio 10 no sentido horário:

for a in range(4):
  t.circle(10)
  t.left(90)

Se eu quiser fazer uma circunferência que tenha o eixo horizontal coincidindo com seu diâmetro? Basicamente seria necessário afastar a tartaruga raio verticalmente, deixar ela virada para a direção adequada e mandar desenhar o círculo de raio raio. O mais natural para codificar para mim seria jogar a tartaruga para baixo e deixar ela olhando para a direita, mas se eu jogar a tartaruga para cima eu consigo lidar com isso apenas virando à esquerda 90º:

  1. levanta a caneta, não queremos riscar o raio
  2. vira à esquerda 90º
  3. avança raio
  4. vira à esquerda 90º
  5. abaixa a caneta
  6. desenha círculo de raio raio
  7. levanta a caneta
  8. vira à esquerda 90º
  9. avança raio
  10. vira à esquerda 90º
  11. baixa a caneta (para voltar ao estado anterior)
t.penup()
t.left(90)
t.fd(raio)
t.left(90)
t.pendown()
t.circle(raio)
t.penup()
t.left(90)
t.fd(raio)
t.left(90)
t.pendown()

Para ficar claro se eu fiz alguma besteira, desloquei a tartaruga 45 unidades da origem. Fazendo o experimento com 60 de raio:

Se eu tivesse seguido minha intuição, de primeiro mandar para baixo a tartaruga, iria mudar as viradas:

  1. direita
  2. esquerda
  3. esquerda
  4. direita

O resto seria idêntico, nas mesmas ordens. Vou já deixar a tartaruga com violeta t.color('violet') e desenhar sobre o círculo anterior para mostrar que está funcionando mesmo:

t.penup()
t.right(90)
t.fd(raio)
t.left(90)
t.pendown()
t.circle(raio)
t.penup()
t.left(90)
t.fd(raio)
t.right(90)
t.pendown()

Note que o gif deixou como artefato no meio da caminho uma tartaruga no terceiro quadrante, mas isso não foi um material natural do meu desenho, assim como também ficou um trecho com coloração fraca, outro artefato do gif ou da ferramenta de captura. Veja como ficou abaixo: