Tutorial Android – Jogo da Velha com Interface 2D
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); } }
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.
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.
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.
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.
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.
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.


Parabéns pelo tutorial, muito simples, bem explicado e direto. Gosto muito dos tutoriais daqui e sempre estou atento ao site.
Se eu ganhar na ultima jogada ele da velha, de resto parabéns pelo tutorial !