quarta-feira, 16 de outubro de 2013

Olhar e ver (II) ou um farol sem faroleiro...

Acontecem coisas fantásticas nos testes como, por exemplo ter que desenhar um lindo farol como o da figura.
Quando olhamos para o problema diante de nós ele parece muito complexo. Então para melhor dominar a complexidade vamos dividir o nosso problema em sub-problemas e vamos começar por esquecer alguns detalhes. Depois, com calma, havemos de chegar ao resultado pretendido, ou mesmo superar o que era pedido! Então, ao olhar para o desenho, o que vemos? Um farol formado por duas componentes: uma torre e uma luz no cimo da torre. Elas não são completamente independentes pois a posição da torre condiciona a posição da luz. Mas vamos esquecer isso tudo para já e pensar apenas na decomposição.
""" Farol."""

import turtle

def farol():
    # torre
    # luz
    pass

if __name__ == '__main__':
    pass
É bizarro mas este programa até pode ser executado... embora não faça nada. Vamos agora concentrarmo-nos no desenho da torre. Uma vez mais, quando olhamos o que vemos? Elementos com a forma de quadrados uns em cima dos outros. E que mais? Bom, o tamanho do quadrado diminui à medida que se sobe e as cores vão alternando entre o vermelho e o branco. Significa isto que se tivermos código que nos permita desenhar um quadrado, na posição da tartaruga, com um dado determinado e uma certa cor podemos ter a nova vida facilitada. Vamos então a isso e fixemos a cor vermelha.
    """ Desenha um quadrado vermelho de um certo tamanho numa dada posição."""
    # Prepara 
    turtle.fillcolor(‘red’)
    turtle.begin_fill()
    # Desenha quadrado
    for i in range(4):
        turtle.forward(tamanho)
        turtle.left(90) 
    turtle.end_fill()
Este código funciona um pouco como um bloco construtor. Vamos usá-lo então para desenhar a torre. Não nos custa perceber que vamos repetir um certo número de vezes o desenho do quadrado. Admitamos para já que nos esquecemos das imposições do problema e nos concentramos apenas na altura.
import turtle

def farol(altura,lado):
    # torre
    for i in range(altura):
        # Desenha quadrado vermelho
        turtle.fillcolor('red')
        turtle.begin_fill()
        for i in range(4):
            turtle.forward(lado)
            turtle.left(90)
        turtle.end_fill()
    # luz
    pass

if __name__ == '__main__':
    farol(3,50)
    turtle.exitonclick()
Como se vê começam a aparecer os parâmetros formais (altura, tamanho do lado). Usando o bloco construtor embebido num ciclo for mandamos desenhar a torre. Mas se corrermos o código o resultado é decepcionante. Em vez de uma torre um único quadrado. Porquê? É óbvio que nos esquecemos de dizer que a posição dos sucessivos quadrado vai mudando. Como? Bom em relação ao eixo dos xx (horizontal) é igual, mudando apenas a coordenada dos yy por um valor igual ao lado. Lembre-se: estamos para já a esquecer que o lado varia em comprimento em função da altura!!! Alteremos então o código.
import turtle

def farol(altura,lado, posx,posy):
    # torre
    vai_para(posx, posy)
    for i in range(altura):
        # quadrado colorido
        turtle.fillcolor('red')
        turtle.begin_fill()
        for i in range(4):
            turtle.forward(lado)
            turtle.left(90)
        turtle.end_fill()
        vai_para(turtle.xcor(), turtle.ycor() + lado)
    # luz
    pass

def vai_para(posx,posy):
    """ Vai para (posx, posy) sem deixar rasto."""
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()

if __name__ == '__main__':
    farol(5,50,0,0)
    turtle.exitonclick()
        
E o novo resultado: Notar que introduzimos um sub-programa (vai_para) para colocar a tartaruga na posição desejada sem deixar rasto enquanto se movimenta. O que pode fazer uma simples alteração do código!!! Só é pena é ser tudo da mesma cor... Então é tempo de pensar um pouco nisso. Numeremos os quadrados como fazemos com as sequências: o da base é o 0, o seguinte o 1 e por aí adiante. Se queremos as cores alternadas então fixemos o vermelho para as posições pares e o branco para as ímpares. Modifiquemos o código por forma a testar se a posição é par ou é ímpar.
def farol(altura,lado, posx,posy):
    # torre
    vai_para(posx, posy)
    for i in range(altura):
        # quadrado colorido
        if i % 2 == 0: # <-- par?
            turtle.fillcolor('red')
        else:
            turtle.fillcolor('white')
        turtle.begin_fill()
        for i in range(4):
            turtle.forward(lado)
            turtle.left(90)
        turtle.end_fill()
        vai_para(turtle.xcor(), turtle.ycor() + lado)
    # luz
    pass
Façamos o boneco de novo.
Fantástico, não é? Agora ... faça-se luz. Vamos escrever código para desenhar um semi-círculo amarelo com um dado raio, no topo da torre e acrescentar ao programa.
def farol(altura,lado, posx,posy):
    # torre
    vai_para(posx, posy)
    for i in range(altura):
        # quadrado colorido
        if i % 2 == 0:
            turtle.fillcolor('red')
        else:
            turtle.fillcolor('white')
        turtle.begin_fill()
        for i in range(4):
            turtle.forward(lado)
            turtle.left(90)
        turtle.end_fill()
        vai_para(turtle.xcor(), turtle.ycor() + lado)
    # luz
    turtle.forward(lado)
    turtle.setheading(90) # <--- Atenção!
    turtle.fillcolor('yellow')
    turtle.begin_fill()
    turtle.circle(lado/2,180)
    turtle.end_fill()
Toca a executar.
Notar como foi resolvido o problema da orientação do semi-círculo. Quase pronto. Falta o detalhe dos quadrados a decrescer de um certo valor, a que chamaremos decremento. Pensemos um pouco. Se cada vez que subimos um degrau o lado decresce de um valor (decremento) e os quadrados têm que estar centrados, onde devemos colocar a tartaruga a cada etapa? E como mudamos o valor do lado? Em que zona do código fazemos as alterações? Não é difícil perceber que só temos que fazer avançar o valor da coordenada xx e diminuir o tamanho do lado. Eis a nova solução.
def farol(altura,lado, posx,posy, decremento):
    # torre
    vai_para(posx, posy)
    for i in range(altura):
        # quadrado colorido
        if i % 2 == 0:
            turtle.fillcolor('red')
        else:
            turtle.fillcolor('white')
        turtle.begin_fill()
        for i in range(4):
            turtle.forward(lado)
            turtle.left(90)
        turtle.end_fill()
        vai_para(turtle.xcor() + decremento/2, turtle.ycor() + lado) # <-- Eureka!
        lado = lado - decremento # <--- Eureka (2)
        
    # luz
    turtle.forward(lado)
    turtle.setheading(90)
    turtle.fillcolor('yellow')
    turtle.begin_fill()
    turtle.circle(lado/2,180)
    turtle.end_fill()
    turtle.hideturtle()
 
E já está!!! Fácil, não é?? Notar o aparecimento do novo parâmetro decremento e a instrução para esconder a tartaruga no final. O boneco é igual ao primeiro que mostrámos...
Vamos à moral da história. Um problema relativamente difícil pode tornar-se mais simples dividindo-o em sub-problemas mais simples e abordando as especificações em sequência. Muitas vezes (sempre?) quando acabamos de resolver um problema olhamos para o código e pensamos: posso melhorar o código (elegância, legibilidade,...), posso usá-lo noutros problemas? Uma modificação simples que podemos fazer é alterar o código para que as cores não sejam fixas. Uma espécie de farol UCB.
import turtle

def farol(altura,lado, posx,posy, decremento, cor_1,cor_2,cor_3):
    # torre
    vai_para(posx, posy)
    for i in range(altura):
        # quadrado colorido
        if i % 2 == 0:
            turtle.fillcolor(cor_1)
        else:
            turtle.fillcolor(cor_2)
        turtle.begin_fill()
        for i in range(4):
            turtle.forward(lado)
            turtle.left(90)
        turtle.end_fill()
        vai_para(turtle.xcor() + decremento/2, turtle.ycor() + lado)
        lado = lado - decremento
        
    # luz
    turtle.forward(lado)
    turtle.setheading(90)
    turtle.fillcolor(cor_3)
    turtle.begin_fill()
    turtle.circle(lado/2,180)
    turtle.end_fill()
    turtle.hideturtle()

def vai_para(posx,posy):
    """ Vai para (posx, posy) sem deixar rasto."""
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.pendown()

if __name__ == '__main__':
    farol(5,100,0,0,10,'blue', 'green', 'orange')
    turtle.exitonclick()

Ei-lo:
That’s all folks!!

Sem comentários:

Enviar um comentário