Pontifícia Universidade Católica do Rio Grande do Sul
Faculdade de Informática — Laboratório de Programação II

Trabalho Prático 2

Tank Escape

1Instruções

2 Enunciado

O trabalho consiste em implementar o jogo Tank Escape, onde o jogador controla um tanque e tem o objetivo de fugir de um ambiente definido. Porém, há diversos adversários no caminho - estes podem ser evitados, ou destruídos. Outro objetivo do trabalho é praticar a programação C++, com herança e polimorfismo. As próximas seções descrevem os diversos aspectos do trabalho.

2.1 Ambiente

O ambiente do jogo é representado na forma de uma matriz, onde cada elemento equivale a uma posição no mapa de jogo. Neste mapa pode haver dois tipos de objetos:
figure mapa.png
Figura 1 Mapa do jogo
O jogador sai de uma posição específica e precisa chegar na saída (veja a figura 1↑ para entender). O tanque do jogador pode se deslocar nas 4 direções, usando as setas do teclado.

2.2 Adversários

Há 3 tipos de adversários nesse ambiente:
O deslocamento dos tanques inimigos no modo de ataque pode ser feito de várias formas, melhores ou piores. Para fazer da forma 100% correta, seria preciso o estudo de algoritmos de Inteligência Artificial, como o A* - são os chamados algoritmos de pathfinding - não é essa a nossa intenção. Portanto, o objetivo aqui é improvisar, criando um algoritmo que seja razoável o suficiente para o jogo.
"Enxergar" consiste em se ter uma linha de visada direta até o jogador. Traça-se então uma linha imaginária do inimigo ao jogador: se não houver uma parede no caminho, o inimigo está "enxergando" o jogador.
"Ouvir" segue o mesmo princípio, porém o adversário "ouve" o jogador se este se movimentar enquanto o adversário estiver próximo o suficiente. Essa distância pode ser definida por experimentação (por exemplo, 5 ou 10 unidades).

3 Detalhes técnicos

Deve ser obrigatoriamente utilizado o framework fornecido no link a seguir: tank-escape-base.zip. Este framework implementa a funcionalidade básica de jogo, incluindo controle da janela gráfica, comandos via teclado, suporte para desenho de imagens, etc. O trabalho deve explorar os conceitos de herança e polimorfismo.
O framework fornece um conjunto de classes básicas para realizar todas essas funções, porém algumas estão incompletas e outras devem ser implementadas integralmente no trabalho. As seções a seguir descrevem essas classes.

3.1 Jogo

O controle principal do jogo é feito pela classe CGame, porém esta não deve ser alterada: a implementação da lógica de jogo em si deve ser feita na classe Play.

3.2 Mapa

Um mapa (ambiente de jogo) é definido por uma classe abstrata Mapa:
class Mapa
{
public:

virtual void loadMap(string arquivo) = 0; // carrega o arquivo do mapa virtual void draw() = 0; // desenha o mapa virtual bool isEmpty(const Point& ponto) const = 0; // retorna true se a posição estiver vazia virtual int getWidth() = 0; // retorna a quantidade de colunas virtual int getHeight() = 0; // retorna a quantidade de linhas virtual bool checkCollision(CImage* obj) = 0; // retorna true se houver colisão do objeto com as paredes };
Essa classe é estendida pela classe MapaTexto, que implementa boa parte dessa funcionalidade. Você deve apenas implementar o método loadMap, que carrega a definição do mapa a partir de um arquivo texto com o seguinte formato:
dim 30 20 // dimensões: colunas x linhas
1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1  
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 1 0 1 0 0 1 0 1
1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1
1 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1 0 0 1 0 1
1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 1
1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 1 0 0 1 0 1
1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1
1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1
1 0 0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 1 0 1 1 1 1 1 1 0 0 0 0 1
1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1
1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1
1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1
1 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 0 0 1 0 0 1
1 0 0 0 0 0 0 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 1
1 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 1
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Observe que após as informações numéricas vem o desenho do mapa, feito com '0's e '1's: obviamente, '1's representam as paredes. Por uma questão de princípio, o jogador SEMPRE começa na parte de cima do mapa (primeira linha), na primeira coluna que estiver vazia. A saída do mapa é qualquer outro local onde não exista uma parede na borda (no exemplo, o último caractere da penúltima linha).

3.3 Inimigo

A partir da classe abstrata Enemy, você deve implementar os 3 tipos de inimigos descritos na seção 2.2↑:
class Enemy
{
public:

virtual void draw() = 0;
virtual void move() = 0;

private:

CImage* img;
};
Ou seja, deve haver um método draw capaz de desenhar o inimigo, e um método move capaz de movimentá-lo (as torres não se movimentam). Observe que provavelmente será necessário ter acesso ao mapa, bem como à posição atual do jogador - para implementar isso, você pode alterar a classe abstrata como achar mais conveniente.
Não esqueça que o jogo precisa ter uma lista de inimigos para desenhar e movimentar - provavelmente, o melhor lugar para incluir isso é na classe Play.

3.4 Imagens

A classe CImage implementa uma imagem que pode ser livremente posicionada pelo cenário. Ela pode ser usada para montar ambientes, ou mesmo para desenhar personagens (nosso caso). Por esse motivo há um ponteiro para CImage na classe Enemy: presume-se que qualquer tipo de inimigo terá um desenho associado. No método draw, você pode apenas desenhar essa imagem, ou fazer algo mais.
Métodos úteis dessa classe incluem:
Por fim, vale a recomendação tradicional: lembre-se que uma das vantagens do paradigma de OO é a facilidade de modelar, implementar e testar cada classe individualmente: se o problema parece complicado demais a princípio, tente imaginar as suas várias partes, e modele as classes mais simples em um primeiro momento.

4Critérios de Avaliação

Leia com atenção os critérios de avaliação:

4.1 Funcionamento

4.2 Código

4.3 Entrega