Arreglos con MobileProcessing

En Tipos de datos de MobileProcessing se introdujeron los arreglos (Array) como un tipo de datos compuesto, ahora voy a ampliar brevemente su concepto.

Un arreglo, al igual que en muchos lenguajes de programación, es un objeto que encapsula una serie finita de variables del mismo tipo referenciadas a través de un índice numérico. De esta definición podemos concluir fácilmente las siguientes afirmaciones relacionadas con los arreglos.

  1. Un arreglo agrupa varias variables/objetos.
  2. Todas y cada una de las celdas de un arreglo son de un mismo tipo de datos.
  3. Los arreglos son finitos: limitados en su cantidad de celdas, teniendo en cuenta MP provee funciones para manipular su longitud en tiempo de ejecución.
  4. Las celdas se referencian a través de un índice numérico (entero) utilizando el operador de corchetes ([ ]).

Al igual que los demás objetos, la creación de un arreglo consta de dos partes que se pueden agrupar en una sola línea de código.

// Declaración del arreglo de celdas de tipo 'entero'.
// En este punto arreglo_enteros = null.
int[] arreglo_enteros;
// Creación del objeto arreglo_enteros como un arreglo
// de 25 celdas de tipo entero.  arreglo_enteros != null.
arreglo_enteros = new int[25];

Como se mencionó anteriormente este proceso se puede realizar en una sola línea de código.

int[] arreglo_enteros = new int[25];    // Instrucción equivalente.

Es posible crear arreglos de cualquiera de los tipos de datos que soporta el lenguaje, inclusive de objetos.

String[] arreglo_cadenas = new String[50];

Las celdas del arreglo se pueden acceder de manera directa tanto para lectura como para escritura a través del operador corchetes.

// Modificar el valor de la celda 13.
arreglo_cadenas[13] = "Hola Mundo";
// Obtener el valor de la celda 20.
String cadena = arreglo_cadenas[20];

Como se mencionó los arreglos son objetos, por tal motivo tienen atributos y métodos provenientes de la clase Object. En este momento nos es interesante el atributo length el cual indica siempre la longitud de arreglo, es decir, el número de celdas del mismo.

int longitud1 = arreglo_enteros.length;
// longitud1 = 25.
int longitud2 = arreglo_cadenas.length;
// longitud2 = 50.

Gracias a este atributo es posible recorrer facilmente el arreglo para obtener o modificar sus valores. A continuación se realiza una iteración que recorre una a una las celdas del arreglo y les asigna un valor.

for(int indice=0; indice
{
    arreglo_cadenas[indice] = "Elemento #" + indice;
}

El ciclo anterior hace iterar a la variable indice desde 0 hasta el valor anterior a la longitud del arreglo (arreglo_cadenas.length), aumentando el valor de la variable indice en una unidad (indice++) en cada iteración.

Al final de la ejecución de este código el contenido de las celdas del arreglo arreglo_cadenas será:

["Elemento #0", "Elemento #1", "Elemento #2", ..., "Elemento #49"]

Nótese que los indices de los arreglos empiezan en cero (0) y terminan consecuentemente en la longitud - 1 del arreglo. Es decir, un arreglo de tres elementos tendrá los índices: 0, 1 y 2 respectivamente.

Existe una forma rápida de instanciar arreglos la cual es conveniente cuando se conocen sus valores iniciales.

int[] arreglo_enteros = {10, 20, 30, 40, 50};

La instrucción anterior crea en arreglo_enteros un arreglo de 5 celdas inicializadas con los valores especificados al lado derecho del igual. Téngase en cuenta que del lado izquierdo a pesar de que no se especificó un tamaño si se especificó que iba a ser un arreglo (int[]). El tamaño del arreglo es determinado finalmente por la cantidad de elementos que se especifiquen al lado derecho de la asignación.

Para conocer mas acerca de las funciones para manipular la longitud de los arreglos consulte las funciones expand() y contract() en Tipos de datos de MobileProcessing.

Las matrices binarias (de dos dimensiones) y n-arias (de n dimensiones) no son mas que construcciones compuestas de arreglos, es decir, son arreglos de arreglos.

Una matriz entera de dos dimensiones puede utilizarse para representar el tablero del juego de Burbujas, donde 0 indica que la celda se encuentra vacía y >0 que se encuentra ocupada, siendo 1 por una burbuja azul, 2 por una verde, 3 por una naranja y 4 por una roja por ejemplo.

Para crear el objeto tablero matriz de dos dimensiones de tipo entero de 15 filas por 10 columnas utilizamos la siguiente instrucción.

int[][] tablero = new int[15][10];

Nótense los elementos adicionales respecto a la creación de arreglos.

  • Se debe indicar que el objeto será matriz de dos dimensiones: int[][]. De manera sucesiva para n-dimensiones.
  • Se deben indicar los tamaños para las dos dimensiones: primero filas (horizontales) y después columnas (verticales). Ejemplo: new int[15][10]; Esto indica que el tablero tendrá 15 filas y 10 columnas. De manera sucesiva para las n-dimensiones.

Para recorrer una matriz bidimencional se utilizan dos ciclos anidados de manera análoga a como se hizo con el arreglo en una única dimensión.

for(int filas=0; filas<15; filas++)
{
    for (int columnas=0; columnas<10; columnas++)
    {
        tablero[filas][columnas] = 0;    // Inicializado como celda vacía.
    }
}

Para acceder a posiciones específicas de la matriz utilice también el operador de corchetes espeficicando la coordenada bidimensional (fila, columna) de la posición a acceder.

// Obtiene información de la burbuja ubicada en la cuarta fila, segunda columna.
int b1 = tablero[3][1]:
// Poner en la posición (6, 3) a una burbuja azul.
tablero[6][3] = 1;

Números al azar con MobileProcessing

Una función que me hacía falta documentar en este mi caché cerebral es la de generar números al azar. Para esto MobileProcessing tiene la función random() de la cual hay dos versiones.

random(int valor_maximo) Retorna un valor al azar entre cero (0) y el valor_maximo inclusive: [0, valor_maximo].
random(int valor_minimo, int valor_maximo) Retorna un valor al azar entre valor_minimo y valor_maximo inclusive [valor_minimo, valor_maximo].

Ejemplo:

// Retorna un número entre 0 y 10.
int azar1 = random(10);

// Retorna un número entre 100 y 200.
int azar2 = random(100, 200);

Maze2, construyendo paredes con MobileProcessing

Como una continuación del primer ejemplo en MobileProcessing ahora le voy a agregar paredes al laberinto y una opción de teletransportar para cuando haya necesidad de sacar al caballero azul de un cuarto cerrado.

Hice algunos cambios que modifican el enfoque que tenía inicialmente el diseño. Antes cada Sprite conocía su posición (x, y) para ser dibujado en la pantalla del móvil, ahora el mundo (World) es en realidad una matriz (maze) de Sprites así que a ellos les interesa conocer su (fila, columna).

Agregué cuatro constantes (up, down, left, right) al Player para referenciar a cualquiera de los movimientos que este puede realizar.

Creé además dos nuevos Sprites: el espacio vacío (EmptySpace) que rellena el éter vacío y la pared (Wall) que simula un muro intraspasable.

En este órden de ideas mejoré el método isValidPlayerMove() para que entendiera correctamente las (filas, columnas) y se hiciera cargo de hacer respetar los muros. El método buildMaze() crea al mundo al azar haciendo uso de la función random(). El método movePlayer() y la función keyPressed() también fueron modificados para actualizarse con estos cambios y para incluír el uso de las constantes de dirección de la clase Player.

El movimiento del Player se convirtió entonces en elegir una nueva posición válida, en poner al Player en la nueva posición y poner un EmptySpace en la anterior, para esto se deben actualizar tanto los diferentes Sprites como la matríz del laberitno (maze) para facilitar esto se agregó el método locateSprite().
El método locatePlayer() ubica al Player en una ubicación al azar verificando que se encuentre vacía y el método teleport() realiza hasta diez intentos de este proceso. Teleport es llamado también cuando se presiona la tecla FIRE del móvil.

La función draw() dibuja a todos los Sprites de manera indistinta ya que cada uno de ellos sabe de que color es y en que posición se encuentra. Aún no estoy seguro que es mas conveniente en MP si incluír todo el código de dibujo en esta función o de incluirlo en las clases (cada Sprite se dibuja el mismo) y llamarlo desde el draw().

Enlace: Maze2.pde.

Un ejemplo sencillo con MobileProcessing

Oh no. Encendí este aparato … hoy tampoco me voy a acostar temprano.

Con las cosas que he aprendido hasta ahora acerca de MP quería hacer algo que se moviera con el teclado y probarlo en mi teléfono a ver si era cierto que funcionaba.

La idea era sencilla, hacer que un cuadrito azul se moviera a lo largo y ancho de la pantalla sin salirse según la presión de las teclas direccionales del teléfono. Para esto la mayoría de las cosas que necesité ya eran conocidas. No se si exista una forma mas eficiente de hacerlo con las clases del API pero esta es mi primera versión.

Creé la clase Sprite que representa cualquier cosa con una ubicación (x, y), unas dimensiones (ancho, alto) y un color. De esta clase hereda el Player cuyo color es azul. Extrañamente la clase color se llama con c minúscula y existe una cómoda función, de igual nombre, que retorna el objeto color a partir de los componentes R, G, B. A estas clases les hice los métodos set/get necesarios.

Creé además una clase World que representa la pantalla del teléfono y que conoce al Player. La clase World tiene un método isValidPlayerMove(x, y) que verifica si el Player puede o no moverse en la forma como lo solicita según la disposición del ambiente.

Creé un objeto world cuyo ámbito es global para tener acceso a él durante todo el código. En la función setup() instancio este objeto y le relaciono a él una instancia de Player cuyo alto y ancho hace que le permita moverse a lo largo de 7 columnas y 9 filas según el tamaño de la pantalla. Para eso utilizo las variables height y width que provee MP.

En la función keyPressed() verifico si se presionó alguna de las teclas especiales (dirección) utilizando la variable keyCode que ya se ha mencionado. Manipulo las coordenadas (x, y) de la ubicación del Player en el World siempre y cuando el movimiento resultante sea válido.

En la función draw() dibujo un rectángulo relleno utilizando las funciones fill() y rect() las cuales aún no se han revisado, según el color y la ubicación actual de la instancia de Player.

Y listo! Extremadamente fácil. Me recuerda mis épocas mozas aprendiendo a programar en C, modo gráfico con BGI (Borland Graphics Interface).

Enlace: Maze1.pde

Tipos de datos de MobileProcessing

Primitivos boolean true/false
byte 8 bits -128 a 127
char 16 bits Carácteres UNICODE.Los carácteres se encierran entre comillas simples (Ejemplo: ‘a’, ‘b’, ‘c’).
int 32 bits -2,147,483,648 a 2,147,483,647.
color 32 bits AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB donde las A representan el valor del canal ALFA, las R son el valor rojo/tinte, las G son el verde/saturación y las B son el azul/brillo.Internamente es manejado como un int en MobileProcessing.
Compuestos Array Son listas finitas de elementos del mismo tipo.Ejemplo: tipo[] arreglo = new tipo[tamaño];

Los elementos del arreglo son referenciados por índices que empiezan en cero y van hasta la longitud del arreglo – 1.

Object Los objetos son instancias de clases, tienen métodos y atributos. Las instancias se crean con el operador new.
String Son secuencias de carácteres. La clase String incluye múltiples métodos para su manipulación.

Conversión a String.

La conversión desde un tipo primitivo hacia String se realiza mediante la función str(valor).

boolean b = true;
byte y = -120;
char c = 'J';
float f = -64.9;
int i = 1024;
String sb = str(b);
String sy = str(y);
String sc = str(c);
String sf = str(f);
String si = str(i);

Funciones y métodos de la clase String.

A continuación se explican las funciones que incluye el lenguaje para el manejo de cadenas.

join(arreglo, separador) Combina un arreglo en una cadena uniéndolo con el separador.
int arreglo = new int[3];
arreglo[0] = 100;
arreglo[1] = 200;
arreglo[2] = 300;
String resultado = join(arreglo, ", ");
// resultado = "100, 200, 300".

nf(entero, digitos)Da formato a un número entero según la cantidad de digitos especificada completando el número con ceros a la izquierda.

int i1 = 7;
int i2 = 30;
int i3 = 2008;
String s1 = nf(i1, 5);
// s1 = "00007".
String s2 = nf(i2, 3);
// s2 = "030".
String s3 = nf(i3, 6);
// s3 = "002008".

nfp(entero, digitos)Similar a nf() pero esta función agrega el símbolo mas (+) como prefijo de los valores positivos y el símbolo menos () como prefijo de los valores negativos.

int i1 = -7;
int i2 = 30;
int i3 = -2008;
String s1 = nfp(i1, 5);
// s1 = "-00007".
String s2 = nfp(i2, 3);
// s2 = "+030".
String s3 = nfp(i3, 6);
// s3 = "-002008".

nfs(entero, digitos)Es similar a nf() para dar formato a números enteros, sin embargo esta función agrega un espacio como prefijo a los valores positivos para que sus cifras se alineen correctamente con los valores negativos.

int i1 = -7;
int i2 = 30;
int i3 = -2008;
String s1 = nfp(i1, 5);
// s1 = "-00007".
String s2 = nfp(i2, 3);
// s2 = " 030".
String s3 = nfp(i3, 6);
// s3 = "-002008".

split(cadena, delimitador)Parte una cadena en subcadenas según las ocurrencias del delimitador y retorna un arreglo de las cadenas resultantes. Es funcionalmente opuesta a la función join().

String fecha = "31/03/2008";
String[] partes = split(fecha, '/');
// partes[0] = "31".
// partes[1] = "03".
// partes[2] = "2008".String numeros = "8 85 52 70";
int arreglo = int(split(numeros));
// arreglo[0] = 8.
// arreglo[1] = 85.
// arreglo[2] = 52.
// arreglo[3] = 70.

trim(cadena)Remueve los siguientes carácteres de ambos lados de la cadena: espacios en blanco, saltos de línea, tabulaciones y el carácter UNICODE “nbsp“.

String cadena = "     Esta cadena tiene espacios de sobra             ";
String corta = trim(cadena);
// corta = "Esta cadena tiene espacios de sobra".

La clase String por su parte incluye una gran variedad de métodos con los siguientes.

charAt(indice) Este método retorna el iésimo elemento de la cadena.
String saludo = "Hola Mundo";
char p1 = saludo.charAt(0);
// p1 = 'H'.
char p2 = saludo.charAt(3);
// p2 = 'a'.
char p3 = saludo.charAt(7);
// p3 = 'n'.

equals(cadena)Este método compara la cadena contra una segunda cadena recibida a través del parámetro y verifica si son la misma o no. Retorna true si las cadenas son la misma, false de lo contrario.indexOf(cadena, indice_inicio)Verifica si la cadena especificada es una subcadena de la propia y en caso de serlo retorna el índice de la ocurrencia, en caso de no serlo retorna siempre -1. El segundo parámetro: indice_inicio es opcional, en caso de especificarse la búsqueda se realiza a partir de dicho índice.

String cadena = "Esta es una cadena de ejemplo";
int p1 = cadena.indexOf("e");
// p1 = 5.
int p2 = cadena.indexOf("e", 6);
// p2 = 15.
int p3 = cadena.indexOf("no_esta");
// p3 = -1.

length()Este método retorna la longitud de la cadena.

String cadena = "Hola Mundo Loco";
int longitud = cadena.length();
// longitud = 15.

substring(indice_inicio, indice_final)Este método retorna una subcadena enmarcada en los índices especificados. El segundo índice: indice_final es opcional, en caso de no especificarse se considerará hasta el final de la cadena.

String cadena = "Esto es lo que somos";
String sub1 = cadena.substring(15);
// sub1 = "somos".
String sub2 = cadena.substring(8, 14);
// sub2 = "lo que".

toLowerCase()Este método retorna la cadena en letras minúsculas completamente.

String cadena = "Esta es una CADENA MuY vaRiaDa";
String lower = cadena.toLowerCase();
// lower = "esta es una cadena muy variada".

toUpperCase()Este método retorna la cadena en letras mayúsculas completamente.

String cadena = "Esta es una CADENA MuY vaRiaDa";
String upper = cadena.toUpperCase();
// upper = "ESTA ES UNA CADENA MUY VARIADA".

Para consultar la información de más métodos de la clase String refiérase a la documentación del API de Java.

Funciones relacionadas con arreglos.

append(arreglo, elemento) Agrega el elemento al final del arreglo. El elemento debe ser de igual tipo que el arreglo
String[] arreglo = {"uno", "dos", "tres"};
String[] nuevo = append(arreglo, "CUATRO");
// nuevo = {"uno", "dos", "tres", "CUATRO"}.

concat(arreglo1, arreglo2)Retorna la unión del arreglo1 con el arreglo2. Ambos arreglos deberán ser de igual tipo.

int[] arr1 = {1, 2, 3, 4};
int[] arr2 = {10, 20, 30, 40};
int[] resultado = concat(arr1, arr2);
// resultado = {1, 2, 3, 4, 10, 20, 30, 40}.

contract(arreglo, nuevo_tamaño)Retorna la versión contraída del arreglo referenciado. Los valores sobrantes son descartados.

int[] arreglo = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int longitud_completa = arreglo.length;
// longitud_completa = 10.
int[] resultado = contract(arreglo, 7);
int longitud_parcial = resultado.length;
// longitud_parcial = 7;

expand(arreglo, nuevo_tamaño)Esta función aumenta el tamaño del arreglo al nuevo_tamaño especificado. Si este segundo parámetro no se especifica se considerará el doble del tamaño actual.

int[] arreglo = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int longitud_completa = arreglo.length;
// longitud_completa = 10.
int[] resultado = expand(arreglo, 15);
int longitud_ampliada = resultado.length;
// longitud_ampliada = 15;

length(arreglo)Retorna la longitud o tamaño del arreglo. Esta función es análoga al atributo length.

String[] arreglo = {"primero", "segundo", "tercero", "cuarto"};
int longitud = length(arreglo);
// longitud = 4.
int tamano = arreglo.length;
// tamano = 4.

reverse(arreglo)Retorna una versión invertida: los elementos dispuestos en orden inverso, del arreglo original.

int[] arreglo = {1, 2, 3, 4, 5};
int[] inverso = reverse(arreglo);
// inverso = {5, 4, 3, 2, 1}.

shorten(arreglo)Retorna una copia del arreglo con su último elemento removido.

String[] arreglo = {"rojo", "azul", "verde", "cafe", "naranja"};
String[] corto = shorten(arreglo);
// corto = {"rojo", "azul", "verde", "cafe"}.

splice(arreglo, elemento, indice)Retorna una versión ampliada del arreglo con el elemento (puede ser un valor u otro arreglo) insertado en la posición indice.

int[] arreglo = {10, 20, 30, 40, 50};
arreglo = splice(arreglo, 15, 1);
// arreglo = {10, 15, 20, 30, 40, 50}.
int[] seccion = {32, 34, 36, 38};
arreglo = splice(arreglo, seccion, 4);
// arreglo = {10, 15, 20, 30, 32, 34, 36, 38, 40, 50}.

Eventos de teclado con MobileProcessing

El manejo de eventos de teclado con MP es bastante sencillo. A manera de callbacks se ejecutan determinadas funciones según suceda el evento.

keyPressed() Es llamado cada vez que se presiona una tecla.
keyReleased() Es llamado cada vez que se libera una tecla, es decir, se deja de presionar.
softKeyPressed(String etiqueta) Es llamado cada vez que es presionada una tecla suave. Para definir la tecla suave (aparece como opción derecha) debe utilizarse la función softkey(String etiqueta).

Las siguientes variables almacenan la información relacionada con la tecla presionada.

key Almacena el valor de la última tecla presionada.
keyCode Permite detectar la presión de una tecla especial mediante el uso de constantes predefinidas: UP, DOWN, LEFT, y RIGHT así como FIRE, GAME_A, GAME_B, GAME_C, y GAME_D

Para mayor información acerca de su uso consultar el siguiente ejemplo: demo_teclas

Introducción a Mobile Processing

Al parecer MobileProcessing es un lenguaje orientado a funciones el cual permite también la especificación de clases y con ellas la creación de objetos. Entiendo también que su ampliación a través de la utilización de código Java es muy fácil.

A continuación enuncio las funciones que pueden ser manipuladas por el desarrollador según su finalidad.

setup()

Se ejecuta una única vez cuando el programa se inicia. Es útil para especificar el estado inicial de la aplicación.

destroy()

Se ejecuta una única vez cuando la aplicación se está terminando. Es útil para disponer adecuadamente de los recursos como por ejemplo guardar y cerrar los archivos abiertos.

draw()

Se ejecuta después de terminado el llamado a setup(). Esta función se ejecuta constantemente según la información proporcionada por las funciones delay() y framerate(). Su utilidad radica principalmente en preparar y actualizar el contenido a mostrarse al usuario a través de la pantalla del dispositivo.

noLoop()

El llamado a esta función detiene la ejecución periódica de la función draw().

redraw()

Ejecuta el contenido de la función draw() una única vez. Es útil para actualizar la presentación después de sucedido un evento.

loop()

Obliga a que la función draw() se ejecute periódicamente.

suspend()

Esta función es llamada por el teléfono cuando este suspende al programa lo cual sucede por motivos como la entrada de una llamada o el cierre de la tapa.

resume()

Esta función es llamada por el teléfono cuando se le permite continuar a la aplicación después de un llamado a suspend().

exit()

El llamado a esta función termina la ejecución del programa.

El punto de inicio de un programa en MobileProcessing es la función setup(). Las funciones destroy(), draw(), suspend() y resume() pueden ser sobreescritas para especificar el código a ejecutarse ante las circunstancias particulares. Las funciones noLoop(), loop() y exit() pueden ser llamadas desde el programa para modificar su comportamiento general.

Instalación de Mobile Processing

Descargué los siguientes paquetes.

  1. Sun Java Wireless Toolkit 2.5.2 for CLDC (http://java.sun.com/javame/downloads/index.jsp)
  2. Mobile Processing 0006 ALPHA (http://mobile.processing.org/download/index.php)

En una carpeta llamada MobileProcessingSoftware en la que descomprimí el archivo mobile-0006-expert.zip bajo la carpeta 0006.

Por la parte de Java también creé la carpeta JavaSoftware en la que instalé el sun_java_wireless_toolkit-2_5_2-windows.exe bajo la carpeta WTK2.5.2.

El JDK se encuentra previamente instalado en mi equipo de desarrollo.

Ejecuto el archivo MobileProcessingSoftware006mobile.exe y aparece el IDE de MobileProcessing.

Voy al menú File > Preferences > Mobile y en la sección Wireless Toolkit location especificó su ubicación: F:JavaSoftwareWTK2.5.2.

En el IDE escribo el siguiente código.

void draw()
{
background(0);
stroke(255);

line(10, 10, 10, 50);
line(10, 30, 30, 30);
line(30, 10, 30, 50);
line(50, 10, 50, 50);
}

Selecciono la opción Sketch > Build and Run y aparece mi primer programa en MobileProcessing corriendo en el emulador.

Aprendiendo MobileProcessing

Bien, por fin he tomado la decisión inaplazable y definitiva. Voy a aprender MobileProcessing de una buena vez. De tantas cosas que hay en este mundo que quisiera tener el tiempo suficiente para aprenderlas, MP va a ser la siguiente que voy a aprender.Hice una búsqueda y no encontré una documentación muy sólida y estructurada que digamos, sin embargo encontré algunas cositas interesantes y suficientes para aprender lo necesario.

También estuve ojeando un poco el libro Processing: A Programming Handbook for Visual Designers and Artists el cual habla de muchas cosas en especial de temas de diseño y de imágenes, sin embargo debido a la hora me tocará posponer una mejor revisión para después.

Espero poder dedicarle mas tiempo para practicar un poco y hacer alguna cosita con el lenguaje, pero ya será la otra semana porque esta semana ya se acabó y el fin de semana tengo clases … otra vez.