Java

 Jogo da Velha XML

Trabalho da Disciplina: Conceitos de XML
Prof. Ms. Marcos Macedo

MBA em Desenvolvimento de Aplicações Java - SOA / FIAP
09/01/2012

Christiny Belini Goulart
Fabio Gonçalves Martins
Robson de Sousa Martins


Download do código-fonte:

Jogo da Velha XML (ZIP ~3MB)


Tecnologias empregadas:

  • XML e Validação Schema (XSD);
  • Parser DOM;
  • Sockets TCP (através do framework Apache Mina);
  • Java Swing;
  • Java2D API.

Enunciado:

O "Jogo da Velha" é um jogo de raciocínio projetado para versões em sistema desktop (PC Windows), internet e celulares.

Objetivo

O objetivo ao desenvolver o "Jogo da Velha" é fornecer um jogo casual para entretenimento em curtos períodos de tempo, baseado no jogo da velha tradicional. O principal objetivo é realizar todas as jogadas entre 2 oponentes através do envio de XML entre máquinas, onde cada um pode desenvolver um Java Servlet ou Java Socket, tanto para receber o XML, criticar se alguém venceu, e devolver para os oponentes a configuração atual do tabuleiro. Como o foco da disciplina é o entendimento de XML, estaremos analisando os aspectos de uso, validações e controles. O grupo está livre para definir todo o contexto tecnológico, assim como os procedimentos de envio e recebimento de XML, e também da estrutura do XML.


Arquivos do Pacote ZIP:

  • ProjetosEclipse/: Diretório que contém os projetos Java para Eclipse.
  • ProjetosEclipse/JogoDaVelhaServer.zip: Projeto Java para o Eclipse, contendo o Servidor de Sockets do Jogo da Velha.
  • ProjetosEclipse/JogoDaVelhaClient.zip: Projeto Java para o Eclipse, contendo um Cliente de Sockets do Jogo da Velha (aplicação com GUI em Swing).
  • ProjetosEclipse/JogoDaVelhaRobot.zip: Projeto Java para o Eclipse, contendo um Robot do Jogo da Velha (aplicação com GUI em Swing). É semelhante ao Cliente, mas este permite o computador jogar contra outro jogador conectado ao Servidor (que poderá ser um "humano" ou outro "robot").
  • ProjetosEclipse/JogoDaVelhaTestsJUnit.zip: Projeto Java para o Eclipse, que contém casos de teste JUnit (4.0) usados durante o desenvolvimento, para testar as classes VelhaEngine e VelhaParser. Não é necessário para a operação normal do jogo: somente incluímos este pacote por questões didáticas.
  • ExemplosXML/: Diretório que contém o arquivo velha.xsd (schema de validação do XML do Jogo da Velha) e alguns arquivos XML de exemplo.

Sobre a implementação:

Arquitetura da Solução

O "Jogo da Velha" utiliza transferência de arquivos XML por meio de sockets de rede.

A implementação de sockets TCP se baseia no framework Apache Mina (http://mina.apache.org/). Esse é um framework Java que fornece uma API simples para lidar com sockets, além de oferecer alto desempenho e gerenciamento automático de threads (sessões).

O servidor de sockets ("Jogo da Velha Server") escuta em uma porta TCP (default: 9123), e espera pela conexão dos clientes.

A partida não inicia enquanto não houver exatamente dois clientes ("Jogo da Velha Client") conectados ao servidor.

A implementação do "Jogo da Velha Server" controla as sessões dos clientes conectados, impedindo que um terceiro jogador se conecte durante a partida (a conexão excedente é automaticamente derrubada).

O "Jogo da Velha Client" é uma aplicação cliente de sockets, com interface gráfica (GUI) escrita em Java Swing e Java2D, permitindo uma boa experiência de "jogabilidade" ao usuário.

Ainda há outro tipo de cliente, o "Jogo da Velha Robot", que é uma aplicação cliente de sockets, que se conecta a um servidor, permitindo o computador jogar.

Sendo assim, podem ser combinados Client's e Robot's em diferentes configurações, para alterar o tipo de partida:

  • 2 Client’s ("humano" contra "humano")
  • 1 Client e 1 Robot ("humano" contra "computador")
  • 2 Robot’s ("computador" contra "computador")

Os arquivos XML que trafegam via sockets de rede são validados através de schema (XSD) e processados (parse) através do DOM.

A lógica que avalia o status do jogo e coordena o envio e recebimento dos arquivos XML, está implementada no servidor.


Formato do XML

O arquivo XML transmitido entre os clientes e o servidor segue um formato específico.
Abaixo há uma breve descrição dos principais elementos.

Identificação do Jogador:

<id>X</id>

Pode ser 'X' ou 'O'.
Representa qual é o jogador que está recebendo ou enviando o XML.

Status do Jogo:

<status>jogue</status>

O status do jogo pode ser um destes:

  • "jogue" → é a vez do jogador definido pelo id
  • "aguarde" → é a vez do outro jogador (oponente)
  • "ganhou" → jogador definido pelo id ganhou
  • "perdeu" → jogador definido pelo id perdeu (oponente ganhou)
  • "empate" → houve empate ("deu velha")
  • "wo" → jogador oponente abandonou jogo antes do final

Os status "ganhou", "perdeu", "empate" e "wo" são considerados como status de "game over".

Estado atual do tabuleiro:

<tabuleiro>
<p0></p0>
<p1></p1>
<p2></p2>
<p3></p3>
<p4></p4>
<p5></p5>
<p6></p6>
<p7></p7>
<p8></p8>
</tabuleiro>

p0..p8: Representam as posições no tabuleiro (de 0 a 8):

[0] [1] [2]
[3] [4] [5]
[6] [7] [8]

Os valores possíveis das posições são:

  • 'X' (letra xis maiúsculo) → jogada do X
  • 'O' (letra ó maiúsculo) → jogada do O
  • ' ' (vazio ou espaço em branco) → posição vazia

Essa posição vazia, no XML, pode ser representada por uma tag vazia (sem conteúdo) ou por um caractere de espaço em branco.

Registro da posição jogada:

<jogada>4</jogada>

Indica em qual posição foi a jogada do Cliente (0 a 8). Essa tag pode ser vazia para sinalizar que não há jogada a ser registrada pelo jogador definido pelo id.

Arquivo XSD (schema):

O arquivo velha.xsd define o formato esperado para o XML que trafega entre o servidor e os clientes, e é usado pelas aplicações para validar o conteúdo recebido.

Exemplos de XML:

  1. O servidor envia o XML ao cliente X:

    <JogoDaVelha>
    <id>X</id>
    <status>jogue</status>
    <jogada></jogada>
    <tabuleiro>
    <p0>X</p0>
    <p1>O</p1>
    <p2></p2>
    <p3></p3>
    <p4></p4>
    <p5></p5>
    <p6></p6>
    <p7>O</p7>
    <p8>X</p8>
    </tabuleiro>
    </JogoDaVelha>

    O tabuleiro desse jogador pode ser mostrado assim:

    Jogador: X
    Faça sua jogada:

    [X] [O] [ ]
    [ ] [ ] [ ]
    [ ] [O] [X]

    E, ao mesmo tempo, o servidor envia o XML ao cliente O:

    <JogoDaVelha>
    <id>O</id>
    <status>aguarde</status>
    <jogada></jogada>
    <tabuleiro>
    <p0>X</p0>
    <p1>O</p1>
    <p2></p2>
    <p3></p3>
    <p4></p4>
    <p5></p5>
    <p6></p6>
    <p7>O</p7>
    <p8>X</p8>
    </tabuleiro>
    </JogoDaVelha>

    O tabuleiro do jogador O pode ser mostrado assim:

    Jogador: O
    Aguarde a sua vez...

    [X] [O] [ ]
    [ ] [ ] [ ]
    [ ] [O] [X]


  2. O cliente X joga na posição 4, e envia esse XML para o servidor:

    <JogoDaVelha>
    <id>X</id>
    <status>jogue</status>
    <jogada>4</jogada>
    <tabuleiro>
    <p0>X</p0>
    <p1>O</p1>
    <p2></p2>
    <p3></p3>
    <p4></p4>
    <p5></p5>
    <p6></p6>
    <p7>O</p7>
    <p8>X</p8>
    </tabuleiro>
    </JogoDaVelha>

    O servidor confere o id (se é realmente o jogador X que está enviando o arquivo), se é a vez de jogar, se a posição 4 (em memória) está vazia, e então, grava a jogada.

    O servidor ignora o tabuleiro enviado no XML: ele usa o que mantém internamente em memória (para evitar fraudes de um jogador que adultere o arquivo XML).

    O servidor faz o parse, analisa se alguém ganhou e monta os dois XML's para os dois clientes.

    O servidor envia o XML para o cliente X:

    <JogoDaVelha>
    <id>X</id>
    <status>ganhou</status>
    <jogada></jogada>
    <tabuleiro>
    <p0>X</p0>
    <p1>O</p1>
    <p2></p2>
    <p3></p3>
    <p4>X</p4>
    <p5></p5>
    <p6></p6>
    <p7>O</p7>
    <p8>X</p8>
    </tabuleiro>
    </JogoDaVelha>

    O tabuleiro do jogador X pode ser mostrado assim:

    Jogador: X
    Você Ganhou! Parabéns!

    [X] [O] [ ]
    [ ] [X] [ ]
    [ ] [O] [X]

    O servidor envia o XML para o cliente O:

    <JogoDaVelha>
    <id>O</id>
    <status>perdeu</status>
    <jogada></jogada>
    <tabuleiro>
    <p0>X</p0>
    <p1>O</p1>
    <p2></p2>
    <p3></p3>
    <p4>X</p4>
    <p5></p5>
    <p6></p6>
    <p7>O</p7>
    <p8>X</p8>
    </tabuleiro>
    </JogoDaVelha>

    O tabuleiro do jogador O pode ser mostrado assim:

    Jogador: O
    Sinto muito, mas você perdeu!
    Vá treinar mais!

    [X] [O] [ ]
    [ ] [X] [ ]
    [ ] [O] [X]


Fluxo de Operação do Jogo da Velha

  1. Servidor inicializa e fica no ar escutando na porta TCP;
  2. Um Cliente (C1) conecta ao Servidor;
  3. O Servidor sorteia um ID (JOGADOR_X ou JOGADOR_O) para o C1;
  4. O Servidor envia um XML para o cliente (C1), com o ID, com o STATUS_AGUARDE e com o tabuleiro limpo;
  5. Outro Cliente (C2) conecta ao Servidor;
  6. O Servidor envia um XML para o Cliente (C2), com o ID, com o STATUS_AGUARDE e com o tabuleiro limpo;
  7. O Servidor sorteia quem vai começar o jogo (digamos que ele escolheu o Cliente C2);
  8. O Servidor envia para o Cliente C2 o XML com o STATUS_JOGUE;
  9. O Cliente C2 recebe esse XML e libera o direito de jogada para esse jogador;
  10. O jogador faz a jogada e o Cliente C2 envia um XML para o Servidor: JOGADA = <posição onde foi a jogada>;
  11. O Servidor valida e confere o XML enviado pelo Cliente C2: se o ID está certo, se a JOGADA é válida (de 0 a 8 e em posição correntemente marcada como JOGADOR_VAZIO) e se a vez da jogada corresponde ao Cliente C2;
  12. O Servidor sempre ignora o tabuleiro escrito no XML enviado pelo cliente; ele usa o que está internamente armazenado (no objeto VelhaEngine);
  13. Se a jogada for inválida, o Servidor repete o envio do XML (passo 8);
  14. Se a jogada for válida, o Servidor a registra no seu tabuleiro interno (no objeto VelhaEngine) e envia os XML's para os Clientes:
  15. Cliente C2 recebe um XML com STATUS_AGUARDE e o novo tabuleiro;
  16. Cliente C1 recebe um XML com STATUS_JOGUE e o novo tabuleiro;
  17. Com isso, a vez da jogada vai para o Cliente C1.
  18. Todo o fluxo é repetido (passos 8 a 17), porém com os Clientes invertidos. O jogo pode acabar em uma das circunstâncias (game over):
  19. VelhaEngine.isEmpate() - Houve um empate. O Servidor envia os XML's para os dois Clientes, contendo o STATUS_EMPATE;
  20. VelhaEngine.isGanhador(JOGADOR_X) - O jogador X ganhou. O Servidor envia para o Cliente cujo ID == JOGADOR_X o XML com STATUS_GANHOU, e para o outro Cliente o XML com STATUS_PERDEU;
  21. VelhaEngine.isGanhador(JOGADOR_O) - O jogador O ganhou. O Servidor envia para o Cliente cujo ID == JOGADOR_O o XML com STATUS_GANHOU, e para o outro Cliente o XML com STATUS_PERDEU;
  22. Um dos Clientes encerra a conexão no meio do jogo: Nesse caso, o Servidor envia para o Cliente que sobrou o XML com STATUS_WO.
  23. Em qualquer caso de encerramento do jogo (citados nos passos 19 a 22), o Servidor encerra a conexão com os dois Clientes após o envio do último XML.
  24. Também, no encerramento do jogo, o Servidor limpa o tabuleiro contido no seu objeto VelhaEngine e o status do jogo encerrado.
  25. Então, para iniciar uma nova partida, os Clientes têm que se reconectar ao Servidor, e tudo recomeça como no passo 2.

Descrição da Implementação Java

Classes comuns a todas as aplicações:

br.com.fiap.velha

VelhaEngine.java: Contém a lógica do Jogo da Velha, usada pelo Servidor para avaliar o status e gerenciar a dinâmica do jogo. Também contém métodos usados pelo "Robot do Jogo da Velha" para analisar o jogo, e escolher as melhores jogadas de forma automatizada. .

VelhaBean.java: Bean (POJO) que representa o XML do Jogo da Velha.

VelhaParser.java: Implementa um Parser baseado em DOM, capaz de transformar um objeto VelhaBean em uma String contendo XML, e vice-versa.

Essa classe possui uma flag DEBUG_ENABLED que pode ser colocada em true para exibir mensagens de depuração (default: false).

Classes do "Jogo da Velha Server":

br.com.fiap.velha.server

VelhaServer.java: Contém o método principal main() que inicia o Servidor do Jogo da Velha.

VelhaServerHandler.java: Contém a implementação do manipulador de sockets das conexões dos Clientes. Implementa toda a dinâmica do jogo, o gerenciamento de sessões e avalia o status do jogo, enviando e recebendo mensagens XML de / para os Clientes.

Essa classe possui uma flag DEBUG_ENABLED que pode ser colocada em true para exibir mensagens de depuração (default: true).

Classes do "Jogo da Velha Client":

br.com.fiap.velha.client

VelhaClient.java: Contém o método principal main() que inicia o Cliente do Jogo da Velha. Também possui métodos callback que são executados quando ocorre algum evento de GUI (como um clique) ou de socket (como o recebimento de uma mensagem).

VelhaHandler.java: Contém a implementação do manipulador de sockets do Cliente. Responsável por chamar os métodos callback da classe VelhaClient quando algum evento de socket ocorre.

Essa classe possui uma flag DEBUG_ENABLED que pode ser colocada em true para exibir mensagens de depuração (default: false).

br.com.fiap.velha.gui

VelhaPanel.java: Contém a implementação necessária para desenhar um tabuleiro de Jogo da Velha (usando Java2D) em um JPanel (componente "painel" do Swing), e converter coordenadas de tela (X,Y) para posições relativas do tabuleiro (0 a 8). Também chama um método callback da classe VelhaClient quando algum clique no painel ocorre.

VelhaGUI.java: Responsável por montar a GUI da aplicação, em Swing, contendo o VelhaPanel e os controles de tela. Também chama um método callback da classe VelhaClient quando um clique em algum controle ocorre.

VelhaListener.java: Interface que define quais métodos callback a classe VelhaClient deve implementar.

Classes do "Robot do Jogo da Velha":

br.com.fiap.velha.client

VelhaRobot.java: Contém o método principal main() que inicia o Robot do Jogo da Velha. Também possui métodos callback que são executados quando ocorre algum evento de GUI (como um clique) ou de socket (como o recebimento de uma mensagem).

VelhaHandler.java: Idêntica à classe VelhaHandler do "Cliente do Jogo da Velha".

br.com.fiap.velha.gui

VelhaPanel.java: Idêntica à classe VelhaPanel do "Cliente do Jogo da Velha".

VelhaRobotGUI.java: Responsável por montar a GUI da aplicação, em Swing, contendo o VelhaPanel e os controles de tela. Também chama um método callback da classe VelhaRobot quando um clique em algum controle ocorre.

VelhaListener.java: Idêntica à interface VelhaListener do "Cliente do Jogo da Velha".


Instruções para funcionamento das aplicações

As instruções a seguir pressupõem que os projetos serão executados a partir do Eclipse:

  1. Importar os projetos nos arquivos JogoDaVelhaServer.zip, JogoDaVelhaClient.zip e JogoDaVelhaRobot.zip, para um Workspace do Eclipse.
  2. Executar a aplicação do Servidor (JogoDaVelhaServer), através da classe VelhaServer do pacote "br.com.fiap.velha.server". O Servidor do Jogo da Velha estará então ouvindo na porta TCP 9123 (default).
  3. Abrir uma nova visualização de Console no Eclipse (se estiver no mesmo computador do servidor), e executar a aplicação Cliente (JogoDaVelhaClient), através da classe VelhaClient do pacote "br.com.fiap.velha.client".
  4. Abrir outra visualização de Console no Eclipse (se estiver no mesmo computador do servidor), e executar mais uma instância da aplicação Cliente (JogoDaVelhaClient).
  5. Nos Clientes, digitar o nome do host (ou endereço IP) do servidor (default: localhost) e a porta TCP (default: 9123) e clicar em "Conectar" para iniciar uma partida.
  6. Para jogar, deve-se clicar nas posições vazias do tabuleiro (na sua vez de jogar), para marcar a jogada.
  7. Para sair do jogo sem que ele esteja terminado, deve-se clicar em "Abortar".
  8. É possível conectar um ou dois Robot’s a um Servidor de Jogo da Velha, executando a aplicação JogoDaVelhaRobotClient através da classe VelhaRobot do pacote "br.com.fiap.velha.client".

O Robot é semelhante ao Cliente, porém ele não permite cliques no tabuleiro, e se auto reconecta ao Servidor do Jogo da Velha quando uma partida é finalizada. Além disso, o Robot permite especificar um "nível de inteligência de defesa" (de 0 a 10): quanto menor esse nível, mais aleatórias serão as jogadas de defesa desse Robot.