En la última clase hicimos una función trivial para recorrer una lista de polígonos y dibujarlos en un lienzo. A continuación les propongo un pequeño reto de construcción de programas de dibujo simple. Disfrútenlo.

Polígono cerrado

Un polígono se puede concebir como un gráfico compuesto por la unión de una secuencia de puntos, y para que la figura sea un gráfico cerrado (líneas contínuas) al final se deben unir el primero de los puntos con el último. Para hacer dibujos simples en Scheme, más específicamente en DrRacket, se debe usar la librería htdp/draw, que define entre otras cosas la estructura posn que representa un punto en un plano bidimensional. Nosotros ya conocemos los puntos (la estructura posn que tiene elementos x y y) y sabemos que el plano de dibujo para los lenguajes de cómputo se llama lienzo (canvas) y no obedece la distribución cartesiana de los puntos (origen en el centro y coordenadas aumentando hacia arriba y hacia la derecha), es decir, el origen de un lienzo está en la esquina superior izquierda (no hay puntos negativos como en el plano cartesiano ya que estarían fuera del plano visible) y los ejes x y y aumentan hacia la derecha y hacia abajo respectivamente. Adicionalmente, también hemos explorado las funciones básicas de dibujo de la librería htdp/draw (draw-solid-line, draw-circle, draw-solid-disk, etc.).

Digamos que vamos a dibujar un cuadrado. Obedeciendo la definición anterior, si un cuadrado está definido por los puntos a, b, c y d y éstos tienen las coordenadas (en el lienzo) a=(50,50), b=(150,50), c=(150,150), d=(50,150), el cuadrado visto como un polígono cerrado sería la unión de los puntos a con b, b con c, c con d y d con a. El código para dibujarlo sería el siguiente:

(require htdp/draw)
(start 300 300)
(define a (make-posn 50 50))
(define b (make-posn 150 50))
(define c (make-posn 150 150))
(define d (make-posn 50 150))
(draw-solid-line a b 'red)
(draw-solid-line b c 'red)
(draw-solid-line c d  'red)
(draw-solid-line d a 'red)

Como vemos hay varias condiciones para dibujar un polígono regular cerrado: los puntos deben estar en la secuencia correcta para que el dibujo salga bien, por ejemplo, si el cuadrado no se define como la lista de los puntos a b c d a, sino a c d b a la figura sería muy distinta (probar con el código dado). Adicionalmente al orden, al final se debe dibujar una línea entre el primero y el último punto, por lo tanto es indispensable repetir el primer punto al final. Un cuadrado es sólo uno de los posibles polígonos cerrados que se pueden dibujar, infinidad de polígonos cerrados se hacen con diferentes combinaciones de puntos consecutivos unidos por rectas, por ejemplo, un polígono regular es aquel en el que todos los lados tienen la misma longitud, osea que si defino un pentágono (cinco puntos), cada lado debe tener la misma distancia.

Si decimos que un polígono se puede construir a partir de unir los puntos del mismo, podemos concebirlo como una lista de puntos (en un orden determinado) que termina con el mismo punto con el cual empieza (condición necesaria para ser cerrado).

Función recursiva de dibujo de polígonos

Dado que un polígono es un conjunto de puntos consecutivos unidos por rectas, se puede pensar en una función genérica que dibuje polígonos arbitrarios con más de 3 puntos (polígono mínimo). Se podrían pensar también versiones degeneradas como una cruz o un asterisco que no necesariamente son polígonos pero que también se dibujarían como una unión de puntos, dependiendo de cómo establezcamos la secuencia de los puntos a unir.

Recordemos la definición que dimos anteriormente sobre un polígono cerrado: Lista de los puntos que componen la figura, si la figura es un polígono cerrado termina con el mismo punto (mismas coordenadas) con el cual comienza.

Dibujar un polígono es un problema perfecto para resolver mediante una función recursiva: recorrer la lista uniendo los dos primeros elementos con una línea y continuar dibujando el resto de la lista. Como sabemos, las funciones de dibujo de htdp/draw devuelven un dato booleano (true/false), por lo tanto la operación del llamado recursivo será un AND entre el dibujo de la línea actual y dibujar el resto del polígono. De lo anterior se deduce que nuestra función recursiva debe retornar un valor booleano, es decir, el caso base debe retornar true o false, pero como false es una condición de fracaso (no se pudo dibujar nada), retornaremos true.

Con el análisis anterior podemos  construir el cuerpo de la función que dibuja polígonos comunicando los puntos de una lista de puntos:

(define (dibujar-polígono lista-de-puntos color)
 (if (empty? (rest lista-de-puntos)) true
  (and
   (draw-solid-line (first lista-de-puntos) (first (rest lista-de-puntos)) color)
   (dibujar-polígono (rest lista-de-puntos) color)
  )
 )
)
; Prueba: Creamos un conjunto de puntos cuya unión en secuencia dibuja un cuadrado.
(define cuadrado (list
  (make-posn 100 100)
  (make-posn 150 100)
  (make-posn 150 150)
  (make-posn 100 150)
  (make-posn 100 100)
  )
)
(dibujar-polígono cuadrado 'red)

Ejercicios

A partir del anterior ejemplo, debería resultar fácil pensar los siguientes problemas

  1. Dibujar una matriz de 4×4 (cuatro cuadrados unidos).
  2. Dibujar una cruz compuesta por 6 cuadrados unidos.
  3. Hacer una función recursiva que tome una lista de figuras (una lista de puntos cada una) y las dibuje todas.
  4. Hacer una función recursiva que tome una lista de figuras y una lista de colores (‘red, ‘blue’, ‘black, ‘yellow, ‘white, ‘green, etc) y los dibuje todos con el color que correspondndiente, por ejemplo: (dibujar (list cuadrado) (list ‘blue) ) dibujaría un cuadrado de color azul, (dibujar (list cuadrado1 cuadrado2) (list ‘blue ‘red) ) –> dibujaría dos cuadrados, el primero (cuadrado1) de color azul y el segundo (cuadrado2) de color rojo.

Extendiendo el ejercicio a las estructuras, ahora definimos las siguientes estructuras que parametrizan las figuras geométricas más comunes:

(define-struct rectangulo (esquina ancho alto))
(define-struct triangulo (punta base alto))
(define-struct cruz (centro alto lado angulo))
(define-struct disco (centro R r))
(define-struct circulo (centro r))

Las estructuras definidas describen objetos gométricos con base en sus parámetros, es decir, los valores que les dan características individuales. Ahora, para dibujar un rectángulo es necesario tomar las coordenadas de la esquina y calcular las coordenadas del resto de puntos para dibujarlo. Por ejemplo, el punto a de un rectángulo sería la esquina superior izquierda, el punto b (la esquina superior derecha) sería el punto cuyas coordenadas son el valor de la coordenada x de a más el ancho del rectángulo, y el mismo valor y de a, es decir, un punto a la misma altura en el lienzo (mismo y) pero desplazado horizontalmente tantos puntos como diga el ancho del rectangulo. Escrito en Scheme sería:

(define r (make-rectangulo (make-posn 100 100) 200 100) )
(define b (make-posn
  (+ (posn-x (rectangulo-esquina r)) (rectangulo-ancho r) )
   (posn-y (rectangulo-esquina r))
  )
)

Queda como ejercicio para el lector definir cómo obtener el resto de los puntos de un rectángulo y una vez que se tengan se puede construir una lista que lo represente como un polígono cerrado y usar la función dibujar-polígono para mostrarlo en pantalla.

Imagínense entonces, que se desea hacer una función muy general que dibuja cualquier figura en pantalla, por ejemplo, si c es un círculo (dato de tipo circulo), el llamado a la función (dibujar-figura c ‘red) muestra un círculo de color rojo, por otro lado si r es un rectángulo (dato de tipo rectangulo) el llamado (dibujar-figura r ‘blue) muestra un rectángulo de color azul.

Más ejercicios

  1. Hacer una función que convierta un dato de tipo rectángulo en una lista de puntos que obedezca la definición de polígono cerrado. Es decir, con los ejemplos escritos anteriormente, el llamado (rec-pol r) debe retornar (list (make-posn 100 100) (make-posn 300 100) (make-posn 300 200) (make-posn 100 200) (make-posn 100 100)).
  2. Hacer dos funciones específicas para convertir un triángulo en una lista de puntos (como un polígono cerrado) y una cruz en una lista de puntos (como un polígono cerrado).
  3. Hacer una función específica que convierta una cruz en una lista de puntos (como un polígono).
  4. Crear datos de tipo rectángulo, triángulo y cruz y hacer llamados a las funciones creadas en el punto anterior para comprobar su correcto funcionamiento.
  5. Cree una función que dibuje una figura de cualquiera de los tipos definidos anteriormente (rectangulo, triangulo, cruz, disco y circulo) con algún color dado.
  6. Cree una función que tome una lista de figuras y una lista de colores (del mismo tamaño) y las dibuje todas en el lienzo con el color correspondiente.
  7. Cree una función que tome un punto en el plano de pixeles y dibuje un tren usando el pixel dado como esquina superior izquierda del mismo. Use todas las funciones anteriores. ¿Cómo haría para que el tren se pueda dibujar de diferentes tamaños?.

Reto:

Defina una función que tome un dato de tipo círculo y un número mayor a 12 y retorne una lista con la cantidad de puntos dada como argumento a la función y que obedezca la definición de polígono cerrado. Si se pasan los puntos a la función dibujar-polígono creada anteriormente debe dibujar un polígono muy similar al círculo pasado como argumento.