domingo, 1 de septiembre de 2013

Instalar Debian en SD Card en mk908

Hoy en día tenemos la oportunidad de tener mini-pc android con suficiente recursos como para correr un sistema linux con su entorno gráfico Gnome u otro que nos interese, en mi caso compre una Tronsmart MK908.



Este equipo tiene los siguientes recursos:

  • 2 GB de RAM DDR3
  • Rk3188 Quad Core 1.6ghz, max 1.8Ghz (Cortex-A9)
  • Quad Core Mali 400
  • On board NAND Flash 8G
  • Micro SD Card/ T-Flash(Maximum support 32GB)
  • WIFI support 802.11 b/g/n
  • Bluetooth v4.0
  • MINI HDMI Port
  • Mini USB,Support OTG/HOST ( USB port RJ45)

Enlace de compra en amazon: http://www.amazon.com/Tronsmart-MK908-Google-Android-RK3188/dp/B00CGYAGL6/ref=sr_1_1?ie=UTF8&qid=1378047748&sr=8-1&keywords=mk908

Ahora vamos a proceder a instalar debian en la Sd Card. Empecemos por hacer una partición al final de la memoria SD en formato EXT-4 y con nombre linuxroot, luego procedemos a montar la partición y a abrir una consola en ella para empezar a instalar. Esto lo podemos hacer con el particionador que deseemos.

Para hacer práctico el ejemplo la montaremos en una carpeta que crearemos en /mnt. (Esto lo corremos desde la terminal en super usuario SU)

mkdir /mnt/debianSD

El dispositivo cambia según la cantidad de discos por lo que deben averiguar cual es el dispositivo.

mount /dev/sdb1 /mnt/debianSD

En la terminal en modo super usuario (su) corremos el siguiente comando. (Recordemos que en --foreign podemos cambiar la distribución que nos plasca)

debootstrap --verbose --arch=armhf --foreign wheezy /mnt/debianSD http://ftp.de.debian.org/debian

Después que termine el proceso desmontamos la o las particiones para poder remover la memoria SD y la insertamos en el equipo. En el siguiente paso pueden tanto hacerlo desde el ordenador como directo desde el sistema Android con el comando adb shell que lo corremos usando el "Android SDK Tools" y el "Android SDK Platform-tools" (lo podemos descargar aqui http://developer.android.com/sdk/index.html).

Yo preferí correrlo desde el dispositivo, pero conectándole un teclado a mi MK908. Sea por cual sea el modo que decidieron proceder a terminar la instalación se tendrán que ejecutar los siguientes comandos para poder hacer lo que queremos.

Para poder ejecutar lo siguiente en el equipo necesitamos instalar una terminal en android (https://play.google.com/store/apps/details?id=jackpal.androidterm&hl=es-419).

Primero iniciamos en modo root.

su

Y ahora procedemos a preparar el entorno

export mnt=/system/debianSD
export PATH=/usr/bin:/usr/sbin:/bin:$PATH
export TERM=linux
export HOME=/root
export SHELL=/bin/bash

Procedemos a montar la partición para terminar de instalar. En caso de que mkdir no funcione pues solo montamos la partición de /system en modo escritura (mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system) esto puede que haga que Android reinicie, así que no se preocupen solo esperen que inicie de nuevo.

mkdir /system/debianSD
mount -t ext4 /dev/block/vold/179:2 /system/debianSD
busybox chroot $mnt

Con la ejecución del ultimo comando nos situamos dentro del sistema Debian en la memoria, por lo que la terminal ya no es Android sino Debian.

Muchos manuales que podrás encontrar en la red no te dirán lo siguiente, pues dentro de Debian debes correr el siguiente comando para poder activar los comandos básicos y terminar la instalación. Este comando debe ser corrido cada vez que entremos a Debian, pero automatizaremos esto más adelante.

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Una vez hecho la exportación del PATH procedemos a terminar de instalar.

/debootstrap/debootstrap --second-stage

Una vez completa vamos a agregar un repositorio y instalar lo que queremos. En /etc/apt/sources.list agrego

echo 'deb http://http.us.debian.org/debian/ wheezy main contrib non-free' > /etc/apt/sources.list
echo 'deb-src http://http.us.debian.org/debian/ wheezy main contrib non-free' > /etc/apt/sources.list

Adicionamos lo siguiente para entregar conexión a Internet

echo 'nameserver 8.8.8.8' > /etc/resolv.conf
echo '127.0.0.1 localhost' > /etc/resolv.conf

Ya en esta etapa hacemos exit para regresar a Android y automatizaremos la entrada a Debian con algunos scripts.

Este script lo ubicaremos en /system/bin/ con el nombre que queramos, en mi caso lo llame "debian". El contenido es el siguiente:

#!/system/bin/sh

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH
export TERM=linux
export HOME=/root
export SHELL=/bin/bash

mount -t ext4 /dev/block/vold/179:2 /system/debianSD
mount -t devpts /devpts /system/debianSD/dev/pts
mount -t proc /proc /system/debianSD/proc
mount -t sysfs /sysfs /system/debianSD/sys
rm -f /system/debianSD/etc/mtab
rm -rf /system/debianSD/tmp/*
rm -rf /system/debianSD/tmp/.X*
ln -s /proc/mounts /system/debianSD/etc/mtab
busybox chroot /system/debianSD /bin/bash

Este paso recomiendo hacerlo en una pc y copiarlo a la memoria para luego copiarlo a el sitio correcto. En caso de tener el script listo en la memoria lo copian de la siguiente manera

cp /mnt/sdcard/debian /system/bin/

Con esto cuando queremos entrar a Debian solo debemos abrir la terminar en android y escribir debian y con esto entramos.

Con esto termina la instalación, pero solo estamos en modo consola, por lo que vamos a instalar algunas cosas mas en nuestro Debian para poder verlo de forma gráfica.

Dentro de Debian ejecutamos lo siguiente para instalar un pequeño servidor de VNC.

apt-get update
apt-get install tightvncserver icewm xterm

Una vez instalador tenemos que iniciar el servidor y lo hacemos con el siguiente comando y la geometría la cambiamos a la que tenemos en el android.

vncserver -geometry 1280x720

Si queremos matarlo solo ejecutamos

vncserver -kill :1

Instalamos un escritorio ligero para mejor rendimiento, pero es decisión nuestra elegir el escritorio.

apt-get install lxde

Con esto solo queda tener un cliente en Android para poder visualizar nuestro sistema.

https://play.google.com/store/apps/details?id=com.iiordanov.freebVNC&hl=es-419

La configuración para el cliente es la siguiente:

Nombre conexión: Debian
Contraseña: La que ponemos cuando configuramos el vnc
Dirección: localhost
Puerto: 5901
Color formato: 24-bit color (4bpp)

Ahora queda la automatización de nuestro PATH que siempre se pierde y esto lo hacemos con lo siguiente (esto se hace dentro de Debian):

cd
echo 'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' > .bashrc

Algunas fotos de Debian corriendo.



Para aquellos que no les gusta hacer particiones en su SD Card, tan solo deben usar un disco virtual (son esos archivos img que sirven como discos) e instalar el Debian hay y en vez de montar la partición solo montan esa img y después el proceso es igual.

Si quieren crear su propio disco solo deben usar dd if=/dev/zero of=miDebian.img bs=1024M count=10 o si prefieren pueden hacerlo con un programa para ello. En caso que no quieran un archivo que tenga tamaño fijo, pueden usar offset para crear esas imágenes que crecen según el sistema lo necesita.


miércoles, 15 de mayo de 2013

Implementación de controles de juegos multiplataforma con código centrado.

En el desarrollo de juegos multiplataforma siempre tenemos el inconveniente de que los controles cambian de un entorno a otro y más cuando tenemos tecnologías que usan teclado, mouse y touch.

Pues en la búsqueda de hacer un control unisono para múltiples plataformas y que el código base del juego este centrado y este no tenga problemas a la hora de identificar los controles, pues usé el paradigma orientado a objetos y cree una clase que me abstrae los controles y los coloca en una especie de caja, por lo que solo se tiene que implementar la forma de captura de cada botón en las distintas plataformas y el juego solo leerá la clase con los botones y la lógica solo se ejecutará 1 vez y no múltiples veces por las diferentes plataformas.

.Pero que librerías o APIS tenemos que nos permiten crear juegos en múltiples plataformas?, pues LibGDX es una de estas y al estar escrita en JAVA permite que esta corra en diversas tecnologías sin problemas y  es hay en donde LibGDX demuestra su potencial, pues usando un proyecto base (Este contiene toda la lógica del juego y es el juego en sí) y los proyectos respectivos para lanzar el juego en las diferentes plataformas, es decir, que si queremos que corra en pc tenemos un proyecto que lanza el código del proyecto base y la librería se encarga de convertirlo en un software de pc y así sucede lo mismo con ANDROID.

Ahora que hablamos un poco de la tecnología a usar y la librería que nos dará soporte, pues queda definir el esquema de organización de un juego multiplataforma y mas o menos se ve de la siguiente manera:

Pues como verán los proyectos solo indican que son lanzadores, pero para los curiosos así se ven las clases main de los proyectos:

PC:

package com.alssoftrd.games.pruebared;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "PruebaRed";
cfg.useGL20 = false;
cfg.width = 800;
cfg.height = 480;

new LwjglApplication(new Prueba(1), cfg);
}
}


ANDROID:

package com.alssoftrd.games.pruebared;

import android.os.Bundle;

import com.alssoftrd.games.pruebared.PruebaRed;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class MainActivity extends AndroidApplication {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
     
        AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
        cfg.useGL20 = false;
        cfg.useAccelerometer = false;
        cfg.useCompass = false;
     
        initialize(new Prueba(0), cfg);
    }
}


Como ven solo existe un poco de código básico en cada proyecto, claro esta que hay que adicionar las librerías de cada plataforma y anexar como parte del código el proyecto base.

Ahora que vemos el esquema de trabajo de forma más gráfica, pues procedemos a hacer la clase de los controles que permitirán que los controles trabajan de forma normal en múltiples plataformas.

Primero definimos los botones y como es para explicar pues tomaré un control simple como los siguientes; que son un PAD (flechas de movimiento) y 2 botones básicos que son A y B.

Ahora que sabemos que botones usaremos pues procedemos a crear la clase con las siguientes caracteristicas: El PAD serán 4 variables booleanos que darán la dirección en sus respectivas axis y los botones A y B serán booleanos que indicarán cuando se presionan y cuando no.

En el PAD como uso la librería LibGDX para facilitarme la creación de el aspecto visual del mismo uso una clase existente y esta solo se usará en el proyecto de android y la clase se vera más o menos así:


import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad;
import com.badlogic.gdx.scenes.scene2d.ui.Touchpad.TouchpadStyle;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;

public class AControl {

private boolean a;
private boolean b;
// private boolean start;
private boolean left;
private boolean right;
private boolean up;
private boolean down;
private Touchpad pad = null;
private TouchpadStyle estiloPad;
private Skin skinPad;

public AControl() {
this.a = false;
this.b = false;
this.left = false;
this.right = false;
this.up = false;
this.down = false;
}

private void cargarPad() {
                // Esto es para darle un aspecto visual, lo cual ignoro.
skinPad = new Skin();
TextureAtlas atlas = Manager.get().get("data/Pad/Pad.pack",
TextureAtlas.class);
estiloPad = new TouchpadStyle();
skinPad.addRegions(atlas);
estiloPad.background = skinPad.get("Pad", Drawable.class);
estiloPad.knob = skinPad.get("ButtonPad", Drawable.class);
pad = new Touchpad(20, estiloPad);
pad.setBounds(15, 15, 150, 150);
}

public boolean isA() {
return a;
}

public boolean getA() {
return a;
}

public void setA(boolean a) {
this.a = a;
}

public boolean isB() {
return b;
}

public boolean getB() {
return b;
}

public void setB(boolean b) {
this.b = b;
}

public boolean isLeft() {
return this.left;
}

public boolean getLeft() {
return this.left;
}

public void setLeft(boolean left) {
this.left = left;
}

public boolean isRight() {
return this.right;
}

public boolean getRight() {
return this.right;
}

public void setRight(boolean right) {
this.right = right;
}

public boolean isUp() {
return this.up;
}

public boolean getUp() {
return this.up;
}

public void setUp(boolean up) {
this.up = up;
}

public boolean isDown() {
return this.down;
}

public boolean getDown() {
return this.down;
}

public void setDown(boolean down) {
this.down = down;
}

public Touchpad getPad() {
if(pad == null) {
cargarPad();
}
return pad;
}
}


Como verán existen métodos que se pueden pensar que están repetidos, pero solo es por comodidad. Como ven todos los botones están abstraídos en una serie de booleanos y así solo tengo que saber en que plataforma estoy corriendo y asignar los botones a sus respectivas variables.

Muchos siguen pensando ¿que hago con esta clase?, es esta es la clase base de donde el juego leerá, pero todavia falta leer los inputs y asignarles y esto se encarga esta clase que usando singleton manejamos los controles en las diversas plataformas.


import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class Control {

private AControl controles;
        // 0 es ANDROID y 1 es PC
private int plataforma;

public Control(int plataforma) {
this.controles = new AControl();
this.plataforma = plataforma;
}

public void captura() {
if (plataforma == 0) {
capturaAndroid();
return;
}
capturaPc();
}

private void capturaAndroid() {

if (get().getPad().getKnobPercentX() > 0) {
controles.setRight(true);
} else {
controles.setRight(false);
}
if (get().getPad().getKnobPercentX() < 0) {
controles.setLeft(true);
} else {
controles.setLeft(false);
}
                // Faltan los botones de el axis Y
                // Faltan los botones A y B
}

private void capturaPc() {
// Movimiento del personaje
controles.setLeft(Gdx.input.isKeyPressed(Keys.A));
controles.setRight(Gdx.input.isKeyPressed(Keys.D));
controles.setUp(Gdx.input.isKeyPressed(Keys.W));
controles.setDown(Gdx.input.isKeyPressed(Keys.S));
// Manejo de los botones de acción
controles.setA(Gdx.input.isButtonPressed(Input.Buttons.LEFT));
controles.setB(Gdx.input.isButtonPressed(Input.Buttons.RIGHT));
}

public AControl get() {
return controles;
}

private int getPlataforma() {
return plataforma;
}

public void setTouchPad(Stage stage) {
if (getPlataforma() == 0) {
stage.addActor(get().getPad());
Gdx.input.setInputProcessor(stage);
}
}
}


Como verán el touchpad que esta en la clase de Acontrol se usa de forma independiente y este solo hay que activarle y gráficar  pues de forma indirecta puedo saber si el usuario esta pulsando el touchpad y saber en que dirección.

Entonces ya tenemos los controles abstraídos y que son manejables en diferentes entornos, pues lo único que hay que hacer es usar el método captura de la clase Control en el ciclo, para que este refresque en cada frame del juego para saber si se pulsan o no los botones y usando la clase get de la clase Control obtenemos los controles abstraídos y podemos usarle para crear la lógica de nuestro juego.

Mas adelante subiré ejemplos de código y proyectos usando LibGDX mostrando como se usan los códigos presentados.

sábado, 6 de abril de 2013

Renderizado con LIBGDX y servidor con WEBSOCKET

Con la tecnología cada vez variando más, empieza a crecer la gama de dispositivos que como ingenieros tenemos que dominar y programar en ellos, pero con tecnologías que corren en múltiples plataformas como JAVA es posible desarrollar de una forma centralizada para muchas tecnologías.

Una tendencia actual es separar cada dispositivo aunque se tenga la misma aplicación. Un ejemplo claro esta en el desarrollo de juegos, es decir, un juego MMO que se desarrollo para Android por su mayoría tiene una versión en Iphone, Web o inclusive PC, pero estos no comparten un solo servicio, lo cual separa a los usuarios en grupos. Solo los usuarios de PC pueden jugar entre sí en el mismo servidor y lo mismo pasa con las diferentes plataformas, pero es posible que se puedan todos conectar a un solo servidor y jugar sin importar la plataforma?, pues la respuesta es si.

Es posible conectar estas plataformas usando WebSocket como un estándar para la conexión, pero requiere de ingenio para que las ventajas y desventajas de usar una plataforma u otra no se sobrepongan en la aplicación o juego.

Aquí les dejo un video mostrando esta posibilidad de desarrollar juegos y aplicaciones que funcionen como una sola.

Los recursos utilizados fueron LIBGDX, WebSocket tanto para pc y web (sí web, ya que son 2 librerías diferentes, pero que cumplen la misma función.).

Desarrollar bajo este paradigma es más complicado, ya que afronta diferentes interfaces y dispositivos, por lo que mantener una GUI estandar para cada uno de estos tiene sus retos.

domingo, 10 de marzo de 2013

Implementación y sobrecarga de un TableModel personalizado en JAVA

Cuando vamos a presentar datos que son muy parecidos a EXCEL usamos un tabla y en JAVA usamos el jXTable, pero por lo general usamos un TablaModel básico, es decir, no se ajusta a las caracteristicas que deseamos y en vez de facilitarnos la vida, nos la complica.

Aquí un ejemplo de un TableModel:

import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;

public class MiTableModel extends AbstractTableModel {
  
    public boolean cellEditable = false;
    public List dataArray;
    public int[] columneditable=null;
    public ArrayList columnIdentifiers;

    @Override
    public int getRowCount() {
        return dataArray.size();
    }
    @Override
    public int getColumnCount() {
        return columnIdentifiers.size();
    }
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
         return dataArray.get(rowIndex);
    }
    @Override
    public String getColumnName(int column) {
        return columnIdentifiers.get(column);
    }
    public ArrayList getColumnIdentifiers() {
        return columnIdentifiers;
    }
    public void removeRow(int row) {
        dataArray.remove(row);
        fireTableRowsDeleted(row, row);
    }
    public void addRow(Object data){
        this.dataArray.add(data);
        fireTableDataChanged();
    }
    public Object getRow(int index){
        return dataArray.get(index);
    }

    public List getData() {
        return dataArray;
    }

    public void setData(List dataArray) {
        this.dataArray = dataArray;
    }
}


Ahora ya tenemos la clase base de nuestro TableModel, pero quiero un TableModel más personalizado y que se ajuste a mi necesidad, pues lo que haremos es lo siguiente:

Crearemos una clase especial que utilizará mi clase anterior como base.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TMPersonalizado extends MiTableModel {
    
    private String[] columnasNombres;
    
    public TMPersonalizado() {
        headerDefault();
        initTable();
    }
    
    public TMPersonalizado(String[] cn) {
        columnasNombres = cn;
        initTable();
    }
    
    private void headerDefault() {
        columnasNombres = new String[2];
        columnasNombres[0] = "Una columna. 1";
        columnasNombres[1] = "Una columna. 2";
    }
    
    public void updateTable(){
        //dataArray = ManejoBanco.getInstancia().getLista();
        fireTableDataChanged();
    }
    
    private void initTable(){
        ArrayList header = new ArrayList();
        header.addAll(Arrays.asList(columnasNombres));
        
        columnIdentifiers = header;
        dataArray = new ArrayList();
        fireTableDataChanged();
    }
    
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        MisDatos misDatos = (MisDatos) dataArray.get(rowIndex);
        switch(columnIndex){
            case 0:
                return misDatos.getMiDato1();
            case 1:
                return misDatos.getMiDato2();
            default:
                return null;
        }
    }
}

Como verán creo un objeto con mis propios constructores y asigno los datos que deseo, pero si se fijan hago uso de un modelo de datos de representación de filas de una base de datos y lo uso en mi TableModel, para facilitarme la vida a la hora de manipular los datos basados en un objeto POJO de JAVA. Si no saben lo que es un objeto POJO de JAVA, pues en un intento de definirlo vulgarmente es una clase que sigue ciertas reglas establecidas y  no extiende de nada. Aqui un ejemplo del modelo que use:

public class MisDatos {
    private int miDato1;
    private String miDato2;

    public int getMiDato1() {
        return miDato1;
    }
    public void setMiDato1(int miDato1) {
        this.miDato1 = miDato1;
    }
    public int getMiDato2() {
        return miDato2;
    }

    public void setMiDato2(int miDato2) {
        this.miDato2 = miDato2;
    }
}

y por ultimo un ejemplo de como instanciar y asignar el TableModel a una tabla:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jdesktop.swingx.JXTable;

public class Prueba {
    private List dats = new ArrayList();
    private TMPersonalizado tm = new TMPersonalizado();
    private JXTable mitabla;

    public Prueba() {
        mitabla = new JXTable();
        tm.setData(dats);
        mitabla.setModel(tm);
    }
}

Como verán es muy sencillo, pero y como consigo los datos de los cuales fueron seleccionados?, pues conseguir los datos seria algo así

Esto supone ser un método dentro de la clase anterior.

    public MisDatos captura() {
        MisDatos fila = (MisDatos) tm.getRow(mitabla.getSelectedRow());
        return fila;
    }

Excelente, pero y como obtengo la fila cuando se ordenan los datos?

    public MisDatos capturaOrdenada() {
        MisDatos fila = (MisDatos) tm.getRow(mitabla.convertRowIndexToModel(mitabla.getSelectedRow()));
        return fila;
    }

Ya con esto tenemos un modelo de un TableModel funcional.

Que haríamos sin @Override?

Cuando programamos tendemos a escribir código con funcionalidades específicas y en combinación con piezas, libs o apis aveces estas no traen lo que necesitamos y por ignorancia o otra razón tendemos a reinventar la rueda.

Es por eso que se creo @Override en los lenguajes de programación POO, para convertir algo que no hace lo que queremos en lo que buscamos.

Un ejemplo claro es cuando tenemos un carro, si el carro no tiene el motor que queremos solo reemplazamos el motor y no el carro completo(reinventar la rueda) y esto es más o menos un ejemplo de @Override en la vida real.

Muchas veces es mejor sobrecargar un método que escribir la clase completa, ya que podríamos añadir métodos específicos de nuestra necesidad si es que no contempla esas caracteristicas el objeto que estamos usando.