12 Jan
[Ejemplos Android] Torres de Hanoi II
Día 1. Descripción del proyecto
- HanoiGameActivity
Extiende de android.app.Activity y representa la clase principal de la aplicación. Contiene view que mostrará el escenario del juego y atenderá el evento Touch para que el usuario interactúe con la aplicación.
- HanoiGameView
Extiende de android.view.View y será la encargada de pintar el escenario, discos y controlar la lógica del juego.
- HanoiDiskShape
Alojada dentro de HanoiGameView, se encarga de representar los discos que se apilan en cada varilla.
Día 2. Programación
HanoiGameActivity
void onCreate(Bundle savedInstanceState)
Se lanza al cargar el activity y se utiliza para inicializar y cargar la interfaz gráfica.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // initialize Hanoi Game hanoiGame = new HanoiGameView(this, DEFAULT_NUMBER_OF_DISKS); // Hide the window title and top bar requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(hanoiGame); }
boolean onTouchEvent(MotionEvent event)
Recoge el evento Touch ACTION_DOWN e pasa el control a la lógica del juego para que determine la acción pertinente. En caso que el juego haya finalizado muestra un mensaje y reinicia la partida.
@Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (hanoiGame.onTouch(event.getX(), event.getY())) { // when game is completed, shows an alert and starts a new game Toast.makeText(this, "You win!!", Toast.LENGTH_LONG).show(); hanoiGame.startGame(DEFAULT_NUMBER_OF_DISKS); } } return true; }
HanoiGameView
HanoiGameView(Context context, int _numberOfDisks)
public HanoiGameView(Context context, int _numberOfDisks) { super(context); // loads the background image that contains the bottom wood and the // figure of the three rods Bitmap hanoiBackground = BitmapFactory.decodeResource(getResources(), R.drawable.hanoi_background); setBackgroundDrawable(new BitmapDrawable(hanoiBackground)); startGame(_numberOfDisks); }
void startGame(int _numberOfDisks)
inicializa el contenido de las varillas con el número de discos pasado por parámetro.
protected void startGame(int _numberOfDisks) { if (_numberOfDisks > MAX_DISKS) { throw new RuntimeException(ERROR_MAX_NUMBER_DISKS_EXCEEDED); } numberOfDisks = _numberOfDisks; // create the three rod objects and fill the left one with // the total number of disks leftRod = new Stack(); middleRod = new Stack(); rightRod = new Stack(); for (int diskSize = numberOfDisks; diskSize >= 1; diskSize--) { leftRod.push(new HanoiDiskShape(diskSize)); } }
void onDraw(Canvas canvas)
pinta el contenido de las tres varillas.
@Override protected void onDraw(Canvas canvas) { // draw the disks of the three rods // the idea is to translate the canvas to the left // for each rod. inside of the loops, the canvas is translated // upward for each disk. // some numbers to understand the translations // first rod located at: x = 90px // first disk located at: y = 230px // distance between rods: x = 150px // distance between disks: y -= 25px canvas.translate(90, 230); canvas.save(); for (HanoiDiskShape disk : leftRod) { disk.draw(canvas); canvas.translate(0, -25); } canvas.restore(); canvas.translate(150, 0); canvas.save(); for (HanoiDiskShape disk : middleRod) { disk.draw(canvas); canvas.translate(0, -25); } canvas.restore(); canvas.translate(150, 0); canvas.save(); for (HanoiDiskShape disk : rightRod) { disk.draw(canvas); canvas.translate(0, -25); } canvas.restore(); }
boolean onTouch(float x, float y)
comprueba sobre cual varilla se ha pulsado y llama al método actionOnTouchedRod.
public boolean onTouch(float x, float y) { boolean gameFinished = false; // limits to detect which rod has been touched int topLimit = 30; int bottomLimit = 250; int leftLimitLeftRod = 20; int rightLimitLeftRod = leftLimitLeftRod + 150; int rightLimitMiddleRod = rightLimitLeftRod + 150; int rightLimitRightRod = rightLimitMiddleRod + 150; if (y > topLimit && y < bottomLimit) { if (x > leftLimitLeftRod && x < rightLimitLeftRod) { gameFinished = actionOnTouchedRod(leftRod); } else if (x > rightLimitLeftRod && x < rightLimitMiddleRod) { gameFinished = actionOnTouchedRod(middleRod); } else if (x > rightLimitMiddleRod && x < rightLimitRightRod) { gameFinished = actionOnTouchedRod(rightRod); } // forces to redraw invalidate(); } return gameFinished; }
boolean actionOnTouchedRod(Stack<HanoiDiskShape> touchedRod)
implementa la lógica del juego recibiendo por parámetro la varila que acaba de ser pulsada.
private boolean actionOnTouchedRod(Stack touchedRod) { boolean gameFinished = false; // if there isn't any selected rod and touchedRod contains disks... if (rodWithDiskSelected == null && touchedRod.size() > 0) { touchedRod.lastElement().select(); rodWithDiskSelected = touchedRod; } else if (rodWithDiskSelected != null) { // there is a rod with a disk selected, so... // if user has touched the same rod -> unselect disk if (rodWithDiskSelected.equals(touchedRod)) { touchedRod.lastElement().unselect(); rodWithDiskSelected = null; // if not, check if it's a valid move } else if (touchedRod.size() == 0 || (rodWithDiskSelected.lastElement().size < touchedRod .lastElement().size)) { rodWithDiskSelected.lastElement().unselect(); touchedRod.push(rodWithDiskSelected.pop()); rodWithDiskSelected = null; } } // if all disks are in the middle or right rod, game finished! if (middleRod.size() == numberOfDisks || rightRod.size() == numberOfDisks) { gameFinished = true; } return gameFinished; }
HanoiDiskGame
HanoiDiskShape(int _size)
Crea un disco del tamaño pasado por parámetro.
public HanoiDiskShape(int _size) { super(new RoundRectShape(diskOuterRadius, null, null)); this.unselect(); this.size = _size * 25; this.setBounds(0, 0, this.size, 20); }
void select()
Colorea el disco de un tono transparente para que se note que está seleccionado.
public void select() { this.getPaint().setColor(diskSelectedColor); }
void unselect()
Vuelva dejar el disco con el color por defecto.
public void unselect() { this.getPaint().setColor(diskUnselectedColor); }
void onDraw(Shape shape, Canvas canvas, Paint paint)
Pinta el disco en el escenario.
@Override protected void onDraw(Shape shape, Canvas canvas, Paint paint) { canvas.save(); // translates the half of the size to the left, to draw // the disk on the center of the rod canvas.translate(-size / 2, 0); shape.draw(canvas, paint); canvas.restore(); }
Anexo
Hay un par de detalles que quedan fuera del código Java y faltaría mencionar.
Forzar Activity en landscape
Para evitar que el activity, HanoiGameActivity en nuestro caso, se gire según si el móvil está en horizontal en vertical, se puede forzar que siempre esté en modo horizontal (landscape) o vertical (portrait). Para esta aplicación nos interesa que siempre se encuentre en posición horizontal para que haya el máximo espacio para manipular los discos. Esto se consigue editando el fichero de configuración AndroidManifest.xml. Localizamos el tag activity que queremos ajustar la posición y lo modificamos añadiendo el valor:
android:screenOrientation="landscape"
Girar orientación emulador
Por útlimo, también es posible girar la vista del emulador para que coincida con la orientación landscape que necesitamos para esta aplicación. Esto se puede hacer presionando la combinación de teclas CTRL-F11. Hay muchas más opciones disponibles en el emulador, para más info: http://developer.android.com/guide/developing/tools/emulator.html
Código fuente completo
Aquí puedes descargar el código fuente en formato .zip: HanoiTowers.zip
También se puede encontrar en el repositorio SVN de: http://code.google.com/p/android-tutorials/. Aunque es posible que contenga modificaciones con respecto a lo aquí mostrado.
Conclusiones
Más que conclusiones autocrítica. Me parece que sirve de poco copy-pastear el código si no he explicado nada. No quedo para nada satisfecho con dejarlo tal cual. Espero que el código fuente sirva a alguien aunque no será gracias a mis explicaciones porque no las hay. Para la próxima o bien hago otro desarrollo enfocando desde el principio que lo único que ofreceré será el codigo fuente sin explicaciones (que no creo que tampoco sea insuficiente) o intentaré abarbar un tema concreto (por ejemplo trabajar con las bases de datos) y en lugar de hacer una aplicación haré pequeños códigos que expliquen los conceptos relacionados al tema.



