Criar um Jogo da Velha em Rede para Android

Desenvolva um jogo da velha em rede, para vários jogadores, usando PHP, XML e o kit de desenvolvimento do Android

Desenvolva, neste artigo, o backend de um jogo da velha em rede, para vários jogadores, com um aplicativo frontend Android.

Jack D. Herrington, Senior Software Engineer, Leverage Software Inc.

Photo of Jack HerringtonJack Herrington é engenheiro, autor e apresentador que mora e trabalha em Bay Area. É possível se manter atualizado em relação ao seu trabalho e aos seus escritos em http://jackherrington.com.



06/Set/2011

Jogo da velha em rede para vários jogadores

Acrônimos usados frequentemente

  • API: Interface de Programação de Aplicativos
  • HPTP: Protocolo de Transporte de Hipertexto
  • IP: Protocolo da Internet
  • SDK: Kit de Desenvolvimento de Software
  • SQL: Linguagem de Consulta Estruturada
  • UI: interface com o usuário
  • XML: Linguagem de Marcação Extensível

Jogos casuais são extremamente populares e muito lucrativos, e é fácil de ver o porquê. Nem todas as pessoas estão interessadas em jogar on-line, sair atirando no FPS contra hordas de pré-adolescentes com reflexos na velocidade da luz. Algumas vezes, é mais interessante participar de um jogo em que você tenha tempo para pensar e organizar uma estratégia, ou cujo objetivo seja cooperar com outros para chegar à vitória.

O grande detalhe sobre jogos casuais, do ponto de vista dos desenvolvedores, é que eles são muito mais fáceis de desenvolver do que os jogos esportivos ou FPS com grande número de elementos gráficos. Assim, é mais fácil para um único desenvolvedor, ou para um grupo de desenvolvedores, produzir a primeira versão de um novo novel game.

Neste artigo, analisaremos os fundamentos básicos da criação de um jogo da velha casual para vários jogadores. O servidor de jogos é um aplicativo da Web baseado em MySQL e em PHP, com uma interface XML. O frontend é um aplicativo nativo Android que funciona em telefones Android.


Desenvolvendo o Backend

O backend inicia com um banco de dados MySQL simples que tem duas tabelas. A Listagem 1 mostra o esquema do banco de dados.

Listagem 1. db.sql
DROP TABLE IF EXISTS games;
CREATE TABLE games(
     id INT NOT NULL AUTO_INCREMENT,
     primary key ( id ) );

DROP TABLE IF EXISTS moves;
CREATE TABLE moves(
     id INT NOT NULL AUTO_INCREMENT,
     game INT NOT NULL,
     x INT NOT NULL,
     y INT NOT NULL,
     color INT NOT NULL,
     primary key ( id ) );

A primeira das duas tabelas é a tabela de jogos, que tem apenas o ID exclusivo do jogo. Em um aplicativo de produção, você provavelmente terá uma tabela de usuários, e a tabela de jogos incluirá os IDs de usuário de ambos os jogadores. Para manter a simplicidade, porém, esqueceremos essa abordagem para nos concentrar nos fundamentos básicos de como armazenar os dados do jogo, como fazer a comunicação entre cliente e servidor e como desenvolver o frontend.

A segunda tabela é a tabela de movimentações, que inclui as movimentações individuais de determinado jogo, assim ela tem cinco colunas. A primeira coluna é o ID exclusivo da movimentação. A segunda coluna é o ID do jogo no qual a movimentação se aplica. Em seguida, estão as posições x e y da movimentação. Esses valores devem ser entre 0 e 2 para x e y, partindo do princípio que você tem uma grade três por três. O último campo é a "cor" da movimentação, que é um inteiro que indica X ou O.

Para criar o banco de dados, primeiro use mysqladmin para criá-lo e, em seguida, use o comando mysql para executar o script db.sql como mostrado aqui:

% mysqladmin --user=root --password=foo create ttt
% mysql --user=root --password=foo ttt < db.sql

Essa etapa cria um novo banco de dados denominado "ttt", que tem o esquema do jogo da velha.

Agora que já se tem o esquema, é necessário criar um modo para iniciar um jogo. Para isso, é necessário ter um script denominado start.php, como na listagem 2.

Listagem 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();
?>

O script é iniciado pela conexão ao banco de dados. Em seguida, ele executa uma instrução INSERT na tabela de jogos e retorna o ID que foi gerado. A partir desse ponto, ele cria um documento XML, inclui o ID em uma tag do jogo e exporta o XML.

É necessário executar esse script para obter um jogo no banco de dados porque o aplicativo Android simples não tem uma interface para criar os jogos. Este é o código:

$ php start.php
<?xml version="1.0"?>
<game id="1"/>
$

Agora, já se tem o primeiro jogo. Para ver a lista de jogos, use o script games.php que está na Listagem 3.

Listagem 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();
?>

Esse script, como o script start.php, é iniciado pela conexão ao banco de dados. Depois disso, ele consulta a tabela de jogos para ver o que está disponível. E, a partir desse ponto, cria um novo documento XML, inclui uma tag de jogos e inclui tags de jogos para cada um dos jogos disponíveis.

Ao executar esse script na linha de comandos, é possível ver algo como:

$ php games.php
<?xml version="1.0"?>
<games><game id="1"/></games>
$

Também é possível executar esse script no navegador da Web para ver a mesma saída.

Excelente! Com a API dos jogos finalizada, é hora de escrever o código do servidor para manipular as movimentações. Esse código inicia com o desenvolvimento de um script auxiliar, denominado show_moves, que obtém as movimentações atuais de determinado jogo e as exporta como XML. A listagem 4 mostra o código PHP para essa função auxiliar.

Listagem 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();
}
?>

O script seleciona um identificador do banco de dados e o ID do jogo. Desse ponto, ele executa o SQL para obter a lista de movimentações. Em seguida, ele cria um documento XML com as movimentações de determinado jogo.

Essa função auxiliar foi criada porque há dois scripts que a usam. O primeiro é um script (moves.php) que retorna as movimentações atuais do jogo especificado. A Listagem 5 mostra esse script.

Listagem 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'] );
?>

Esse é um script simples que inclui o código de função auxiliar, conecta-se ao banco de dados e chama a função show_moves com o ID do jogo especificado. Para testar esse código, use o comando curl para chamar o script no servidor a partir da linha de comandos:

$ curl "http://localhost/ttt/moves.php?game=1"
<?xml version="1.0"?>
<moves/>
$

Infelizmente, não foi feita nenhuma movimentação ainda, então ela não é uma saída especialmente interessante. Para resolver isso, é necessário incluir o script final na API do servidor. A Listagem 6 mostra o script move.php.

Listagem 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'] );
?>

Esse script é iniciado pela inclusão da função auxiliar e conexão ao banco de dados. Ele executa, em seguida, duas instruções SQL. A primeira remove qualquer movimentação que possa entrar em conflito com a movimentação que está sendo enviada. A segunda insere uma nova linha na tabela de movimentações para a movimentação especificada. O script então retorna a lista de movimentações para o cliente. Essa etapa evita que o cliente tenha de fazer duas solicitações cada vez que fizer uma movimentação. A largura de banda não é barata, assim, sempre que for possível, é recomendável conglomerar solicitações.

Para testar isso, tudo o que se pode fazer é uma movimentação:

$ 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>

Com o código do servidor de jogo completo, é possível desenvolver o frontend do Android para esse jogo em rede para vários jogadores.


Desenvolvendo o Frontend do Android

Primeiro, instale o Android SDK e algumas versões da plataforma do Android e, finalmente, o Eclipse e o plug-in do Android Eclipse. Felizmente, tudo isso está bem documentado no site do Android (consulte Recursos para obter os links). Uma descrição detalhada de como configurar o seu ambiente de desenvolvimento ocuparia este artigo inteiro e mais.

Depois de configurar o ambiente de desenvolvimento, ative o Eclipse e inicie um novo projeto Android. Deve-se visualizar algo similar à figura 1.

Figura 1. Criando o Aplicativo Android no Eclipse
Criando o Aplicativo Android no Eclipse

A figura 1 mostra o assistente de projeto para aplicativos Android. Insira um nome de projeto, selecione o botão de opções Create new project in workspace e especifique o local para o código com os elementos da UI. Na lista de verificação Build Target, selecione uma plataforma Android. Para este código, usei o Android 2.3.1. O código é bem simples, assim é possível usar qualquer versão que preferir. Se nenhuma plataforma constar na lista, então será necessário fazer download e instalar as plataformas como descrito nas instruções de configuração do Android SDK. Esteja ciente de que o download dessas plataformas pode demorar.

Na seção Properties, preencha o nome do aplicativo e o nome do pacote. Usei "Tic Tac Toe" e "com.jherrington.tictactoe" nos respectivos campos. Em seguida, selecione a caixa de seleção Create Activity e insira um nome para a atividade. Usei "TicTacToeActivity" como nome da atividade.

Clique em Finish para ver um novo projeto parecido com a figura 2.

Figura 2. Os arquivos do projeto TicTacToe
Os arquivos do projeto TicTacToe

A figura 2 mostra os diretório de nível superior e arquivos de um aplicativo Android (os diretórios são src, gen, Android 2.3.1 e res, e os arquivos são assets, AndroidManifest.xml, default.properties e proguard.cfg). Os itens importantes são:

  • O diretório res, que contém os recursos
  • O diretório src, que tem a origem Java™
  • O arquivo de manifesto, que contém as informações biográficas sobre o aplicativo

Sua primeira edição é para o arquivo de manifesto. A maior parte do arquivo já está correta, mas é necessário incluir a permissão para a Internet de modo que o aplicativo possa fazer solicitações sobre a Internet. A Listagem 7 mostra o arquivo de manifesto completo.

Listagem 7. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      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>

A única mudança foi incluir a tag de permissão de uso na parte superior do arquivo.

Sua próxima tarefa é projetar a UI. Para isso, ajuste o arquivo layout.xml, que está contido no diretório res/layout. A Listagem 8 mostra os novos conteúdos para esse arquivo.

Listagem 8. layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <LinearLayout android:layout_height="wrap_content"
                android:layout_width="match_parent" android:id="@+id/linearLayout1">
        <Button android:text="Play X" android:id="@+id/playx"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"></Button>
        <Button android:text="Play O" android:id="@+id/playo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"></Button>
    </LinearLayout>
    <com.jherrington.tictactoe.BoardView android:id="@+id/bview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
    ></com.jherrington.tictactoe.BoardView>
</LinearLayout>

Esse é um layout simples. Na parte superior está um conjunto de dois botões agrupados em um layout de linha com uma orientação horizontal. Esses dois botões são os botões X e O que os usuários usam para especificar com que cor estão jogando.

O resto do código é preenchido por uma classe BoardView, que mostra o quadro do jogo da velha com o jogo atual. O código para a classe BoardView está na Listagem 11.

Com o layout em mãos, agora é o momento de escrever algum código Java para o aplicativo. Esse código é iniciado com a classe TicTacToeActivity na Listagem 9. As atividades são os blocos de construção básicos dos aplicativos Android. Cada aplicativo tem uma ou mais atividades que representam os diversos estados do aplicativo. À medida que se navega pelo aplicativo, é possível criar uma pilha de atividades que pode ser abandonada usando o botão voltar do telefone. O aplicativo TicTacToe tem apenas uma única atividade.

Listagem 9. TicTacToeActivity.java
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 );
      }
    }
}

A atividade tem dois métodos. O primeiro é o método onCreate, que desenvolve a interface com o usuário, conecta o manipulador onClick aos botões X e O e inicia o timer de atualização. O timer de atualização é usado para atualizar o estado do jogo a cada 200 milissegundos. Esse recurso permite que ambos os jogadores vejam quando o outro jogador faz uma movimentação.

O manipulador onClick define a cor atual do quadro dependendo de se o usuário clica no botão X ou O.

A classe GameService class, na Listagem 10, é uma classe singleton que representa o servidor de jogos e o estado atual de determinado jogo.

Listagem 10. GameService.java
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());
    }
  }
}

Esse código é a parte do código mais interessante no aplicativo. Primeiro, há o método updatePositions, que toma o XML retornado do servidor, procura pelos elementos da movimentação e, em seguida, atualiza o array com o conjunto atual das movimentações. O array de posições tem um valor para cada posição no quadro. Zero indica um espaço vazio, 1 representa "O" e 2 representa "X".

As outras duas funções, startGame e setPosition, são o modo como você se comunica com o servidor. O método startGame solicita o conjunto atual de movimentações do servidor e atualiza a lista de posições. O método setPosition publica a movimentação para o servidor criando uma solicitação de postagem HTTP e configurando os dados para a postagem ao usar um array de pares nome-valor, que são codificados para o transporte. Em seguida, analisa o XML da resposta para atualizar a lista de posições.

Se observado de perto, o IP usado para conexão ao servidor é realmente interessante. Não é "localhost" nem "127.0.0.1", é "10.0.2.2," que é um alias para a máquina na qual o emulador está executando. Como o telefone Android é, em si mesmo, um sistema UNIX®, ele tem seus próprios serviços no host local. Fascinante, não? Nem sempre é tão claro que o telefone não é um telefone em si, mas um computador completo que cabe em suas mãos e que, por ventura, contém um telefone integrado a ele.

Então, em que ponto estamos? Temos a atividade, que é o componente principal para o aplicativo. Temos também a configuração do layout da UI e o código Java para conexão ao servidor. Agora, é necessário desenhar o quadro do jogo, o que é feito pela classe BoardView na Listagem 11.

Listagem 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++ ) {
        Rect r = new Rect( ( offsetX + ( x * lineSize ) ),
            ( offsetY + ( y * lineSize ) ),
            ( ( offsetX + ( x * lineSize ) ) + lineSize ),
            ( ( offsetY + ( y * lineSize ) ) + lineSize ) );
        if ( r.contains( (int)event.getX(), (int)event.getY() ) ) {
          GameService.getInstance().setPosition(0, x, y, _color);
          invalidate();
          return true;
        }
      }
    }
    return true;
  }

  private int getSize() {
    return (int) ( (float)
    ( ( getWidth() < getHeight() ) ? getWidth() : getHeight() ) * 0.8 );
  }

  private int getOffsetX() {
    return ( getWidth() / 2 ) - ( getSize( ) / 2 );
  }

  private int getOffsetY() {
    return ( getHeight() / 2 ) - ( getSize() / 2 );
  }

  private int getLineSize() {
    return ( getSize() / 3 );
  }

  protected void onDraw(Canvas canvas) {
    Paint paint = new Paint();
    paint.setAntiAlias(true);
    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);
        }
      }
    }
  }

}

A maioria do trabalho aqui é realizada no método onTouch, que responde ao toque do usuário em uma célula específica no quadro do jogo, e o método onDraw, que pinta o quadro do jogo usando o mecanismo de pintura do Android.

O método onTouch usa as funções de dimensionamento para descobrir um retângulo para cada posição na célula. Em seguida, usa o método contains no retângulo para ver se o usuário clicou dentro da célula. Em caso afirmativo, ele dispara uma solicitação para o serviço do jogo para fazer a movimentação.

A função onDraw usa as funções de dimensionamento para desenhar as linhas do quadro e desenhar quaisquer Xs e Os jogados. O singleton GameServer é usado para seu array de posições, que tem o estado atual de cada quadrado no quadro do jogo.

A última classe que você precisa é o UpdateTimer, que usa o serviço do jogo para atualizar as posições do quadro com seus valores mais recentes. A Listagem 12 mostra o código do timer.

Listagem 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 );
    boardView.post(new Runnable(){ public void run(){ boardView.invalidate(); } });
  }
}

O timer é inicializado pela classe TicTacToeActivity quando o aplicativo inicializa. Esse timer é um mecanismo de pesquisa. Esse não é o modo mais eficiente de comunicação entre o cliente e o servidor, mas é o modo mais simples e confiável. O modo mais eficiente é usar a versão 1.1 do protocolo HTTP para manter a conexão aberta e fazer o servidor enviar atualizações para o cliente quando movimentações são feitas. Essa abordagem é um pouco mais complexa e requer que o cliente e o servidor suportem o protocolo 1.1, bem como também tem problemas de escalabilidade com o número de conexões. Essa abordagem está fora do escopo deste artigo. Para jogos de simples demonstração como este, um mecanismo de pesquisa funciona suficientemente bem.

Com o código feito, é possível testar o aplicativo. Isso significa inicializar o emulador. Deve-se ver algo como a figura 3 depois da inicialização.

Figura 3. Ativando o emulador Android
Ativando o emulador Android

Este é o emulador carregando uma excelente interface "A N D R O I D". Depois de ela ser carregada, é possível ver a tela de ativação na figura 4.

Figura 4. O emulador ativado e pronto para uso
O emulador ativado e pronto para uso

Para inserir no telefone, deslize o ícone de bloqueio para a direita. Essa ação o levará à tela de início e, geralmente, ativará o aplicativo que você está depurando. Neste caso, essa ação exibirá a tela do jogo na figura 5.

Figura 5. O jogo antes de uma movimentação ter sido realizada
O jogo antes de uma movimentação ter sido realizada

Dependendo do estado do servidor, será possível ou não visualizar alguma movimentação. Neste caso, o jogo estava vazio. Os botões Play X e Play O estão no alto com o quadro do jogo da velha no meio da exibição. Em seguida, clique em Play X, e clique no quadro do centro para ver algo como a Figura 6.

Figura 6. X inserido no quadrado central, é claro
X inserido no quadrado central, é claro

A Figura 6 mostra o quadro do jogo com um X agora ocupando o quadrado central. Para verificar se o servidor estava conectado, é possível executar o comando curl no script moves.php no servidor para obter a lista mais recente de movimentações do jogo.

Para testar se os Os funcionam, clique em Play O e selecione um quadrado no canto, como na Figura 7.

Figura 7. O é inserido em um quadro no canto
O é inserido em um quadro no canto

É possível jogar com Xs e Os. O aplicativo se conecta ao servidor para manter o estado do jogo em um local compartilhado. E devido ao timer de atualização, cada usuário pode ver as movimentações feitas pelo outro.

Conclusão

O jogo está completo? Na verdade, não. Não há nenhuma verificação de condição de vitória, os jogadores podem sobrescrever posições e não há nenhuma verificação de quem é a vez. Mas as peças da tecnologia básica estão presentes: um servidor de jogos com estado armazenado compartilhado entre os jogadores, e um aplicativo gráfico nativo em um dispositivo móvel que se conecta ao servidor de jogos para fornecer uma interface para o jogo. É possível usar esse jogo como ponto de partida para seu próprio jogo e desenvolvê-lo como quiser. Lembre-se apenas de mantê-lo casual e divertido, e você pode ter, por conta própria, o próximo Words With Friends ou Angry Birds de vários jogadores.

Recursos

Aprender

  • Eclipse: saiba sobre o IDE usado neste artigo para desenvolver o aplicativo Android. Localize também downloads e plug-ins do Eclipse.
  • Ferramentas de Desenvolvimento de PHP para Eclipse: necessita de um IDE para PHP? O projeto Eclipse tem uma extensão para isso, além de outros plug-ins do Eclipse para praticamente quase tudo.
  • Mercado do Android: depois de escrever o seu jogo casual em rede do Android, faça upload dele para o mercado de trabalho do Android. E permita-nos saber o que você fez usando a seção comentários deste artigo.
  • Site PHP : explore a melhor referência para PHP que está disponível.
  • A W3C: Visite um ótimo site sobre padrões, em particular o padrão XML é relevante para este artigo.
  • Mais artigos desse autor (Jack Herrington, developerWorks, março de 2005-hoje): Leia artigos sobre Ajax, JSON, PHP, XML e outras tecnologias.
  • Iniciante em XML? Obtenha os recursos necessários para aprender XML.
  • Área de XML do developerWorks: localize os recursos necessários para avançar em conhecimentos na arena XML, incluindo DTDs, esquemas e XSLT. Consulte o site Biblioteca técnica de XML para obter um intervalo amplo de artigos técnicos e dicas, tutoriais, padrões e IBM Redbooks.
  • Certificação XML da IBM: Descubra como se tornar um Desenvolvedor Certificado pela IBM em XML e tecnologias relacionadas.
  • eventos técnicos e webcasts do developerWorks: Mantenha-se atualizado em relação à tecnologia nessas sessões.
  • DeveloperWorks no Twitter: Inscreva-se hoje para seguir os tweets do developerWorks.
  • Podcasts do developerWorks: Ouça entrevistas e discussões interessantes para desenvolvedores de software.
  • Demos on demand do developerWorks: Acompanhe demos que abrangem desde a instalação de produto e configuração para iniciantes até funcionalidade avançada para desenvolvedores experientes.

Obter produtos e tecnologias

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=755923
ArticleTitle=Criar um Jogo da Velha em Rede para Android
publish-date=09062011