Este artigo detalha a implementação em C do clássico jogo Campo Minado. Abordaremos a estrtuura do projeto, as funções essenciais e a lógica por trás da detecção de minas e da propagação de espaços vazios.
Estrutura Geral do Projeto
O projeto é dividido em três arquivos principais:
main.c: Contém a função principal, o menu de interação com o usuário e a chamada para a função principal do jogo.game.h: Arquivo de cabeçalho que declara as funções, define as constantes (dimensões do tabuleiro, número de minas) e inclui as bibliotecas necessárias.game.c: Contém a implementação de todas as funções do jogo, incluindo inicialização do tabuleiro, posicionamento das minas, lógica de jogo e exibição.
main.c - Ponto de Entrada e Menu
O arquivo main.c gerencia o fluxo principal do programa:
#include "game.h"
void exibirMenu() {
printf("********************\n");
printf("***** 1. Jogar *****\n");
printf("***** 0. Sair *****\n");
printf("********************\n");
}
int main() {
int opcao = 0;
srand((unsigned int)time(NULL)); // Inicializa o gerador de números aleatórios
do {
exibirMenu();
printf("Escolha uma opcao: >");
scanf("%d", &opcao);
switch (opcao) {
case 1:
iniciarJogo();
break;
case 0:
printf("Saindo do jogo...\n");
break;
default:
printf("Opcao invalida. Tente novamente.\n");
break;
}
} while (opcao);
return 0;
}
A função iniciarJogo(), definida em game.c, orquestra a execução de uma partida.
void iniciarJogo() {
char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS] = {0};
char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS] = {0};
int resultado = 1;
inicializarTabuleiro(tabuleiroMinas, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS, '0'); // Inicializa tabuleiro de minas
inicializarTabuleiro(tabuleiroExibicao, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS, '*'); // Inicializa tabuleiro de exibição
posicionarMinasAleatorias(tabuleiroMinas, LINHA_JOGO, COLUNA_JOGO, '0', '1'); // Posiciona as minas
// printTabuleiro(tabuleiroMinas, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS); // Debug: Descomente para ver o posicionamento das minas
printTabuleiro(tabuleiroExibicao, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS); // Exibe o tabuleiro inicial para o jogador
while (1) {
resultado = jogarTurno(tabuleiroMinas, tabuleiroExibicao, LINHA_JOGO, COLUNA_JOGO, '0', '1');
if (!resultado) { // Se o jogador pisou em uma mina (resultado é 0)
printTabuleiro(tabuleiroMinas, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS); // Revela o tabuleiro com as minas
break; // Sai do loop da partida
}
printTabuleiro(tabuleiroExibicao, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS); // Exibe o tabuleiro atualizado
}
}
Note o uso de duas dimensões para os tabuleiros: uma para a lógica interna (tabuleiroMinas) e outra para a exibição ao jogador (tabuleiroExibicao). As constantes LINHA_JOGO e COLUNA_JOGO definem a área jogável, enquanto TAMANHO_TOTAL_LINHAS e TAMANHO_TOTAL_COLUNAS incluem uma borda para simplificar os cálculos de vizinhança.
game.h - Declarações e Constantes
O arquivo game.h centraliza as definições:
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define LINHA_JOGO 9
#define COLUNA_JOGO 9
#define TAMANHO_TOTAL_LINHAS LINHA_JOGO + 2
#define TAMANHO_TOTAL_COLUNAS COLUNA_JOGO + 2
#define NUMERO_MINAS 10 // Quantidade de minas a serem posicionadas
// Inicializa um tabuleiro com um caractere específico
void inicializarTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas, char valorPadrao);
// Imprime o tabuleiro formatado
void printTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas);
// Posiciona minas aleatoriamente no tabuleiro
void posicionarMinasAleatorias(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina);
// Executa um turno do jogo, retornando 0 se o jogador perder, 1 caso contrário
int jogarTurno(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina);
// Conta o número de minas adjacentes a uma coordenada
int contarMinasAdjacentes(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y);
// Revela áreas vazias recursivamente quando uma célula sem minas adjacentes é aberta
void revelarAreaVazia(char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y, int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina, int* contadorCelulasAbertas);
// Inicia a partida do jogo
void iniciarJogo();
game.c - Implementação das Funções
Inicialização dos Tabuleiros
Duas funções cuidam da inicialização:
void inicializarTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas, char valorPadrao) {
for (int i = 0; i < linhas; i++) {
for (int j = 0; j < colunas; j++) {
tabuleiro[i][j] = valorPadrao;
}
}
}
inicializarTabuleiro preenche um tabuleiro com um caractere especificado. tabuleiroMinas é preenchido com '0' (representando ausência de mina), e tabuleiroExibicao com '*' (representando células não reveladas).
Posicionamento das Minas
A função posicionarMinasAleatorias garante que as minas sejam distribuídas aelatoriamente na área de jogo:
void posicionarMinasAleatorias(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina) {
int minasRestantes = NUMERO_MINAS;
while (minasRestantes > 0) {
// Gera coordenadas aleatórias dentro da área de jogo (excluindo a borda)
int linhaAleatoria = rand() % linhaAreaJogo + 1;
int colunaAleatoria = rand() % colunaAreaJogo + 1;
if (tabuleiro[linhaAleatoria][colunaAleatoria] == charVazio) {
tabuleiro[linhaAleatoria][colunaAleatoria] = charMina;
minasRestantes--;
}
}
}
Exibição do Tabuleiro
printTabuleiro formata a saída para o console, incluindo índices de linha e coluna:
void printTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas) {
// Imprime os índices das colunas
printf(" "); // Espaçamento inicial para alinhar com os índices das linhas
for (int j = 1; j < colunas - 1; j++) {
printf("%d ", j);
}
printf("\n");
// Imprime os índices das linhas e o conteúdo do tabuleiro
for (int i = 1; i < linhas - 1; i++) {
printf("%d ", i); // Índice da linha
for (int j = 1; j < colunas - 1; j++) {
printf("%c ", tabuleiro[i][j]);
}
printf("\n");
}
}
Lógica do Jogo (Turno do Jogador)
A função jogarTurno é o coração do jogo:
// Conta o número de minas adjacentes a uma coordenada (x, y)
int contarMinasAdjacentes(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y) {
int contador = 0;
// Verifica as 8 células vizinhas
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
// Garante que não estamos fora dos limites do tabuleiro interno e que a célula não é a própria célula (x, y)
if (i >= 0 && i < TAMANHO_TOTAL_LINHAS && j >= 0 && j < TAMANHO_TOTAL_COLUNAS) {
if (tabuleiroMinas[i][j] == '1') { // '1' representa uma mina
contador++;
}
}
}
}
// Se a célula atual for uma mina, não a conte como adjacente a si mesma
if (tabuleiroMinas[x][y] == '1') {
return -1; // Sinaliza que a célula clicada é uma mina
}
return contador;
}
// Revela células adjacentes se a célula atual não tiver minas por perto
void revelarAreaVazia(char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y, int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina, int* contadorCelulasAbertas) {
// Define os limites da área a ser revelada (incluindo a célula atual)
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
// Verifica se a célula está dentro da área de jogo válida e se ainda não foi revelada ou é uma mina
if (i >= 1 && i <= linhaAreaJogo && j >= 1 && j <= colunaAreaJogo) {
if (tabuleiroExibicao[i][j] == '*') { // Se a célula ainda estiver oculta
int minasProximas = contarMinasAdjacentes(tabuleiroMinas, i, j);
if (minasProximas == 0) {
tabuleiroExibicao[i][j] = ' '; // Revela como espaço vazio
(*contadorCelulasAbertas)++;
revelarAreaVazia(tabuleiroExibicao, tabuleiroMinas, i, j, linhaAreaJogo, colunaAreaJogo, charVazio, charMina, contadorCelulasAbertas); // Chamada recursiva
} else if (minasProximas > 0) {
tabuleiroExibicao[i][j] = minasProximas + '0'; // Revela o número de minas adjacentes
(*contadorCelulasAbertas)++;
}
}
}
}
}
}
int jogarTurno(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina) {
int linhaEscolhida, colunaEscolhida;
static int celulasAbertas = 0; // Contador para verificar se o jogador venceu
// Verifica se o jogador já venceu (todas as células não-mina foram abertas)
if (linhaAreaJogo * colunaAreaJogo - NUMERO_MINAS == celulasAbertas) {
printf("Parabens! Voce limpou o campo minado!\n");
return 0; // Termina o jogo (simulando uma perda para sair do loop principal)
}
printf("Digite as coordenadas (linha coluna): >");
scanf("%d %d", &linhaEscolhida, &colunaEscolhida);
// Validação das coordenadas
if (linhaEscolhida >= 1 && linhaEscolhida <= linhaAreaJogo && colunaEscolhida >= 1 && colunaEscolhida <= colunaAreaJogo) {
if (tabuleiroExibicao[linhaEscolhida][colunaEscolhida] == '*') { // Só processa se a célula não foi revelada ainda
if (tabuleiroMinas[linhaEscolhida][colunaEscolhida] == charMina) {
printf("BOOM! Voce explodiu.\n");
return 0; // Jogador perdeu
} else {
int minasAdjacentes = contarMinasAdjacentes(tabuleiroMinas, linhaEscolhida, colunaEscolhida);
if (minasAdjacentes == 0) {
tabuleiroExibicao[linhaEscolhida][colunaEscolhida] = ' '; // Célula vazia
celulasAbertas++;
// Revela área vazia recursivamente
revelarAreaVazia(tabuleiroExibicao, tabuleiroMinas, linhaEscolhida, colunaEscolhida, linhaAreaJogo, colunaAreaJogo, charVazio, charMina, &celulasAbertas);
} else {
tabuleiroExibicao[linhaEscolhida][colunaEscolhida] = minasAdjacentes + '0'; // Mostra o número de minas adjacentes
celulasAbertas++;
}
return 1; // Jogador continua jogando
}
} else {
printf("Esta celula ja foi revelada.\n");
return 1; // Continua jogando
}
} else {
printf("Coordenadas invalidas.\n");
return 1; // Continua jogando
}
}
A lógica de contarMinasAdjacentes verifica os 8 vizinhos de uma célula. Se uma célula sem minas adjacentes for aberta, revelarAreaVazia é chamada recursivamente para abrir todas as células conectadas que também não possuem minas adjacentes, preenchendo as bordas dessas áreas com o número de minas próximas.
O Código Completo
A seguir, apresentamos o código completo organizado em seus respectiovs arquivos.
test.c (main.c)
#define _CRT_SECURE_NO_WARNINGS // Para compatibilidade com scanf_s em alguns compiladores
#include "game.h"
void exibirMenu() {
printf("********************\n");
printf("***** 1. Jogar *****\n");
printf("***** 0. Sair *****\n");
printf("********************\n");
}
void iniciarJogo() {
char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS] = {0};
char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS] = {0};
int resultado = 1;
inicializarTabuleiro(tabuleiroMinas, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS, '0');
inicializarTabuleiro(tabuleiroExibicao, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS, '*');
posicionarMinasAleatorias(tabuleiroMinas, LINHA_JOGO, COLUNA_JOGO, '0', '1');
// printTabuleiro(tabuleiroMinas, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS); // Debug
printTabuleiro(tabuleiroExibicao, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS);
while (resultado) { // Continua enquanto o jogador não perder (resultado == 1)
resultado = jogarTurno(tabuleiroMinas, tabuleiroExibicao, LINHA_JOGO, COLUNA_JOGO, '0', '1');
if (!resultado) { // Se jogarTurno retornar 0 (perdeu ou ganhou)
printTabuleiro(tabuleiroMinas, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS);
break;
}
printTabuleiro(tabuleiroExibicao, TAMANHO_TOTAL_LINHAS, TAMANHO_TOTAL_COLUNAS);
}
}
int main() {
int opcao = 0;
srand((unsigned int)time(NULL));
do {
exibirMenu();
printf("Escolha uma opcao: >");
scanf("%d", &opcao);
switch (opcao) {
case 1:
iniciarJogo();
break;
case 0:
printf("Saindo do jogo...\n");
break;
default:
printf("Opcao invalida. Tente novamente.\n");
break;
}
} while (opcao);
return 0;
}
game.h
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <Windows.h> // Pode ser necessário para algumas funcionalidades específicas do Windows, como gotoxy, embora não usado aqui diretamente
#define LINHA_JOGO 9
#define COLUNA_JOGO 9
#define TAMANHO_TOTAL_LINHAS LINHA_JOGO + 2
#define TAMANHO_TOTAL_COLUNAS COLUNA_JOGO + 2
#define NUMERO_MINAS 10
void inicializarTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas, char valorPadrao);
void printTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas);
void posicionarMinasAleatorias(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina);
int jogarTurno(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina);
int contarMinasAdjacentes(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y);
void revelarAreaVazia(char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y, int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina, int* contadorCelulasAbertas);
void iniciarJogo();
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void inicializarTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas, char valorPadrao) {
for (int i = 0; i < linhas; i++) {
for (int j = 0; j < colunas; j++) {
tabuleiro[i][j] = valorPadrao;
}
}
}
void printTabuleiro(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhas, int colunas) {
printf(" ");
for (int j = 1; j < colunas - 1; j++) {
printf("%d ", j);
}
printf("\n");
for (int i = 1; i < linhas - 1; i++) {
printf("%d ", i);
for (int j = 1; j < colunas - 1; j++) {
printf("%c ", tabuleiro[i][j]);
}
printf("\n");
}
}
void posicionarMinasAleatorias(char tabuleiro[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina) {
int minasRestantes = NUMERO_MINAS;
while (minasRestantes > 0) {
int linhaAleatoria = rand() % linhaAreaJogo + 1;
int colunaAleatoria = rand() % colunaAreaJogo + 1;
if (tabuleiro[linhaAleatoria][colunaAleatoria] == charVazio) {
tabuleiro[linhaAleatoria][colunaAleatoria] = charMina;
minasRestantes--;
}
}
}
int contarMinasAdjacentes(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y) {
// Verifica se a célula clicada é uma mina antes de contar vizinhos
if (tabuleiroMinas[x][y] == '1') {
return -1; // Indica que a célula é uma mina
}
int contador = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
// Verifica limites do tabuleiro interno (onde as minas estão)
if (i >= 0 && i < TAMANHO_TOTAL_LINHAS && j >= 0 && j < TAMANHO_TOTAL_COLUNAS) {
if (tabuleiroMinas[i][j] == '1') {
contador++;
}
}
}
}
return contador;
}
void revelarAreaVazia(char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int x, int y, int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina, int* contadorCelulasAbertas) {
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
// Limites da área de jogo
if (i >= 1 && i <= linhaAreaJogo && j >= 1 && j <= colunaAreaJogo) {
if (tabuleiroExibicao[i][j] == '*') { // Se ainda não foi revelada
int minasProximas = contarMinasAdjacentes(tabuleiroMinas, i, j);
if (minasProximas == 0) {
tabuleiroExibicao[i][j] = ' '; // Espaço vazio
(*contadorCelulasAbertas)++;
revelarAreaVazia(tabuleiroExibicao, tabuleiroMinas, i, j, linhaAreaJogo, colunaAreaJogo, charVazio, charMina, contadorCelulasAbertas);
} else if (minasProximas > 0) {
tabuleiroExibicao[i][j] = minasProximas + '0';
(*contadorCelulasAbertas)++;
}
}
}
}
}
}
int jogarTurno(char tabuleiroMinas[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], char tabuleiroExibicao[TAMANHO_TOTAL_LINHAS][TAMANHO_TOTAL_COLUNAS], int linhaAreaJogo, int colunaAreaJogo, char charVazio, char charMina) {
int linhaEscolhida, colunaEscolhida;
static int celulasAbertas = 0; // Conta quantas células seguras foram abertas
// Verifica condição de vitória antes de pedir nova jogada
if (linhaAreaJogo * colunaAreaJogo - NUMERO_MINAS == celulasAbertas) {
printf("Parabens! Voce limpou o campo minado!\n");
return 0; // Finaliza o loop do jogo
}
printf("Digite as coordenadas (linha coluna): >");
scanf("%d %d", &linhaEscolhida, &colunaEscolhida);
if (linhaEscolhida >= 1 && linhaEscolhida <= linhaAreaJogo && colunaEscolhida >= 1 && colunaEscolhida <= colunaAreaJogo) {
if (tabuleiroExibicao[linhaEscolhida][colunaEscolhida] == '*') { // Só processa se a célula ainda estiver oculta
int minasAdjacentes = contarMinasAdjacentes(tabuleiroMinas, linhaEscolhida, colunaEscolhida);
if (minasAdjacentes == -1) { // O jogador clicou em uma mina
printf("BOOM! Voce explodiu.\n");
return 0; // Jogador perdeu
} else if (minasAdjacentes == 0) { // Célula sem minas adjacentes
tabuleiroExibicao[linhaEscolhida][colunaEscolhida] = ' ';
celulasAbertas++;
revelarAreaVazia(tabuleiroExibicao, tabuleiroMinas, linhaEscolhida, colunaEscolhida, linhaAreaJogo, colunaAreaJogo, charVazio, charMina, &celulasAbertas);
} else { // Célula com minas adjacentes
tabuleiroExibicao[linhaEscolhida][colunaEscolhida] = minasAdjacentes + '0';
celulasAbertas++;
}
return 1; // Jogador continua jogando
} else {
printf("Esta celula ja foi revelada.\n");
return 1; // Continua jogando
}
} else {
printf("Coordenadas invalidas.\n");
return 1; // Continua jogando
}
}