Guião 6 de POO - moodle.fct.unl.pt · Neste guião vamos exercitar conceitos como a herança e a...
Transcript of Guião 6 de POO - moodle.fct.unl.pt · Neste guião vamos exercitar conceitos como a herança e a...
Guião 6 de POO 2008/2009
1
Celebutante 1.0
Programação Orientada pelos Objectos, 2008/2009
Guião 6, versão 1.0, 7 de Maio de 2009 (principais actualizações a vermelho)
Notas prévias: Este guião deve ser entregue no mooshak até às 23h55 de 18 de Maio.
São fornecidos ficheiros de exemplo que deve usar para verificar a sua resolução com uma ferramenta
do tipo diff antes de submeter o seu trabalho ao mooshak. As primeiras cinco submissões de cada tarefa
não penalizam a cotação da tarefa. No entanto, por cada submissão subsequente serão descontados
10% da cotação da tarefa. Por favor inclua o seu nome e número de aluno em todos os seus ficheiros,
num comentário com a tag @author.
Objectivos didácticos deste guião Neste guião vamos exercitar conceitos como a herança e a polimorfia, bem como preparar a utilização
de interfaces gráficas e eventos nos nossos projectos. O projecto será construindo de acordo com um
padrão arquitectural recorrente na programação: o Model-View-Controller. Neste guião vamo-nos
concentrar no modelo (Model).
O Model-View-Controller é um padrão especialmente concebido para isolar claramente a parte lógica de
um sistema da sua apresentação e interface com o utilizador. Por outras palavras, deve ser possível
substituir a camada de interface e apresentação do sistema sem que isso tenha impacto na camada
lógica do sistema, e vice-versa. O modelo (Model) representa a informação, ou dados, geridos pelo
sistema. A vista (View) representa os elementos da interface visual do sistema com o seu utilizador, e
pode incluir áreas de texto, representações gráficas, botões, etc. O controlador (Controller) é
responsável pela comunicação entre o modelo e a vista.
O guião cobre a primeira de 3 partes distintas: neste guião vamos concentrar-nos no modelo que está
por detrás do jogo, que será avaliado com o apoio do mooshak; num segundo guião, vamos construir
uma interface visual (a vista + controlador) para a aplicação que desenvolvemos na primeira fase. Como
evidência do seu sucesso desse guião, deverá capturar um pequeno filme com a execução da sua
aplicação, colocar o filme online e enviar através do moodle o respectivo link para que o docente possa
assistir à demonstração do seu trabalho. Além disso, deverá submeter o código completo via moodle,
juntamente com o link para o seu filme. Finalmente, num terceiro guião, faremos um upgrade ao motor
do jogo, dotando-o de adversários mais inteligentes para o nosso personagem.
Guião 6 de POO 2008/2009
2
Celebutante 1.0 Confesse lá. Está mortinho para saber o que é um Celebutante, não é? Estupendo! Aqui vai: Celebutante
é a contracção da palavra celebridade com a palavra debutante. Portanto, um celebutante é uma
celebridade debutante. O conceito é algo parecido com o de socialite, mas o celebutante é célebre,
enquanto que o socialite é convidado para tudo o que é festa, mesmo se não for famoso. Um
celebutante digno desse título é famoso por ser famoso. Ou seja, é alguém que é tremendamente
famoso, ninguém sabe muito bem porquê, dado ser absolutamente desprovido de qualquer talento
conhecido. Normalmente, é parente de alguém tremendamente rico. É uma razão como outra qualquer.
E o que faz na vida um Celebutante? hmmmm…errr... nada!
Em rigor, estamos a ser injustos. Um celebutante vai a festas, muitas festas, onde se esforça por
coleccionar o maior número possível de fotografias com todos os outros participantes das festas. Assim,
aparece nas revistas e vai ficando cada vez mais famoso.
O que lhe propomos neste guião é que crie o jogo de computador, o Celebutante, em que temos de
conduzir uma jovem celebutante rumo à glória dos mexericos. O objectivo da nossa heroína é ser
fotografada com todas as pessoas existentes numa festa, sem dar de caras com um dos seus ex-
namorados. Isso seria a morte social da celebutante. Desagradável que só visto. Tá a ver? Super!
Vamos então conhecer as regras do jogo. O jogo tem dois tipos de personagens. Um herói (a
celebutante) e um número indeterminado de vilões (os ex-namorados da celebutante que são uns
monstros a evitar o mais possível, pelo menos na perspectiva dela). Todos estes personagens deslocam-
se na festa. Os movimentos do herói são controlados pelo jogador. Os movimentos dos vilões são
controlados pelo computador.
No início, a festa tem um número indeterminado de convidados, para além dos nossos personagens que
estão espalhados pela casa. Felizmente, é gente muito parada, que não se mexe. Ou seja, têm uma
posição fixa, ao longo de toda a festa. Quando a celebutante passa pelo local onde está um destes
convidados, tira uma fotografia com ele. Para todos os efeitos, o convidado “deixa de existir” para o
jogo, porque a celebutante só tira uma fotografia com cada convidado. O convidado literalmente deixa
de aparecer no écran. A celebutante ganha 10 pontos de celebridade por cada fotografia conseguida. O
jogo termina quando a celebutante tirar fotografias com todos os convidados, ou quando esbarrar com
um ex-namorado (o que quer que aconteça primeiro).
Em determinadas localizações especiais, existem bandas musicais. Quando passa por uma banda, a
celebutante pode pedir, apenas uma vez, para que a banda toque o “Poeira”, da Ivete Sangallo. Durante
20 jogadas, todos os convidados dançam, levantando poeira. No meio de tanto pó, a celebutante tem
uma oportunidade dourada para passar por um ex-namorado e dar-lhe uma bofetada (200 pontos de
celebridade). Só pode dar uma bofetada a cada namorado, durante a música. Ao fim das 20 jogadas, o
pó assenta e os ex-namorados voltam a ser pessoas a evitar pela celebutante. Durante o período da
música, a celebutante continua a tirar fotos com os convivas por quem passa. Depois de tocar uma
música, a banda desaparece, a exemplo dos convidados já fotografados.
Guião 6 de POO 2008/2009
3
Os personagens deslocam-se numa de 4 direcções. Cima, baixo, esquerda e direita. Finalmente, e para
terminar esta descrição, a ausência de talento destes personagens é tanta que eles não conseguem
atravessar paredes. Ok? Lembre-se disto, que é um detalhe importante.
Se você não gosta de revistas de mexericos, pode pensar que o objectivo do seu herói é comer todas as
miniaturas de pastéis de nata de uma festa para a qual não foi convidado, enquanto foge aos
seguranças, que os bónus são luzes que se desligam, dando-lhe a possibilidade de dar um tabefe ao
segurança, enquanto a luz não volta. Mesmo esquema de pontos e restantes regras.
Se também não gosta de pastéis de nata, imagine que o seu herói é o Pac-Man, a comer pontinhos
brancos e a fugir aos fantasmas, excepto quando passa pelos locais de bónus, que lhe permitem atacar
os fantasmas durante um tempo limitado. Vai ver que o jogo é sempre o mesmo. Só muda a
apresentação. Pronto. Admitimos. O Celebutante não é mais que um Pacman disfarçado. Ainda não
tinha reparado?
Para lhe aguçar a curiosidade, aqui fica o aspecto dos maravilhosos jogos (Celebutante à esquerda,
Pacman à direita). Nós vamos explicar tudo com celebutantes e ex-namorados. Verá, dentro em breve,
que em vez de um jogo está a implementar pelo menos 3 jogos diferentes, com um mesmo modelo.
Uma nota final na introdução deste guião. Vamos começar por lhe explicar o que pretendemos em cada
uma das tarefas que vai submeter ao mooshak. Após a conclusão das 4 tarefas deste guião, terá
construído o motor destes jogos. ANTES de começar a implementar o que quer que seja, leia o guião
TODO. Depois de descrevermos o que pretendemos, vamos descrever detalhadamente a
implementação de todas as classes necessárias a este guião. A recomendação que lhe deixamos é a
seguinte: comece por criar versões esqueleto de todas as classes do motor do jogo, com a
implementação básica para permitir a compilação com sucesso. À medida que for progredindo nas
submissões ao mooshak, preencherá, progressivamente, a implementação de todas as classes. Não
avance para uma tarefa sem terminar com sucesso a anterior. A funcionalidade das tarefas anteriores é
sempre integrada nas tarefas seguintes.
Guião 6 de POO 2008/2009
4
Tarefa A – Leitura do mapa do jogo
Os palacetes em que decorrem as gloriosas festas da nossa celebutante são autênticos labirintos, com
vários corredores, salas que nunca mais acabam, etc.
A sua primeira tarefa no mooshak consiste precisamente na construção de labirintos. Vamos verificar
que a leitura do seu labirinto foi executada com sucesso. Leia da consola a especificação de um labirinto.
A estrutura dos dados na entrada é a seguinte: na primeira linha, temos dois inteiros, com a altura e
largura do labirinto. Seguem-se altura linhas, cada uma com largura carácteres, representando o
labirinto e seu conteúdo. Cada carácter dessa linha tem um significado especial:
„#‟ – Parede do labirinto
„ „ – Posição vazia no labirinto
„.‟ – Convidado mesmo a pedir uma fotografia
„H‟ – Herói, no caso, a nossa celebutante.
„R‟, „G‟, „B‟ – Monstros vermelho (Red), verde (Green), ou azul (Blue)
„O‟ – Ponto de bónus no labirinto
Assuma que, ao ler o estado inicial do labirinto, as posições iniciais do herói e dos monstros estão vazias.
Ou seja, se removêssemos os personagens, nessas posições estaria o carácter „ „. A especificação do
labirinto é seguida de uma sequência de comandos. Será assim, para todas as tarefas. Para já, temos
apenas dois comandos:
„p‟ – imprime o labirinto, no seu estado actual, representando todos os elementos acima
descritos.
„w‟ – imprime o labirinto, representando apenas as paredes.
Pode obter no moodle vários exemplos desta tarefa. Tenha em atenção que é absolutamente essencial
usar o diff para verificar que está realmente a produzir os resultados exactamente como o esperado. Por
exemplo, um dos testes é totalmente desprovido de paredes, mas os espaços em branco têm mesmo de
ser representados tal e qual como especificado. Aqui fica um pequeno exemplo. Este labirinto tem 9
linhas e 27 colunas. Temos um herói (H), quatro pontos de bónus (O), e três vilões, representados por R,
G e B. Repare que os dois comandos mandam escrever, sucessivamente, o labirinto preenchido e o
labirinto por preencher. Neste exemplo, tal como noutros que se seguem, representamos a verde, o
input, a preto, o output.
9 27
###########################
#...........O.............#
#.######.R######.H#######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
###########################
p w
Guião 6 de POO 2008/2009
5
###########################
#...........O.............#
#.######.R######.H#######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
###########################
###########################
# #
# ###### ###### ####### #
# # # # #
# ### # # #
# # # # #
# # ###### # #
# #
###########################
Tarefa B – Movimentos dos personagens
Nesta tarefa vamos aprender a movimentar os personagens. Implementemos na classe AvatarClass
os métodos up(), down(), left() e right() e move(). É natural que necessite de implementar
alguns métodos auxiliares, nesta ou noutras classes. Veja a discussão detalhada das classes e interfaces
a implementar, no fim do guião. Voltemos aos movimentos dos personagens. Lembre-se, ninguém
consegue atravessar paredes. No entanto, e porque esta gente vive num mundo de fantasia, as casas
têm uma propriedade especial. Se em duas paredes exteriores opostas existirem portas (ou seja,
espaços em branco) perfeitamente alinhadas, ao sair por uma das portas o personagem entra pela porta
oposta. Por exemplo, se as duas portas estiverem alinhadas horizontalmente e o personagem sair pela
porta do lado esquerdo, entra directamente pela porta do lado direito. Se sair pela porta do lado direito,
entra pela do lado esquerdo. O raciocínio é semelhante, quando pensamos em parede extremas
opostas, com portas alinhadas verticalmente. Falta saber o que acontece se um personagem tentar sair
por uma porta que não tem uma porta do lado oposto, perfeitamente alinhada. O que acontece é
simples. Não consegue sair. É como se esbarrasse na parede do lado oposto.
Redefina os mesmos métodos na classe HeroClass, de modo a que o nosso herói vá tirando as fotos
(com o efeito secundário de fazer desaparecer os convidados). Repare que, ao contrário do que
acontece com o nosso herói, os vilões não tiram fotos com os convidados, portanto, nada acontece a um
convidado quando o vilão passa por ele. Mas, para já, deixemos os vilões descansados. Vão ficar quietos,
nesta tarefa. Vamos ignorar o que acontece quando o herói passa por um ponto de bónus, ou mesmo
por um vilão. Ou seja, por agora, não acontece nada.
Vamos então ver se tudo isto funciona bem. A tarefa B do mooshak serve precisamente para verificar
que as regras de movimento estão correctas. Como sempre, use os exemplos disponíveis no moodle
para verificar que tudo está bem, com o diff, antes de submeter a sua tarefa ao mooshak. O formato de
Guião 6 de POO 2008/2009
6
entrada de dados é semelhante ao que existia para a task A, mas agora temos novos comandos a
especificar tentativas de movimentos. Algumas tentativas têm sucesso, outras nem por isso, quando os
personagens esbarram em paredes. Caso seja possível, o personagem desloca-se uma posição no
sentido solicitado. Caso isso não seja possível, pela existência de uma parede, o personagem fica
parado. Os comandos disponíveis são os seguintes:
„u‟ – Tenta deslocar o celebutante para cima.
„d‟ – Tenta deslocar o celebutante para baixo.
„l‟ – Tenta deslocar o celebutante para a esquerda.
„r‟ – Tenta deslocar o celebutante para a direita.
„m‟ – Tenta deslocar o celebutante para a última direcção por ele tomada.
A estes comandos juntam-se os anteriores, que nos permitem verificar o estado do labirinto, após
algumas jogadas. Nada como um pequeno exemplo, para ilustrar como tudo funciona. Voltemos ao
nosso conhecido labirinto, agora com duas pequenas alterações: Abrimos um par de portas no labirinto
anterior, alinhadas verticalmente. Lembre-se, é importante que elas estejam alinhadas.
A celebutante começa por tentar mexer-se, mas não consegue, por estar parada e sem direcção prévia.
Mandamos imprimir o mapa. Tudo conforme esperado? Avancemos. Ela tenta ir para a direita e esbarra
na parede. Depois, desloca-se quatro posições para cima. Imprimimos o labirinto de novo, e ei-la cá em
baixo. Saiu pela porta de cima, entrou pela de baixo. Repare o que aconteceu aos pontos que estavam
pelo trajecto. Desapareceram, tendo sido substituídos por espaços em branco. Vamos andar mais um
bocado. Três casas para a direita (a primeira, com „r‟, as restantes duas com „m‟, uma tentativa
falhada de descer que esbarra na parede, e uma casa para a esquerda. Ao longo deste processo a
celebutante passou por um ex-namorado, mas ignorou-o. Entretanto, tirou fotos por onde passou.
9 27
################# #########
#...........O.............#
#.######.R######.H#######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
################# #########
m p r u u u u p r m m d l p
################# #########
#...........O.............#
#.######.R######.H#######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
################# #########
Guião 6 de POO 2008/2009
7
################# #########
#...........O.... ........#
#.######.R######. #######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#................HB ...O..#
################# #########
################# #########
#...........O.... ........#
#.######.R######. #######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#................ BH ..O..#
################# #########
Tarefa C – Pontuações, bónus, fim de jogo
Nesta tarefa, os vilões continuam paralizados. Todo o resto do jogo deve funcionar. Os comandos são
exactamente os mesmos que vimos até agora, mas o programa faz mais que na tarefa anterior. Além do
tabuleiro, imprime uma linha indicando o total de pontos obtidos até ao momento, o tempo de
actividade que resta ao bónus (quando se passa por um bónus, é criado um contador inicializado a 20 e
que é decrementado, 1 por jogada, até chegar a 0; enquanto este contador for maior que 0, os vilões
estão vulneráveis e são representados com uma minúscula; os comandos p e w não decrementam este
contador. Depois de terminar o jogo, o único comando que funciona é o ‘P’. Ignoramos os outros.
Vamos a um exemplo, no mesmo labirinto. Começamos por ir a correr até ao bónus mais próximo, indo
primeiro para cima e depois para a esquerda. Imprimimos imediatamente antes e depois de consumir o
bónus. Seguimos para o vilão R, para lhe dar uma bela bofetada. Depois, como bons celebutantes,
fazemos algo estúpido. Vamos para cima, descemos de novo para ver que isso não dá pontos, voltamos
a subir e damos várias cabeçadas na parede. Quando o vilão acorda, perdemos o jogo sem glória,
atirando-nos nos seus braços e provocando assim um escândalo. Não esperava por esta, pois não? A
celebutante reconcilia-se com o desamado, perdendo assim o jogo. Uma triste comédia romântica.
9 27
################# #########
#...........O.............#
#.######.R######.H#######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
################# #########
u l m m m p m p m m m d p u d u u u u u u u u u u u u u u u u d p
Guião 6 de POO 2008/2009
8
################# #########
#...........OH ........#
#.######.R######. #######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
################# #########
Points: 50 Timer: 0 Game over: false
################# #########
#...........H ........#
#.######.r######. #######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....g#...........#....#
#.#.....O.######.....#....#
#.................b....O..#
################# #########
Points: 50 Timer: 20 Game over: false
################# #########
#........ ........#
#.######.H######. #######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....g#...........#....#
#.#.....O.######.....#....#
#.................b....O..#
################# #########
Points: 280 Timer: 16 Game over: false
################# #########
#........ ........#
#.######.H######. #######.#
#.#......#...........#....#
#O###....#...........#....#
#.#.....G#...........#....#
#.#.....O.######.....#....#
#.................B....O..#
################# #########
Points: 280 Timer: 0 Game over: true
Tarefa D – Dotar os vilões de “inteligência artificial”
Talvez seja um exagero falar em “inteligência” dos nossos vilões. Afinal, estamos a falar de pessoas que
são conhecidas por serem ex-namorados de celebutantes. Não se pode pedir demais. Ainda assim, os
nossos vilões têm o mérito de já não estar nas boas graças da nossa heroína, o que demonstra alguma
inteligência. Como é que esses dois neurónios dos nossos vilões se manifestam no jogo? Na prática,
vamos apenas estabelecer, por ordem de prioridades, o sentido em que os nossos vilões se tentam
Guião 6 de POO 2008/2009
9
deslocar em cada momento. A regra é simples: vamos criar uma lista circular de prioridades para cada
um deles. Em cada momento, o vilão vai tentar seguir o sentido que estiver seleccionado na sua lista de
prioridades. Se não conseguir, passa ao sentido seguinte. E assim sucessivamente. Temos três tipos de
vilões estúpidos neste jogo. Um vermelho, um verde e um azul. Cada um deles tem a sua lista de
prioridades. Se houver num jogo dois vermelhos, eles têm listas de prioridades iguais. Mas cada um tem
a sua, portanto, consoante a sua localização no labirinto, podem estar em estados diferentes.
Aqui ficam as 3 listas de prioridades, uma por cor. Por omissão, os vilões começam sempre na posição 0,
depois passam para a 1, a seguir para a 2, depois para a 3, mais tarde voltam à 0 e assim
sucessivamente.
Vilão 0 1 2 3
Vermelho UP DOWN LEFT RIGHT
Verde DOWN UP LEFT RIGHT
Azul RIGHT LEFT UP DOWN
Pensemos no caso do vilão vermelho. Enquanto puder, ele vai-se deslocar para cima. Mais cedo ou mais
tarde, é natural que encontre uma parede. Nessa altura, passa a deslocar-se para baixo e assim faz, até
encontrar uma parede. A partir dessa altura, desloca-se para a esquerda, até encontrar uma parede.
Finalmente, desloca-se para a direita, até encontrar uma parede. E o que acontece quando encontra
essa parede? Simples, volta ao princípio do vector de prioridades, ou seja, desloca-se para cima. Não é
um comportamento lá muito inteligente, pois não? Lá está. Nós começamos por avisar que eles eram
pouco espertos.
Mas não se preocupe. Nós temos um plano.
Num dos próximos guiões, você vai conhecer o futuro ex-marido da celebutante, que é, no fundo, o boss
deste jogo. Eles estão separados, mas são bons amigos, dizem as revistas da especialidade. Na verdade,
fontes anónimas próximas da mãe da celebutante dizem que essa amizade está para terminar, assim
que o ex-marido conseguir que a celebutante assine os papéis do divórcio. Dizem as más línguas que
este vilão implacável persegue a celebutante em busca do desejado autógrafo que lhe permitirá dar o
golpe do baú. E você vai ajudá-lo, a troco de uma simbólica percentagem paga em géneros (leia-se, em
pontos do respectivo guião).
Vá, mãos à obra. Este guião é bastante maior que o código que vai ter de escrever, mas mesmo assim
tem muito por onde se entreter. Idealmente, ao fim de uma semana deverá ter isto praticamente
pronto, para então tratar da bonecada.
Passe à página seguinte e veja como pretendemos que implemente este motor de jogo. É importante
que siga estas indicações, para que depois seja fácil acrescentar a interface gráfica e o ex-marido da
celebutante, claro.
Guião 6 de POO 2008/2009
10
Como implementar este guião? Ponto prévio. Como sempre, vamos implementar tudo isto dentro de um package chamado poo.
Além de uma classe Main, para ir fazendo os testes que lhe vamos solicitando, deverá ir criando, para
cada tarefa a entregar no mooshak, a seguinte estrutura de classes e interfaces, que deve usar como
referência para todo o guião. Use este diagrama como fonte de consulta para saber exactamente que
métodos são esperados em cada classe e interface. Siga estas indicações à risca, por favor. A explicação
para cada um destes elementos será dada já a seguir.
Observe o diagrama de classes em UML. Se não está bem recordado da notação, aqui vai um guia ultra
curto. Reveja a matéria, para mais detalhes.
No diagrama, o círculo no canto superior direito dos rectângulos indica que estamos a representar uma
interface, e não uma classe. Temos 3 interfaces, como pode observar. A primeira coisa a fazer neste
guião será mesmo declarar essas interfaces no seu projecto. Cada uma destas interfaces é
implementada por uma ou mais classes. A relação de implementação representa-se com uma linha
tracejada, com um triângulo junto à interface. Por exemplo, a classe MazeClass implementa a interface
Maze.
Os métodos e atributos marcados com + são públicos, com – privados, e com # protegidos (protected).
{readOnly} indica uma constante. Sublinhado indica que o atributo ou método é de classe (static).
Itálico significa abstracto (abstract).
O diamante na associação entre MazeClass e MazeItem significa que existe na classe MazeClass uma
variável (privada, denominada myMaze) que contém uma colecção de objectos do tipo MazeItem.
Sugerimos que implemente essa colecção usando uma tabela (private MazeItem[][] myMaze;). O
outro diamante, que aparece na associação entre MazeClass e Monster indica que MazeClass tem uma
colecção de objectos do tipo Monster. Embora o diagrama UML não especifique como devemos
implementar essa colecção, a sugestão que lhe damos é que a implemente como uma ArrayList
(private ArrayList<Monster> monsters;), dado que vai necessitar de uma para criar os iteradores de
monstros.
A seta de MazeClass para Hero significa que existe uma variável de instância privada, denominada hero,
declarada na classe MazeClass (ou seja, declarada como private Hero hero;). De igual modo, a seta
de AvatarClass para Maze significa que em AvatarClass deve declarar uma variável privada chamada
myMaze.
É evidente que você seria capaz de criar uma estrutura de classes por si só para resolver este problema.
Mas estamos a dar-lhe estas indicações de modo a deixar tudo pronto para que seja mais fácil construir
uma interface gráfica em cima deste motor de jogo, por um lado, e acrescentar o boss do jogo, por
outro.
Se necessitar de métodos privados ou protegidos adicionais, acrescente-os. Mas mantenha a interface
pública das classes, por favor. Reporte alguma falha importante que detecte ao seu docente.
Guião 6 de POO 2008/2009
11
A interface Maze
Pois bem, vamos criar uma interface java Maze para representar estes labirintos. Consulte o diagrama
para saber quais as operações a declarar na interface Maze. E, por favor, não confunda o conceito de
interface Java com as interfaces gráficas do guião que virá mais tarde, para implementar a interface
visual do jogo.
A operação getRows() devolve o número total de linhas no labirinto.
A operação getColumns() devolve o número total de colunas do labirinto.
A operação toString() devolve a representação em String do labirinto. Esta representação
deve incluir não só as paredes, mas também todos os elementos que se encontram no labirinto,
tais como as personagens e outros objectos que lá se encontrem.
Guião 6 de POO 2008/2009
12
A operação printWalls é semelhante a toString, mas apenas representa o labirinto,
ignorando os objectos e personagens que nele se encontram.
A operação elementAt(int r, int c) devolve o elemento na posição (r,c), em que r
representa as linhas (rows) e c representa as colunas (columns). Se não existir nenhum
elemento na posição (r,c) a operação devolve null.
A operação characterAt(int r, int c) devolve a personagem que se encontra na posição
(r,c), se lá estiver alguma personagem, ou null, se não existir nenhuma personagem nessa
posição. No caso de existir mais do que um personagem na posição (r,c), algo que pode
acontecer, por exemplo, se uma celebutante esbarrar com um ex-namorado. A operação
devolve, por esta prioridade, a celebutante, ou os ex-namorados, por ordem de criação no jogo.
Repare que esta ordem dos ex-namorados é a ditada no momento da criação do jogo. De cima
para baixo, da esquerda para a direita, tal e qual como quando lemos um texto em Português.
A operação getHero() devolve a nossa celebutante.
getMonsterIterator() devolve um iterador com todos os ex-namorados, que são iterados pela
sua ordem de criação.
A operação moveAll() faz movimentar todos os personagens do jogo, começando pela
celebutante, e continuando, por ordem, por cada um dos ex-namorados.
A operação moveMonsters() movimenta apenas os ex-namorados. Para que queremos duas
operações? Suponha que a celebutante está parada. Queremos poder mandar mover os
monstros, mesmo quando a celebutante está a descansar. Para evitar duplicação desnecessária
de código, sugerimos que use moveMonsters() como uma operação auxiliar de moveAll().
A operação consume(int r, int c) consome o recurso na posição (r, c). Esta operação
permite ao jogador ir ganhando pontos. Tantos quantos o recurso na posição (r, c) valer. Se
nessa posição existir um objecto do tipo Collectable, isso vale 10 pontos. Se existir um ex-
namorado assustado que ainda não levou uma bofetada desde que o período de bónus
começou, isso vale 200 pontos.
A operação isGameOver() devolve true se o jogo já terminou, false caso contrário. O jogo
termina quando não há mais objectos do tipo Consumable disponíveis, ou na sequência de um
escândalo.
A operação getCurrentPoints() devolve o número de pontos atingidos até ao momento neste
jogo. Como deve imaginar, estes são precisamente os pontos que foram sendo acumulados de
cada vez que se chamou a operação consume(int r, int c).
A operação getRemainingTime() indica quantas jogadas faltam para terminar o período de
bónus, iniciado quando a celebutante consome um bónus.
A operação updateMorale() actualiza a moral dos ex-namorados, consoante o valor devolvido
por getRemainingTime(). Se este último for 0, os ex-namorados estão no seu estado normal e
são perigosos para a celebutante. Se o tempo for maior que 0, os ex-namorados passam ao
estado assustado, em que podem ser esbofeteados pela celebutante. Adiante veremos como se
gere a moral dos ex-namorados.
A operação addPoints(int points) é uma operação auxiliar que permite adicionar pontos ao
jogo (por exemplo, na sequência de uma foto tirada).
Guião 6 de POO 2008/2009
13
A operação gameOver() termina o jogo.
A operação rowUp(int r) indica a linha imediatamente acima de r. Devolve r-1 sempre,
excepto quando r = 0. Nesse caso, devolve a última linha do labirinto.
A operação rowDown(int r) é a dual de rowUp(int r). Como calcula, devolve 0 quando r
corresponde à última linha do labirinto.
A operação colLeft(int c) devolve c-1, excepto quando c = 0. Nesse caso, devolve a última
coluna do labirinto.
A operação colRight(int c) devolve a coluna à direita de c, se existir, ou 0, caso contrário.
A classe MazeClass
Programe uma classe MazeClass que implementa a interface Maze. Para além das operações definidas
na interface, a sua classe deverá ter ainda um construtor que recebe a altura (r), largura (c), um
ArrayList<String> (m) em que cada String representa uma linha do labirinto. Repare que este
construtor não só cria a representação interna do labirinto, mas também cria todos os objectos dentro
do labirinto, bem como os personagens que por ele vão passear. Como pode observar no diagrama de
classes, pedimos-lhe que declare uma série de variáveis nesta classe:
maxRows representa o número de linhas do labirinto.
maxCols representa o número de colunas do labirinto. remainingCollectables representa o número de pessoas que ainda não foram fotografadas
no jogo.
remainingBonuses representa o número de bónus por consumir no jogo.
currentPoints representa o total de pontos obtidos no jogo.
scareTimer representa o tempo que falta até que os ex-namorados voltem ao seu estado normal, na sequência da activação de um bónus.
scandal indica se ocorreu um escândalo (se a celebutante foi encontrada por um ex-namorado.
myMaze representa uma tabela com todos os items guardados no labirinto (paredes, consumíveis, bónus e espaços livres).
monsters representa a lista de ex-namorados da celebutante.
hero representa a celebutante.
A interface MazeItem
Esta interface define duas operações partilhadas por todos os items que podem estar no labirinto:
toString() devolve a String correspondente ao item.
getPoints() devolve o número de pontos que o item vale.
A classe Empty
As instâncias desta classe representam posições que não têm nenhum item no labirinto.
toString() devolve a String “ “. Repare que há mesmo um espaço em branco na String.
getPoints() devolve 0.
Guião 6 de POO 2008/2009
14
A classe Bonus
As instâncias desta classe representam posições do labirinto em que se encontram bónus. Sempre que a
celebutante passar por um ponto de bónus, este é consumido e substituído por um objecto da classe
Empty. A classe Bonus implementa os seguintes métodos:
toString() devolve a string “O” (de bOnus).
getPoints() devolve 0 pontos.
A classe Collectable
As instâncias desta classe representam posições do labirinto em que se encontram objectos colectáveis.
Neste caso, convidados junto dos quais a celebutante pode tirar fotografias. Uma vez tirada a fotografia,
a instância de Collectable deixa de ter qualquer interesse para a celebutante. Amealhados os pontos,
ela é descartada e substituída por uma instância de Empty, no labirinto. A classe Collectable
implementa os seguintes métodos:
toString() devolve a String “.”.
getPoints() devolve 10 pontos.
A classe Wall
As instâncias desta classe representam as posições do labirinto ocupadas por paredes. As paredes são
intransponíveis para os personagens deste jogo. A classe Wall implementa os seguintes métodos:
toString() devolve a String “#”.
getPoints() devolve 0 pontos.
A interface Avatar
Esta interface é genérica para todas as personagens do nosso jogo. Como tal, vai conter as operações
necessárias à movimentação dessas personagens, obtenção das suas coordenadas, etc.
toString() devolve uma String com o carácter correspondente à personagem que implementa esta interface.
up() tenta deslocar o personagem para cima. Pode conseguir, ou não, dependendo da existência de uma parede a bloquear a progressão.
down() tenta deslocar a personagem para baixo. Pode conseguir, ou não.
left() tenta deslocar a personagem para a esquerda. Pode conseguir, ou não.
right() tenta deslocar a personagem para a direita. Pode conseguir, ou não.
getRow() devolve a linha em que a personagem se encontra.
getColumn() devolve a coluna em que a personagem se encontra.
setMaze(Maze aMaze) passa a referência do labirinto para a personagem.
isMoving() devolve true se a personagem tiver algum sentido de deslocação (por exemplo, para a esquerda), ou false, caso contrário.
move() ordena à personagem para se mover no sentido em que se tentou deslocar da última vez. Por exemplo, se anteriormente uma personagem se tentou deslocar para a direita, move() vai ordenar que se desloque para a direita. Se a última tentativa foi para baixo, move() vai tentar que se desloque para baixo. E assim sucessivamente.
Guião 6 de POO 2008/2009
15
A classe AvatarClass
Programe uma classe abstracta AvatarClass que implementa a interface Avatar. Numa primeira fase,
limite-se a implementar os getters e setters relativos às coordenadas das personagens deste jogo. Os
restantes métodos ficam para mais tarde. Ou seja, deixe a sua implementação vazia, mas implemente-
os, para que o seu programa possa compilar. Apesar de ser uma classe abstracta, vamos definir nela
algumas constantes que apresentamos de seguida. UP, DOWN, LEFT e RIGHT são constantes inteiras,
usadas apenas como forma de enumerar as possíveis direcções de forma consistente no programa (por
exemplo, quando queremos passar uma direcção por argumento, ou guardar a última direcção usada).
São declaradas com o modificador protected, para que possam ser acedidas nas sub-classes, mas não
noutras classes. Os valores atribuídos a estas constantes não são particularmente importantes, desde
que sejam inteiros e todos diferentes uns dos outros:
UP = 1
DOWN = 2;
LEFT = 3;
RIGHT = 4;
Além destas constantes, vamos também definir uma série de variáveis, todas declaradas com o
modificador private, que passamos a descrever:
isMoving vale true se a personagem tiver alguma direcção de movimentação, false caso contrário. Por exemplo, quando começa, a celebutante está parada.
myMaze guarda uma referência para o labirinto em que a personagem se movimenta.
row guarda a linha do labirinto em que a personagem se encontra.
col guarda a coluna do labirinto em que a personagem se encontra.
moveDirection guarda a direcção em que a personagem se tentou deslocar pela última vez.
A classe Hero
Programe uma classe HeroClass, subclasse de AvatarClass, que implementará o nosso herói. Numa
primeira fase, basta implementar o construtor e o método toString(), que deve devolver “H”.
Posteriormente, quando implementar os métodos para tentar deslocar a celebutante em cada uma das
direcções, sugerimos também que implemente o método privado updateMaze(), que consome os
recursos (com o método consume, declarado em Maze) que possam existir na posição de destino após a
tentativa de movimentação da celebutante.
A classe Monster
Programe uma classe Monster, subclasse de AvatarClass, que representa os vilões deste nosso jogo.
Esta classe é abstracta. Neste guião, apenas vamos criar uma sub-classe de Monster, denominada
StupidMonster. Como você imagina, estamos a criar esta classe abstracta a pensar num futuro guião,
em que implementaremos novos vilões. Mas, para já, concentremo-nos nesta classe abstracta. Vamos
criar algumas variáveis, que passamos a descrever:
isScared é uma variável de classe, ou seja, o seu valor é partilhado por todos os (ex-namorados) da nossa celebutante. O seu valor fica a true sempre que a celebutante passa por
Guião 6 de POO 2008/2009
16
um ponto de bónus. Durante o período activo do bónus, isScared mantem-se a true. Assim que o período do bónus termina, isScared volta ao estado false.
isHit é uma variável de instância que por omissão tem o valor false. No entanto, durante o período em que o bónus é activado, a celebutante pode dar uma bofetada num ex-namorado, e é nesse momento que a variável assume o valor de true. Este valor é mantido até ao final do período de bónus. Quando termina o bónus, o valor volta a false. Esta variável tem apenas um propósito. Durante um período de bónus, a celebutante só ganha pontos da primeira vez em que der uma bofetada num ex-namorado. Uma vez dada esta bofetada, a consulta a esta variável permite que não se continuem a contabilizar pontos por contactos posteriores, dentro do mesmo período de bónus. Naturalmente, se houver mais tarde um outro período de bónus, a celebutante pode, novamente, descarregar a sua fúria de novo no mesmo ex-namorado.
points guarda o número de pontos que este ex-namorado vale, quando leva uma bofetada. Além destas variáveis, necessitamos de algumas operações adicionais:
scare() é usada para assustar os monstros (ou seja, para colocar a variável isScared a true). Além disso, coloca isHit a false. Esta função deve ser chamada quando o bónus é activado.
encourage() coloca isScared a false, tal como isHit.
isScared() devolve true se o monstro está assustado, false caso contrário.
updateMaze() actualiza o estado do jogo após o movimento de um monstro. Repare, se o monstro se movimentar e chocar com a celebutante, isso significa que apanha um estalo, se estiver assustado, ou faz a celebutante perder o jogo, se não estiver.
getPoints() devolve o número de pontos que este monstro vale neste momento.
A classe StupidMonster
Programe a classe StupidMonster. Esta classe implementa todos os vilões, neste guião. Tal como o
nome indica, estes vilões não serão particularmente inteligentes, mas guardamos o verdadeiro boss
deste jogo para outro guião.
Como certamente se recorda, dissemos na explicação da tarefa D que pretendemos que implemente um
vector circular para tratar da escolha da direcção a tomar pelo monstro, em cada momento. Como
apenas existem 4 direcções possíveis, use um vector com 4 posições, em que coloca cada uma das
direcções possíveis, por ordem de prioridades iniciais. Use uma variável auxiliar para saber qual a
direcção escolhida pelo seu vilão num dado momento. Quando esse sentido estiver temporariamente
indisponível (porque o monstro esbarraria numa parede), passe ao sentido seguinte. Se chegar ao final
do vector, volte ao princípio. Garanta apenas que se o seu monstro estiver fechado em 4 paredes o seu
programa não entra num ciclo infinito. Seria a morte social, não tanto do monstro, mas pelo menos da
sua submissão ao mooshak.
Esta classe vai ter 3 variáveis de instância:
priorities – Vector de inteiros com 4 elementos, representando as prioridades do monstro.
currentPriorityIndex – Índice inteiro que indica qual a direcção a tomar em cada
momento. Por razões óbvias, este valor varia entre 0 e 3.
color – um carácter que pode assumir os valores „R‟, „G‟, e „B‟, consoante a cor do
monstro.