A cada jogada que faz, um jogador aproxima-se do fim do jogo, ou seja, do número de pontos definido como objetivo a alcançar. Quando algum(ns) jogador(es) alcança(m) uma pontuação igual ou maior que o objetivo do jogo, este termina e o(s) vencedor(es) pode(m) ser identificado(s).
Se tivermos um método que calcula quantos pontos tem o jogador que tem maior pontuação, e compararmos esse valor com o objetivo do jogo, então ficamos a saber se o jogo já terminou ou não.
Vamos então criar um método que calcula a maior pontuação dos jogadores do jogo. Isto é um problema de cálculo do máximo de um conjunto de valores, tal como fizemos, de forma muito geral, na secção 9.6.2.
A diferença aqui é que o nosso array é o atributo de instância jogadores
e não um parâmetro do método. Além disso, aqui não temos um array com os inteiros a comparar, mas sim um array de jogadores.
Os valores que queremos comparar não são os jogadores propriamente ditos mas as suas pontuações, ou seja, para um dado jogador referenciado por um elemento do array jogadores
, por exemplo, jogadores[i]
, o que queremos é a sua pontuação, ou seja, o resultado da invocação jogadores[i].pontuacao()
.
/**
* A maior pontuacao dos jogadores deste jogo
* @return O valor da maior pontuacao
* @requires this.quantosEmJogo() > 0
*/
public int maiorPontuacao () {
int maiorPontuacao = 0;
for (int i = 0 ; i < this.quantosEmJogo ; i++) {
int pontos = this.jogadores[i].pontuacao();
if (pontos > maiorPontuacao) {
maiorPontuacao = pontos;
}
}
return maiorPontuacao;
}
Então, para verificar se o jogo terminou basta comparar o resultado do método maiorPontuacao
com o valor do atributoobjetivo
:
xxxxxxxxxx
/**
* Este jogo jah terminou?
*/
public boolean terminou () {
return this.maiorPontuacao() >= this.objetivo;
}
Um exemplo de utilização deste método numa nova classe ClienteDeJogo
:
xpublic class ClienteDeJogo {
public static void main (String [] args) {
Jogo meuJogo = new Jogo(30);
String[] participantes = {"Maria","Pedro","Rui","Margarida","Antonio"};
for (int i = 0 ; i < participantes.length ; i++) {
meuJogo.juntarJogador(participantes[i]);
}
Random gerador = new Random();
int max = Jogo.MAX_PONTUACAO_JOGADA;
int min = Jogo.MIN_PONTUACAO_JOGADA;
// O jogo eh jogado em rondas.
// Em cada ronda jogam todos os jogadores.
while(!meuJogo.terminou()) {
for (int j = 0 ; j < participantes.length ; j++) {
// gera um valor no intervalo [min,max]
int pontuacao = gerador.nextInt(max - min + 1) + min;
// regista a pontuacao do proximo jogador
meuJogo.registarPontosJogada(participantes[j], pontuacao);
System.out.println(participantes[j] + " ganhou mais " +
pontuacao + " pontos");
}// fim do ciclo for
}// fim do ciclo while
System.out.println("Acabou o jogo!");
}
}
Vamos analisar os vários passos do método main
:
É criado um objeto do tipo Jogo
;
Com o intuito de registar 5 jogadores no jogo, em vez de termos cinco instruções quase iguais meuJogo.juntarJogador(...);
em que a única coisa que muda é o nome do jogador, declaramos e criamos um array de String
s contendo os nomes dos jogadores a registar; de seguida executamos um ciclo cujo corpo invoca o método juntarJogador
usando, a cada iteração, um novo elemento desse array ; no fim do ciclo todos os jogadores estão registados; o mesmo array participantes
irá ser usado mais adiante, para registar os pontos que cada jogador irá ter nas várias rondas do jogo;
O programa cria um gerador de aleatórios (objeto do tipo Random
, como já falado na secção 8.2) para gerar valores para as várias jogadas dos jogadores; os valores a serem gerados têm que estar no intervalo [Jogo.MAX_PONTUACAO_JOGADA
, Jogo.MAX_PONTUACAO_JOGADA
]; para simplificarmos os identificadores usados no programa atribuímos esses valores a duas variáveis de nomes min
e max
;
O jogo é jogado em várias rondas, até que algum(ns) jogador(es) alcance(m) uma pontuação igual ou superior ao objetivo do jogo; em cada ronda todos os jogadores jogam; então vamos ter dois ciclos encaixados, o externo para fazer as várias rondas, até o jogo terminar e o interno, para que , a cada ronda, todos os jogadores possam jogar:
while
(repetição não limitada) com a guarda !meuJogo.terminou()
(significando "enquanto o jogo não terminar"); o corpo deste ciclo é o ciclo interno;min
, max
] e invoca o método registarPontosJogada
tendo como valores para os parâmetros o nome j-ésimo jogador e o valor acabado de gerar; a instrução System.out.println
serve somente para informar o utilizador do que se vai passando no jogo;Quando o jogo termina, o utilizador é informado.
O output de uma possível execução deste programa:
xxxxxxxxxx
Maria ganhou mais 9 pontos
Pedro ganhou mais 0 pontos
Rui ganhou mais 10 pontos
Margarida ganhou mais 7 pontos
Antonio ganhou mais 8 pontos
Maria ganhou mais 10 pontos
Pedro ganhou mais 1 pontos
Rui ganhou mais 3 pontos
Margarida ganhou mais 3 pontos
Antonio ganhou mais 9 pontos
Maria ganhou mais 7 pontos
Pedro ganhou mais 3 pontos
Rui ganhou mais 2 pontos
Margarida ganhou mais 2 pontos
Antonio ganhou mais 6 pontos
Maria ganhou mais 8 pontos
Pedro ganhou mais 9 pontos
Rui ganhou mais 9 pontos
Margarida ganhou mais 6 pontos
Antonio ganhou mais 10 pontos
Acabou o jogo!
Era bom que o vencedor fosse calculado pelo próprio programa para que o utilizador não tenha que estar a fazer contas.
Quem sabe qual é o vencedor do jogo? É quem tem informação para isso, ou seja, o próprio jogo, que pode perguntar a cada um dos seus jogadores qual a sua pontuação e decidir qual o vencedor.
Vamos então agora ver como fazer isso. Se o critério para vencer o jogo fosse somente alcançar uma pontuação maior ou igual à do objetivo do jogo, então o método seguinte, que devolve o nome do vencedor, pode ser uma primeira abordagem:
xxxxxxxxxx
/**
* Vencedor deste jogo
* @return O nome do jogador que atingiu o objetivo
* @requires this.quantosEmJogo() > 0 && this.terminou()
*/
public String vencedor () {
int pontos = this.jogadores[0].pontuacao();
String resultado = this.jogadores[0].nome();
for (int i = 1 ; i < this.jogadores.length && pontos < this.objetivo ; i++) {
pontos = this.jogadores[i].pontuacao();
if (pontos >= this.objetivo) {
resultado = this.jogadores[i].nome();
}
}
return resultado;
}
Começamos por guardar nas variáveis pontos
e resultado
a pontuação e o nome do primeiro jogador registado no jogo (que é o primeiro elemento do array jogadores
). O ciclo permite-nos aceder aos outros jogadores, por ordem de registo no jogo (a mesma ordem dos elementos no array), até encontrarmos algum cuja pontuação seja igual ou superior ao objetivo.
Se o primeiro jogador tiver uma pontuação igual ou superior ao objetivo do jogo, é o nome dele que resulta deste método. Caso contrário, o ciclo é executado para procurar o primeiro jogador, dos seguintes, a ter alcançado o objetivo. O ciclo pára logo que um jogador nessas condições é encontrado.
Repare na pré-condição: this.quantosEmJogo() > 0 && this.terminou()
. Além de especificar que tem que existir pelo menos um jogador registado no jogo, requer também que o jogo já tenha terminado, ou seja, que o resultado da invocação do método terminou
seja true
. Isso garante que existe um vencedor, pois o jogo só termina quando pelo menos um jogador alcançou o objetivo.
Mas esta solução tem vários defeitos:
Numa segunda tentativa, vamos fazer as seguintes modificações:
xxxxxxxxxx
/**
* Vencedor(es) deste jogo
* @return O nome do jogador que atingiu o objetivo. No caso de
* haver varios, retorna os seus nomes
* @requires this.quantosEmJogo() > 0 && this.terminou()
*/
public String[] vencedores () {
// No maximo serao this.quantosEmJogo vencedores
String[] resultado = new String[this.quantosEmJogo];
int k = 0;
for (int i = 0 ; i < this.quantosEmJogo ; i++) {
if (this.jogadores[i].pontuacao() >= this.objetivo) {
resultado[k] = this.jogadores[i].nome();
k++;
}
}
return resultado;
}
Voltando ao main
da nossa classe ClienteDeJogo
acima descrita, vamos fazer as seguintes alterações:
while
, para imprimir no ecrã o(s) nome(s) do(s) vencedore(s),xxxxxxxxxx
String[] vencedores = meuJogo.vencedores();
for (int i = 0 ; i < vencedores.length ; i++) {
System.out.println(vencedores[i] + " venceu com " +
meuJogo.pontuacao(vencedores[i]) + " pontos");
}
Ao executar o main
, vamos obter o seguinte output (só apresentamos a parte produzida pelas novas instruções) seguido de uma exceção que provoca a terminação abrupta do programa:
xxxxxxxxxx
...
Maria venceu com 30 pontos
Exception in thread "main" java.lang.NullPointerException
at Jogo.obterJogador(Jogo.java:88)
at Jogo.pontuacao(Jogo.java:289)
at ClienteDeJogo.main(ClienteDeJogo.java:48)
Observamos que, dos dois jogadores em jogo, só a Maria conseguiu alcançar o objetivo dos 30 pontos, o que faz com que faça parte dos vencedores. Relembre que, por gerarmos as pontuaçãos com um objeto Random
, o output será potencialmente diferente a cada execução. Mas só em casos muito particulares, nomeadamente quando todos os jogadores são vencedores, é que o programa prossegue sem exceções. Já vai perceber porquê.
Ao imprimir os nomes dos vencedores, no main
da classe ClienteDeJogo
, o método pontuação
da classe Jogo
é invocado sobre o objeto meuJogo
tantas vezes quantas o número de elementos do array vencedores
.
String
contida em vencedores[i]
.i
, vai tomar valores desde zero até vencedores.length
.Quantos elementos tem o array vencedores
?
vencedores
da classe Jogo
. vencedores
o array resultado
é criado através da instrução String[] resultado = new String[this.quantosEmJogo];
. Como foram registados 2 jogadores no jogo, o atributo this.quantosEmJogo
tem o valor 2.Quantos vencedores há na verdade? Um!
vencedores
da classe Jogo
, embora o array resultado
tenha 2 elementos (todos inicializados a null
, como já sabe), a instrução resultado[k] = jogadores[i].nome();
só é executada uma vez.resultado
fica com o valor null
.
Então, o array vencedores
no main
da classe ClienteDeJogo
terá também dois elementos, sendo o último null
.
Logo, quando a instrução
xxxxxxxxxx
System.out.println(vencedores[i] + " venceu com " + meuJogo.pontuacao(vencedores[i]) + " pontos");
é executada para o segundo e último desses elementos (quando i
é 1), o valor dado para o parâmetro na invocação do método pontuacao
é null
.
Neste método pontuacao
, o método obterJogador
é invocado, usando exatamente o mesmo valor do parâmetro nomeJog
, que neste caso é null
.
No método obterJogador
o valor do parâmetro nomeJog
é usado na instrução
xxxxxxxxxx
if (nomeJog.equals(this.jogadores[i].nome())){
result = this.jogadores[i];
}
Repare que na guarda do if
é invocado o método equals
sobre o parâmetro nomeJog
, cujo valor é null
! É exatamente aqui que é lançada a exceção NullPointerException
que provoca a terminação abrupta do programa (releia o texto da exceção mais acima nesta página e verifique a origem da exceção e a forma como ela se propaga).
Resumindo e concluindo:
O método vencedores
da classe Jogo
não deve retornar um array de nomes que contenha elementos com valor null
.
A seguinte versão aumentada do mesmo método vencedores
resolve esse problema:
xxxxxxxxxx
/**
* Vencedor(es) deste jogo
* @return O nome do jogador que atingiu o objetivo. No caso de
* haver varios, retorna os seus nomes
* @requires this.quantosEmJogo() > 0 && this.terminou()
*/
public String[] vencedores () {
// No maximo serao this.quantosEmJogo vencedores
String[] resultado = new String[this.quantosEmJogo];
int k = 0;
for (int i = 0 ; i < this.quantosEmJogo ; i++) {
if (this.jogadores[i].pontuacao() >= this.objetivo) {
resultado[k] = this.jogadores[i].nome();
k++;
}
}
// Soh interessa retornar os k primeiros elementos do vetor resultado,
// pois os outros estao a null
String[] resultadoFinal = new String[k];
for (int i = 0 ; i < k ; i++) {
resultadoFinal[i] = resultado[i];
}
return resultadoFinal;
}
Se quisessemos obter somente o jogador que tem a maior pontuação, e devolver esse como vencedor do jogo, poderíamos deparar-nos com situações em que há mais que um jogador que tem a pontuação máxima. O seguinte método privado calcula e devolve os jogadores que têm a melhor pontuação. Iremos invocá-lo numa nova versão do método vencedores
.
x/**
* Os jogadores que teem a maior pontuacao.
* Metodo privado, auxiliar.
* @return Os jogadores que teem a maior pontuacao
* @requires this.quantosEmJogo() > 0
*/
private Jogador[] osMelhores () {
// Qual a melhor pontuacao?
int maiorPontuacao = this.maiorPontuacao();
// Nao sabemos quantos serao. No maximo serao this.quantosEmJogo
Jogador[] melhoresJogadores = new Jogador[this.quantosEmJogo];
int k = 0;
for (int i = 0 ; i < this.quantosEmJogo ; i++) {
if (this.jogadores[i].pontuacao() == maiorPontuacao) {
melhoresJogadores[k] = this.jogadores[i];
k++;
}
}
// Soh interessa retornar os k primeiros elementos do vetor, pois
// os outros estao a null
Jogador[] resultado = new Jogador[k];
for (int i = 0 ; i < k ; i++) {
resultado[i] = melhoresJogadores[i];
}
return resultado;
}
No caso de serem mais do que um, e quisermos desempatar, podemos fazê-lo escolhendo o que tem o valor maior para o atributo que representa o "máximo numa só jogada". De novo, aqui, podem ser mais do que um. A nova versão do método vencedores
ficaria então:
xxxxxxxxxx
/**
* Vencedor(es) deste jogo
* @return O nome do jogador que atingiu o objetivo. No caso de
* haver varios, aquele que teve a pontuacao mais alta numa
* soh jogada. No caso de ainda haver varios, retorna os seus nomes
* @requires this.quantosEmJogo() > 0 && this.terminou()
*/
public String[] vencedores () {
// Os jogadores que tiveram a melhor pontuacao ex-aequo
Jogador[] melhores = osMelhores();
// entre esses, calcular o valor maior do atributo maximo numa soh jogada
// o(s) jogador(es) que o obteve(iram) serah(ao) o(s) vencedor(es)
Jogador jog = melhores[0];
int maximo = jog.maximoNumaJogada();
for (int i = 1 ; i < melhores.length ; i++) {
jog = melhores[i];
int pontos = jog.maximoNumaJogada();
if (pontos > maximo) {
maximo = pontos;
}
}
// Selecionar, de entre os que teem pontuacao melhor, os que
// teem o maior valor do atributo maximo numa jogada
String[] vencedores = new String[melhores.length];
int k = 0;
for (int i = 0 ; i < melhores.length ; i++) {
if (melhores[i].maximoNumaJogada() == maximo) {
vencedores[k] = melhores[i].nome();
k++;
}
}
// Soh interessa retornar os k primeiros elementos do vetor
String[] resultado = new String[k];
for (int i = 0 ; i < k ; i++) {
resultado[i] = vencedores[i];
}
return resultado;
}
O seguinte método devolve os nomes dos jogadores cuja pontuação é superior a um valor dado:
xxxxxxxxxx
/**
* Os nomes dos jogadores com pontuacao superior a um dado valor
* @param limiteInf O valor de comparacao.
* @requires this.quantosEmJogo() > 0
* @return Um vetor com os nomes dos jogadores deste jogo com
* pontuacao superior a limiteInf
*/
public String[] pontuacaoSuperiorA (int limiteInf) {
// Nao sabemos quantos serao. No maximo serao serao this.quantosEmJogo
String [] melhores = new String [this.quantosEmJogo];
int k = 0;
for (int j = 0 ; j < this.quantosEmJogo ; j++){
if (this.jogadores[j].pontuacao() >= limiteInf){
melhores[k] = this.jogadores[j].nome();
k++;
}
}
// Soh interessa retornar os elementos do vetor que
// nao sao null
String [] result = new String [k];
for (int i = 0 ; i < k ; i++){
result[i] = melhores[i];
}
return result;
}
Anterior: 11.6. O método toString na classe Jogo
Seguinte: 11.8. A classe Jogo completa.