2. Apresentação
• Jogos multiplayer fazem uso de threads para
gerenciar múltiplas tarefas, além de sockets
para envio de pacotes entre computadores.
• Na aplicação a seguir temos vários exemplos
de como isso pode ser implementado.
3. DadosDeConexao
package com.javagames.tabuleiro;
/**
* Armazena os dados de conexao da aplicacao
*
* Porta local - utilizada para o ServerSocket
* Porta remota - utilizada para o socket cliente
* Servidor - nome/ip da maquina remota
* O servidor + porta remota sao utilizados para enviar os dados a cada jogada
* Nao e necessario indicar o nome/ip da maquina local
* Nome do usuario - utilizado para personalizar sua partida
*
* @author marcoreis
*
*/
public class DadosDeConexao {
private int portaLocal;
private int portaRemota;
private String servidor;
private String nomeDoUsuario;
{...Metodos de acesso...}
}
4. UtilComunicacao
package com.javagames.tabuleiro;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* Transporta os dados entre duas maquinas utilizando socket
* Deve ser atualizada para suportar diferentes tipos de dados
* Os metodos tem suporte apenas a Map
*
* @author marcoreis
*
*/
public class UtilComunicacao {
/**
* Abre um socket entre o cliente e o servidor
* O servidor deve estar esperando (accept) esta requisicao
*
* @param dadosDeConexao Nome do servidor remoto e porta
* @param mapa - dados que serao enviados
*/
public void enviaDadosParaComputadorRemoto(DadosDeConexao dadosDeConexao,
Map mapa) {
try {
Socket socket = new Socket(dadosDeConexao.getServidor(), dadosDeConexao
.getPortaRemota());
OutputStream outSocket = socket.getOutputStream();
ObjectOutputStream saida = new ObjectOutputStream(outSocket);
saida.writeObject(mapa);
saida.close();
outSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
5. UtilComunicacao
/**
* Sincroniza dois mapas
* Por definicao, um mapa nao tem repeticao de chaves (keys)
* Isso garante que nao havera posicoes repetidas
*
* @param mapaRecuperado
* @return Mapa sincronizado
*/
public Map sincronizaMapas(Map<String, String> mapaRecuperado) {
Map mapa = new HashMap();
for (Object o : mapaRecuperado.keySet()) {
mapa.put(o.toString(), mapaRecuperado.get(o));
}
return mapa;
}
6. UtilComunicacao
/**
*
* E o outro lado do metodo enviaDadosParaComputadorRemoto
* Enquanto o primeiro esta no cliente, este esta no servidor
* Converte os dadso em Map, ou seja, nao suporta outros tipos de dados
*
* @param socket Conexao aberta entre cliente e servidor
* @return Mapa enviado
*/
public Map extrairMapa(Socket socket) {
try {
InputStream dados = socket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(dados);
Object o = ois.readObject();
Map<String, String> mapaRecuperado = (Map<String, String>) o;
return mapaRecuperado;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
7. JogoDaVelha
package com.javagames.tabuleiro;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import com.javagames.util.*;
/**
* Jogo da velha multiplayer
* Utiliza sockets para comunicar as jogadas entre jogadores
*
* @author marcoreis
*
*/
@SuppressWarnings("serial")
public class JogoDaVelha extends JFrame {
private static final int QUANTIDADE_DE_CASAS = 3;
private int ALTURA = 350;
private int LARGURA = 300;
private Map<String, String> mapaDeCasas;
private String jogadorAtual;
private Point pontoClicado;
private DadosDeConexao dadosDeConexao;
private UtilComunicacao utilComunicacao;
8. JogoDaVelha
/**
* MŽtodo construtor
* Inicializacao dos parametros obrigatorios
*/
public JogoDaVelha() {
setSize(LARGURA, ALTURA);
setBackground(Color.BLUE);
adicionaEventoCliqueDoMouse();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
/**
* Chama o construtor padrao
* @param dados - Dados de conexao informado pelo usuario
*/
public JogoDaVelha(DadosDeConexao dados) {
this();
this.dadosDeConexao = dados;
setTitle("Bem-vindo, " + dados.getNomeDoUsuario());
}
/**
* Adicionar listener para cliques do mouse
*/
private void adicionaEventoCliqueDoMouse() {
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
eventoCliqueDoMouse(e);
}
});
}
9. JogoDaVelha
/**
* Algoritmo a ser implementado
* @return Se o jogador atual ganhou ou nao
*/
private boolean ganhou() {
try {
int contador = 0;
//Verifica se ganhou na vertical
for (int i = 0; i < 3; i++) {
//Verifica se as posicoes estao ocupadas pelo jogador atual
for (int j = 0; j < 3; j++) {
if (getMapaDeCasas().get(i + "," + j) != null && getMapaDeCasas().get(i + "," + j).equals(jogadorAtual)) {
contador++;
}
//Caso tenha as tres posicoes ocupadas, retorna true, jogador atual ganhou
if (contador == 3) {
return true;
}
}
//Quando muda de coluna, zera o contador
contador = 0;
}
} catch (Exception e) {
}
return false;
}
10. JogoDaVelha
/**
* Calcula a celular que deve ser utilizada a partir do ponto clicado na tela
* @param pontoClicado
*/
private void marcaPosicao(Point pontoClicado) {
int indiceX = pontoClicado.x / (getTamanhoDaCasaX());
int indiceY = pontoClicado.y / (getTamanhoDaCasaY());
String posicao = indiceX + "," + indiceY;
getMapaDeCasas().put(posicao, jogadorAtual);
//Envia os dados para a thread do oponente, que esta esperando
getUtilComunicacao().enviaDadosParaComputadorRemoto(dadosDeConexao, getMapaDeCasas());
setTitle(dadosDeConexao.getNomeDoUsuario() + ", voce jogou. Aguarde.");
}
/**
*
* @return {@link}
*/
private UtilComunicacao getUtilComunicacao() {
if (utilComunicacao == null) {
utilComunicacao = new UtilComunicacao();
}
return utilComunicacao;
}
11. JogoDaVelha
/**
*
* @param pontoClicado Coordenadas do clique do usuario
* @return Se o quadrante clicado est‡ ou nao ocupado
*/
private boolean posicaoJahMarcada(Point pontoClicado) {
int indiceX = pontoClicado.x / (getTamanhoDaCasaX());
int indiceY = pontoClicado.y / (getTamanhoDaCasaY());
String posicao = indiceX + "," + indiceY;
if (getMapaDeCasas().get(posicao) != null) {
return true;
}
return false;
}
/**
* Calculo para definir o tamanho das celulas utilizadas.
* Jamais utilize valores fixos, pois isso impede a evolucao do sistema
* @return Tamanhos
*/
private int getTamanhoDaCasaY() {
return ALTURA / QUANTIDADE_DE_CASAS;
}
private int getTamanhoDaCasaX() {
return LARGURA / QUANTIDADE_DE_CASAS;
}
12. /**
* Desenha/redesenha a tela a cada clique
* Ou, na versao multiplayer, a cada jogada
*/
public void paint(Graphics g) {
super.paint(g);
//Vertical
g.drawLine((LARGURA / 3), 30, (LARGURA / 3), ALTURA - 10);
g.drawLine((LARGURA / 3) * 2, 30, (LARGURA / 3) * 2, ALTURA - 10);
//Horizontal
g.drawLine(10, ALTURA / 3, LARGURA - 10, ALTURA / 3);
g.drawLine(10, (ALTURA / 3) * 2, LARGURA - 10, (ALTURA / 3) * 2);
imprimeMapaNoTabuleiro();
//
}
JogoDaVelha
/**
* Imprime o mapa no tabuleiro
* A chave (key) do mapa esta no formato 'x,y'
* Por isso temos o stringtokenizer que recupera cada valor separado pela ','
* Em seguida le cada posicao marcada do mapa, recuperando o jogador marcado
* Por fim, desenha na celula x,y a imagem respectiva
* O loop le todos os elementos que compoe o mapa naquele momento
*
*/
public void imprimeMapaNoTabuleiro() {
for (Object o : getMapaDeCasas().keySet()) {
//o (formato x,y)
try {
StringTokenizer s = new StringTokenizer(o.toString(), ",");
int posicaoX = new Integer(s.nextToken()) * getTamanhoDaCasaX() + 30;
int posicaoY = new Integer(s.nextToken()) * getTamanhoDaCasaY() + 50;
//
String jogador = getMapaDeCasas().get(o);
desenhaIcone(posicaoX, posicaoY, jogador);
//
} catch (Exception e) {
System.out.println(e);
}
}
}
13. JogoDaVelha
/**
* Desenha a respectiva imagem dada uma posicao e um jogador
* @param posicaoX
* @param posicaoY
* @param jogador
*/
private void desenhaIcone(int posicaoX, int posicaoY, String jogador) {
Graphics g = getGraphics();
if (jogador.equals("X")) {
g.drawImage(UtilImagem.getImagem("xis.png"), posicaoX, posicaoY, this);
} else {
g.drawImage(UtilImagem.getImagem("zero.png"), posicaoX, posicaoY, this);
}
}
/**
* Armazena todas as jogadas ate o presente instante
* @return Mapa atual
*/
public Map<String, String> getMapaDeCasas() {
if (mapaDeCasas == null) {
mapaDeCasas = new HashMap<String, String>();
}
return mapaDeCasas;
}
public void setMapaDeCasas(Map<String, String> mapaDeCasas) {
this.mapaDeCasas = mapaDeCasas;
}
14. JogoDaVelha
/**
* Efetua todas as operacoes referentes ao evento clique do mouse
* A cada jogada do usuario, o jogo deve aplicar todas as regras definidas
* Alem de informar se algo nao esta correto
* @param e Ponto clicado na tela
*/
private void eventoCliqueDoMouse(MouseEvent e) {
pontoClicado = e.getPoint();
defineJogador();
if (posicaoJahMarcada(pontoClicado)) {
JOptionPane.showMessageDialog(null, "Posicao ja ocupada.", "Atencao",
JOptionPane.WARNING_MESSAGE);
} else {
repaint();
marcaPosicao(pontoClicado);
}
if (ganhou()) {
JOptionPane.showMessageDialog(null, "Ganhou");
}
}
/**
* Nas duas primeiras jogadas da partida verifica quem e quem
* O primeiro jogador sera '0'
* O segundo sera 'X'
*/
private void defineJogador() {
if (getMapaDeCasas().keySet().size() == 0 && jogadorAtual == null) {
jogadorAtual = "0";
} else if (getMapaDeCasas().keySet().size() == 1 && jogadorAtual == null) {
jogadorAtual = "X";
}
}
}
15. ThreadJogoDaVelha
package com.javagames.tabuleiro;
import java.net.*;
import java.util.*;
public class ThreadJogoDaVelha extends Thread {
private DadosDeConexao dadosDeConexao;
private JogoDaVelha jogo;
private UtilComunicacao utilComunicacao;
/**
* Metodo construtor que exige os dados de conexao e o seu tabuleiro
*
* Esta classe gerencia as jogadas do oponente atraves do socket
* Observacao: Nao ha controle para bloquear o jogador depois do clique
* Isso permite que alguem jogue duas vezes
*
* @param jogo Tabuleiro do seu jogo
* @param dados Informacoes necessarias para conectar os tabuleiros
*/
public ThreadJogoDaVelha(JogoDaVelha jogo, DadosDeConexao dados) {
this.dadosDeConexao = dados;
this.jogo = jogo;
}
16. ThreadJogoDaVelha
/**
* A thread fica aguardando ate a jogada do oponente
* Apos a jogada, bloqueia a thread (accept)
* Assim que o oponente jogar, sincroniza os mapas
*
*/
@SuppressWarnings("unchecked")
public void run() {
//
while (true) {
try {
jogo.repaint();
Thread.sleep(1000);
//Aguarda jogada do oponente
//Bloqueia a thread
ServerSocket server = new ServerSocket(dadosDeConexao.getPortaLocal());
Socket socket = server.accept();
//O oponente jogou
jogo.setTitle(dadosDeConexao.getNomeDoUsuario() + ", agora Ž sua vez.");
//Processa jogada
//Recupera o mapa de jogadas do oponente
Map<String, String> mapaRecuperado = getUtilComunicacao().extrairMapa(
socket);
//Sincroniza o seu tabuleiro
Map mapaDeCasas = getUtilComunicacao().sincronizaMapas(mapaRecuperado);
jogo.setMapaDeCasas(mapaDeCasas);
jogo.imprimeMapaNoTabuleiro();
//Desbloquear os recursos apos sua utilizacao
socket.close();
server.close();
//
} catch (Exception e) {
System.out.print("...");
}
}
}
18. Splash Screen
•
No Netbeans, crie uma tela com
os componentes indicados.
•
Inicie seu jogo e informe os
dados do oponente.
•
O botão de conectar deve iniciar
uma thread do JogoDaVelha
passando todos os dados
necessários (veja o próximo
slide).
19. Conexão
private void btnConectarActionPerformed(java.awt.event.ActionEvent evt) {//GENFIRST:event_btnConectarActionPerformed
DadosDeConexao dados = new DadosDeConexao();
dados.setPortaLocal(new Integer(txtPortaLocal.getText()));
dados.setPortaRemota(new Integer(txtPortaServidor.getText()));
dados.setServidor(txtServidor.getText());
dados.setNomeDoUsuario(txtNome.getText());
JogoDaVelha jogo = new JogoDaVelha(dados);
new ThreadJogoDaVelha(jogo, dados).start();
setVisible(false);
}//GEN-LAST:event_btnConectarActionPerformed