Siempre creciendo, siempre aprendiendo. Cultura maker.

¡Hola!. ¡Saludos!. ¡Pasad!, ¡pasad!, ¡mi casa es vuestra casa!.

Así que habéis vuelto a interesaros por esta humilde serie de artículos, ¡no sabéis cuánto me emociona!. ¡Me alegraría mucho saber que a alguien le aprovechan estos humildes escritos!.

Bueno, pues empezamos otra clase. Hoy vamos a hablar de funciones, rutinas o subrutinas. Dependiendo del lenguaje de programación, puede haber sutiles diferencias, pero a grandes rasgos, estamos hablando de conjuntos de órdenes que se cumplen ordenadamente cuando se llama a la función.

gear 240137 1280

 Funciones predefinidas

En muchos lenguajes de programación (y Processing no iba a ser menos), existen funciones predefinidas que realizan tareas comúnmente demandadas por los programadores. En nuestro caso, ya hemos trabajado con las dos funciones predefinidas por excelencia en processing, setup() y draw(). No hace falta recordar que la primera realiza tareas de configuración y preparación del programa, mientras que la segunda se repite en un bucle indefinido, produciendo el flujo normal de proceso de nuestro programa.  Aparte, existen otras funciones predefinidas orientadas, por ejemplo, a reaccionar a acciones del usuario sobre el teclado y el ratón, interrumpiendo el flujo normal de draw(). Después devuelven el control a draw().Las que vamos a ver en esta introducción a la programación son:

  • keyPressed(): se ejecuta cuando el usuario pulsa una tecla.
  • keyReleased(): se ejecuta cuando el usuario deja de pulsar una tecla.
  • mouseClicked(): se ejecuta al hacer click con el ratón.
  • mouseMoved(): se ejecuta al mover el ratón.

Las posibilidades que nos ofrecen sólo estas cuatro funciones son infinitas, y aunque hay muchas más funciones predefinidas por este estilo (aquí tienes la referencia de todos los comandos y funciones de Processing), nosotros vamos a hacer sólamente un pequeño programa de dibujo para que veáis cómo se realizan las llamadas a cada funcion dentro del flujo normal de trabajo guiado por draw():

//coordenadas del puntero
int x,y;
void setup(){
  size(800,600);
  //quitamos el trazo
  noStroke();
  x=400;
  y=300;
  background(255,255,255);
  fill(0,0,0);
}
void draw(){
  rect(x,y,10,10);
}
void keyPressed(){
  //movemos el puntero con el combo w/a/s/d
  //1,2,3,4 cambian los colores de trazo
  //q sube el lapiz con noFill() y e baja el lapiz negro
 switch (key){
  case 'd':
  x=x+10;
  break;
  case 'a':
  x=x-10;
  break;
    case 'w':
  y=y-10;
  break;
    case 's':
  y=y+10;
  break;
  case '1':
  fill(255,0,0);
  break;
    case '2':
  fill(0,255,0);
  break;
    case '3':
  fill(0,0,255);
  break;
    case '4':
  fill(50,200,100);
  break;
    case 'q':
  noFill();
  break;
    case 'e':
  fill(0,0,0);
  break;
 }
}
void mouseMoved(){
  //Al mover el ratón, borramos y reiniciamos 
  background(255,255,255);
  x=400;
  y=300;
}
void mouseClicked(){
  //Color de fondo aleatorio
  background((int)random(255),(int)random(255),(int)random(255));
}

Aunque es un código muy sencillo, ya tenemos un minijuego de dibujo muy potente: si damos a alguna tecla, se ejecuta la función keyPressed(), en la cual la orden switch(key) busca entre los posibles case si hemos pulsado una tecla en particular, cambiando los colores de dibujo o moviendo el cursor, dibujando así un trazo. Si nuestra tecla no está entre dichos casos, simplemente devuelve el control a draw(). Si movemos el ratón, draw() se interrumpe para ejecutar la función mouseMoved(), que reinicia las coordenadas del cursor al centro de la pantalla, borrando el dibujo anterior al hacer un background a blanco.

La función random() y convertir tipos de variables (casting)

Aprovecho la función mouseClicked() para presentaros otro comando que a mí me gusta mucho utilizar, la función aleatoria random(). Random tiene dos formas de expresión:

  • random(valor) devuelve un número aleatorio entre 0 y valor
  • random(vminimo,vmaximo) devuelve un número aleatorio entre vminimo y vmaximo

El problema de random es que devuelve un número en decimal, esto es, de tipo float. Por ejemplo, random (4) me puede devolver un número como el 2.48743. La función background() necesita que le pase tres argumentos o parámetros de tipo int, esto es, sin decimales. En estos casos, en muchos lenguajes de programación necesito hacer una conversión entre tipos de variables o casting (ojo, sólo en aquellos casos en que esto sea posible).

En lenguajes con sintaxis JAVA como Processing, esto se hace incluyendo delante del valor el tipo de variable a que queremos convertir entre paréntesis, por ejemplo:

valorFloat=0.4783;

valorEntero=(int)valorFloat;

Resumiendo, para obtener un color de fondo totalmente aleatorio, el comando sería background((int)random(255),(int)random(255),(int)random(255));

Sintaxis en la creación de funciones

Hablemos ahora de algo que nos ha preocupado desde el inicio de esta serie de artículos, ¿qué significa el misterioso void que precede a setup y draw?. ¿Por qué ambas funciones acaban entre paréntesis vacíos?.

Por definición, una función está pensada en programación para cumplir una serie de órdenes o comandos en el orden en que se han escrito, pero pensando en si al final de la ejecución vamos a necesitar un valor o no. De ahí la necesidad de incluir, antes del nombre de la propia función, void (si no se espera ningún valor de retorno), o int, float, boolean, etc... dependiendo del tipo de valor que deseemos retornar. Salvo en void, todas las otras funciones acabarán con el comando return, seguido del valor a devolver.

Veamos un ejemplo. Vamos a crear una función que nos da un número aleatorio entero entre 0 y 49:

int numeroPrimitiva(){
int n=(int)random(0,49);
return n;
}

 Dentro de la propia función hemos declarado una variable local (esto es, sólo se utiliza en el ámbito de esta función) llamada n, que es el nº aleatorio en sí. Al final de la función, return n nos devoverá dicho número. Es decir, cada vez que ejecutemos numeroPrimitiva(), obtendremos un número aleatorio de tipo entero. Veamos ahora cómo utilizaríamos dicha función. El siguiente programa nos va a predecir la próxima combinación de la Bonoloto:

void setup(){
  size(800,600);
  background(255,0,0);
  stroke(255,255,255);
  textSize(32);
  for (int i=0;i<6;i++){
   text("Nº "+i+": "+numeroPrimitiva(),400,i*100); 
  }
}
void draw(){
 //No utilizamos esta función en esta ocasión 
}
int numeroPrimitiva(){
int n=(int)random(0,49);
return n;
}

El resultado (por ejemplo):

processingTuto4b

 

Supongo que ya lo habéis pillado (de lo contrario, ya estáis tardando en protestarme en el área de comentarios). Pasemos ahora a la segunda parte de esta pequeña explicación sintáctica, ¿para qué diablos sirven los paréntesis que se ponen siempre detrás del nombre de la función?.

El motivo es sencillo, mis pequeños padawan: lo mismo que en ocasiones necesitamos que la función nos devuelva un valor al final de su ejecución, otras veces pasa lo contrario: para que la función se ejecute, necesita que le pasemos uno o varios valores. Es precisamente entre dichos paréntesis que incluiremos dichos valores, separados por comas (in. Si no se necesitan valores, se escriben los paréntesis solos (). Veamos algunos ejemplos:

int dameMayor (int a, int b){
//Esta función devuelve el mayor de los dos valores que introduzcamos
if (a>b){
return a;
}else{
return b;}
}

 Como podéis ver, esta función pide dos números enteros y devuelve un número entero. Para ejecutarla, bastaría con escribir (por ejemplo), dameMayor(3,10); que me devolvería el número 10 (obviamente).

De los dos ejemplos anteriores, podéis ver que al diseñar una función nos encontraremos en una de estas cuatro situaciones:

  • La función no necesita parámetros ni devuelve parámetros. En ese caso, el formato sería del tipo void nombreFuncion(){  }
  • La función no necesita parámetros pero devuelve un parámetro. En ese caso, el formato será tipoVariable nombreFuncion(){    }, por ejemplo, boolean esPar() {   }
  • La función necesita parámetros aunque no devuelve ningún parámetro. Esta situación responde al formato void nombreFuncion(tipoVariable nombreVariable, tipoVariable nombreVariable2,...){   }. Por ejemplo, void dibujaCasa(int x, int y){  }
  • La función necesita parámetros y devuelve un parámetro. En ese caso, escribiremos tipoVariable nombreFuncion(tipoVariable nombreVariable, tipoVariable nombreVariable2...){   }. Por ejemplo, una función que nos devolviera el área de una circunferencia dado su radio sería float dameArea(float radio){   }

Como siempre, el uso de un ejemplo puede ayudarnos a comprender mejor todo esto. En el siguiente programa, utilizaremos la función mouseClicked() para dibujar una circunferencia de radio aleatorio entre 10 y 50, de colores asímismo aleatorios, y que a su vez nos deje impreso en pantalla el valor del área en píxeles de dicha área:

void setup(){
  size(800,600);
  background(255,255,255);

  textSize(32);

}
void draw(){
 //No utilizamos esta función en esta ocasión 
}
void mouseClicked(){
  //Borramos el dibujo anterior
  background(255,255,255);
 fill ((int)random(255),(int)random(255),(int)random(255));
 //declaramos una variable LOCAL llamada radio;
 int radio=(int)random(10,50);
 //Llamamos a la funcion dameArea() pasándole el valor radio
float area=dameArea(radio);
 ellipse(mouseX,mouseY,radio,radio);
 text ("Area: "+area+" píxeles",400,200);
 
}
float dameArea(int x){
 //variable LOCAL llamada area
  float  area=PI*x*x;
  //devolvemos el valor area
  return area;
}

Y el resultado será algo así como:

processingTuto4c

¡Apa!. ¡Y eso es todo por hoy!. ¡Ya me contaréis los problemas que hayáis podido tener!. ¡Hasta la próxima, amigos buscadores!. ¡Sed felices!. ¡Siempre creciendo!. ¡Siempre aprendiendo!. ¡Cultura maker!.