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:
import turtle

def grelha_1(dim, lado):
    """ Solução básica."""
    # verticais
    turtle.setheading(90)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(i * lado,0)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)
    # horizontais
    turtle.setheading(0)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(0,i*lado)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)    
    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.
def grelha_2(dim, lado,pos_x,pos_y):
    """ Controlando a posição do canto inferior esquerdo."""
    # verticais
    turtle.setheading(90)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x + i * lado,pos_y)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)
    # horizontais
    turtle.setheading(0)
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x,pos_y+i*lado)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)    
    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…
def grelha_4(dim, lado,pos_x,pos_y,orient):
    """ Controlando a posição e a orientação. """
    # verticais
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x,pos_y)
        turtle.setheading(orient)
        turtle.forward(i*lado)
        turtle.setheading(90+orient)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)
    # horizontais
    for i in range(dim+1):
        # posiciona
        turtle.penup()
        turtle.goto(pos_x,pos_y)
        turtle.setheading(90+orient)
        turtle.forward(i*lado)
        turtle.setheading(orient)
        turtle.pendown()
        # desenha
        turtle.forward(dim*lado)    
    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.
def linha(pos_x,pos_y, orient, tam):
    # posiciona
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orient)
    turtle.pendown()
    # desenha
    turtle.forward(tam)
    turtle.hideturtle()
Na posse desta nova construção (abstracção), podemos refazer as soluções acima apresentadas. Comecemos pela básica:
def grelha_5(dim, lado):
    """ Solução básica."""
    # horizontais
    for i in range(dim+1):
        linha(0,i*lado,0,dim*lado)
    # verticais
    for i in range(dim+1):
        linha(i*lado,0,90,dim*lado)
    turtle.hideturtle()
O leitor concordará que corresponde de modo mais claro à forma como enunciámos a solução. Passemos à posição.
def grelha_6(dim, lado,pos_x,pos_y):
    """ Solução com controlo da posição do canto inferior esquerdo."""
    # horizontais
    for i in range(dim+1):
        linha(pos_x,pos_y+i*lado,0,dim*lado)
    # verticais
    for i in range(dim+1):
        linha(pos_x+i*lado,pos_y,90,dim*lado)
    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:
import math

def grelha_7(dim, lado,pos_x,pos_y,orient):
    """ Solução com controlo da posição do canto inferior esquerdo e a orientação."""
    deg_rad = math.pi/180
    # horizontais
    for i in range(dim+1):
        linha(pos_x+i*lado*math.cos((orient+90)* deg_rad),pos_y+ i*lado*math.sin((orient+90)* deg_rad),orient,dim*lado)
    # verticais
    for i in range(dim+1):
        linha(pos_x+i*lado*math.cos(orient * deg_rad),pos_y+ i*lado*math.sin(orient * deg_rad),90+orient,dim*lado)
    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:
def quadrado(pos_x, pos_y, lado, orient):
    # posiciona
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orient)
    turtle.pendown()
    # desenha
    for i in range(4):
        turtle.forward(lado)
        turtle.lt(90)
    turtle.hideturtle()
E vamos percorrer de novo a nossa via sacra. Primeiro a versão básica:
def grelha_8(dim, lado):
    """ Solução básica."""
    # Por linhas
    for i in range(dim):
        # linha i
        for j in range(dim):
            quadrado(j*lado,i*lado,lado,0)    
    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:
def grelha_9(dim, lado, pos_x, pos_y):
    """ Solução com controlo da posição do canto inferior esquerdo."""
    # Por linhas
    for i in range(dim):
        # linha i
        for j in range(dim):
            quadrado(pos_x+j*lado,pos_y+i*lado,lado,0)    
    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:
def grelha_10(dim, lado, pos_x, pos_y, orient):
    """ Solução com controlo da posição do canto inferior esquerdo e da orientação."""
    deg_rad = math.pi/180
    # Por linhas
    for i in range(dim):
        # linha i
        p_x = pos_x+i*lado*math.cos((orient+90)*deg_rad)
        p_y = pos_y+i*lado*math.sin((orient+90)*deg_rad)        
        for j in range(dim):
            quadrado(p_x,p_y,lado,orient)
            p_x = p_x+lado*math.cos(orient*deg_rad)
            p_y = p_y+lado * math.sin(orient*deg_rad)                   
    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…
def quadrado_cor(pos_x, pos_y, lado, orient,cor):
    # cor
    turtle.color(cor)
    # posiciona
    turtle.penup()
    turtle.goto(pos_x,pos_y)
    turtle.setheading(orient)
    turtle.pendown()
    # desenha
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(lado)
        turtle.lt(90)
    turtle.end_fill()
    turtle.hideturtle()
E agora eis o nosso tabuleiro bi-color:
def grelha_11(dim, lado, pos_x, pos_y, orient):
    """ Solução com controlo da posição do canto inferior esquerdo e da orientação."""
    deg_rad = math.pi/180
    # Por linhas
    for i in range(dim):
        # linha i
        p_x = pos_x+i*lado*math.cos((orient+90)*deg_rad)
        p_y = pos_y+i*lado*math.sin((orient+90)*deg_rad)        
        for j in range(dim):
            if (i+j)%2 == 0:
                quadrado_cor(p_x,p_y,lado,orient,'black')
            else:
                quadrado_cor(p_x,p_y,lado,orient,'gray')
            p_x = p_x+lado*math.cos(orient*deg_rad)
            p_y = p_y+lado * math.sin(orient*deg_rad)                   
    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