Exercício-Programa Instruções: Este EP divide-se em 2 partes : a.Um relatório b.Uma apresentação presencial Do relatório devem constar: - diagrama de classes completo (incluindo métodos ‘private’); - respostas às questões propostas - listagem completa do programa. A apresentação presencial do EP inclui a demonstração da execução do programa e a arguição individual. Assim, todos os elementos do grupo devem estar presentes à apresentação. A nota individual, portanto, poderá ser diferente, tendo em vista as respostas à arguição oral. A nota será atribuída da seguinte forma: Item Diagrama de classes completo Respostas corretas às questões Implementação correta da Parte 1 Implementação correta da Parte 2 Arguição oral Tempo estimado (horas) 1 0.5 16 Valor máximo 2 1 4 8 2 0.2 2 Note que, mesmo tendo um peso menor, a implementação da Parte 2, que trata do jogo via Internet/Intranet, é bastante interessante!! Não deixe de fazê-la!! Data da apresentação presencial: 3 de julho, entre 13h00 e 17h00, no laboratório, para todos os alunos. Descrição Geral Deseja-se implementar um jogo de batalha naval, que permita os seguintes modos: 1. Jogador Humano contra Computador 2. Jogador Humano contra Jogador Humano, via rede Para maior facilidade de implementação, as regras do jogo serão relaxadas no seguinte sentido: Não é necessário verificar se o tabuleiro está corretamente preenchido; Não é necessário informar que tipo de embarcação foi atingida. O diagrama de classes em anexo mostra o projeto do jogo. Ele foi preparado utilizando o programa “MagicDraw” que conta com uma versão “community” para download e uso grátis. O método jogarBatalhaNaval() da classe BatalhaNaval (anexa) controla a execução do jogo. Basicamente, consiste em : _playerA.montarTabuleiroDefesa(); _playerB.montarTabuleiroDefesa(); boolean fim = false; while (! fim) { fim = jogar(_playerA, _playerB); if (fim) { _playerA.exibirMensagemVencedor(); _playerB.exibirMensagemPerdedor(); } else { fim = jogar(_playerB, _playerA); if (fim) { _playerB.exibirMensagemVencedor(); _playerA.exibirMensagemPerdedor(); } } } // while _playerA.terminar(); _playerB.terminar(); Fig.1 : Laço principal do jogo Note, pelo projeto, que há 3 tipos de jogadores, cada qual correspondendo a uma classe: JogadorHumano, JogadorComputadorizado e JogadorRemoto. Para permitir escrever a lógica do jogo sem ter de levar em conta cada tipo de jogador, foi criada a interface Player. Dessa forma, pode-se caracterizar que tipos de comportamentos são exigidos de um jogador, qualquer que seja o seu tipo. A classe Posicao será utilizada em vários pontos do programa, contendo uma linha, coluna, que pode representar a posição de um navio ou um tiro. A classe InterfaceGrafica (em anexo) contém uma proposta de interface gráfica, bastante simples, embora funcional. Sugiro que você a utilize como está, para não perder tempo criando uma nova. Ela trabalha em conjunto com a classe JogadorHumano (como mostra a seta de ‘dependência’ no diagrama de classes). As classes Tabuleiro, TabuleiroDefesa e TabuleiroAtaque contém a marcação dos navios e dos tiros dados por um jogador. São semelhantes àquelas com que você trabalhou em sala de aula. JogadorHumano e InterfaceGrafica Para que você não perca tempo codificando uma interface gráfica, a classe InterfaceGráfica está em anexo. Os métodos que estão implementados para fazer interface com a classe JogadorHumano são : InterfaceGrafica(JogadorHumano player) : construtor, que recebe como parâmetro a referência da instância de JogadorHumano associada; void exibirMensagem(String msg) : exibe uma mensagem na barra de ‘status’ void habilitarJogada() : libera o tabuleiro de ataque para uma nova jogada boolean atirou() : retorna true se já há um tiro pronto para processamento Posicao obterTiro() : retorna o último tiro, para processamento. boolean tabuleiroDefesaPronto() : retorna true se o tabuleiro de defesa já foi preparado. Para facilitar a implementação (embora resulte em uma certa ineficiência), a inter-relação entre a interface gráfica e a classe JogadorHumano é síncrona. Em outras palavras, foram implementados métodos que permitem verificar se uma determinada atividade já foi realizada e, se não foi, espera-se por um intervalo de tempo e verifica-se novamente. Assim, por exemplo, a implementação do método JogadorHumano.montarTabuleiroDefesa() torna-se: public void montarTabuleiroDefesa(){ while (! _interface.tabuleiroDefesaPronto() ) { try { Thread.sleep(1000); }catch (Exception e) {} } // while _defesaPronta = true; } // montarTabuleirDefesa Fig.2 : Método montarTabuleiroDefesa Já o método JogadorHumano.obterJogada() pode ser implementado como: public Posicao obterJogada() { _jogadaPronta = false; _interface.habilitarJogada(); _interface.exibirMensagem("Sua vez!"); while ( ! _interface.atirou() ) { try { Thread.sleep(1000); } catch (Exception e) {} } // while _ultimaJogada = _interface.obterTiro(); _interface.exibirMensagem(""); _jogadaPronta = true; return _ultimaJogada; } // obterJogada Fig.3: Método obterJogada Adicionalmente, foram implementados para suporte ao modo de jogo via rede os seguintes métodos: void exibirTiro(Posicao tiro) : exibe, graficamente, um tiro no tabuleiro de ataque. Parte 1: Modo Jogador Humano contra Computador Sempre que se implementa um comportamento ‘inteligente’ em um computador, é preciso estabelecer algum tipo de heurística. Para o EP, sugiro que você utilize as seguintes heurísticas: JogadorComputadorizado.montarTabuleiroDefesa() : utilize uma configuração prédefinida e fixa para os navios do Computador. JogadorComputadorizado.obterJogada() : faça com que o computador dê tiros aleatoriamente. O método Math.random() pode ser utilizado para tal (não esqueça de importar a classe java.lang.Math). As heurísticas são bem simples, mas lembre-se: o objetivo do EP é o treinamento em Orientação a Objetos! Os demais métodos são de implementação simples. Parte 2: Modo Jogador Humano x Jogador Humano, via rede Para a comunicação via rede, é preciso que um lado faça uma requisição e o outro, tenha a capacidade de respondê-la. Mas uma requisição pode chegar a qualquer instante! Para permitir que isso aconteça, é preciso que haja uma ‘thread’ separada, somente para atender às requisições remotas. Esta é a função da classe ServidorJogadorLocal : recebe requisições do jogador remoto (por exemplo: o tabuleiro de defesa já está pronto ?) e é capaz de interfacear com o JogadorHumano local e responder a este tipo de requisição. A classe ServidorJogadorLocal é instanciada pela classe JogadorRemoto em seu construtor, para que esteja pronta para atender este tipo de requisição o mais cedo possível. Para organizar o relacionamento entre as classes ServidorJogadorLocal e JogadorHumano (local), foi projetada a interface JogadorHumanoRemoto, que também é implementada pela classe JogadorHumano. Para jogar neste modo, na linha de comando para a invocação do programa devem ser fornecidos 3 parâmetros: o endereço de rede do jogador remoto, a porta para conexão e a porta que deve ser utilizada para receber as requisições do jogador remoto. Assim, por exemplo, para testar localmente o programa, você poderia utilizar : java BatalhaNaval localhost 8080 8081 e, em outra janela: java BatalhaNaval localhost 8081 8080 (perceba que as portas são trocadas!) As requisições e as respostas podem ser ‘strings’, com algum caracter especial (por exemplo, ‘#’) sendo utilizado como terminador. Questões 1.A interface Player foi criada para que a classe BatalhaNaval pudesse ser escrita de forma genérica, sem levar em conta as especificidades de cada tipo de jogador (Humano, Computadorizado, Remoto). Você acha que esta foi uma boa decisão de projeto ? Teria sido melhor utilizar uma hierarquia de classes ? Justifique. 2.Foi estabelecida uma hierarquia de classes entre Tabuleiro, TabuleiroDefesa e TabuleiroAtaque. Você acha que esta foi uma boa decisão de projeto ? Teria sido melhor não utilizar a super-classe Tabuleiro ? Justifique. Anexos Diagrama de Classes Classe BatalhaNaval Classe InterfaceGrafica Classe Posicao public class BatalhaNaval { public static int CONTRA_COMPUTADOR = 1; public static int CONTRA_JOGADOR_REMOTO = 2; private int _modo = CONTRA_COMPUTADOR; private Player _playerA = null; private Player _playerB = null; private String _ipComputadorRemoto = "localhost"; private int _portaComputadorRemoto = 8080; private int _portaLocal = 8081; public static void main(String[] args) { BatalhaNaval jogo = new BatalhaNaval(); jogo.jogarBatalhaNaval(args); } // main public void jogarBatalhaNaval(String[] args) { try { _modo = determinarModo(args); configurar(); _playerA.montarTabuleiroDefesa(); _playerB.montarTabuleiroDefesa(); boolean fim = false; while (! fim) { fim = jogar(_playerA, _playerB); if (fim) { _playerA.exibirMensagemVencedor(); _playerB.exibirMensagemPerdedor(); } else { fim = jogar(_playerB, _playerA); if (fim) { _playerB.exibirMensagemVencedor(); _playerA.exibirMensagemPerdedor(); } } } // while _playerA.terminar(); _playerB.terminar(); } catch(Exception e) { System.out.println ("Erro:" + e.toString()); e.printStackTrace(); } } //main public int obterModo() { return _modo; } // obterModo private boolean jogar(Player atacante, Player defensor) throws Exception { Posicao tiro = atacante.obterJogada(); defensor.exibirJogada(tiro); return defensor.verificarFim(); } // jogar private int determinarModo(String[] args) { if (args.length == 0) { return CONTRA_COMPUTADOR; } else { _ipComputadorRemoto = args[0]; _portaComputadorRemoto = Integer.parseInt(args[1]); if (args.length == 3) { _portaLocal = Integer.parseInt(args[2]); } return CONTRA_JOGADOR_REMOTO; } } // determinarModo private void configurar() throws Exception { if (_modo == CONTRA_COMPUTADOR) { _playerA = new JogadorHumano(); _playerA.setModo(BatalhaNaval.CONTRA_COMPUTADOR); _playerB = new JogadorComputadorizado(); _playerB.setModo(BatalhaNaval.CONTRA_COMPUTADOR); } else { _playerA = new JogadorHumano(); _playerA.setModo(BatalhaNaval.CONTRA_JOGADOR_REMOTO); _playerB = new JogadorRemoto(_ipComputadorRemoto, _portaComputadorRemoto, _portaLocal, (JogadorHumanoRemoto)_playerA); _playerB.setModo(BatalhaNaval.CONTRA_JOGADOR_REMOTO); } } // configurar } // BatalhaNaval import java.util.StringTokenizer; public class Posicao { public int _linha = 0; public int _coluna = 0; public Posicao (int linha, int coluna) { _linha = linha; _coluna = coluna; } // construtor public Posicao (String posicao) { String str = posicao.substring(2); // pula a letra A ou D inicial StringTokenizer st = new StringTokenizer(str, ","); _linha = Integer.parseInt(st.nextToken()); _coluna = Integer.parseInt(st.nextToken()); } } // Tiro import java.awt.*; import java.awt.event.*; class InterfaceGrafica extends java.awt.Frame implements ActionListener { Button[][] _defense = new Button[20][20]; Button[][] _attack = new Button[20][20]; Button _defesaPronta; boolean _tabuleiroDefesaPronto = false; JogadorHumano _player = null; Posicao _tiro = null; Label _status = null; InterfaceGrafica(JogadorHumano player) { _player = player; initComponents(); createBoard(_defense, 10, 50, "d "); createBoard(_attack, 300, 50, "a "); enableButtons(_attack, false); setVisible(true); } void exibirMensagem(String msg) { _status.setText(msg); _status.repaint(); } // exibirMensagem void exibirTiro(Posicao t) { int linha = t._linha; int coluna = t._coluna; Color c = _defense[linha][coluna].getBackground(); if ( c == Color.GRAY ) c = Color.GREEN; else c = Color.CYAN; _defense[linha][coluna].setBackground(c); } // exibirTiro boolean tabuleiroDefesaPronto() { return _tabuleiroDefesaPronto; } // tabuleiroDefesaPronto private void defesaPronta() { enableButtons(_defense, false); _defesaPronta.setEnabled(false); _tabuleiroDefesaPronto = true; } // defesaPronta void habilitarJogada() { _tiro = null; enableButtons(_attack, true); } // enableButtons boolean atirou() { if (_tiro == null) return false; return true; } // atirou Posicao obterTiro() { return _tiro; } // obterTiro private void botaoAtaque(String posicao) { enableButtons(_attack, false); _tiro = new Posicao(posicao); marcar(_attack, _tiro._linha, _tiro._coluna); } // botaoAtaque private void botaoDefesa(String posicao) { _tiro = new Posicao(posicao); marcar(_defense, _tiro._linha, _tiro._coluna); _player.marcarNavio( _tiro); } // botaoDefesa public void actionPerformed(ActionEvent evt) { String comando = evt.getActionCommand(); if (comando.equals("DefesaPronta")) { defesaPronta(); } else if ( comando.startsWith("a", 0)) { botaoAtaque(comando); } else { botaoDefesa(comando); } } // actionPerformed private void createBoard(Button[][] board, int x0, int y0, String comando) { for (int i=0; i < 20; i++) { for (int j=0; j<20; j++){ Button b = new Button(); board[i][j] = b; b.setBounds(x0 + (10 * j), y0 + (10* i), 10, 10); b.setActionCommand(comando + i + "," + j); b.setBackground(Color.GRAY); b.addActionListener(this); add(b); } } } // createBoard private void enableButtons(Button[][] board, boolean enable) { for (int i=0; i < 20; i++) { for (int j=0; j < 20; j++) { board[i][j].setEnabled(enable); } } } // disableButtons private void marcar(Button[][] board, int linha, int coluna) { Color c = board[linha][coluna].getBackground(); if ( c == Color.GRAY ) c = Color.BLUE; else c = Color.GRAY; board[linha][coluna].setBackground(c); } // marcar private void initComponents() { setLayout(null); setBounds(0, 0, 530, 350); setTitle("Batalha Naval"); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { exitForm(evt); } }); _defesaPronta = new java.awt.Button(); _defesaPronta.setActionCommand("DefesaPronta"); _defesaPronta.setLabel("Defesa OK"); _defesaPronta.addActionListener(this); _defesaPronta.setBounds(10, 276, 90, 24); add(_defesaPronta); Label l1 = new Label(); l1.setBounds(10, 30, 50, 20); l1.setText("Defesa"); add(l1); Label l2 = new Label(); l2.setBounds(300, 30, 50, 20); l2.setText("Ataque"); add(l2); _status = new Label(); _status.setBounds(300, 250, 100, 20); add(_status); } // initComponents /** Exit the Application */ private void exitForm(java.awt.event.WindowEvent evt) { System.exit(0); } } // InterfaceGrafica