Exercício de Programação: Simulador de Combate por Turnos (Versão
Mega Estendida)
Neste exercício, você desenvolverá um jogo simples de combate por
turnos entre dois personagens. Cada personagem terá atributos como
vida, ataque, defesa e agilidade. O combate ocorrerá em um mapa
simplificado, exigindo movimentação para que os ataques sejam
possíveis quando há distância. Além disso, adicionaremos a mecânica
de desvio de ataques, múltiplos tipos de ataque, itens de cura, a
possibilidade de classes de personagens e um menu inicial.
Conhecimentos Cobrados:
* Condições (if, else if, else)
* Funções (criação e uso, passagem de structs por referência)
* Matrizes (para o "mapa" de combate)
* Operadores (aritméticos, relacionais, lógicos)
* Ponteiros (especialmente para passar structs para funções e
permitir a modificação de seus membros)
* Laços de Repetição (for, while, do-while)
* Strings (para nomes de personagens e descrições)
* Tipos Definidos (struct) (para representar personagens, ataques e
itens)
* Vetores (para armazenar múltiplos ataques, itens, ou personagens)
* printf e scanf
Bibliotecas Adicionais:
Além de stdio.h e string.h, você precisará da biblioteca stdlib.h para
as funções rand() (geração de números aleatórios) e srand() (para
inicializar o gerador de números aleatórios). A biblioteca time.h é
usada junto com srand() para garantir uma semente aleatória
diferente a cada execução.
Para a funcionalidade de pausa/atraso entre os turnos e limpeza de
tela, você pode usar:
* #include <unistd.h> e a função sleep(segundos) para sistemas
Unix/Linux/macOS. Para limpar a tela, system("clear");.
* #include <windows.h> e a função Sleep(milissegundos) para
Windows. Para limpar a tela, system("cls");.
* Observação: Use diretivas de pré-processador (#ifdef _WIN32 ou
#ifndef _WIN32) para incluir a biblioteca e a função correta
dependendo do sistema operacional.
O Exercício (Dividido em Partes):
Parte A: Definição dos Elementos do Jogo
* Defina um struct para o Tipo de Ataque: Crie um struct chamado
TipoAtaque que contenha:
* char nome[30]; (ex: "Ataque Rápido", "Golpe Poderoso")
* int multiplicadorDano; (ex: 1 para ataque normal, 1.5 para
poderoso)
* int custoMana; (se quiser adicionar um recurso de mana para
ataques especiais)
* int alcance; (quantos "quadrados" de distância o ataque pode
atingir, ex: 1 para ataques corpo a corpo, 5 para ataques a distância)
* Defina um struct para o Item: Crie um struct chamado Item que
contenha:
* char nome[30]; (ex: "Poção de Vida Pequena")
* int valorCura; (o quanto de vida o item restaura)
* int quantidade; (quantos desse item o personagem possui)
* Defina um struct para a Classe do Personagem: Crie um struct
chamado Classe que contenha:
* char nome[30]; (ex: "Guerreiro", "Mago", "Ladino")
* int bonusVida;
* int bonusAtaque;
* int bonusDefesa;
* int bonusAgilidade;
* Defina um struct para o Personagem (Atualizado): Crie um struct
chamado Personagem que contenha os seguintes membros:
* char nome[30];
* int vidaAtual;
* int vidaMaxima;
* int ataqueBase; (dano base antes de modificadores)
* int defesaBase; (redução de dano antes de modificadores)
* int agilidade; (novo atributo para desviar)
* char simboloNoMapa;
* int linha; (posição no mapa)
* int coluna; (posição no mapa)
* struct Classe classe; (o tipo de classe do personagem)
* struct TipoAtaque ataques[3]; (um vetor de diferentes tipos de
ataque que o personagem pode usar)
* int numAtaques;
* struct Item inventario[3]; (um vetor de itens que o personagem
possui)
* int numItens;
* Função para Inicializar Personagem: Crie uma função chamada
inicializarPersonagem que receba um ponteiro para um struct
Personagem, o nome, atributos base, símbolo, e a struct Classe que
será atribuída. Esta função deve configurar todos os membros,
aplicando os bônus da classe aos atributos base e definindo uma
posição inicial no mapa.
Parte B: Exibição e "Mapa" Interativo
* Defina Constantes para o Mapa: Use #define para definir o
tamanho do mapa (ex: MAPA_LINHAS 5, MAPA_COLUNAS 10).
* Função para Exibir Ficha do Personagem (Atualizado): Crie uma
função chamada exibirFicha que receba um ponteiro para um struct
Personagem. Esta função deve imprimir de forma formatada os
atributos do personagem (nome, vida atual/máxima, ataque efetivo,
defesa efetiva, agilidade, classe).
* Função para Imprimir Mapa: Crie uma função chamada
imprimirMapa que receba uma matriz de caracteres (o mapa em si), e
os ponteiros para os structs Personagem do jogador e do inimigo. Esta
função deve:
* Limpar a matriz com um caractere de "chão" (ex: '.').
* Colocar os simboloNoMapa dos personagens em suas linha e
coluna atuais.
* Imprimir a matriz na tela, representando o "campo de batalha".
Parte C: Lógica de Combate, Ações e
Movimentação
* Função para Calcular Distância: Crie uma função chamada
calcularDistancia que receba as coordenadas de dois pontos (linha1,
coluna1, linha2, coluna2). A função deve retornar a distância
Manhattan entre eles (abs(linha1 - linha2) + abs(coluna1 - coluna2)).
* Função para Tentar Desviar: Crie uma função chamada
tentarDesviar que receba a agilidade do defensor. Esta função deve:
* Gerar um número aleatório entre 1 e 10 (simulando um dado de
10 lados).
* Se o resultado do dado for maior ou igual à agilidade do defensor,
retornar true (ou 1) indicando sucesso no desvio.
* Caso contrário, retornar false (ou 0).
* Imprimir uma mensagem informando se o desvio foi bem-sucedido
ou falhou.
* Função para Calcular Dano: Crie uma função chamada calcularDano
que receba o ataque do agressor, a defesa do defensor e o
multiplicadorDano do tipo de ataque. A função deve calcular o dano
real aplicando a lógica: dano_base = ataque * multiplicadorDano;
dano_real = dano_base - defesa. O dano mínimo deve ser 1. Adicione
uma pequena aleatoriedade ao dano utilizando rand().
* Função para Realizar Ataque: Crie uma função chamada
realizarAtaque que receba ponteiros para os dois structs Personagem:
um atacante e um defensor, e o índice do TipoAtaque a ser usado.
Esta função deve:
* Obter o TipoAtaque selecionado do vetor ataques do atacante.
* Verificar o alcance: Calcule a distância entre atacante e defensor
usando calcularDistancia. Se a distância for maior que o alcance do
ataque, informe que o alvo está muito longe e retorne (não consume
turno, ou pode fazer o personagem se mover automaticamente).
* Se o alvo estiver dentro do alcance:
* Chame tentarDesviar com a agilidade do defensor.
* Se o desvio for bem-sucedido, imprima uma mensagem e não
aplique dano.
* Se o desvio falhar, chame calcularDano para determinar o dano.
* Decrementar a vidaAtual do defensor pelo dano calculado.
* Imprimir uma mensagem informando quem atacou, qual ataque
usou, quem foi atingido e quanto dano foi causado.
* Função para Usar Item: Crie uma função chamada usarItem que
receba um ponteiro para o struct Personagem que usará o item e o
índice do Item a ser usado. Esta função deve:
* Verificar se o personagem possui o item selecionado (quantidade
> 0).
* Se sim, aumentar a vidaAtual do personagem pelo valorCura do
item, garantindo que a vida não exceda a vidaMaxima.
* Decrementar a quantidade do item.
* Imprimir uma mensagem informando qual item foi usado e quanto
de vida foi recuperado.
* Retornar um valor booleano (ou 0/1) indicando se o item foi usado
com sucesso.
* Função para Mover Personagem: Crie uma função chamada
moverPersonagem que receba um ponteiro para um struct
Personagem e uma direção (ex: 'w' para cima, 's' para baixo, 'a' para
esquerda, 'd' para direita). Esta função deve:
* Calcular a nova posição.
* Validar a nova posição: Verificar se a nova linha e coluna estão
dentro dos limites do mapa.
* Se a posição for válida, atualizar as coordenadas linha e coluna do
personagem.
* Imprimir uma mensagem de confirmação ou erro de movimento.
Parte D: Controle do Fluxo do Jogo
* Função para Exibir Menu de Ações (Atualizado): Crie uma função
exibirMenuAcoes que receba um ponteiro para o struct Personagem
do jogador. Deve imprimir as opções disponíveis para o jogador (ex: 1.
Atacar, 2. Usar Item, 3. Mover, 4. Ver Ficha).
* Se o jogador escolher atacar, deve listar os TipoAtaque disponíveis
com seus alcances.
* Se escolher usar item, deve listar os Item disponíveis no
inventário.
* Se escolher mover, deve pedir a direção (W, A, S, D).
* Função Principal (main): Na função main:
* Inicialize o gerador de números aleatórios com
srand(time(NULL));.
* Defina Classes: Crie algumas variáveis struct Classe predefinidas
(ex: "Guerreiro" (alto ataque/defesa, baixa agilidade), "Mago"
(ataques de longo alcance, baixa vida), "Ladino" (alta agilidade, baixo
ataque/defesa)).
* Defina Tipos de Ataque: Crie algumas variáveis struct TipoAtaque
predefinidas (ex: "Soco" (alcance 1), "Corte Poderoso" (alcance 1),
"Bola de Fogo" (alcance 5), "Flecha Precisa" (alcance 8)).
* Defina Itens: Crie algumas variáveis struct Item predefinidas (ex:
"Poção Pequena", "Poção Média").
* Crie duas variáveis do tipo struct Personagem: um jogador e um
inimigo.
* Menu Inicial:
* Implemente um menu inicial onde o jogador pode escolher a
classe do seu personagem. Com base na escolha, chame
inicializarPersonagem para o jogador, atribuindo a classe e seus
ataques/itens iniciais. Defina a posição inicial do jogador (ex: linha =
0, coluna = 0).
* Inicialize o inimigo com uma classe e atributos fixos ou
aleatórios. Defina a posição inicial do inimigo (ex: linha =
MAPA_LINHAS - 1, coluna = MAPA_COLUNAS - 1).
* Crie uma matriz char mapa[MAPA_LINHAS][MAPA_COLUNAS]; para
representar o campo de batalha.
* Implemente um laço de repetição (while) que continue enquanto a
vidaAtual de ambos os personagens for maior que 0.
* Dentro do laço, simule os turnos:
* Limpar Tela: Use a função de limpeza de tela adequada para seu
OS.
* Turno do Jogador:
* Chame exibirFicha para ambos.
* Chame imprimirMapa com as posições atualizadas dos
personagens.
* Chame exibirMenuAcoes e solicite a escolha do jogador.
* Use um switch ou if/else if para processar a escolha do jogador:
* Se escolher atacar, peça para escolher qual ataque usar. Antes
de chamar realizarAtaque, certifique-se de que o inimigo está dentro
do alcance do ataque escolhido (a função realizarAtaque já fará essa
verificação, mas você pode avisar o jogador antes).
* Se escolher usar item, peça para escolher qual item usar.
* Se escolher mover, peça a direção (W, A, S, D) e chame
moverPersonagem.
* Valide as escolhas do jogador (número de ataque/item válido,
item disponível, movimento válido).
* Verifique se o inimigo foi derrotado. Se sim, saia do laço e
declare a vitória do jogador.
* Turno do Inimigo:
* Se o jogador ainda estiver vivo, o inimigo deve decidir sua ação.
* Lógica do Inimigo: O inimigo pode priorizar atacar se o jogador
estiver ao alcance de um de seus ataques. Caso contrário, o inimigo
pode se mover em direção ao jogador.
* Chame realizarAtaque ou moverPersonagem para o inimigo.
* Verifique se o jogador foi derrotado. Se sim, saia do laço e
declare a derrota do jogador.
* Pausa entre turnos: Adicione um pequeno atraso (usando sleep()
ou Sleep() ou um loop vazio) para melhorar a experiência.
* Após o laço, determine o vencedor e exiba uma mensagem final,
revelando a vidaAtual restante do vencedor.
Este exercício é bem completo e desafiador. Ele te forçará a pensar
em como diferentes módulos de um jogo interagem. Lembre-se de
testar cada funcionalidade separadamente antes de integrar tudo.
Qualquer dúvida ou se precisar de ajuda com a implementação de
alguma parte, é só me chamar!