cuaderno teórico de gobstones

37
Cuaderno Teórico de Gobstones Introducción Este cuaderno complementa las actividades diseñadas en Gobstones con información teórica. Si bien las actividades en sí mismas son importantes, también lo son las conclusiones a las que arribamos sobre los conceptos que se van presentando en un curso. El objetivo de este documento es sólo el de aproximar un poco de teoría a las actividades. Si se desean estudiar estos temas en profundidad, deben tomarse cursos acordes o consultar bibliografía adecuada para tal fin. Temario Los contenidos son explicados en el orden en que deseamos sean presentados. Programas, valores y comandos Procedimientos Subtareas Comentarios y precondiciones Parametrización Otro ejemplo de parametrización Repetición Repetición indexada Alternativa condicional Repetición condicional Recorridos Recorridos por todo el tablero Variables Programas, valores y comandos Si bien en Scratch se programa combinando y encajando bloques entre sí, actualmente la mayoría de los programadores no define programas de esta manera. En cambio, desarrollan programas escribiendo texto en cierto lenguaje de programación, tarea que se conoce como codificar. Por ende los programas son definidos como descripciones ejecutables. Descripciones porque lo que define un programa es un texto, distinto a los que escribimos en español porque conlleva seguir una serie de reglas estrictas que están incluidas en el lenguaje de programación, a la que llamamos sintaxis; y ejecutables porque esta descripción debe poder ser ejecutada por una computadora. En este caso utilizaremos un lenguaje de programación llamado Gobstones, el cual permite

Upload: sansergioniz

Post on 22-Dec-2015

319 views

Category:

Documents


32 download

DESCRIPTION

Ejercicios para aprender programación

TRANSCRIPT

Cuaderno Teórico de Gobstones Introducción Este cuaderno complementa las actividades diseñadas en Gobstones con información teórica. Si bien las actividades en sí mismas son importantes, también lo son las conclusiones a las que arribamos sobre los conceptos que se van presentando en un curso. El objetivo de este documento es sólo el de aproximar un poco de teoría a las actividades. Si se desean estudiar estos temas en profundidad, deben tomarse cursos acordes o consultar bibliografía adecuada para tal fin. Temario Los contenidos son explicados en el orden en que deseamos sean presentados.

Programas, valores y comandos Procedimientos

Subtareas Comentarios y precondiciones

Parametrización Otro ejemplo de parametrización

Repetición Repetición indexada Alternativa condicional Repetición condicional Recorridos

Recorridos por todo el tablero Variables

Programas, valores y comandos

Si bien en Scratch se programa combinando y encajando bloques entre sí, actualmente la mayoría de los programadores no define programas de esta manera. En cambio, desarrollan programas escribiendo texto en cierto lenguaje de programación, tarea que se conoce como codificar. Por ende los programas son definidos como descripciones ejecutables. Descripciones porque lo que define un programa es un texto, distinto a los que escribimos en español porque conlleva seguir una serie de reglas estrictas que están incluidas en el lenguaje de programación, a la que llamamossintaxis; y ejecutables porque esta descripción debe poder ser ejecutada por una computadora.

En este caso utilizaremos un lenguaje de programación llamado Gobstones,el cual permite

presentar conceptos básicos de programación.

En Gobstones describen tableros con celdas que contienen bolitas de colores. Además todo tablero posee un cabezal apuntando a una determinada celda del mismo.

Por otra parte, el lenguaje posee valores y comandos. Los valores son simples datos, y en Gobstones (por ahora) disponemos de colores, direcciones, números y booleanos. Los colores pueden ser Azul, Negro, Rojo y Verde; las direcciones pueden ser Norte, Sur, Este y Oeste; los números ya los conocemos (1, 54145, 23, etc), y los booleanos simplemente son True (verdadero) y False (falso).

Por su parte, los comandos, que son acciones dirigidas al cabezal del tablero, consisten en:

Poner, que toma un color y pone una bolita de ese color en la celda a la que apunta el cabezal.

Mover, que toma una dirección y mueve el cabezal hacia dicha dirección. Sacar, que toma un color y saca una bolita de ese color en la celda a la que apunta

el cabezal.

Un programa Gobstones siempre empieza de la siguiente manera: program

Las llaves encierran un bloque de código, y dentro de todo bloque de código podemos ejecutar los comandos antes mencionados. Para aquellos que anteriormente usaron Scratch, estas llaves son similares a los huecos que poseen algunos bloques:

En Gobstones hay reglas que hay que respetar:

Los nombres de comandos empiezan con mayúscula Los nombres de valores empiezan con mayúscula Los nombres de procedimientos empiezan con mayúscula procedure empieza con minúscula Los bloques de código quedan definidos por una llave que abre y otra que cierra

Para esto programar utilizaremos una herramienta denominada PyGobstones que implementa el lenguaje Gobstones. Esta herramienta consiste de un editor de textos sobre el que codificaremos y que definirán programas que al ejecutarse mostrarán un tablero final como resultado. El siguiente programa pone una bolita de color rojo en la celda a la que apunta el cabezal actualmente (que puede ser cualquiera en un principio) program Poner(Rojo) A continuación verán dos tableros. Uno es el inicial (izquierda) y el otro es el tablero resultante de ejecutar el programa sobre el tablero inicial (derecha)

Procedimientos

En Gobstones podemos aprovechar un concepto que vimos en Scratch: asignar nombres a secuencias de comandos, que denominamos procedimientos. Esto se realiza con la palabra procedure seguida de un nombre a elección, que debe empezar con mayúsculas.

Por ejemplo, podemos dibujar un cuadrado compuesto de líneas. Los procedimientos a utilizar podrían ser LíneaRoja y CuadradoRojo.

program

IrAlOrigen()

CuadradoRojo()

procedure CuadradoRojo()

LineaRoja(); Mover(Norte)

LineaRoja(); Mover(Norte)

LineaRoja()

procedure LineaRoja()

Poner(Rojo); Mover(Este)

Poner(Rojo); Mover(Este)

Poner(Rojo)

Mover(Oeste); Mover(Oeste) // retornamos a la posición inicial de la linea

El resultado es:

Subtareas

Esta idea de separar el problema en distintos procedimientos se conoce como división en subtareas. La idea es que CuadradoRojo utilice LíneaRoja en su definición, lo que hace que el código sea más fácil de escribir y de entender. Si, por ejemplo, luego se pide modificar el largo

de la figura, sólo debemos añadir una LineaRoja más dentro del bloque de código de CuadradoRojo. Si, por el contrario, queremos modificar el ancho, modificamos LineaRoja para que dibuje una bolita más.

Por otra parte, es importante definir buenos nombres de procedimientos, lo que permite intuir sin mirar el código qué es lo que hace el programa, sólo basándonos en los nombres de los procedimientos y cómo se relacionan entre sí.

Comentarios y precondiciones

Además podemos agregar comentarios en el código, simplemente agregando dos barras (“//”) seguidas de texto en idioma cotidiano. Los comentarios son simplemente texto que no será ejecutado pero que permiten al programador entender qué hace el programa o qué cosas debe tener en cuenta el leerlo o utilizarlo.

Los comentarios son también útiles para definir las precondiciones de un procedimiento, que son aquello que requiere el procedimiento para ejecutarse correctamente. En este caso la precondición sería:

// Precondición: el cuadrado necesita un espacio de 3x3 para poder ser dibujado.

Así como sucede en Scratch, ciertos comandos requieren ciertas condiciones para no fallar, y en este caso, el comando “Mover”, requiere que haya una celda hacia la dirección en la que se desplaza el cabezal. Si esta celda no existe, el programa terminará de forma abrupta. Otro comando que puede fallar es “Sacar”, si no hay bolitas del color a sacar en la celda actual.

Parametrización

Una actividad que podemos realizar sobre el cuadrado anterior es cambiar el color con el que se dibuja, de Rojo a Verde. Lo que haremos será buscar cada comando Poner que había y lo modificaremos para que ponga una bolita del nuevo color. También modificaremos los nombres de procedimientos para que reflejen este cambio de color:

procedure LineaVerde()

Poner(Verde); Mover(Este) // Antes decía Poner(Rojo)

Poner(Verde); Mover(Este)

Poner(Verde)

Mover(Oeste); Mover(Oeste) // retornamos a la posición inicial de la linea

procedure CuadradoVerde()

LineaVerde(); Mover(Norte)

LineaVerde(); Mover(Norte)

LineaVerde()

Si nuevamente pedimos que vuelvan a cambiar el color por otro de los existentes harán lo mismo: buscar dónde figura el color actual para cambiarlo por otro color, siempre modificando el código de los procedimientos.

No obstante, no es práctico andar modificando el procedimiento para elegir de qué color deseamos pintar un cuadrado. De esta manera, existe un mecanismo mucho mejor para hacer esto, que permite seleccionar un determinado valor al momento de invocar un procedimiento. Esto posibilita como usuarios del procedimiento elegir el color a dibujar al momento de utilizarlo. De esta manera, podríamos escribir los cuatro cuadrados de cada color posible como:

program

IrAlOrigen(); Cuadrado(Rojo)

IrAlOrigen(); Cuadrado(Azul)

IrAlOrigen(); Cuadrado(Verde)

IrAlOrigen(); Cuadrado(Negro)

Esta es una técnica ya utilizada con los comandos Poner, Mover y Sacar, es decir, indicamos con qué valor deseamos que trabajar al invocar el procedimiento. Al que brindabamos al momento de utilizar al procedimiento lo llamaremos parámetro, y para que el procedimiento pueda utilizarlo debemos modificar lo siguiente:

procedure Cuadrado(colorDeCuadrado)

procedure Linea(colorDeLinea)

Como puede observarse, añadimos un nombre con mínusculas (a elección) entre los paréntesis a continuación de los procedimientos que deben elegir el color a dibujar. Como Cuadrado utiliza Linea, este último también necesitará definir este color arbitrario. El nombre que inventamos, al invocar el procedimiento, es reemplazado en todas sus apariciones por un valor concreto (Rojo, Verde, Azul o Negro), pero nos permite mientras tanto, utilizarlo para ocupar el lugar de aquellos valores concretos, aunque no sepamos cuál será elegido todavía. A la acción de añadir un parámetro al procedimiento para reemplazar un valor concreto dentro de éste, la denominaremos parametrizar.

El ejemplo completo de Cuadrado parametrizando este valor es:

program

IrAlOrigen(); Cuadrado(Azul)

IrAlOrigen(); Cuadrado(Rojo)

IrAlOrigen(); Cuadrado(Negro)

IrAlOrigen(); Cuadrado(Verde)

procedure Cuadrado(colorDeCuadrado)

Linea(colorDeCuadrado); Mover(Norte)

Linea(colorDeCuadrado); Mover(Norte)

Linea(colorDeCuadrado)

procedure Linea(colorDeLinea)

Poner(colorDeLinea); Mover(Este)

Poner(colorDeLinea); Mover(Este)

Poner(colorDeLinea)

Mover(Oeste); Mover(Oeste) // retornamos a la posición inicial de la linea

Es sútil pero interesante no utilizar exactamente el mismo nombre para los parámetros de ambos procedimientos. Para el procedimiento Cuadrado el parámetro se llama "colorDeCuadrado" y para Linea el parámetro se llama "colorDeLinea". Con esto queda en evidencia que cada uno define el nombre del parámetro para sí mismo, y que cada uno utiliza este nombre dentro del bloque de código que lo define (de hecho, no es posible utilizar un nombre de parámetro fuera del bloque de código del procedimiento). Esto se conoce como "scoping" o alcánce del

parámetro.

Si este tema no queda claro, operacionalmente el pasaje de un parámetro funciona de la siguiente manera. En program indicábamos como usuarios de qué color queremos el cuadrado

program

IrAlOrigen()

Cuadrado(Rojo)

Con escribir Cuadrado(Rojo), le indicamos al procedimiento Cuadrado que el parámetro con nombre "color" es igual a Rojo, por lo que cada aparición de esta palabra se reemplaza por el color concreto (en este caso Rojo). Podemos pensar en dos tipos de procedimientos, uno general, antes de saber de qué color vamos a dibujar, y uno particular, cuando ya seleccionamos dicho color. Al escribir Cuadrado(Rojo), transformamos al procedimiento general en uno particular.

El procedimiento general sería:

procedure Cuadrado(colorDeCuadrado)

Linea(colorDeCuadrado); Mover(Norte)

Linea(colorDeCuadrado); Mover(Norte)

Linea(colorDeCuadrado)

que no indica (todavía) con qué color particular se dibujará el cuadrado, y el procedimiento particular (cuando ya se sabe el color) sería algo como:

procedure Cuadrado(Rojo) // esto es sólo con fines ilustrativos, no es un programa válido

Linea(Rojo); Mover(Norte)

Linea(Rojo); Mover(Norte)

Linea(Rojo)

Es la misma tarea que realizábamos a mano, pero esta vez hecha de manera automática por el lenguaje.

A su vez Linea también recibe el valor concreto, por lo que el procedimiento general de Linea sería:

procedure Linea(colorDeLinea)

Poner(colorDeLinea); Mover(Este)

Poner(colorDeLinea); Mover(Este)

Poner(colorDeLinea)

Mover(Oeste); Mover(Oeste) // retornamos a la posición inicial de la linea

Y luego de aplicar el color concreto se transforma en:

procedure Linea(Rojo)

Poner(Rojo); Mover(Este)

Poner(Rojo); Mover(Este)

Poner(Rojo)

Mover(Oeste); Mover(Oeste) // retornamos a la posición inicial de la linea

Lo mismo sucederá con cada color que elijamos para dibujar el cuadrado.

Otro ejemplo de parametrización

Definamos un procedimiento más, que será una variación de dibujar un cuadrado. Dibujaremos en este caso sólo el perímetro del cuadrado (sin la parte interna pintada). Haremos que sea además de color Rojo. El resultado sería:

Antes de empezar a codificar sin pensar, debemos razonar las subtareas que utilizaríamos:

program

PerimetroCuadrado()

procedure PerimetroCuadrado()

procedure LineaArriba()

procedure LineaAbajo()

procedure LineaIzquierda()

procedure LineaDerecha()

de tal forma que PerimetroCuadrado quedaba definido como

procedure PerimetroCuadrado()

LineaArriba()

LineaDerecha()

LineaAbajo()

LineaIzquierda()

A su vez las distintas subtareas para lineas quedan definidas como

procedure LineaArriba()

Poner(Rojo)

Mover(Norte)

Poner(Rojo)

Mover(Norte)

Poner(Rojo)

Mover(Norte)

procedure LineaAbajo()

Poner(Rojo)

Mover(Sur)

Poner(Rojo)

Mover(Sur)

Poner(Rojo)

Mover(Sur)

procedure LineaDerecha()

Poner(Rojo)

Mover(Este)

Poner(Rojo)

Mover(Este)

Poner(Rojo)

Mover(Este)

procedure LineaIzquierda()

Poner(Rojo)

Mover(Oeste)

Poner(Rojo)

Mover(Oeste)

Poner(Rojo)

Mover(Oeste)

Como podemos observar, los cuatro procedimientos son equivalentes en todo menos en la

dirección concreta hacia la que se mueven para pintar.

Para reducir tanto código similar, podemos parametrizar la dirección hacia donde se dibujará cada línea, de tal manera que pasamos a tener un sólo procedimiento:

procedure LineaHacia(dir)

Poner(Rojo)

Mover(dir)

Poner(Rojo)

Mover(dir)

Poner(Rojo)

Mover(dir)

Por lo que PerimetroCuadrado será tan simple como:

procedure PerimetroCuadrado()

LineaHacia(Norte)

LineaHacia(Este)

LineaHacia(Sur)

LineaHacia(Oeste)

Iremos eligiendo valores concretos al momento de utilizar el procedimiento, y en LineaHacia utilizamos un nombre para designar que el valor será reemplazado luego por algo concreto, el cual llamamos dir (por dirección)

¿Pero si ahora también queremos parametrizar el color? Es tan fácil como agregar otro nombre de parámetro más, junto a "dir", separado por una coma.

procedure LineaHacia(dir, color)

Poner(color)

Mover(dir)

Poner(color)

Mover(dir)

Poner(color)

Mover(dir)

Y también modificamos PerimetroCuadrado:

procedure PerimetroCuadrado(color)

LineaHacia(Norte, color)

LineaHacia(Este, color)

LineaHacia(Sur, color)

LineaHacia(Oeste, color)

En este caso PerimetroCuadrado utiliza LineaHacia primero pasándole una dirección concreta (Norte, Este, Sur y Oeste) pero también pasándole el parámetro que recibe, denominado "color". LineaHacia es un procedimiento totalmente general, trabaja con una dirección y un color que todavía no sabe cuáles son y su elección queda a gusto del usuario que utilice el procedimiento.

Por su parte, program queda definido como:

program

PerimetroCuadrado(Rojo)

Repetición

Veamos otro caso totalmente distinto. Definamos un procedimiento simple pero tedioso, que consista en colocar 300 bolitas de algún color. Lo llamaremos Poner300(). Para simplificarlo crearemos dos subtareas Poner10 y Poner100. De esta manera Poner300 es Poner100 3 veces y Poner100 es 10 veces Poner10:

procedure Poner10()

Poner(Rojo); Poner(Rojo); Poner(Rojo); Poner(Rojo); Poner(Rojo);

Poner(Rojo); Poner(Rojo); Poner(Rojo); Poner(Rojo); Poner(Rojo);

procedure Poner100()

Poner10();Poner10();Poner10();Poner10();Poner10();

Poner10();Poner10();Poner10();Poner10();Poner10();

procedure Poner300()

Poner100();Poner100();Poner100();

Pero qué sucede si ahora quisiéramos poner 1000 bolitas, 512 bolitas, 215 bolitas, etc. El programa se torna difícil de modificar, pese a que la tarea de poner una cantidad arbitraria de bolitas debería ser trivial.

En programación solemos utilizar constantemente el concepto de repetición, para ejecutar una cantidad arbitraria de veces cierta tarea. Gobstones posee una construcción llamadarepeat, que permite repetir una tarea dependiendo de un rango de valores. Además permite utilizar cada elemento del rango para hacer algo.

En este caso poner 300 bolitas rojas sería algo tan simple como

repeat(300)

Poner(Rojo)

La parte que indica "300" representa es la cantidad de veces que queremos repetir una tarea.

Ahora pensemos un procedimiento que aprovecha el poder de la repetición colocando una cantidad de bolitas a elección del usuario. Nuestros procedimientos concretos que ponían una cantidad fija de bolitas se llamarían Poner300, Poner100, Poner10, etc., y ahora necesitamos algo como PonerLasQueQueramos(cantidad, color) o PonerX(cantidad, color). Con la “X” queremos significar “número arbitrario”, así como antes poníamos como parte del nombre de los procedimientos un número concreto (300, 200, 100, 10, etc.). Ahora indicamos con una “X” que el número puede ser cualquiera que elijamos. Esto es simplemente una convención.

El procedimiento que definamos deberá tomar un número y un color, y pondrá ese número de bolitas del color dado. Una posible definición de program que utiliza este procedimiento puede ser:

program

PonerX(10, Rojo) // pone 10 bolitas rojas

PonerX(25, Negro) // pone 25 bolitas negras

PonerX(5, Verde) // pone 5 bolitas verdes

Como podemos ver, el comportamiento del procedimiento varía cuando le pasamos parámetros distintos. El primer parámetro indicará la cantidad de bolitas a poner y el segundo de qué color queremos a estas bolitas. En la definición de PonerX debemos utilizar estos parámetros para reflejar este comportamiento variable:

procedure PonerX(cantidad, color)

// código que utiliza “cantidad” y “color”

Lo primero que podemos imaginar es que debemos utilizar el comando “Poner” junto con el parámetro “color” para colocar bolitas de ese color que nos pasan por parámetro:

procedure PonerX(cantidad, color)

Poner(color)

...

Esto coloca sólo sólounabolita del color dado. Lo que queremos es utilizar el número “cantidad” para ejectuar exactamente ese número de veces el comando Poner. Aquí es donde entra en juego la repetición.

procedure PonerX(cantidad, color)

repeat(cantidad)

Poner(color)

Como podemos ver, utilizamos el parámetro “cantidad” junto con elrepeat, en lugar de repetir un número concreto de veces. Si “cantidad” fuese 300, entonces “Poner(color)” se repetirá 300 veces, pero en principio, dentro de la definición de PonerX, la cantidad de veces a repetir puede ser cualquiera, siendo el usuario quien elige cuántas exactamente.

De la misma manera nos podemos imaginar la implementación de SacarX(cantidad, color), que toma un número y un color y saca esa cantidad de bolitas del color dado, y MoverX(cantidad, direccion), que se mueve la cantidad dada por parámetro de veces hacia la dirección dada por parámetro:

procedure SacarX(cantidad, color)

repeat(cantidad)

Sacar(color)

procedure MoverX(cantidad, dir)

repeat(cantidad)

Mover(dir)

Repetición indexada

Existe otro tipo de repetición, denominada repetición indexada. En lugar de utilizar un número para repetir tareas una cantidad determinada de veces, esta repetición recorre una lista de valores que serán utilizados en una tarea que hace uso de estos (generalmente se pasan por parámetro).

Un ejemplo sencillo es el de poner una bolita de todos los colores posibles. Bien podríamos hacer lo siguiente:

procedure PonerUnaDeCadaColor()

Poner(Azul); Poner(Negro); Poner(Rojo); Poner(Verde)

Pero utilizando una repetición indexada, esto se resuelve tan simple como:

procedure PonerUnaDeCadaColor()

foreach color in [minColor() .. maxColor()]

Poner(color)

En Gobstones, la repetición indexada se denomina foreach. Es similar al repeat, dado que utiliza un bloque de código para definir la tarea que va a repetirse, pero posee más elementos:

La parte que dice "color" es uníndice, es decir, es el nombre que representa a cada valor de la lista para poder utilizarlo dentro. Este nombre es totalmente inventado por nosotros, y bien podría ser otro. El índice del foreach no puede ser utilizado en otro lado que no sea el bloque definido para esta construcción.

La parte que encerrada entre corchetes representa la lista con los valores a recorrer. Como queremos arrancar desde el primer color hasta el último, utilizamos las expresiones minColor() (que representa el Azul) y maxColor() (que representa el Verde), situadas entre “..” (dos puntos), para que se completen de forma automática los elementos que hay entre medio de los dos extremos. Los colores están ordenados alfabéticamente, por lo que la lista resultante será idéntica a[Azul, Negro, Rojo, Verde], pero la forma en la que construimos la lista (como un rango del mínimo al máximo valor) nos facilita no tener que escribirlos todos.

Operacionalmente, el foreach pasará por cada elemento de la lista, y repetirá la tarea por cada cada elemento por los que pasa. En cada momento, el índice apuntará a un elemento distinto, y pasará por el primer valor de la lista, hasta el último. En el ejemplo anterior, el índice “color”, empieza valiendo Azul, luego pasa a valer Negro, prosigue con Rojo y termina siendo Verde.

Otro ejemplo sencillo es uno que utilice todas las direcciones:

procedure PonerUnaEnCadaDireccion(color)

foreach dir in [minDir() .. maxDir()]

Mover(dir); Poner(color); Mover(opuesto(dir))

Para construir una lista con todas las direcciones utilizamos minDir() y maxDir(). La primera dirección es Norte, y la última es Oeste. Las direcciones están ordenadas en sentido horario, por lo que la lista resultante de [minDir() .. maxDir()] termina siendo [Norte,Este,Sur,Oeste]. El índice en esta ocasión lo nombramos como dir. Lo que hacemos como tarea es movernos hacia cada dirección de la lista, ponemos una bolita de un color que recibimos por parámetro, y volvemos sobre el opuesto de esa misma dirección.

La expresión opuesto toma una dirección y devuelve la dirección contraria a esta. Por ejemplo, opuesto(Norte) es igual a escribir Sur, yopuesto(Oeste) es lo mismo que Este. Utilizamos esta expresión, porque no sabemos de qué dirección estamos hablando en cada momento (sabemos que va a ser alguna de la lista, pero no cuál exactamente). Pero sí sabemos que es una dirección, y como con toda dirección, podemos averiguar su opuesto. Esto nos permite ir y volver

para cualquier dirección de la que estemos hablando.

Un program construido con este procedimiento que acabamos de definir podría ser:

program

IrAlOrigen(); Mover(Norte); Mover(Este)

PonerUnaEnCadaDireccion(Azul)

Los dos ejemplos mencionados, que utilizan colores y direcciones, al ser cuatro valores de cada tipo, no demoraríamos mucho en enumerarlos todos a mano si quisiéramos. Pero qué sucede si queremos recorrer una lista de números, del 1 al 10, para realizar una tarea. Aquí el trabajo sería más pesado.

Un ejemplo como el anterior puede ser el siguiente:

procedure PonerX(cantidad, color)

repeat(cantidad)

Poner(color)

procedure ProgresionVerde(longitud)

foreach numero in [1 .. longitud]

PonerX(numero, Verde); Mover(Este)

program

IrAlOrigen()

ProgresionVerde(10)

PonerX, es un procedimiento definido en el apartado deRepetición. El procedimiento a observar es ProgresionVerde, al que le pasamos un número y construye una sucesión de distintas cantidades de bolitas verdes, empezando por el 1 hasta el número que hayamos indicado. En este programa utilizamos ProgresionVerde(10), por lo que la sucesión se detiene en 10 bolitas verdes.

Funciona de la siguiente manera. El parámetro es utilizado para formar la lista que se va a recorrer [1 .. longitud]. Al índice lo nombramos numero, dado que cada elemento va a ser un número de al lista. El índice es pasado a PonerX, junto con el color Verde, para que cada vez, se coloquen numero bolitas verdes, es decir, que vayan colocando bolitas verdes usando cada

número que haya en la lista. En cada momento el cabezal es movido al Este, para que las bolitas se coloquen en distintas celdas, pero contiguas. El resultado de todo esto es el observado en la imagen.

Funciones

Hasta el momento definimos nuestros propios procedimientos, que representan tareas a ejecutar sobre el tablero. Los procedimientos se componen de comandos, que son ordenados de forma secuencial en un bloque de código. En síntesis, la herramienta llamada “procedimiento” permite asignarle un nombre a una tarea.

Anteriormente vimos algo llamado “expresiones”, que son elementos que describen valores. Por ejemplo, la expresión “3+1” describe el valor 4 y la expresión “opuesto(Norte)” describe el valor Sur. Más aún, “opuesto(Norte)” es equivalente a “Sur”, y son intercambiables justamente por denotar el mismo valor. En este ejemplo “opuesto” representa una expresión que, al recibir una dirección, equivale a la dirección opuesta a esta. Si recibe Norte sería equivalente a Sur, si recibe Oeste sería equivalente a Este, y así con cada dirección. Esta operación ya existe en Gobstones, al igual que existe la suma, la multiplicación y la resta entre números. Sin embargo, esta expresión opera sobre colores, a diferencia de estas últimas que trabajan con números.

También existen expresiones que no reciben ningún valor y directamente equivalen a uno. Por ejemplo, minColor() equivale a Azul. Con esto queremos decir que “es lo mismo” poner minColor() que Azul en un programa. Pero lo que sucede es que a veces conviene una forma u otra de describir lo mismo. Si en el futuro el primer color no fuese Azul sino, por ejemplo, amarillo, minColor() pasaría a describir amarillo, porque su función es describir el primer color de los existentes en Gobstones. Si por el contrario ponemos Azul en un lugar en donde en realidad queremos “el primer color existente” y en el futuro cambian los colores, nuestro programa quedaría desactualizado ante la nueva versión de Gobstones (cosa que pasa en muchos programas donde esto se toma a la ligera).

Por lo que podemos observar, las expresiones se relacionan con valores, pueden recibir valores por parámetro y equivalen a otros valores, y se diferencian de los comandos y procedimientos, que representan tareas o acciones, dado que los valores no “hacen algo”, sino que simplemente son datos. De la misma manera, las expresiones no son comandos, ya que no describen acciones sino que describen valores. La expresión “3+1” no es la “acción de sumar 3 a 1” sino que simplemente describe 4 al igual que “2+2” describe 4. Por ejemplo, decir “repeatWith i in 1 .. 2+2” es equivalente a decir “repeatWith i in 4”. Y esto es lo que nos interesará cuando hablemos de expresiones: qué valor describen en última instancia.

La nueva herramienta que veremos en esta clase sirve para “ponerle un nombre a una expresión”. Así como antes, de manera opuesta, asignábamos nombres a tareas a través de procedimientos, ahora asignaremos nombres a expresiones con una herramienta llamada

función. Un ejemplo fácil es el siguiente:

function igualA4()

return(2+2)

Esta función simplemente equivale a la expresión “2+2”. Es decir, si en un programa encontramos escrito “igualA4()”, la invocación a esta función equivaldrá a encontrar “2+2”. Este simple ejemplo nos sirve para distinguir algunas cosas importantes:

Así como antes escribíamos “procedure <nombre del procedimiento>” ahora escribimos “function <nombre de la función>”.

El nombre de una función empieza en minúscula, contrariamente al de los procedimientos, que empieza en mayúscula.

Existe una palabra llamada return que significa “esta función equivale a la siguiente expresión”. Viene del inglés “devolver” o “retornar”, y esto es así porque tradicionalmente se dice que la función “devuelve” o “retorna”, en este caso, “2+2”. Pero en lugar de decir “devolver” también podemos decir “describe” o “equivale” a la siguiente expresión.

La expresión junto al “return” va entre paréntesis.

La palabra “return” por ahora será la única que encontraremos dentro del bloque de código que define a la función.

Un ejemplo un tanto más interesante es la función “sumarUno”:

function sumarUno(x)

return(x+1)

Podemos ver en este caso que las funciones también pueden definir parámetros. En este caso la invocación “sumarUno(2)” describirá al número 3, y la invocación “sumarUno(5)” describirá el número 6. Esto es así porque el parámetro recibido se utiliza en la expresión “x+1”, que describirá distintos valores conforme varíe el valor del parámetro.

Por último las funciones también pueden llevar una parte de “procesamiento”, antes de “return”. La palabra “return” siempre debe ser la última en dentro del bloque de código que define a una función. Por “procesamiento” entendemos que podemos ejecutar comandos (y procedimientos)

dentro de este bloque de código.

Por ejemplo, la siguiente función devuelve el número de bolitas rojas de la celda lindante al Norte:

function nroDeBolitasRojasAlNorte()

Mover(Norte)

return(nroBolitas(Rojo))

Los comandos y procedimientos ejecutados antes del return, afectarán lo que describa la expresión final que describe la función. En este caso la expresión “nroBolitas(Rojo)”, describe el número de bolitas de la celda en la que está posicionado el cabezal. Como primero nos movemos al Norte, describirá la cantidad de bolitas rojas que hay una celda hacia el Norte desde donde esté posicionado el cabezal al momento de ejecutar la función. Si no hay una celda al Norte, la función fallará al igual que un procedimiento.

La mayor diferencia entre procedimientos y funciones con procedimiento, además de que los procedimientos no devuelven ningún valor, es que luego del “return” de la función todos los efectos generados dentro de la función desaparecerán. En el caso anterior, el cabezal volverá a su posición original (la posición que tenía antes de ejecutar la función). Esto significa que lo procesado dentro de una función no genera un efecto final, dado que esa es la tarea de los procedimientos y no el de las funciones. Lo procesado dentro de una función sólo modifica el contexto dentro de esa función, pero luego de retornar la expresión deseada (objetivo de las funciones), los efectos internos generados son descartados (se reintegra el estado inicial antes de ejecutar la función).

Para ilustrar mejor este efecto, podemos pensar en el siguiente procedimiento:

procedure PonerTantasAzulesComoRojasHayaAlNorte()

PonerX(nroDeBolitasRojasAlNorte(), Azul)

Analicemos qué valores pasamos como argumentos de PonerX. Por un lado (segundo parámetro) indicamos con el valor Azul el color de las bolitas a poner; y por otro lado (primer parámetro) indicamos que queremos poner exactamente la cantidad de bolitas que haya en la celda al Norte de la actual.

Con el siguiente estado inicial, deberíamos estar poniendo 10 bolitas de color Azul en la celda actual, copiadas de la cantidad de bolitas rojas, al Norte de esta celda:

Antes de ejecutar el programa Después de ejecutar el programa

La cantidad de bolitas rojas al Norte de la celda actual es dada por la invocación de la función “nroDeBolitasRojasAlNorte()”, que simplemente equivale a dicho número. Así como podríamos haber escrito “PonerX(10, Azul)” para poner 10 bolitas azules, ahora escribimos “PonerX(nroDeBolitasRojasAlNorte(), Azul)” para poner exactamente tantas bolitas azules como el número de bolitas rojas haya al Norte.

Recordamos la definición de esta función:

function nroDeBolitasRojasAlNorte()

Mover(Norte)

return(nroBolitas(Rojo))

En ninguna parte de la secuencia volvemos al Sur. Esto no es necesario ya que el movimiento al Norte será eliminado luego de retornar el número de bolitas rojas. Es aquí entonces donde se ve que la función “nroDeBolitasRojasAlNorte” no generá efectos finales sobre el tablero sobre el que trabaja “PonerX”. Si bien la función “nroDeBolitasRojasAlNorte” debe moverse al Norte para devolver la cantidad de bolitas rojas de esa celda, ese movimiento no cuenta al terminar de devolver el valor deseado. Simplemente el cabezal sigue en la misma posición sobre la cual se comenzó a ejectuar el procedimiento “PonerTantasRojasComoHayaAlNorte”, por lo que PonerX colocará las bolitas sobre la celda actual.

Alternativa condicional

En Gobstones también disponemos de lo que se conoce comoalternativa condicional, una forma de condicionar la ejecución de una tarea en base al valor de una expresión booleana. Por ejemplo:

if(puedeMover(Norte))

Mover(Norte)

La función puedeMover es algo existente en Gobstones. Cuando recibe una dirección indica si hay una celda en esa dirección. En este caso si al Norte no hay más espacio (el cabezal está en el borde Norte del tablero), entonces devolverá False (falso), caso contrario devolverá True (verdadero). Combinando esta expresión con la alternativa condicional, si el valor de la expresión que toma entre paréntesis es verdadera, entonces ejecuta la tarea que se encuentra entre las llaves (el bloque de código). Si la expresión es falsa, entonces no ejecuta nada. Esto sirve para aquellos casos en donde queremos comprobar el estado de una situación antes de ejecutar una tarea.

Otro caso en donde se utiliza una alternativa, es cuando queremos ejecutar una tarea si una expresión es verdadera, y otra tarea en caso de ser falsa. Por ejemplo, imaginemos una función que dada una dirección marca con una bolita roja la celda en caso de estar en el borde perteneciente a esa dirección, y con una bolita azul en caso de no estar en ese borde:

procedure MarcarBorde(dir)

if(not puedeMover(dir))

Poner(Rojo)

else

Poner(Azul

La forma de decir “sino se cumple ejecutar esta otra tarea” es else, a continuación del primer bloque de código de la alternativa condicional.

Las alternativas condicionales también sirven para chequear que cierta condición se cumpla antes de ejecutar un comando parcial:

procedure MoverSiSePuede(dir)

if(puedeMover(dir))

Mover(dir)

Este procedimiento funcionará correctamente en todos los casos, dado que antes de ejecutar el movimiento, verifica si es posible hacerlo. Sin embargo, esto no significa que debamos siempre chequear estas condiciones, dado que podemos estar modificando el significado de lo que queremos hacer en situaciones en las que no lo deseamos. Por ejemplo, si queremos movernos 10 veces al Norte, simplemente podemos hacer:

repeatWith i in 1 .. 10 Mover(Norte)

Esta tarea fallará al no tener suficiente espacio, y no se comportará igual que esta otra:

repeatWith i in 1 .. 10 MoverSiSePuede(Norte)

El problema de esta última tarea es que si no hay suficiente espacio, se moverá las veces que pueda. Si nuestra intención es movernos exactamente 10 veces, esto no será lo mismo que movernos 4, 6 u 8 veces. La tarea igualmente no se estaría cumpliendo, verifiquemos o no en cada movimiento si nos vamos a caer del tablero, y probablemente sea preferible que directamente no se realice, a que se realice parcialmente. Si bien depende de lo que queramos modelar, hay que tener estos detalles en cuenta.

Otra pregunta útil en Gobstones es hayBolitas, que toma un color y responde si hay bolitas de ese color en la celda actual. ¿Para qué sirve? Podemos asociarla al comando Sacar. Para el comando Sacar la precondición que debe cumplir es que haya bolitas del color a sacar. Si queremos escribir un programa que exprese la idea de “sacar de un color sólo en el caso que haya bolitas de ese color”, entonces necesitamos esta nueva herramienta para preguntar antes si hay o no bolitas, y en caso afirmativo sacar, y sino no hacer nada:

procedure SacarSIHay(color)

if(hayBolitas(color))

Sacar(color)

Repetición condicional

Otra herramienta interesante sería aquella que permitarepetir una acción MIENTRAS se cumpla

una condición. Observen que hasta el momento sólo podíamos repetir un número fijo de veces una determinada tarea (o utilizar cada elemento de una lista bien definida, que es similar), pero no podíamos resolver problemas en los que no se sabe cuánto hay que repetir la tarea.

Por ejemplo, deseamos definir un procedimiento que mueve el cabezal hacia el Norte mientras no haya una bolita de color Negro. El cabezal puede encontrarse en cualquier parte del tablero, al igual que las bolitas negras. Queremos detenernos ante la presencia de alguna, pero no sabemos cuánta distancia debemos recorrer hasta terminar. No podemos resolver este problema con un repeat, por lo que precisamos una nueva construcción existente en Gobstones:

while(not hayBolitas(Negro))

Mover(Norte)

La construcción while al igual queif, recibe una expresión que puede ser verdadera o falsa (una condición o pregunta) y un bloque de código que será la tarea a ejecutar. En este caso, cuando la condición sea falsa, la tarea se dejará de ejecutar, pero MIENTRAS esa condición sea verdadera se seguirá ejecutando. En otras palabras, mientras NO haya bolitas de color negro, el cabezal se moverá al Norte, y cuando exista una celda en la que haya una bolita de color negro, la tarea se dejará de ejecutar.

Un problema típico que es posible resolver con un while, pero no con lo que vimos hasta el momento, consta de ir hacia alguno de los bordes del tablero. Si no sabemos dónde se encuentra el cabezal ni el tamaño del tablero en donde ejecutamos nuestro programa, no podemos saber cuánto deberíamos movernos hasta alcanzar cualquiera de los bordes. Pero utilizando un while, podemos movernos MIENTRAS podamos movernos hacia una determinada dirección. Cuando no podamos movernos más, significará que alcanzamos un borde del tablero.

procedure IrAlBorde(dir)

while(puedeMover(dir))

Mover(dir)

Un problema que se introduce con el uso de esta construcción es la posibilidad de que un programa no termine. ¿Cómo es posible esto? Si la condición del while nunca llega a ser falsa, jamás terminará de ejecutar una tarea. El siguiente ejemplo puede no terminar:

while(hayBolitas(Negro))

Poner(Verde)

Si existen bolitas negras en la celda actual, se seguirán poniendo bolitas verdes, y no hay nada que se alcance el caso contrario. El programa efectivamente no terminará nunca.

Por esta razón, cuando utilizamos un while, debemos pensar si la tarea que definimos hará que la condición en algún momento pase a ser falsa, dado que si esto no sucede, la tarea, como acabamos de ver, no terminará de ejecutarse jamás.

Recorridos

La idea de repetir una tarea a través de una condición nos permite pensar en la idea de recorrido. Un recorrido es una tarea que consiste en ir pasando por una serie de elementos, desde el primero hasta el último, sin importar CUANTOS sean. La idea de recorrido la hemos utilizado junto con la repetición indexada para recorrer valores de una lista, pero esta vez lo que recorreremos serán celdas del tablero.

Un ejemplo puede ser el de pintar toda una columna de negro:

procedure PintarColumnaDeNegro ()

IrAlBorde(Sur) // Voy al borde sur de la columna

Poner(Negro) // Pinto la primera columna de negro

while(puedeMover(Norte)) // mientras pueda moverme al norte

Mover(Norte) // paso a la siguiente celda

Poner(Negro) // pinto la celda

Todo recorrido sobre el tablero sigue el siguiente esquema:

procedure UnRecorrido()

EmpezarRecorrido()

PrimerPasoDeLaTarea()

while(puedoContinuarRecorrido())

PasarAlSiguienteElemento()

RealizarTarea()

Este procedimiento no está bien definido porque esas subtareas no existen, pero representa un recorrido típico en Gobstones. Lo único que tenemos que hacer para implementar recorridos concretos es pensar cómo cambiar esas subtareas por otras que tengan sentido para el problema que se desea resolver. Para el caso de pintar toda la columna con bolitas negras, el recorrido empieza con IrAlOrigen(), sigue con Poner(Negro) para pintar ya la primer celda, y

continúa pintando (procesando la celda actual) y moviéndose (pasando a la siguiente celda) mientras haya celdas al norte (mientras sea posible continuar el recorrido).

Como podemos observar, “recorrido” no es una herramienta del lenguaje Gobstones, como lo son repeat, foreach, while o if, sino que es una construcción mental que permite estructurar un procedimiento o función para reflejar el pasaje por distintos elementos en los que se procesa alguna tarea.

Recorridos por todo el tablero

En el ejemplo anterior hicimos un recorrido sólo por una columna. Pero lo interesante en Gobstones es realizar una tarea para todas las celdas del tablero. Siguiendo el esquema abstracto de recorrido, podemos construir lo siguiente: function haySiguienteCelda() // no estoy en el borde superior derecho return(puedeMover(Norte) || puedeMover(Este)) procedure PasarASiguienteCelda() // mover hacia el Norte y sino pasar a siguiente columna (Este) desde el Sur if(puedeMover(Norte)) Mover(Norte) else Mover(Este); IrAlBorde(Sur) procedure UnRecorridoPorTodoElTablero() // recorrido abstracto por todo el tablero

IrAlOrigen()

// procesar primer celda

while(haySiguienteCelda())

PasarASiguienteCelda()

// procesar celda actual

En este caso, como sabemos de antemano que lo que vamos a recorrer serán todas las celdas del tablero, podemos fijar cierto comportamiento para gran parte del esquema abstracto de recorrido. En particular, podemos definir cómo va a recorrerse el tablero. En este caso decidimos recorrerlo desde el borde inferior izquierdo hasta el borde superior derecho, procesando las columnas desde su extremo sur hacia su extremo norte, y pasando a la siguiente columna (moviéndo el cabezal al este) cuando alcanzamos este último extremo. En otras palabras, dejamos de procesar cuando ya no nos podemos mover ni al Norte ni al Este. Lo único que faltaría definir es qué hacemos con cada celda en particular.

Si quisiéramos poner una bolita roja en cada una, completamos lo siguiente:

procedure PonerUnaRojaEnTodoElTablero()

IrAlOrigen()

Poner(Rojo)

while(haySiguienteCelda())

PasarASiguienteCelda()

Poner(Rojo)

Si quisiéramos en realidad vaciar todas las celdas del tablero, podemos hacer lo siguiente:

procedure VaciarCelda()

foreach c in [minColor() .. maxColor()]

SacarX(nroBolitas(c), c)

procedure VaciarElTablero()

IrAlOrigen()

VaciarCelda()

while(haySiguienteCelda())

PasarASiguienteCelda()

VaciarCelda()

Como podemos observar, toda la estructura del recorrido se mantiene intacta, y sólo cambia lo que haremos en cada celda.

Variables

Por último, existe una herramienta que se utililza en aquellos casos en los que queremos guardar información que será utilizada en un futuro, y que si no guardamos ya no podremos consultar.

Imaginen el problema de copiar la cantidad de bolitas verdes de una celda a otra. La forma de saber la cantidad de bolitas de la celda ACTUAL es preguntar con la expresión “nroBolitas”. Es decir, si escribimos “nroBolitas(Verde)” esto representará el número de bolitas verdes de la celda actual. Pero qué pasa si queremos llevarnos ese dato a la celda al Norte, para colocar esa cantidad de verdes pero de rojas en esa celda al Norte. Al mover el cabezal, ya no podemos consultar la información de la celda anterior, dado que sólo podemos consultar datos de la celda en donde esté posicionado el cabezal. Sin embargo, este problema se resuelve fácilmente haciendo uso de variables:

procedure CopiarVerdesHaciaElNorte()

cantidadVerdes := nroBolitas(Verde)

Mover(Norte)

PonerX(cantidadVerdes, Rojo)

La línea resaltada es exactamente la que hace uso de esta nueva herramienta. Del lado izquierdo tenemos un nombre, inventando de la misma manera que pensamos nombres para

procedimientos o parámetros, que luego se puede utilizar como cualquier otro valor o expresión dentro del programa. El valor asignado a esta palabra será el resultado de la expresión que aparezca del lado derecho. A este proceso de relacionar una palabra (una variable) a una expresión se denominaasignación de la variable. Además, las variables pueden ser reasignadas las veces que queramos. Por ejemplo:

distintasCantidades := nroBolitas(Verde)

distintasCantidades := 10

La variable “distintasCantidades” primero vale “nroBolitas(Verde)”, pero después decidimos asignarle arbitrariamente el número 10. Dicha variable es un nombre que podemos asignar a cualquier número. En general, si una variable es utilizada para nombrar un tipo de dato, sólo es reasignada para seguir representando valores de ese tipo. Por ejemplo, las siguientes líneas no tienen sentido:

unaVariable := 15

unaVariable := Rojo

Primero dijimos que “unaVariable” describe el valor 15, y a continuación le decimos que vale “Rojo”. Debemos intentar evitar este tipo de comportamiento, y pensar qué tipo tiene una variable cuando la asignamos a un dato.

En lugar de hacer lo anterior, es posible utilizar distintas variables para distintos tipos de datos:

unNumero := 15

unColor := Rojo

Otro uso para las variables es “acumular resultados”. Dijimos anteriormente que el nombre de la variable luego puede ser utilizado como cualquier valor o expresión dentro del programa, por lo que también podemos asignar el valor de una variable a otras variables:

unaVariable := 10

otraVariable := unaVariable

Como es de esperar, si “unaVariable” representa el valor 10, entonces “otraVariable” también vale 10, porque la primera vale exactamente eso. Y yendo aún más lejos podemos hacer lo siguiente:

unaVariable := 10

unaVariable := unaVariable

Esto es totalmente válido. Primero decimos que “unaVariable” vale 10, y luego reasignamos ese nombre al valor que posee ese mismo nombre, que es 10. Al final la variable vuelve a valer 10. Si bien este ejemplo concreto carece de sentido, sí tiene sentido algo como lo siguiente:

unaVariable := 10

unaVariable := unaVariable + 1

En este caso, la variable empieza valiendo 10, y luego pasamos a asignarle ese mismo valor que tenía pero sumándole 1. La variable ahora pasa a valer 11. ¿Para qué sirve esto? Existen casos en donde queremos acumular resultados bajo un mismo nombre de variable. Por ejemplo, si queremos sumar la cantidad de bolitas de cada celda de una columna podemos realizar un recorrido en donde sumamos al resultado actual de una variable cada cantidad de bolitas que encontramos en las distintas celdas.

Supongamos que contamos entonces la cantidad de bolitas negras de toda la columna:

function cantidadNegrasDeColumna()

IrAlBorde(Sur)

cantidadTotal := nroBolitas(Negro)

while(puedeMover(Norte))

Mover(Norte)

cantidadTotal := cantidadTotal + nroBolitas(Negro)

return(cantidadTotal)

Esta función empieza por la primera celda al sur de la columna actual y cuenta la cantidad de bolitas de esa celda (la recuerda). Luego continua su recorrido hacia el norte, y mientras haya celdas al norte se mueve y suma, a la cantidad actual de bolitas, las distintas cantidades de bolitas de cada celda por la que pasa. Estamos acumulando el número de bolitas de todas las celdas de la columna en un único número final, que terminamos por devolver al final de este recorrido (cuando no hay más celdas al norte).