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.
01.import turtle
02. 
03.def radioactividade():
04.    # desenha quadrado
05.    quadrado()
06.    # desenha sectores
07.    sectores()
08.    #desenha circunferência
09.    circunferencia()
10.     
11.     
12.def quadrado():
13.    pass
14. 
15.def sectores():
16.    pass
17. 
18.def circunferencia():
19.    pass
20. 
21.if __name__ == '__main__':
22.    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.
01.import turtle
02. 
03.def radioactividade(lado,posx,posy,orientacao,cor):
04.    # desenha quadrado
05.    quadrado(lado,posx,posy,orientacao,cor)
06.    # desenha sectores
07.    sectores()
08.    #desenha circunferência
09.    circunferencia()
10.     
11.     
12. 
13.def quadrado(lado,posx,posy,orientacao,cor):
14.    turtle.up()
15.    turtle.goto(posx,posy)
16.    turtle.setheading(orientacao)
17.    turtle.pendown()
18.    turtle.fillcolor(cor)
19.    turtle.begin_fill()
20.    for i in range(4):
21.        turtle.forward(lado)
22.        turtle.left(90)
23.    turtle.end_fill()
24.    turtle.hideturtle()
25. 
26.def sectores():
27.    pass
28. 
29.def circunferencia():
30.    pass
31. 
32.if __name__ == '__main__':
33.    lado = 100
34.    posx = -50
35.    posy = -50
36.    orientacao = 30
37.    cor = 'yellow'
38.    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.
01.def circunferencia(raio,posx,posy,orienta,cor):
02.    turtle.penup()
03.    turtle.goto(posx,posy)
04.    turtle.setheading(orienta)
05.    turtle.pendown()
06.    turtle.fillcolor(cor)
07.    turtle.begin_fill()
08.    turtle.circle(raio)
09.    turtle.end_fill()
10.    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.
01.import turtle
02. 
03.def radioactividade(lado,posx,posy,orientacao,cor, cor_c):
04.    # desenha quadrado
05.    quadrado(lado,posx,posy,orientacao,cor)
06.    # desenha sectores
07.    sectores()
08.    #desenha circunferência
09.    raio = lado/10
10.    posx_c = posx + lado/2 + raio
11.    posy_c = posy + lado/2
12.    orientacao_c = orientacao + 90
13.    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
14.     
15.     
16. 
17.def quadrado(lado,posx,posy,orientacao,cor):
18.    turtle.up()
19.    turtle.goto(posx,posy)
20.    turtle.setheading(orientacao)
21.    turtle.pendown()
22.    turtle.fillcolor(cor)
23.    turtle.begin_fill()
24.    for i in range(4):
25.        turtle.forward(lado)
26.        turtle.left(90)
27.    turtle.end_fill()
28.    turtle.hideturtle()
29. 
30.def sectores():
31.    pass
32. 
33.def circunferencia(raio,posx,posy,orienta,cor):
34.    turtle.penup()
35.    turtle.goto(posx,posy)
36.    turtle.setheading(orienta)
37.    turtle.pendown()
38.    turtle.fillcolor(cor)
39.    turtle.begin_fill()
40.    turtle.circle(raio)
41.    turtle.end_fill()
42.    turtle.hideturtle()
43. 
44.if __name__ == '__main__':
45.    lado = 100
46.    posx = -50
47.    posy = -50
48.    orientacao = 0
49.    cor = 'yellow'
50.    raio = lado/10
51.    cor_c = 'black'
52.    #circunf(raio,posx,posy,orientacao,cor)
53.    radioactividade(lado,posx,posy,orientacao,cor, cor_c)
54.    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.
01.def sector(raio,posx,posy,orientacao,cor,amplitude):
02.    turtle.penup()
03.    turtle.goto(posx,posy)
04.    turtle.setheading(orientacao)
05.    turtle.color(cor)
06.    turtle.pendown()
07.    turtle.begin_fill()
08.    turtle.forward(raio)
09.    turtle.left(90)
10.    turtle.circle(raio,amplitude)
11.    turtle.left(90)
12.    turtle.forward(raio)
13.    turtle.end_fill()
14.    turtle.setheading(orientacao)
15.    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???
1.def sectores(raio,posx,posy,orientacao,cor,amplitude):
2.    for i in range(3):
3.        sector(raio,posx,posy,orientacao,cor,amplitude)
4.        orientacao = orientacao + 120
Para terminar o trabalho temos que incorporar esta solução no programa principal.
01.import turtle
02. 
03.def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
04.    # desenha quadrado
05.    quadrado(lado,posx,posy,orientacao,cor)
06.    # desenha sectores
07.    raio_s = lado/4
08.    posx_s = posx + lado/2
09.    posy_s = posy + lado/2
10.    orientacao_s = orientacao
11.    amplitude = 60
12.    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
13.    #desenha circunferência
14.    raio = lado/10
15.    posx_c = posx + lado/2 + raio
16.    posy_c = posy + lado/2
17.    orientacao_c = orientacao + 90
18.    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
19.     
20.     
21. 
22.def quadrado(lado,posx,posy,orientacao,cor):
23.    turtle.up()
24.    turtle.goto(posx,posy)
25.    turtle.setheading(orientacao)
26.    turtle.pendown()
27.    turtle.fillcolor(cor)
28.    turtle.begin_fill()
29.    for i in range(4):
30.        turtle.forward(lado)
31.        turtle.left(90)
32.    turtle.end_fill()
33.    turtle.hideturtle()
34. 
35.def sectores(raio,posx,posy,orientacao,cor,amplitude):
36.    for i in range(3):
37.        sector(raio,posx,posy,orientacao,cor,amplitude)
38.        orientacao = orientacao + 120
39.         
40. 
41.def sector(raio,posx,posy,orientacao,cor,amplitude):
42.    turtle.penup()
43.    turtle.goto(posx,posy)
44.    turtle.setheading(orientacao)
45.    turtle.color(cor)
46.    turtle.pendown()
47.    turtle.begin_fill()
48.    turtle.forward(raio)
49.    turtle.left(90)
50.    turtle.circle(raio,amplitude)
51.    turtle.left(90)
52.    turtle.forward(raio)
53.    turtle.end_fill()
54.    turtle.setheading(orientacao)
55.    turtle.hideturtle()
56. 
57.def circunferencia(raio,posx,posy,orienta,cor):
58.    turtle.penup()
59.    turtle.goto(posx,posy)
60.    turtle.setheading(orienta)
61.    turtle.pendown()
62.    turtle.fillcolor(cor)
63.    turtle.begin_fill()
64.    turtle.circle(raio)
65.    turtle.end_fill()
66.    turtle.hideturtle()
67. 
68.if __name__ == '__main__':
69.    lado = 100
70.    posx = -50
71.    posy = -50
72.    orientacao = 0
73.    cor = 'yellow'
74.    raio = lado/10
75.    cor_c = 'black'
76.    raio_s = lado/4
77.    cor_s = 'black'
78.    amplitude = 60
79.    #circunferencia(raio,posx,posy,orientacao,cor)
80.    #sector(raio_s,posx,posy,orientacao,cor_s,amplitude)
81.    #sectores(raio_s,posx,posy,orientacao,cor_s,amplitude)
82.    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
83.    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.

01.def circunferencia(raio,posx,posy,orienta,cor):
02.    turtle.penup()
03.    turtle.goto(posx,posy)
04.    turtle.setheading(orienta)
05.    turtle.pencolor('white')
06.    turtle.pendown()
07.    turtle.fillcolor(cor)
08.    turtle.begin_fill()
09.    turtle.circle(raio)
10.    turtle.end_fill()
11.    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.
01.import turtle
02.import math
03. 
04. 
05.def radioactividade(lado,posx,posy,orientacao,cor, cor_c, cor_s):
06.    # desenha quadrado
07.    quadrado(lado,posx,posy,orientacao,cor)
08.    # desenha sectores
09.    raio_s = lado/4
10.    ang_base = (orientacao* math.pi / 180)
11.    ang = ang_base + math.pi/4    
12.    posx_s = posx + lado/math.sqrt(2) * math.cos(ang)
13.    posy_s = posy + lado/math.sqrt(2) * math.sin(ang)
14.    orientacao_s = orientacao
15.    amplitude = 60
16.    sectores(raio_s,posx_s,posy_s,orientacao_s,cor_s,amplitude)
17.    #desenha circunferência
18.    raio = lado/10
19.    posx_c = posx_s +  raio * math.cos(ang_base)
20.    posy_c = posy_s + raio * math.sin(ang_base)
21.    orientacao_c = orientacao + 90
22.    circunferencia(raio,posx_c,posy_c,orientacao_c,cor_c)
23.     
24.     
25. 
26.def quadrado(lado,posx,posy,orientacao,cor):
27.    turtle.up()
28.    turtle.goto(posx,posy)
29.    turtle.setheading(orientacao)
30.    turtle.pendown()
31.    turtle.fillcolor(cor)
32.    turtle.begin_fill()
33.    for i in range(4):
34.        turtle.forward(lado)
35.        turtle.left(90)
36.    turtle.end_fill()
37.    turtle.hideturtle()
38. 
39.def sectores(raio,posx,posy,orientacao,cor,amplitude):
40.    for i in range(3):
41.        sector(raio,posx,posy,orientacao,cor,amplitude)
42.        orientacao = orientacao + 120
43.         
44. 
45.def sector(raio,posx,posy,orientacao,cor,amplitude):
46.    turtle.penup()
47.    turtle.goto(posx,posy)
48.    turtle.setheading(orientacao)
49.    turtle.color(cor)
50.    turtle.pendown()
51.    turtle.begin_fill()
52.    turtle.forward(raio)
53.    turtle.left(90)
54.    turtle.circle(raio,amplitude)
55.    turtle.left(90)
56.    turtle.forward(raio)
57.    turtle.end_fill()
58.    turtle.setheading(orientacao)
59.    turtle.hideturtle()
60. 
61.def circunferencia(raio,posx,posy,orienta,cor):
62.    turtle.penup()
63.    turtle.goto(posx,posy)
64.    turtle.setheading(orienta)
65.    turtle.pencolor('white')
66.    turtle.pendown()
67.    turtle.fillcolor(cor)
68.    turtle.begin_fill()
69.    turtle.circle(raio)
70.    turtle.end_fill()
71.    turtle.hideturtle()
72. 
73.if __name__ == '__main__':
74.    lado = 200
75.    posx = -50
76.    posy = -50
77.    orientacao = 45
78.    cor = 'yellow'
79.    raio = lado/10
80.    cor_c = 'black'
81.    raio_s = lado/4
82.    cor_s = 'black'
83.    amplitude = 60
84.    radioactividade(lado,posx,posy,orientacao,cor, cor_c,cor_s)
85.    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