Abordagem Modular em C para Implementação do Jogo
Para desenvolver um jogo interativo como o da velha em C, é essencial adotar uma estrutura de código modular. Isso envolve a separação em arquivos distintos: um cabeçalho (.h) para declarações e definições de constantes, e dois arquivos de código-fonte (.c) – um para a lógica principal e outro para as funções do jogo. Esta prática melhora a manutenibilidade, facilita a reutilização de código e permite uma melhor organização do projeto.
Estrutura de Arquivos e Declarações
Crie três arquivos: jogo.h (para macros e protótipos), principal.c (para o fluxo do jogo) e funcoes.c (para implementação das funções). No cabeçalho, defina as dimensões do tabuleiro e declare as funções necessárias.
// jogo.h
#ifndef JOGO_H
#define JOGO_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define TAMANHO_TABULEIRO 3
void inicializar_tabuleiro(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col);
void mostrar_tabuleiro(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col);
void jogada_jogador(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col);
void jogada_computador(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col);
char verificar_vitoria(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col);
#endif
Implementação do Menu Principal
O menu principal utiliza um laço do-while para repetir as opções até que o usuário decida sair. A função switch-case direciona a execução com base na escolha do jogador.
// principal.c
#include "jogo.h"
void menu_principal() {
printf("==== Jogo da Velha ====\n");
printf("1. Iniciar partida\n");
printf("0. Sair\n");
printf("=======================\n");
}
void iniciar_partida() {
char tabuleiro[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO];
inicializar_tabuleiro(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
mostrar_tabuleiro(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
char resultado = 'C'; // 'C' para continuar
while (resultado == 'C') {
jogada_jogador(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
mostrar_tabuleiro(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
resultado = verificar_vitoria(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
if (resultado != 'C') break;
jogada_computador(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
mostrar_tabuleiro(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
resultado = verificar_vitoria(tabuleiro, TAMANHO_TABULEIRO, TAMANHO_TABULEIRO);
}
if (resultado == 'X') printf("Jogador venceu!\n");
else if (resultado == 'O') printf("Computador venceu!\n");
else if (resultado == 'E') printf("Empate!\n");
}
int main() {
srand((unsigned int)time(NULL));
int opcao;
do {
menu_principal();
printf("Opção: ");
scanf("%d", &opcao);
switch (opcao) {
case 1:
printf("Iniciando jogo...\n");
iniciar_partida();
break;
case 0:
printf("Encerrando...\n");
break;
default:
printf("Opção inválida.\n");
}
} while (opcao != 0);
return 0;
}
Inicialização e Exibição do Tabuleiro
A inicialização preenche o tabuleiro com espaços em branco usando memset. A exibição percorre a matrriz bidimensional, imprimindo os símbolos com separadores visuais.
// funcoes.c
#include "jogo.h"
void inicializar_tabuleiro(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col) {
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
tab[i][j] = ' ';
}
}
}
void mostrar_tabuleiro(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col) {
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
printf(" %c ", tab[i][j]);
if (j < col - 1) printf("|");
}
printf("\n");
if (i < lin - 1) {
for (int k = 0; k < col; k++) {
printf("---");
if (k < col - 1) printf("|");
}
printf("\n");
}
}
}
Lógica das Jogadas
O jogador insere coordenadas (baseadas em 1), que são validadas para garantir que estejam dentro dos limites e em uma posição vazia. O computador gera coordenadas aleatórias até encontrar uma célula livre.
void jogada_jogador(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col) {
int linha, coluna;
printf("Jogador - insira linha e coluna (ex: 1 2): ");
scanf("%d %d", &linha, &coluna);
while (linha < 1 || linha > lin || coluna < 1 || coluna > col || tab[linha-1][coluna-1] != ' ') {
printf("Posição inválida. Tente novamente: ");
scanf("%d %d", &linha, &coluna);
}
tab[linha-1][coluna-1] = 'X';
}
void jogada_computador(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col) {
int l, c;
printf("Computador jogando...\n");
do {
l = rand() % lin;
c = rand() % col;
} while (tab[l][c] != ' ');
tab[l][c] = 'O';
}
Verificação do Resultado
A função verificar_vitoria checa linhas, colunas e diagonais para três símbolos iguais. Retorna 'X' ou 'O' para vitórias, 'E' para empate (tabuleiro cheio) e 'C' para continuação.
char verificar_vitoria(char tab[TAMANHO_TABULEIRO][TAMANHO_TABULEIRO], int lin, int col) {
// Verificar linhas e colunas
for (int i = 0; i < lin; i++) {
if (tab[i][0] != ' ' && tab[i][0] == tab[i][1] && tab[i][1] == tab[i][2])
return tab[i][0];
if (tab[0][i] != ' ' && tab[0][i] == tab[1][i] && tab[1][i] == tab[2][i])
return tab[0][i];
}
// Verificar diagonais
if (tab[0][0] != ' ' && tab[0][0] == tab[1][1] && tab[1][1] == tab[2][2])
return tab[0][0];
if (tab[0][2] != ' ' && tab[0][2] == tab[1][1] && tab[1][1] == tab[2][0])
return tab[0][2];
// Verificar empate
for (int i = 0; i < lin; i++) {
for (int j = 0; j < col; j++) {
if (tab[i][j] == ' ') return 'C';
}
}
return 'E';
}
Este código demonstra a implementação completa de um jogo da velha em C, destacando a modularidade e a lógica de controle de fluxo.