sábado, 21 de outubro de 2017

Exercícios de Programação Descendente (I)

Durante as aulas foi colocado o problema de desenvolver uma solução para a criação da imagem visual do símbolo da radioactividade.
Perante este problema a reacção primeira deve ser a de equacionar a possibilidade de decompor o problema em sub-problemas, se possível, independentes. Caso não sejam independentes temos que acordar primeiro o modo como se relacionam.

Não creio ser difícil identificar três sub-problemas: desenhar um quadrado, desenhar três sectores e desenhar uma circunferência. A dependência neste caso é a posição relativa de cada uma das três componentes. Com base nesta abordagem podemos escrever um primeiro esboço de solução.
import turtle

def radioactividade():
    # desenha quadrado
    quadrado()
    # desenha sectores
    sectores()
    #desenha circunferência
    circunferencia()
    
    
def quadrado():
    pass

def sectores():
    pass

def circunferencia():
    pass

if __name__ == '__main__':
    radioactividade()
Esta solução já pode ser testada embora não faça nada! Note-se que as definições ainda não têm argumentos… Vamos tomar nova decisão: dar o máximo liberdade à resolução de cada um dos sub-problemas. Por exemplo, no caso do quadrado, vamos criar uma definição que permita desenhar um quadrado parametrizado pelo tamanho do lado, a posição, a orientação e a cor. Já sabemos como fazer isso.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    sectores()
    #desenha circunferência
    circunferencia()
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores():
    pass

def circunferencia():
    pass

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 30
    cor = 'yellow'
    radioactividade(lado,posx,posy,orientacao,cor)
Podemos testar isoladamente a definição quadrado e/ou testá-la no contexto da definição do símbolo da radioactividade. O normal, em programas grandes é que o teste seja feito primeiro isoladamente e só depois no interior do programa principal. Trata-se de mais uma vantagem deste tipo de programação, apelidada de programação descendente, que conduz a soluções modulares. Para além de os testes serem mais fáceis de fazer, também ficamos com código reutilizável!

Resolvida esta questão vamos escolher um dos dois sub-problemas restantes. Optamos pela circunferência. Também aqui já sabemos o que fazer.
def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Se testarmos esta definição verificamos que está tudo funcional como pretendido. Naturalmente queremos testar esta solução no interior do nosso programa principal, e aqui, somos confrontados com o facto dos tamanhos (lado e raio), das posições e das orientações estarem relacionadas. Com um pouco de análise não será difícil chegar a uma solução aceitável.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor, cor_c):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    sectores()
    #desenha circunferência
    raio = lado/10
    posx_c = posx + lado/2 + raio
    posy_c = posy + lado/2
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores():
    pass

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 0
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    #circunf(raio,posx,posy,orientacao,cor)
    radioactividade(lado,posx,posy,orientacao,cor, cor_c)
    turtle.exitonclick()
Como se pode ver o desenho da circunferência é precedido do cálculo dos valores apropriados para o tamanho do raio, a posição e a orientação. Repare que para que o centro da circunferência coincida com o centro do quadrado, a tartaruga tem que ser colocada numa posição em que veja esse centro à sua esquerda e à distância do raio! O leitor atento notará que a orientação inicial é de zero graus, pois é isto que nos é pedido. Caso o valor seja diferente o posicionamento do centro da circunferência também é diferente.

Deixámos para o fim o problema dos sectores. Também aqui é possível decompor este problema em três sub-problemas equivalentes. Vejamos então como podemos desenhar um sector com total liberdade.
def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()
O desenho tem três partes: desenho do raio, desenho do arco, desenho do raio. Notar que no final queremos ter a tartaruga com a orientação inicial. Porquê?? Uma vez mais, um leitor atento pode ser levado a pensar que o desenho da circunferência e o desenho do sector poderiam ser unidos numa única definição. Afinal, parece que apenas diferem do parâmetro amplitude. Mas será mesmo assim? Quem quiser pode explorar esse caminho e verificar as questões que se colocam.

Como desenhamos os três sectores? Não é difícil perceber que temos que repetir o desenho de um sector alterando apenas a orientação de 120 graus. Será que o facto de a tartaruga que desenha um sector terminar com a mesma orientação que a inicial ajudou???
def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
Para terminar o trabalho temos que incorporar esta solução no programa principal.
import turtle

def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    raio_s = lado/4
    posx_s = posx + lado/2
    posy_s = posy + lado/2
    orientacao_s = orientacao
    amplitude = 60
    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
    #desenha circunferência
    raio = lado/10
    posx_c = posx + lado/2 + raio
    posy_c = posy + lado/2
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
        

def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 100
    posx = -50
    posy = -50
    orientacao = 0
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    raio_s = lado/4
    cor_s = 'black'
    amplitude = 60
    #circunferencia(raio,posx,posy,orientacao,cor)
    #sector(raio_s,posx,posy,orientacao,cor_s,amplitude)
    #sectores(raio_s,posx,posy,orientacao,cor_s,amplitude)
    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
    turtle.exitonclick()
A definição de alguns parâmetros dos sectores em função dos parâmetros do quadrado não deve oferecer dúvidas. Se executarmos o programa verificamos que não aparece a separação entre a circunferência e os sectores. A solução desse problema é trivial e passa por colocar a cor da caneta da tartaruga, quando desenha a circunferência, a branco.

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pencolor('white')
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()
Como dissemos atrás esta solução global funciona quando a orientação inicial é zero. Se for outra não funciona. Como alterar a nossa solução mexendo o mínimo possível, por forma ao programa funcionar mesmo quando o quadrado tem uma orientação qualquer? A solução passa por recalcular o centro da figura tendo a orientação em linha de conta. Para isso basta usar um pouco do nosso conhecimento de trigonometria.
import turtle
import math


def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
    # desenha quadrado
    quadrado(lado,posx,posy,orientacao,cor)
    # desenha sectores
    raio_s = lado/4
    ang_base = (orientacao* math.pi / 180)
    ang = ang_base + math.pi/4     
    posx_s = posx + lado/math.sqrt(2) * math.cos(ang) 
    posy_s = posy + lado/math.sqrt(2) * math.sin(ang)
    orientacao_s = orientacao
    amplitude = 60
    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
    #desenha circunferência
    raio = lado/10
    posx_c = posx_s +  raio * math.cos(ang_base)
    posy_c = posy_s + raio * math.sin(ang_base)
    orientacao_c = orientacao + 90
    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
    
    

def quadrado(lado,posx,posy,orientacao,cor):
    turtle.up()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.pendown()
    turtle.fillcolor(cor)
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.left(90)
    turtle.end_fill()
    turtle.hideturtle()

def sectores(raio,posx,posy,orientacao,cor,amplitude):
    for i in range(3):
        sector(raio,posx,posy,orientacao,cor,amplitude)
        orientacao = orientacao + 120
        

def sector(raio,posx,posy,orientacao,cor,amplitude):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orientacao)
    turtle.color(cor)
    turtle.pendown()
    turtle.begin_fill()
    turtle.forward(raio)
    turtle.left(90)
    turtle.circle(raio,amplitude)
    turtle.left(90)
    turtle.forward(raio)
    turtle.end_fill()
    turtle.setheading(orientacao)
    turtle.hideturtle()

def circunferencia(raio,posx,posy,orienta,cor):
    turtle.penup()
    turtle.goto(posx,posy)
    turtle.setheading(orienta)
    turtle.pencolor('white')
    turtle.pendown()
    turtle.fillcolor(cor) 
    turtle.begin_fill()
    turtle.circle(raio)
    turtle.end_fill()
    turtle.hideturtle()

if __name__ == '__main__':
    lado = 200
    posx = -50
    posy = -50
    orientacao = 45
    cor = 'yellow'
    raio = lado/10
    cor_c = 'black'
    raio_s = lado/4
    cor_s = 'black'
    amplitude = 60
    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
    turtle.exitonclick()
Como se pode ver apenas mexemos no programa principal e em zonas localizadas do código, as zonas de definem a interacção entre as partes. E pronto. Espero que da próxima vez que programar procure usar este princípio da decomposição de um problema em sub-problemas!

Sem comentários:

Enviar um comentário