Este projeto, desenvolvido para a disciplina de Programação em C no outono de 2022, foca na criação de um jogo de console interativo. O jogo utiliza o padrão de linguagem C99 e é compilado para um executável Windows (.exe) utilizando GCC 11.2.0. As funcionalidades foram projetadas para rodar em um ambiente de console Windows.
A base de código utiliza predominantemente inglês para evitar problemas de codificação, com poucas exceções em comentários. Para uma correta visualização, o código-fonte deve ser aberto com a codificação UTF-8. Os exemplos de código apresentados neste documento são simplificados para clareza e podem diferir ligeiramente do código-fonte real disponível na pasta 'src'.
Funcionalidades
-
Geração Aleatória do Tabuleiro
O jogo opera em um tabuleiro de 10x10. Inicialmente, cada célula tem uma probabilidade de 50% de conter um item (neste caso, um "lata").
-
Impressão do Tabuleiro
O estado atual do tabuleiro é exibido na saída padrão, que em sistemas Windows corresponde à janela do console.
-
Processamento de Comandos do Usuário
Os comandos são processados de forma insensível a maiúsculas e minúsculas, sendo convertidos para minúsculas. Comandos inválidos após a conversão recebem uma notificação.
- Comandos Predefinidos:
exitouCtrl+Z: Encerra o jogo e exibe a pontuação final.- Um único dígito de
0a6: Controla as ações do jogador.- Movimento Aleatório: Executa uma das ações de movimento (2 a 7) aleatoriamente.
- Mover para Cima: O jogador se move para cima. Se a célula superior for uma parede, a posição do jogador permanece inalterada, contando como uma colisão com a parede.
- Mover para Baixo: Similar ao movimento para cima, mas na direção para baixo.
- Mover para a Esquerda: Similar ao movimento para cima, mas na direção para a esquerda.
- Mover para a Dirieta: Similar ao movimento para cima, mas na direção para a direita.
- Não Mover: O jogador permanece na posição atual.
- Coletar Lata: Se houver uma lata na posição atual do jogador, ela é coletada, e a célula fica vazia.
- Comandos Adicionais:
help: Exibe a documentação de ajuda.wasd: Ativa o modo rápido. Neste modo, os comandos de movimento (WASD para direcional, E para coletar, Q para movimento aleatório) são processados sem a necessidade de pressionar Enter. As teclas I, J, K, L, O, U também executam as mesmas funções. Pressionar Enter sai deste modo.
- Comandos Predefinidos:
-
Atualização em Tempo Real do Painel
Após cada comando, o nome do comando é exibido em um painel lateral direito, e a pontuação atualizada é mostrada no painel esuqerdo.
Implementação do Código
Visão Geral
A abordagem de desenvolvimento seguiu um processo de "top-down", refinando gradualmente os detalhes. O código apresentado aqui é uma representação simplificada, omitindo alguns detalhes e com formatação de indentação alterada para facilitar a leitura. O código fonte real na pasta 'src' é a versão definitiva.
main.c
A função principal, main, orquestra a lógica geral do programa. Ela inicializa o jogo, gerencia o loop principle de entrada do usuário e processa os comandos até que o jogo seja encerrado.
#include "main.h" // Inclui definições e protótipos
// Função para exibir mensagens de ajuda
void display_help() {
// Implementação da exibição de ajuda...
printf("Comandos disponiveis: exit, help, wasd, 0-6\n");
}
// Função para processar um comando de movimento
bool process_move(char command_char) {
int move_code = command_char - '0'; // Converte char para int
// Lógica de movimento aqui...
// Retorna true se o jogo deve continuar, false caso contrário
return true;
}
// Função para lidar com comandos inválidos
void handle_invalid_command() {
printf("Comando invalido. Digite 'help' para mais informacoes.\n");
}
int main(int argc, char **argv) {
initialize_game(); // Função de inicialização (definida em init.c)
char user_input[512];
// Loop principal do jogo
while (1) {
printf("Digite um comando: ");
if (scanf("%s", user_input) == EOF) {
break; // Sai se for fim de arquivo
}
// Converte para minúsculas para comparação insensível a maiúsculas/minúsculas
for(int i = 0; user_input[i]; i++){
user_input[i] = tolower(user_input[i]);
}
if (strcmp(user_input, "exit") == 0) {
break; // Sai do loop se o comando for 'exit'
} else if (strcmp(user_input, "help") == 0) {
display_help();
} else if (isdigit(user_input[0]) && user_input[0] >= '0' && user_input[0] <= '6') {
if (!process_move(user_input[0])) {
break; // Sai se process_move retornar false (ex: jogo terminou)
}
} else {
handle_invalid_command();
}
update_dashboard(); // Atualiza os painéis de status
}
printf("Pontuacao Final:\n");
printf("O jogo terminou! Sua pontuacao final e: %d\n", get_current_score());
// system("pause"); // Pausa em sistemas Windows
return 0;
}
Funções como display_help(), process_move() e handle_invalid_command() podem ser definidas em arquivos separados para melhor organização.
main.h
Este arquivo de cabeçalho declara os tipos de dados personalizados, variáveis globais e funções essenciais utilizadas em todos os arquivos fonte. Para otimização de desempenho, algumas funções podem ser implementadas como macros.
#ifndef PROJECT_MAIN_HEADER
#define PROJECT_MAIN_HEADER
#include <stdlib.h>
#include <string.h>
#include <windows.h> // Necessário para funções do console Windows
#include <ctype.h> // Para tolower, isdigit
// Estrutura para representar a posição (x, y)
typedef struct {
unsigned int x_coord : 4;
unsigned int y_coord : 4;
} PlayerPosition;
// Constante para o espaçamento horizontal no tabuleiro
const int HORIZONTAL_SPACING = 3; // Espaçamento de (HORIZONTAL_SPACING - 1) colunas
// Variáveis globais
extern int current_score;
extern int item_count;
extern char game_board[11][11]; // Matriz para o tabuleiro do jogo
extern PlayerPosition player_pos; // Instância da posição do jogador
// Macro para mover o cursor do console.
// Adaptada para corresponder ao estilo de nomenclatura da API do Windows.
// Reduz a sobrecarga de chamadas de função.
#define MOVE_CURSOR_TO(row, col) \
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), (COORD){(short)(row), (short)(col)})
// Protótipo da função principal
int main(int argc, char *argv[]);
// Outros protótipos de funções globais (definidos em outros .c)
void initialize_game();
void update_dashboard();
int get_current_score();
#endif // PROJECT_MAIN_HEADER
A macro MOVE_CURSOR_TO é usada extensivamente para posicionar o cursor e atualizar o tabuleiro com precisão.
init.c
Este arquivo contém a lógica para inicializar o tabuleiro, desenhá-lo no console e atualizar elementos específicos na tela.
#include "init.h" // Inclui definições e protótipos de inicialização
// Função para desenhar o tabuleiro e a posição do jogador
void draw_board() {
// Desenha a borda superior
for (int j = 0; j <= HORIZONTAL_SPACING * 10; ++j) {
putchar(j % HORIZONTAL_SPACING ? ' ' : '#');
}
putchar('\n');
// Desenha o corpo do tabuleiro
for (int i = 1; i <= 10; ++i) { // Linhas
putchar('#'); // Borda esquerda
for (int j = 1; j <= HORIZONTAL_SPACING * 10; ++j) { // Colunas
if (j % HORIZONTAL_SPACING == 0) {
putchar(game_board[j / HORIZONTAL_SPACING][i]);
} else {
putchar(' '); // Preenche com espaços
}
}
puts("#"); // Borda direita
}
// Desenha a borda inferior
for (int j = 0; j <= HORIZONTAL_SPACING * 10; ++j) {
putchar(j % HORIZONTAL_SPACING ? ' ' : '#');
}
putchar('\n'); // Nova linha
// Linha divisória
for (int j = 0; j <= HORIZONTAL_SPACING * 10; ++j) {
putchar('_');
}
puts("\n"); // Nova linha e espaço
// Desenha o jogador ('!') na sua posição atual
MOVE_CURSOR_TO(player_pos.x_coord * HORIZONTAL_SPACING, player_pos.y_coord);
putchar('!');
}
// Função para criar o tabuleiro com itens aleatórios
inline void generate_items() {
// Inicializa o gerador de números aleatórios
srand(time(0));
// Inicializa todas as células como vazias
memset(game_board, ' ', sizeof(game_board));
// Preenche o tabuleiro com itens aleatórios
for (int i = 1; i <= 10; ++i) {
for (int j = 1; j <= 10; ++j) {
// 50% de chance de colocar um item ('@')
if (rand() % 2 == 0) {
game_board[j][i] = '@';
item_count++; // Conta o número de itens
}
}
}
}
// Outras funções auxiliares
inline void initialize_prompts() { /* Implementação do painel */ }
void display_help_documentation() { /* Implementação da ajuda */ }
Funções como initialize_prompts() e display_help_documentation() realizam tarefas mais simples e não são detalhadas aqui.
move.c
Este arquivo gerencia a lógica de movimento do jogador e a interação com os itens no tabuleiro.
#include "move.h" // Inclui definições e protótipos de movimento
// Enumeração para os códigos de ação (para clareza)
typedef enum {
ACTION_RANDOM = 1,
ACTION_UP,
ACTION_DOWN,
ACTION_LEFT,
ACTION_RIGHT,
ACTION_STAY,
ACTION_COLLECT
} ActionCode;
// Função principal de movimento do jogador
// Retorna true se todos os itens foram coletados, false caso contrário.
bool perform_player_action(int action_code) {
// Se o código for 0, seleciona uma ação aleatória válida
if (action_code == 0) {
action_code = (rand() % 6) + 1; // Gera número entre 1 e 6
}
switch (action_code) {
case ACTION_UP:
case ACTION_DOWN:
case ACTION_LEFT:
case ACTION_RIGHT:
// Lógica de movimento direcional
// Verificar colisão com paredes e atualizar player_pos se o movimento for válido
// Se colidir com parede, subtrair pontos: current_score -= 5;
printf("Movimento executado.\n");
break;
case ACTION_COLLECT:
printf("Tentando coletar...\n");
// Verifica se há um item na posição atual
if (game_board[player_pos.x_coord][player_pos.y_coord] == '@') {
game_board[player_pos.x_coord][player_pos.y_coord] = ' '; // Remove o item
current_score += 10; // Adiciona pontos
if (--item_count == 0) {
return true; // Todos os itens foram coletados
}
} else {
current_score -= 2; // Penalidade por tentar coletar onde não há item
}
break;
default: // ACTION_STAY ou ação não definida
printf("Nenhuma acao realizada.\n");
break;
}
update_score_display(); // Atualiza a exibição da pontuação
return false; // Continua o jogo
}
// Função para movimento rápido (reutiliza perform_player_action)
void handle_fast_move(char key) {
// Mapeia teclas para action_code e chama perform_player_action
// Ex: 'w' -> ACTION_UP, 'e' -> ACTION_COLLECT, 'q' -> ACTION_RANDOM
int code = -1;
switch(tolower(key)) {
case 'w': case 'i': code = ACTION_UP; break;
case 's': case 'k': code = ACTION_DOWN; break;
case 'a': case 'j': code = ACTION_LEFT; break;
case 'd': case 'l': code = ACTION_RIGHT; break;
case 'e': case 'o': code = ACTION_COLLECT; break;
case 'q': case 'u': code = ACTION_RANDOM; break;
}
if (code != -1) {
perform_player_action(code);
}
}
Uma função handle_fast_move() foi implementada para reutilizar a lógica de perform_player_action(), evitando duplicação de código. Detalhes completos estão no código fonte.
Desafios e Soluções
Abordagens Iniciais
-
Leitura de Caractere Único: Em plataformas não-Windows, a leitura de um único caractere sem esperar Enter pode ser feita usando bibliotecas como
termios.h. O código a seguir ilustra essa técnica: ```#include <termios.h> #include <unistd.h> // Para STDIN_FILENO
char get_single_char() { struct termios old_tio, new_tio; char input_char;
// Obtém os atributos atuais do terminal tcgetattr(STDIN_FILENO, &old_tio); new_tio = old_tio; // Desabilita o modo canônico (buffer de linha) e o eco new_tio.c_lflag &= ~(ICANON | ECHO); // Aplica os novos atributos imediatamente tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); // Lê um caractere input_char = getchar(); // Restaura os atributos originais do terminal tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); return input_char;}
-
Manipulação do Cursor: Para tabuleiros pequenos, o cursor poderia ser movido usando sequências de escape como
\\b(backspace) ou recriando a linha inteira com\\r(carriage return). No entanto, essas abordagens podem ser ineficientes.
Solução Adotada
Após pesquisa na documentação do Windows, a função SetConsoleCursorPosition foi utilizada. Ela permite mover o cursor para coordenadas específicas (nRows, nCols) no console, possibilitando a atualização precisa do tabuleiro.
void move_cursor_to(short nRows, short nCols) {
COORD position = {nRows, nCols};
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
Esta função, implementada como a macro MOVE_CURSOR_TO no cabeçalho, é crucial para a atualização eficiente da interface do console.
Depuração
Um bug comum encontrado foi a inversão das coordenadas (x, y) em relação às variáveis de loop do console, que geralmente correspondem a j (coluna) e i (linha). A correção envolveu garantir a correspondência correta entre as coordenadas do jogo e os índices da matriz.
Referências
As principais referências para este projeto foram:
- Documentação oficial do Windows Console (Microsoft Learn), especificamente sobre a função
SetConsoleCursorPosition.
Sugestões de Melhoria
- Interface Gráfica (GUI): A interface de console preta e branca pode ser substituída por uma GUI para uma experiência de usuário mais amigável. Alterar cores no console, como a cor do jogador, oferece melhorias limitadas.
- Documentação para Console: Para projetos que utilizam interfaces de console, fornecer guias claros sobre como interagir com essa UI pode ser benéfico, pois é um padrão comum em muitos executáveis.
- Comandos Intuitivos: Comandos como "0123456" são menos intuitivos. A adoção de esquemas de controle comuns em jogos, como "WASD" para movimento e "Q", "E", "Espaço" para interações, melhoraria a usabilidade. Essas teclas são de caractere único e fáceis de integrar em estruturas
switch.