sexta-feira, 20 de novembro de 2015

Desenhar uma grelha: um exercício de programação

Nas aulas discutimos o problema de desenhar uma grelha rectangular com n células, cada uma um quadrado com um dado comprimento do lado. Uma das soluções que apareceu baseava-se na ideia de desenhar a grelha como nós humanos geralmente fazemos: desenhar separadamente as linhas verticais e as horizontais. Vejamos uma solução básica:
01.import turtle
02. 
03.def grelha_1(dim, lado):
04.    """ Solução básica."""
05.    # verticais
06.    turtle.setheading(90)
07.    for i in range(dim+1):
08.        # posiciona
09.        turtle.penup()
10.        turtle.goto(i * lado,0)
11.        turtle.pendown()
12.        # desenha
13.        turtle.forward(dim*lado)
14.    # horizontais
15.    turtle.setheading(0)
16.    for i in range(dim+1):
17.        # posiciona
18.        turtle.penup()
19.        turtle.goto(0,i*lado)
20.        turtle.pendown()
21.        # desenha
22.        turtle.forward(dim*lado)   
23.    turtle.hideturtle()
Como se pode ver as linhas são desenhadas em separado e cada tipo de linha (verticais ou horizontais) é desenhada no interior de um ciclo. A parte relevante em cada ciclo é o posicionamento da tartaruga para desenhar a linha. Fazemos isso através de um goto.
Uma primeira alteração possível é considerar a possibilidade de controlar a localização do canto inferior esquerdo. Vejamos como se pode fazer.
01.def grelha_2(dim, lado,pos_x,pos_y):
02.    """ Controlando a posição do canto inferior esquerdo."""
03.    # verticais
04.    turtle.setheading(90)
05.    for i in range(dim+1):
06.        # posiciona
07.        turtle.penup()
08.        turtle.goto(pos_x + i * lado,pos_y)
09.        turtle.pendown()
10.        # desenha
11.        turtle.forward(dim*lado)
12.    # horizontais
13.    turtle.setheading(0)
14.    for i in range(dim+1):
15.        # posiciona
16.        turtle.penup()
17.        turtle.goto(pos_x,pos_y+i*lado)
18.        turtle.pendown()
19.        # desenha
20.        turtle.forward(dim*lado)   
21.    turtle.hideturtle()
Como se notará a alteração é mínima, e traduz-se a colocar o valor das coordenadas do canto inferior esquerdo no sítio certo.
Suponhamos agora que nos pedem uma solução em que seja também possível controlar a orientação do quadrado. Esta questão já obriga a uma ginástica adicional, mas a questão central mantém-se a mesma: definir os pontos em que se iniciam as linhas. Eis uma solução, não muito elegante, mas que funciona…
01.def grelha_4(dim, lado,pos_x,pos_y,orient):
02.    """ Controlando a posição e a orientação. """
03.    # verticais
04.    for i in range(dim+1):
05.        # posiciona
06.        turtle.penup()
07.        turtle.goto(pos_x,pos_y)
08.        turtle.setheading(orient)
09.        turtle.forward(i*lado)
10.        turtle.setheading(90+orient)
11.        turtle.pendown()
12.        # desenha
13.        turtle.forward(dim*lado)
14.    # horizontais
15.    for i in range(dim+1):
16.        # posiciona
17.        turtle.penup()
18.        turtle.goto(pos_x,pos_y)
19.        turtle.setheading(90+orient)
20.        turtle.forward(i*lado)
21.        turtle.setheading(orient)
22.        turtle.pendown()
23.        # desenha
24.        turtle.forward(dim*lado)   
25.    turtle.hideturtle()
Outras alterações poderiam ser feitas, como seja mudar a espessura das linhas ou a sua cor. Mas o que não nos agrada é a legibilidade do código. Afinal o nosso ponto de partida foi considerar o desenho da grelha com base no conceito de linha, mas esse conceito está explicitamente ausente nas soluções acima. Vamos remediar a situação definindo uma função que nos permite desenhar uma linha, com uma dada posição inicial uma orientação e um dado comprimento. Não é difícil.
01.def linha(pos_x,pos_y, orient, tam):
02.    # posiciona
03.    turtle.penup()
04.    turtle.goto(pos_x,pos_y)
05.    turtle.setheading(orient)
06.    turtle.pendown()
07.    # desenha
08.    turtle.forward(tam)
09.    turtle.hideturtle()
Na posse desta nova construção (abstracção), podemos refazer as soluções acima apresentadas. Comecemos pela básica:
01.def grelha_5(dim, lado):
02.    """ Solução básica."""
03.    # horizontais
04.    for i in range(dim+1):
05.        linha(0,i*lado,0,dim*lado)
06.    # verticais
07.    for i in range(dim+1):
08.        linha(i*lado,0,90,dim*lado)
09.    turtle.hideturtle()
O leitor concordará que corresponde de modo mais claro à forma como enunciámos a solução. Passemos à posição.
01.def grelha_6(dim, lado,pos_x,pos_y):
02.    """ Solução com controlo da posição do canto inferior esquerdo."""
03.    # horizontais
04.    for i in range(dim+1):
05.        linha(pos_x,pos_y+i*lado,0,dim*lado)
06.    # verticais
07.    for i in range(dim+1):
08.        linha(pos_x+i*lado,pos_y,90,dim*lado)
09.    turtle.hideturtle()
Elementar, não acha? Finalmente a orientação. Aqui decidimos usar um pouco dos nossos conhecimentos de trignometria, para definir as novas posições de início das linhas.
Na posse deste conhecimento a solução vem, finalmente, como:
01.import math
02. 
03.def grelha_7(dim, lado,pos_x,pos_y,orient):
04.    """ Solução com controlo da posição do canto inferior esquerdo e a orientação."""
05.    deg_rad = math.pi/180
06.    # horizontais
07.    for i in range(dim+1):
08.        linha(pos_x+i*lado*math.cos((orient+90)* deg_rad),pos_y+ i*lado*math.sin((orient+90)* deg_rad),orient,dim*lado)
09.    # verticais
10.    for i in range(dim+1):
11.        linha(pos_x+i*lado*math.cos(orient * deg_rad),pos_y+ i*lado*math.sin(orient * deg_rad),90+orient,dim*lado)
12.    turtle.hideturtle()
Chegados a este ponto podemos achar que o trabalho está feito e, por isso, podemos passar a outro problema. Mas… e se alguém nos pedir a nossa solução para criar um tabuleiro de xadrez? Precisamos colorir as células, mas como fazer? A dificuldade reside no facto de termos olhado para a grelha não como uma grelha, isto é formada por células justapostas, mas como linhas que se cruzam. E precisamos partir de novo à aventura: criar a dita grelha formada por quadrados. Mas aprendemos algo com o caso anterior, a saber: usar abstração para criar primitivas é positivo.
Para começar precisamos de uma primitiva para desenhar um quadrado:
01.def quadrado(pos_x, pos_y, lado, orient):
02.    # posiciona
03.    turtle.penup()
04.    turtle.goto(pos_x,pos_y)
05.    turtle.setheading(orient)
06.    turtle.pendown()
07.    # desenha
08.    for i in range(4):
09.        turtle.forward(lado)
10.        turtle.lt(90)
11.    turtle.hideturtle()
E vamos percorrer de novo a nossa via sacra. Primeiro a versão básica:
1.def grelha_8(dim, lado):
2.    """ Solução básica."""
3.    # Por linhas
4.    for i in range(dim):
5.        # linha i
6.        for j in range(dim):
7.            quadrado(j*lado,i*lado,lado,0)   
8.    turtle.hideturtle()
Não muito diferente, certo? Só que agora temos uma perspectiva matricial, pelo que precisamos de um ciclo dentro de outro ciclo. Controlar a posição é trivial:
1.def grelha_9(dim, lado, pos_x, pos_y):
2.    """ Solução com controlo da posição do canto inferior esquerdo."""
3.    # Por linhas
4.    for i in range(dim):
5.        # linha i
6.        for j in range(dim):
7.            quadrado(pos_x+j*lado,pos_y+i*lado,lado,0)   
8.    turtle.hideturtle()
A quatro da orientação pede um pouco mais de atenção como na versão anterior, mas a lógica é semelhante: trata-se de definir os pontos iniciais para cada quadrado:
01.def grelha_10(dim, lado, pos_x, pos_y, orient):
02.    """ Solução com controlo da posição do canto inferior esquerdo e da orientação."""
03.    deg_rad = math.pi/180
04.    # Por linhas
05.    for i in range(dim):
06.        # linha i
07.        p_x = pos_x+i*lado*math.cos((orient+90)*deg_rad)
08.        p_y = pos_y+i*lado*math.sin((orient+90)*deg_rad)       
09.        for j in range(dim):
10.            quadrado(p_x,p_y,lado,orient)
11.            p_x = p_x+lado*math.cos(orient*deg_rad)
12.            p_y = p_y+lado * math.sin(orient*deg_rad)                  
13.    turtle.hideturtle()
Notar que nesta solução o ciclo interior apenas controla o número de vezes que desenhamos um quadrado numa linha.

. Agora sim podemos dar o trabalho por encerrado!!! Mas, espere aí, ouço-o dizer, a passagem para o ponto de vista das células quadradas não era para termos mais graus de liberdade, nomeadamente em relação à cor dos quadrados??? É verdade sim senhor. Então vamos a isso. A solução mais simples consistirá em poder desenhar quadrados coloridos…
01.def quadrado_cor(pos_x, pos_y, lado, orient,cor):
02.    # cor
03.    turtle.color(cor)
04.    # posiciona
05.    turtle.penup()
06.    turtle.goto(pos_x,pos_y)
07.    turtle.setheading(orient)
08.    turtle.pendown()
09.    # desenha
10.    turtle.begin_fill()
11.    for i in range(4):
12.        turtle.forward(lado)
13.        turtle.lt(90)
14.    turtle.end_fill()
15.    turtle.hideturtle()
E agora eis o nosso tabuleiro bi-color:
01.def grelha_11(dim, lado, pos_x, pos_y, orient):
02.    """ Solução com controlo da posição do canto inferior esquerdo e da orientação."""
03.    deg_rad = math.pi/180
04.    # Por linhas
05.    for i in range(dim):
06.        # linha i
07.        p_x = pos_x+i*lado*math.cos((orient+90)*deg_rad)
08.        p_y = pos_y+i*lado*math.sin((orient+90)*deg_rad)       
09.        for j in range(dim):
10.            if (i+j)%2 == 0:
11.                quadrado_cor(p_x,p_y,lado,orient,'black')
12.            else:
13.                quadrado_cor(p_x,p_y,lado,orient,'gray')
14.            p_x = p_x+lado*math.cos(orient*deg_rad)
15.            p_y = p_y+lado * math.sin(orient*deg_rad)                  
16.    turtle.hideturtle()
Veja apenas como conseguimos o efeito das cores alternadas… Experimente o código e aprecie o resultado.
Se quiser outro tipo de tabuleiros coloridos é só adaptar. Agora é mesmo a sua vez de fazer alguma coisa. Eu vou descansar um pouco!

Sem comentários:

Enviar um comentário