Tutorial Android – Jogo da Velha com Interface 2D

On 13 de janeiro de 2016 by Thiago Urzedo

Olá pessoal, tudo bem?

Agora vamos dar continuidade ao tutorial onde construimos o nosso primeiro aplicativo. Hoje vamos construir um app simples de Jogo da Velha utilizando alguns  dos mecanismos de interface gráfica 2D.

 

Parte 1 – Configurações iniciais

Para começar, crie um novo projeto vazio no Android Studio. No tutorial anterior, usamos o arquivo activity_main.xml, que fica dentro da pasta res/layout, para definir o layout de nosso aplicativo. Desse vez o faremos programaticamente, utilizando apenas o arquivo MainActivity contido dentro da pasta java/”nome.do.seu.pacote”.

Dentro do arquivo MainActivity, crie uma classe chamada Tela que seja uma extensão da classe View. Essa classe Tela nos permitir desenhar qualquer qualquer forma ou imagem dentro de uma Activity. Após criada, apertando Alt+Enter, o Android Studio importará o pacote  da classe view View e criará um método construtor pára a classe Tela. Agora vamos criar uma instância da classe Tela e defini-la como “View” do app. Desse modo, ao invés de usar o layout padrão para a Activity, usaremos o que será feito na classe Tela.

Definindo o background do jogo com a Interface 2D

Vamos agora criar um background para o app. Dentro da pasta drawable, insira alguma imagem do seu gosto, do tipo png ou jpg. Agora dentro do construtor da classe Tela, insira o método “setBackgroundResource(R.drawable.nomeDaSuaImagem);” para usa-la como background. Até agora, o código deverá ficar assim:

public class MainActivity extends AppCompatActivity {

   Tela t;

   @Override
public void onCreate(Bundle savedInstanceState) {
   // Faz com que o app rode em tela cheia
   getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
   WindowManager.LayoutParams.FLAG_FULLSCREEN);
   super.onCreate(savedInstanceState);
   t = new Tela(this);
   setContentView(t);
}

   class Tela extends View{
   Tela(Context context){
       super(context);
       setBackgroundResource(R.drawable.blackboard);
   }
}
Background do aplicativo

Background do aplicativo

Agora vamos desenhar o tabuleiro utilizando o método onDraw, que recebe como parâmetro um objeto Canvas. O objeto Canvas define o que será desenhado (como um retângulo ou um círculo, por exemplo). Também precisamos de um objeto Paint, que define como será desenhado (um círculo de bordas vermelhas com o fundo branco, por exemplo). Para infromações mais detalhadas sobre Canvas e Paint, acesse a documentação oficial do Android. Dito isso, insira o código abaixo na classe Tela:

@Override
protected void onDraw(Canvas c) {
   super.onDraw(c);

   // Tamanho da tela
   Display display = getWindowManager().getDefaultDisplay();
   Point size = new Point();
   display.getSize(size);
   int width = size.x;
   int height = size.y;

   // Define a pintura
   Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
   p.setStyle(Paint.Style.STROKE);
   p.setColor(getResources().getColor(R.color.branco));

   //Linhas Verticais
   c.drawLine(width/3,0,width/3,height,p);
   c.drawLine((width/3)*2,0,(width/3)*2,height,p);

   //Linhas Horizontais
   c.drawLine(0,height/3,width,height/3,p);
   c.drawLine(0,(height/3)*2,width, (height / 3) * 2, p);
}

Primeiro pegamos o tamanho da tela do dispositivo e salvamos nas variáveis width e height, que representam a largura e a altura da tela respectivamente. Depois criamos um objeto Paint que irá definir o estilo e a cor do que será desenhado (Repare no atributo “Paint.ANTI_ALIAS_FLAG”, ele fará com que as bordas do que for desenhado sejam suavizadas, um recurso utilizado em praticamente em todos os jogos hoje em dia). E finalmente, as linhas que compoem o tabuleiro foram desenhadas utilizando o método drawLine, que recebe como parâmetro as coordenadas iniciais e finais de x e y e um objeto Paint respectivamente, para informações mais detalhadas sobre esse método, acesse a documentação oficial do android.

Tabuleiro desenhado

Tabuleiro desenhado

Definindo as cores a serem usadas na Interface 2D

Ainda temos que definir as cores que vamos utilizar. Vá ao arquivo colors.xml que se encontra no diretório res/values e acrescente as seguintes cores ao arquivo:

<color name="branco">#FFFAFA</color>
<color name="corDoX">#8A2BE2</color>
<color name="corDoO">#FFFF00</color>
<color name="riscaLinha">#B22222</color>

Assim, o erro que estava sendo acusado desaparecerá.

 

Parte 2 – Desenhando as formas em seus devidos lugares

Agora, vamos criar uma variável bi-dimensional, que irá armazenar o estado de cada célula do tabuleiro, e, uma variável booleana que irá definir se será desenhado um X ou um O no tabuleiro (declare-as antes do método onCreate).

int[][] tabuleiro = {
       {0,0,0},
       {0,0,0},
       {0,0,0}
};

boolean vezdox = true;

String msg = "";

Feito isso, insira o seguinte código dentro da classe Tela:

@Override
public boolean onTouchEvent(MotionEvent me) {
   if(me.getAction() == MotionEvent.ACTION_UP) {
       float x = me.getX();
       float y = me.getY();

       // Tamanho Da tela
       Display display = getWindowManager().getDefaultDisplay();
       Point size = new Point();
       display.getSize(size);
       int width = size.x;
       int height = size.y;

       for(int n = 0;n<3;n++){
           for(int m = 0;m<3;m++){
               if(x>m*(width/3) && x<(m+1)*(width/3) && y>n*(height/3) && y<(n+1)*(height/3)){
                   if(tabuleiro[m][n] == 0) {
                       // Operador ternário, igual a: if(vezdox)tabuleiro[m][n] = 1; else tabuleiro[m][n] = 2;
                       tabuleiro[m][n] = (vezdox?1:2);
                       vezdox = !vezdox;
                   }
               }
           }
       }
       t.invalidate();
   }
   return true;
}

E o seguinte código dentro do método onDraw:

p.setColor(getResources().getColor(R.color.corDoO));
for(int n = 0;n<3;n++) {
   for(int m = 0;m<3;m++){
       if(tabuleiro[m][n] == 2)
           c.drawCircle( ((width/3)/2)+(m*(width/3)), ((height/3)/2)+(n*(height/3)) ,((width/3)/2),p );
   }
}

p.setColor(getResources().getColor(R.color.corDoX));
for(int n = 0;n<3;n++) {
   for(int m = 0;m<3;m++) {
       if(tabuleiro[m][n] == 1) {
           c.drawLine(m*(width/3),n*(height/3),(m+1)*(width/3),(n+1)*(height/3),p);
           c.drawLine(m*(width/3),(n+1)*(height/3),(m+1)*(width/3),n*(height/3),p);
       }
   }
}

O método onTouchEvent nos permite verificar em qual parte da tela o usuário tocou, por meio dos métodos getX() e getY(), que retornam as coordenadas x e y do toque. Depois disso, com os valores da altura e largura, podemos fácilmente achar em qual célula do campo será desenhado o X ou o O, verificando em qual intervalo as coordenadas se encontram. Então, a posição correspondente da variável tabuleiro é atualizada (com o valor 1 se for ser escrito um X ou 2 se  for O). Em seguida, o método invalidate chama o método onDraw, que vai desenhar a respectiva forma na regiao determinada anteriormente.

Formas desenhadas, com o flag Anti Alias (Direita) e sem o flag (Esquerda)

Formas desenhadas, com o flag Anti Alias ativado (Direita) e desativado (Esquerda)

 

Parte 3 – Configurações finais na Interface 2D

 

Traçando uma linha sobre as células quando há um ganhador

Vamos agora fazer com que o aplicativo risque uma linha vermelha sobre 3 células adjacentes quando elas possuirem o mesmo valor, ou seja, quando algum dos jogadores ganhar. Insira o seguinte código ao final do método onDraw:

//Seta cor e espessura da linha
p.setColor(getResources().getColor(R.color.riscaLinha));
p.setStrokeWidth(4f);

//Linhas Horizontais
if(tabuleiro[0][0] != 0) {
   if (tabuleiro[0][0] == tabuleiro[1][0] && tabuleiro[0][0] == tabuleiro[2][0]) {
       c.drawLine(0, ((height / 3) / 2), width, ((height / 3) / 2), p);
   }
}
if(tabuleiro[0][1] != 0) {
   if (tabuleiro[0][1] == tabuleiro[1][1] && tabuleiro[0][1] == tabuleiro[2][1]) {
       c.drawLine(0, ((height / 3) / 2) + (height / 3), width, ((height / 3) / 2) + (height / 3), p);
   }
}
if(tabuleiro[0][2] != 0) {
   if (tabuleiro[0][2] == tabuleiro[1][2] && tabuleiro[0][2] == tabuleiro[2][2]) {
       c.drawLine(0, ((height / 3) / 2) + ((height / 3) * 2), width, ((height / 3) / 2) + ((height / 3) * 2), p);
   }
}

//Linhas Verticais
if(tabuleiro[0][0] != 0) {
   if (tabuleiro[0][0] == tabuleiro[0][1] && tabuleiro[0][0] == tabuleiro[0][2]) {
       c.drawLine((width / 3) / 2, 0, (width / 3) / 2, height, p);
   }
}
if(tabuleiro[1][0] != 0) {
   if (tabuleiro[1][0] == tabuleiro[1][1] && tabuleiro[1][0] == tabuleiro[1][2]) {
       c.drawLine(((width / 3) / 2) + (width / 3), 0, ((width / 3) / 2) + (width / 3), height, p);
   }
}
if(tabuleiro[2][0] != 0) {
   if (tabuleiro[2][0] == tabuleiro[2][1] && tabuleiro[2][0] == tabuleiro[2][2]) {
       c.drawLine(((width / 3) / 2) + ((width / 3) * 2), 0, ((width / 3) / 2) + ((width / 3) * 2), height, p);
   }
}

//Linhas Diagonais
if(tabuleiro[0][0] != 0) {
   if (tabuleiro[0][0] == tabuleiro[1][1] && tabuleiro[0][0] == tabuleiro[2][2]) {
       c.drawLine(0, 0, width, height, p);
   }
}
if(tabuleiro[2][0] != 0) {
   if (tabuleiro[2][0] == tabuleiro[1][1] && tabuleiro[2][0] == tabuleiro[0][2]) {
       c.drawLine(0, height, width, 0, p);
   }
}

Primeiro, mudamos a cor e a espessura da linha a ser traçada, depois verificamos se há células adjacentes com o valor igual, caso sim, uma linha vermelha é traçada no meio delas, indicando que o jogo acabou.

Linha vermelha que indica fim de jogo

Linha vermelha que indica fim de jogo

Definindo um vencedor e reiniciando a aplicação

Apesar do app traçar uma linha sobre as células quando há um vencedor, o usuário ainda pode continuar jogando e, ainda precisamos verificar se não houve nenhum vencedor. Vamos corrigir isso fazendo algumas alterações no código.

O jogador ainda pode continuar jogando mesmo depois da linha ser traçada

O jogador ainda pode continuar jogando
mesmo depois da linha ser traçada

Insira o seguinte código ao final do método onDraw:

// Verifica se não houve ganhador
for(int i=0,m=0;m<3;m++){
   for(int n=0;n<3;n++){
       if(tabuleiro[m][n] != 0)
           i++;
   }
   if(i == 9)
       fimDeJogo(1);
}

Essa parte do código irá fazer uma verificação em todas as posições da variável tabuleiro e, se todas estiverem com valor diferente de 0 (que é o estado inicial de todas elas), ele chama a função fimDeJogo (que será exemplificada logo a baixo), com o parâmetro 1, indicando que não houve ganhadores.
Agora crie o seguinte método fora da classe Tela:

public void fimDeJogo(int i) {
   AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
   alertDialogBuilder.setTitle("Fim de Jogo!");

   if(i == 0){
       if(vezdox)
           msg = "O Ganhou!";
       else
           msg = "X Ganhou!";
   }
   else
       msg = "Velha!";

   alertDialogBuilder.setMessage(msg + " Clique em OK para jogar novamente").setCancelable(false).setPositiveButton("OK", new DialogInterface.OnClickListener() {
       public void onClick(DialogInterface dialog, int id) {
           Intent intent = getIntent();
           finish();
           startActivity(intent);
       }
   });

   AlertDialog alertDialog = alertDialogBuilder.create();
   alertDialog.show();
}

Por fim, faça a chamada desse método em todas as verificações de fim de jogo, passando como parâmetro o valor 0, indicando que houve um ganhador.

O método fimDeJogo cria um diálogo de alerta onde irá informar ao usuário o fim de jogo, qual foi o vencedor ou se não houve nenhum vencedor, além de dar a possibilidade do usuário reiniciar a aplicação, começando o jogo com o tabuleiro limpo.

Como o jogo ficará ao final

Como o jogo ficará ao final

O código final do jogo ficara da seguinte forma:

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.WindowManager;

public class MainActivity extends Activity {
   Tela t;

   int[][] tabuleiro = {
           {0,0,0},
           {0,0,0},
           {0,0,0}
   };

   boolean vezdox = true;
   String msg = "";

   @Override
   public void onCreate(Bundle savedInstanceState) {
       // Faz com que o app rode em tela cheia
       getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
       WindowManager.LayoutParams.FLAG_FULLSCREEN);
       super.onCreate(savedInstanceState);
       t = new Tela(this);
       setContentView(t);
   }

   public void fimDeJogo(int i) {
       AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
       alertDialogBuilder.setTitle("Fim de Jogo!");

       if(i == 0){
           if(vezdox)
               msg = "O Ganhou!";
           else
               msg = "X Ganhou!";
       }
       else
           msg = "Velha!";

       alertDialogBuilder.setMessage(msg + " Clique em OK para jogar novamente").setCancelable(false).setPositiveButton("OK", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               Intent intent = getIntent();
               finish();
               startActivity(intent);
           }
       });

       AlertDialog alertDialog = alertDialogBuilder.create();
       alertDialog.show();
   }

   class Tela extends View{
       Tela(Context context){
           super(context);
           setBackgroundResource(R.drawable.blackboard);
       }

       @Override
       public boolean onTouchEvent(MotionEvent me) {
           if(me.getAction() == MotionEvent.ACTION_UP) {
               float x = me.getX();
               float y = me.getY();

               // Tamanho Da tela
               Display display = getWindowManager().getDefaultDisplay();
               Point size = new Point();
               display.getSize(size);
               int width = size.x;
               int height = size.y;

               // Acha o lugar
               for(int n = 0;n<3;n++){
                   for(int m = 0;m<3;m++){
                       if(x>m*(width/3) && x<(m+1)*(width/3) && y>n*(height/3) && y<(n+1)*(height/3)){
                           if(tabuleiro[m][n] == 0) {
                               // Operador ternário: if(vezdox)tabuleiro[m][n] = 1; else tabuleiro[m][n] = 2;
                               tabuleiro[m][n] = (vezdox?1:2);
                               vezdox =! vezdox;
                           }
                       }
                   }
               }
               t.invalidate();
           }
           return true;
       }

       @Override
       protected void onDraw(Canvas c) {
           super.onDraw(c);

           // Tamanho Da tela
           Display display = getWindowManager().getDefaultDisplay();
           Point size = new Point();
           display.getSize(size);
           int width = size.x;
           int height = size.y;

           // Define a pintura
           Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
           p.setStyle(Paint.Style.STROKE);
           p.setColor(getResources().getColor(R.color.branco));

           //Linhas Verticais
           c.drawLine(width/3,0,width/3,height,p);
           c.drawLine((width/3)*2,0,(width/3)*2,height,p);
           //Linhas Horizontais
           c.drawLine(0,height/3,width,height/3,p);
           c.drawLine(0,(height/3)*2,width, (height / 3) * 2, p);

           p.setColor(getResources().getColor(R.color.corDoO));
           for(int n = 0;n<3;n++) {
               for(int m = 0;m<3;m++){
                   if(tabuleiro[m][n] == 2)
                       c.drawCircle( ((width/3)/2)+(m*(width/3)), ((height/3)/2)+(n*(height/3)) ,((width/3)/2),p );
               }
           }

           p.setColor(getResources().getColor(R.color.corDoX));
           for(int n = 0;n<3;n++) {
               for(int m = 0;m<3;m++) {
                   if(tabuleiro[m][n] == 1) {
                       c.drawLine(m*(width/3),n*(height/3),(m+1)*(width/3),(n+1)*(height/3),p);
                       c.drawLine(m*(width/3),(n+1)*(height/3),(m+1)*(width/3),n*(height/3),p);
                   }
               }
           }

           //Seta cor e espessura da linha
           p.setColor(getResources().getColor(R.color.riscaLinha));
           p.setStrokeWidth(4f);
    
           // Verifica se há ganhador
    
           //Linhas Horizontais
           if(tabuleiro[0][0] != 0) {
               if (tabuleiro[0][0] == tabuleiro[1][0] && tabuleiro[0][0] == tabuleiro[2][0]) {
                   c.drawLine(0, ((height / 3) / 2), width, ((height / 3) / 2), p);
                   fimDeJogo(0);
               }
           }
           if(tabuleiro[0][1] != 0) {
               if (tabuleiro[0][1] == tabuleiro[1][1] && tabuleiro[0][1] == tabuleiro[2][1]) {
                   c.drawLine(0, ((height / 3) / 2) + (height / 3), width, ((height / 3) / 2) + (height / 3), p);
                   fimDeJogo(0);
               }
           }
           if(tabuleiro[0][2] != 0) {
               if (tabuleiro[0][2] == tabuleiro[1][2] && tabuleiro[0][2] == tabuleiro[2][2]) {
                   c.drawLine(0, ((height / 3) / 2) + ((height / 3) * 2), width, ((height / 3) / 2) + ((height / 3) * 2), p);
                   fimDeJogo(0);
               }
           }

           //Linhas Verticais
           if(tabuleiro[0][0] != 0) {
               if (tabuleiro[0][0] == tabuleiro[0][1] && tabuleiro[0][0] == tabuleiro[0][2]) {
                   c.drawLine((width / 3) / 2, 0, (width / 3) / 2, height, p);
                   fimDeJogo(0);
               }
           }
           if(tabuleiro[1][0] != 0) {
               if (tabuleiro[1][0] == tabuleiro[1][1] && tabuleiro[1][0] == tabuleiro[1][2]) {
                   c.drawLine(((width / 3) / 2) + (width / 3), 0, ((width / 3) / 2) + (width / 3), height, p);
                   fimDeJogo(0);
               }
           }
           if(tabuleiro[2][0] != 0) {
               if (tabuleiro[2][0] == tabuleiro[2][1] && tabuleiro[2][0] == tabuleiro[2][2]) {
                   c.drawLine(((width / 3) / 2) + ((width / 3) * 2), 0, ((width / 3) / 2) + ((width / 3) * 2), height, p);
                   fimDeJogo(0);
               }
           }

           //Linhas Diagonais
           if(tabuleiro[0][0] != 0) {
               if (tabuleiro[0][0] == tabuleiro[1][1] && tabuleiro[0][0] == tabuleiro[2][2]) {
                   c.drawLine(0, 0, width, height, p);
                   fimDeJogo(0);
               }
           }
           if(tabuleiro[2][0] != 0) {
               if (tabuleiro[2][0] == tabuleiro[1][1] && tabuleiro[2][0] == tabuleiro[0][2]) {
                   c.drawLine(0, height, width, 0, p);
                   fimDeJogo(0);
               }
           }

           // Verifica se não houve ganhador
           for(int i=0,m=0;m<3;m++){
               for(int n=0;n<3;n++){
                   if(tabuleiro[m][n] != 0)
                       i++;
               }
               if(i == 9)
                   fimDeJogo(1);
           }
       }
   }
}

Bom, espero que eu tenha conseguido explicar da forma mais clara e objetiva possível e, que você leitor tenha conseguido entender como funciona a aplicação. Caso tenha ficado com alguma dúvida em relaçao ao código, me mande um email em: moc.liamgnull@01odezru.ogaiht que tentarei lhe responder o mais rápido possível.

Esse foi um tutorial bem simples onde tento exemplificar o uso de alguns recursos gráficos 2D disponíveis para o Android. Recomendo fortemente que você leia o material oficial do Android pois, apesar de estar em inglês, é um material rico de conteúdo e bastante esclarecedor.

Summary
Tutorial Android - Jogo da Velha com Interface 2D
Article Name
Tutorial Android - Jogo da Velha com Interface 2D
Description
Um app simples de Jogo da Velha para mostrar alguns mecanismos de interface gráfica 2D. Android.
Author
Publisher Name
iMobilis
Publisher Logo

2 Responses to “Tutorial Android – Jogo da Velha com Interface 2D”

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *