para Android
Construir un juego de tres en lnea de multijugador en red con PHP, XML y el
kit de desarrollo de Android
Tabla de contenidos
Los juegos casuales son extremadamente populares y muy lucrativos, y es fcil saber por
qu. No todas las personas de todos los grupos de edades estn interesadas en jugar
online videojuegos de disparos en primera persona contra hordas de adolescentes con
reflejos tan rpidos como la velocidad de la luz. Algunas veces, es ms interesante jugar
videojuegos donde tiene tiempo para pensar y realizar una estrategia o donde la meta es
cooperar unos con otros para ganar el juego.
Lo genial sobre los videojuegos casuales desde la perspectiva de los desarrolladores es
que son mucho ms fciles de construir que los videojuegos intensivos en grficos de
disparos en primera persona o de deportes. As que es ms fcil para un solo
desarrollador, o para un grupo de desarrolladores, producir una primera versin de un
nuevo videojuego original.
En este artculo, pasamos a travs de las bases para crear un juego de lnea de tres
casual de multijugador en red. El servidor del videojuego es una aplicacin web basada
en MySQL y PHP con una interfaz XML. La parte frontal es una aplicacin nativa de
Android que funciona en telfonos de Android.
Volver arriba
Construyendo el fondo
El fondo inicia con una base de datos simple de MySQL que tiene dos tablas. El Listado
1 muestra el esquema para la base de datos.
Listado 1. db.sql
2 para 'x' y 'y', ya que cuenta con una red de tres por tres. El ltimo campo es el "color" del
movimiento, que es un entero que indica X u O.
Para construir la base de datos, primero use mysqladmin para crearla y despus use el
comando mysql para ejecutar el script db.sql como se muestra aqu:
% mysqladmin --user=root --password=foo create ttt
% mysql --user=root --password=foo ttt < db.sql
Esta etapa crea una nueva base de datos llamada "ttt", que tiene el esquema del juego de
tres en lnea.
ahora que tiene el esquema, necesita crear una forma de iniciar un juego. Para esto,
usted cuenta con un script llamado start.php, como en elListado 2.
Listado 2. start.php
<?php
header( 'Content-Type:text/xml' );
$dd = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'INSERT INTO games VALUES ( 0 )';
$sth = $dd->prepare($sql);
$sth->execute( array() );
$qid = $dd->lastInsertId();
$doc = new DOMDocument();
$r = $doc->createElement( "game" );
$r->setAttribute( 'id', $qid );
$doc->appendChild( $r );
print $doc->saveXML();
?>
El script comienza por conectarse a la base de datos. Despus ejecuta una sentencia
INSERT en la tabla de juegos y recupera el ID que fue generado. Desde ah crea un
documento XML, aade el ID a una etiqueta de juego y exporta el XML.
Necesita ejecutar este script para poner un juego en la base de datos, ya que la simple
aplicacin de Android no tiene una interfaz para crear juegos. Este es el cdigo:
$ php start.php
<?xml version="1.0"?>
<game id="1"/>
$
Ahora ya tiene su primer juego. Para ver la lista de juegos, use el script games.php que
est en el Listado 3.
Listado 3. games.php
<?php
header( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'SELECT * FROM games';
$q = $dbh->prepare( $sql );
$q->execute( array() );
$doc = new DOMDocument();
$r = $doc->createElement( "games" );
$doc->appendChild( $r );
foreach ( $q->fetchAll() as $row) {
$e = $doc->createElement( "game" );
$e->setAttribute( 'id', $row['id'] );
$r->appendChild( $e );
}
print $doc->saveXML();
?>
Este script, igual que el script.php, comienza por conectarse a la base de datos. Despus
de eso, consulta la tabla de juegos para ver qu est disponible. Y desde ah crea un
nuevo documento XML, aade una etiqueta de juegos, despus aade etiquetas de juego
para cada uno de los juegos disponibles.
Cuando ejecuta este script desde la lnea de comandos, ve algo como esto:
$ php games.php
<?xml version="1.0"?>
<games><game id="1"/></games>
$
Tambin puede ejecutar este script desde el navegador web para ver la misma salida.
Excelente! Con la API de juegos fuera del camino, es momento de escribir el cdigo de
servidor para manejar los movimientos. Este cdigo inicia con la construccin de un script
ayudante llamado show_moves que obtiene los movimientos actuales para un juego dado
y los exporta como XML.El Listado 4 muestra el cdigo PHP para esta funcin de
ayudante.
Listado 4. show_moves.php
<?php
function show_moves( $dbh, $game ) {
$sql = 'SELECT * FROM moves WHERE game=?';
$q = $dbh->prepare( $sql );
$q->execute( array( $game ) );
$doc = new DOMDocument();
$r = $doc->createElement( "moves" );
$doc->appendChild( $r );
foreach ( $q->fetchAll() as $row) {
$e = $doc->createElement( "move" );
$e->setAttribute( 'x', $row['x'] );
$e->setAttribute( 'y', $row['y'] );
$e->setAttribute( 'color', $row['color'] );
$r->appendChild( $e );
}
print $doc->saveXML();
}
?>
El script toma un manejador de base de datos y el ID de juego. Desde ah ejecuta el SQL
para obtener la lista de movimientos. Despus crea un documento XML con los
movimientos para el juego dado.
Usted cre esta funcin de ayudante porque hay dos scripts que la usan; el primero es un
script moves.php que retorna los movimientos actuales para el juego especificado. El
Listado 5 muestra este script.
Listado 5. moves.php
<?php
require_once( 'show_moves.php' );
header( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
show_moves( $dbh, $_REQUEST['game'] );
?>
Este simple script incluye el cdigo de la funcin de ayudante, se conecta a la base de
datos y despus invoca la funcin show_moves con el ID del juego especificado. Para
probar este cdigo, use el comando curl para invocar el script en el servidor desde la lnea
de comandos:
$ curl "http://localhost/ttt/moves.php?game=1"
<?xml version="1.0"?>
<moves/>
$
Desgraciadamente, an no ha hecho ningn movimiento, as que no es una salida
particularmente interesante. Para remediar eso necesita aadir el script final a la API del
servidor. El Listado 6 muestra el script move.php.
Listado 6. move.php
<?php
require_once( 'show_moves.php' );
header( 'Content-Type:text/xml' );
$dbh = new PDO('mysql:host=localhost;dbname=ttt', 'root', '');
$sql = 'DELETE FROM moves WHERE game=? AND x=? AND y=?';
$sth = $dbh->prepare($sql);
$sth->execute( array(
$_REQUEST['game'],
$_REQUEST['x'],
$_REQUEST['y']
) );
$sql = 'INSERT INTO moves VALUES ( 0, ?, ?, ?, ? )';
$sth = $dbh->prepare($sql);
$sth->execute( array(
$_REQUEST['game'],
$_REQUEST['x'],
$_REQUEST['y'],
$_REQUEST['color']
) );
show_moves( $dbh, $_REQUEST['game'] );
?>
Este script comienza por incluir la funcin de ayudante y conectarse a la base de datos.
Despus ejecuta dos sentencias SQL. La primera elimina cualquier movimiento que
pueda chocar con la que est siendo enviada. La segunda inserta una nueva fila en la
tabla de movimientos para el movimiento especificado. El script despus retorna la lista de
movimientos al cliente. Esta etapa salva al cliente de tener que hacer dos solicitudes cada
vez que hace un movimiento. El ancho de banda no es barato, as que siempre que pueda
conglomerar solicitudes debe hacerlo.
Para probar que todo esto funciona, puede hacer un movimiento:
$ curl "http://localhost/ttt/move.php?game=1&x=1&y=2&color=1"
<?xml version="1.0"?>
<moves><move x="1" y="2" color="1"/></moves>
Con el cdigo del servidor del juego completo, puede construir la parte frontal de Android
para este juego en red de multijugador.
Volver arriba
La Figura 2 muestra los directorios de alto nivel y los archivos para una aplicacin de
Android (los directorios son src, gen, Android 2.3.1 y res y los archivos son assets,
AndroidManifest.xml, default.properties y proguard.cfg). Los elementos importantes son:
android:versionCode="1"
android:versionName="1.0" package="com.jherrington.tictactoe">
<uses-permission
android:name="android.permission.INTERNET" />
<uses-sdk android:minSdkVersion="5" />
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name="TicTacToeActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
El nico cambio fue aadir la etiqueta uses-permission en la parte superior del archivo.
Su siguiente tarea es disear la IU. Para esto, ajuste el archivo layout.xml, que est
contenido en el directorio res/layout. El Listado 8 muestra el nuevo contenido para este
archivo.
Listado 8. layout.xml
package com.jherrington.tictactoe;
import java.util.Timer;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.Gallery;
import android.widget.LinearLayout;
public class TicTacToeActivity extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button playx = (Button)this.findViewById(R.id.playx);
playx.setOnClickListener( this );
Button playo = (Button)this.findViewById(R.id.playo);
playo.setOnClickListener( this );
Timer timer = new Timer();
UpdateTimer ut = new UpdateTimer();
ut.boardView = (BoardView)this.findViewById(R.id.bview);
timer.schedule( ut, 200, 200 );
}
public void onClick(View v) {
BoardView board = (BoardView)this.findViewById(R.id.bview);
if ( v.getId() == R.id.playx ) {
board.setColor( 2 );
}
if ( v.getId() == R.id.playo ) {
board.setColor( 1 );
}
}
}
La actividad tiene dos mtodos. El primero es el mtodo onCreate, que construye la
interfaz de usuario, conecta al manejador onClick a los botones X y O e inicia el
package com.jherrington.tictactoe;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import android.util.Log;
public class GameService {
private static GameService _instance = new GameService();
public int[][] positions = new int[][] {
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 }
};
public static GameService getInstance() {
return _instance;
}
private void updatePositions( Document doc ) {
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
positions[x][y] = 0;
}
}
doc.getDocumentElement().normalize();
NodeList items = doc.getElementsByTagName("move");
for (int i=0;i<items.getLength();i++){
Element me = (Element)items.item(i);
int x = Integer.parseInt( me.getAttribute("x") );
int y = Integer.parseInt( me.getAttribute("y") );
int color = Integer.parseInt( me.getAttribute("color") );
positions[x][y] = color;
}
}
public void startGame( int game ) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://10.0.2.2/ttt/moves.php");
try {
List<NameValuePair> nameValuePairs = new
ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("game",
Integer.toString(game)));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
updatePositions( db.parse(response.getEntity().getContent()) );
} catch (Exception e) {
Log.v("ioexception", e.toString());
}
}
public void setPosition( int game, int x, int y, int color ) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://10.0.2.2/ttt/move.php");
positions[x][y] = color;
try {
List<NameValuePair> nameValuePairs = new
ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("game",
Integer.toString(game)));
nameValuePairs.add(new BasicNameValuePair("x", Integer.toString(x)));
nameValuePairs.add(new BasicNameValuePair("y", Integer.toString(y)));
nameValuePairs.add(new BasicNameValuePair("color",
Integer.toString(color)));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
updatePositions( db.parse(response.getEntity().getContent()) );
} catch (Exception e) {
Log.v("ioexception", e.toString());
}
}
}
Este cdigo es algo del cdigo ms interesante en la aplicacin. Primero, tiene el mtodo
updatePositions, que toma el XML retornado del servidor y busca los elementos de
movimiento, despus actualiza la matriz de posiciones con el conjunto de movimientos
actual. La matriz de posiciones tiene un valor para cada posicin en el tablero; cero indica
un espacio vaco, 1 representa "O" y 2 es para "X".
Las otras dos funciones, startGame y setPosition, son la forma en que se comunica con el
servidor. El mtodo startGame solicita el conjunto de movimientos actual del servidor y
actualiza la lista de posiciones. El mtodo setPosition publica el movimiento en el servidor
al crear una solicitud de publicacin HTTP y configurar los datos para la publicacin
usando una matriz de pares de valores de nombre, los cuales son despus son
codificados para transporte. Despus analiza el XML de respuesta para actualizar la lista
de posiciones.
Si observa detenidamente, el IP usado para conectarse al servidor es realmente
interesante. No es "localhost" o "127.0.0.1"; es "10.0.2.2", que es un alias para la mquina
en la que se ejecuta el emulador. Ya que el telfono de Android es en s mismo un sistema
de UNIX , tiene sus propios servicios en localhost. Fascinante, verdad? No es
frecuente que sea tan claro que el telfono no es en realidad un telfono per se, sino una
computadora en toda regla que se ajusta a la palma de su mano y sucede que tiene un
telfono integrado en l.
Entonces, dnde estamos? Usted tiene la actividad, que es el componente principal para
la aplicacin; tiene la configuracin del diseo de IU; tiene el cdigo de Java para
conectarse al servidor. Ahora necesita dibujar el tablero del juego. Esto es realizado por la
clase BoardView en elListado 11.
Listado 11. BoardView.java
package com.jherrington.tictactoe;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class BoardView extends View {
private int _color = 1;
public void setColor( int c ) {
_color = c;
}
public BoardView(Context context) {
super(context);
GameService.getInstance().startGame(0);
}
public BoardView(Context context, AttributeSet attrs) {
super(context,attrs);
GameService.getInstance().startGame(0);
}
public BoardView(Context context, AttributeSet attrs, int defStyle) {
super(context,attrs,defStyle);
GameService.getInstance().startGame(0);
}
public boolean onTouchEvent( MotionEvent event ) {
if ( event.getAction() != MotionEvent.ACTION_UP )
return true;
int offsetX = getOffsetX();
int offsetY = getOffsetY();
int lineSize = getLineSize();
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
paint.setColor(Color.BLACK);
canvas.drawRect(0,0,canvas.getWidth(),canvas.getHeight(), paint);
int size = getSize();
int offsetX = getOffsetX();
int offsetY = getOffsetY();
int lineSize = getLineSize();
paint.setColor(Color.DKGRAY);
paint.setStrokeWidth( 5 );
for( int col = 0; col < 2; col++ ) {
int cx = offsetX + ( ( col + 1 ) * lineSize );
canvas.drawLine(cx, offsetY, cx, offsetY + size, paint);
}
for( int row = 0; row < 2; row++ ) {
int cy = offsetY + ( ( row + 1 ) * lineSize );
canvas.drawLine(offsetX, cy, offsetX + size, cy, paint);
}
int inset = (int) ( (float)lineSize * 0.1 );
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth( 10 );
for( int x = 0; x < 3; x++ ) {
for( int y = 0; y < 3; y++ ) {
Rect r = new Rect( ( offsetX + ( x * lineSize ) ) + inset,
( offsetY + ( y * lineSize ) ) + inset,
( ( offsetX + ( x * lineSize ) ) + lineSize ) - inset,
( ( offsetY + ( y * lineSize ) ) + lineSize ) - inset );
if ( GameService.getInstance().positions[ x ][ y ] == 1 ) {
canvas.drawCircle( ( r.right + r.left ) / 2,
( r.bottom + r.top ) / 2,
( r.right - r.left ) / 2, paint);
}
if ( GameService.getInstance().positions[ x ][ y ] == 2 ) {
canvas.drawLine( r.left, r.top, r.right, r.bottom, paint);
canvas.drawLine( r.left, r.bottom, r.right, r.top, paint);
}
}
}
}
}
La mayora del trabajo aqu es realizado en el mtodo onTouch, que responde al usuario
tocando una clula particular en el tablero del juego, y el mtodo onDraw, que pinta el
tablero del juego usando el mecanismo de pintura de Android.
El mtodo onTouch usa las funciones de tamao para definir un rectngulo para cada
posicin de clula. Despus usa el mtodo contains en el rectngulo para ver si el usuario
hizo clic dentro de la clula. Si lo hizo, ejecuta una solicitud al servicio del juego para que
haga un movimiento.
La funcin onDraw usa las funciones de tamao para dibujar las lneas del tablero y para
dibujar cualquier X u O jugada. El singleton GameServer es usado para su matriz de
posiciones, que tiene el estado actual de cada cuadro en el tablero del juego.
La ltima clase que necesita es UpdateTimer, que usa el servicio del juego para actualizar
las posiciones del tablero con sus ltimos valores. El Listado 12 muestra el cdigo para el
temporizador.
Listado 12. UpdateTimer.java
package com.jherrington.tictactoe;
import java.util.TimerTask;
public class UpdateTimer extends TimerTask {
public BoardView boardView;
@Override
public void run() {
GameService.getInstance().startGame( 0 );
Este es el emulador cargando una fantstica interfaz "A N D R O I D". Despus de ser
cargada, ve la pantalla power-on en la Figura 4.
Figura 4. El emulador lanzado y listo para usarse
Para entrar al telfono, deslice el icono de bloqueo hacia la derecha. Esa accin lo lleva a
la pantalla de inicio y generalmente lanza la aplicacin que est depurando. En este caso,
esta accin muestra la pantalla del juego en la Figura 5.
Figura 5. El juego antes de que se hagan movimientos
Dependiendo del estado de su servidor, puede ver o no ningn movimiento. En este caso,
el juego estaba vaco. Los botones Play X y Play O estn en la parte superior con el
tablero del juego de lnea de tres en el centro de la visualizacin. A continuacin, haga clic
en Play X, despus haga clic en el cuadro del centro para ver algo como la Figura 6.
Figura 6. X toma el cuadro del centro, por supuesto
La Figura 6 muestra la visualizacin del tablero del juego con una X ahora llenando el
cuadro del centro. Para verificar que el servidor estaba conectado, puede ejecutar el
Puede jugar con Xs y Os. La aplicacin se conecta al servidor para alojar el estado del
juego en una ubicacin compartida. Y debido al temporizador de actualizacin, cada
usuario puede ver los movimientos realizados por el otro.
Conclusin
Es este un juego completo? No realmente. No hay verificacin de condicin de victoria,
los jugadores pueden sobrescribir posiciones y no hay verificacin de turno. Pero las
piezas de tecnologa bsicas estn presentes: un servidor de juego con el estado
almacenado compartido entre los jugadores y una aplicacin grfica nativa en un
dispositivo mvil que se conecta al servidor del juego para proporcionar un interfaz para el
juego. Puede usar este juego como un punto de partida para su propio juego y construirlo
como desee. Slo recuerde mantenerlo casual y divertido, y tal vez sea el creador del
siguiente Words With Friends o multijugador de Angry Birds.