/*patrik lima pereira*/
// === Cabeçalhos ===
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/gpio.h"
#include "ssd1306.h" // Biblioteca para display OLED
#include "pico/binary_info.h"
#include "hardware/pwm.h"
// #include <IAByPatrikLimaPereira.h> // Biblioteca de IA personalizada (comentada)
// === Constantes do Jogo ===
#define TAMANHO 3 // Tamanho do tabuleiro (3x3)
#define SIMULACOES_POR_MOVIMENTO 10000 // Número de simulações por movimento no MCTS
#define POPULACAO 10 // Tamanho da população para algoritmos genéticos
#define GERACOES 100 // Número de gerações para algoritmos genéticos
#define MUTACAO_PROB 10 // Probabilidade de mutação em algoritmos genéticos
#define LCD_BACKLIGHT 0x08 // Bit para controle da luz de fundo do LCD
#define LCD_ENABLE_BIT 0x04 // Bit para habilitar o LCD
#define MAX_STRINGS 100000 // Número máximo de strings armazenadas
#define MAX_TAMANHO 100000 // Tamanho máximo de cada string
// === Estruturas ===
/**
* @brief Estrutura para representar uma jogada no tabuleiro.
*/
typedef struct {
int linha; // Linha da jogada (0 a 2)
int coluna; // Coluna da jogada (0 a 2)
} Jogada;
// === Variáveis Globais ===
// Tabuleiro do jogo da velha (inicializado como vazio)
char tabuleiro[TAMANHO][TAMANHO] = {
{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '}
};
// Endereço I2C padrão do LCD
static int addr = 0x27;
// Estatísticas do jogo
int vitoriasJogador = 0; // Contador de vitórias do jogador
int vitoriasComputador = 0; // Contador de vitórias do computador
int empates = 0; // Contador de empates
// Configurações do jogo
bool mostrarNumeros = true; // Mostra números das posições na primeira exibição
int algoritmoIA = 0; // Algoritmo de IA ativo (0 = Minimax padrão)
bool flagLiberaMain = true; // Trava para o menu principal
// Pinos I2C para comunicação com o LCD
const uint I2C_SDA = 14; // Pino SDA para I2C
const uint I2C_SCL = 15; // Pino SCL para I2C
// Pinos dos LEDs
const int ledGold = 13; // LED dourado (ilumina teclado e serve como lanterna)
const int ledGreen = 12; // LED verde
const int ledBlue = 11; // LED azul
// Configuração do teclado matricial
const int rows[4] = {2, 3, 4, 5}; // Pinos das linhas do teclado
const int cols[4] = {6, 7, 8, 9}; // Pinos das colunas do teclado
// Mapeamento das teclas do teclado matricial
char keyboardLayout[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// Controle dos LEDs
int blueLedFlag = 1; // Estado do LED azul (1 = ligado, 0 = desligado)
int greenLedFlag = 1; // Estado do LED verde (1 = ligado, 0 = desligado)
// Buffer para o display OLED
uint8_t ssd[ssd1306_buffer_length]; // Buffer do SSD1306
struct render_area frame_area; // Área de renderização do display
// Buffer para strings dinâmicas
char *text[MAX_STRINGS]; // Array de ponteiros para strings
int count = 0; // Contador de strings armazenadas
// === Protótipos de Funções ===
// Funções de controle do LCD
void lcd_init(void); // Inicializa o LCD
void lcd_clear(void); // Limpa o display do LCD
void lcd_set_cursor(int line, int position); // Define a posição do cursor
void lcd_string(const char *s); // Escreve uma string no LCD
// Funções de hardware
void initializeHardware(void); // Inicializa GPIO, I2C e LEDs
char getPressedKey(void); // Lê a tecla pressionada no teclado matricial
// Funções de manipulação de strings
void adicionar_string(const char *formato, ...); // Adiciona string formatada ao buffer
char *concatenar_strings(void); // Concatena todas as strings armazenadas
void zerar_strings(void); // Limpa o buffer de strings
// Funções do jogo da velha
void resetarTabuleiro(void); // Reseta o tabuleiro para o estado inicial
void imprimirTabuleiro(void); // Exibe o tabuleiro no console ou display
bool verificarVitoria(char jogador); // Verifica se o jogador venceu
bool verificarEmpate(void); // Verifica se houve empate
void limparBufferEntrada(void); // Limpa o buffer de entrada do teclado
int verificarVitoriaTabuleiro(char tab[TAMANHO][TAMANHO], char jogador); // Verifica vitória em um tabuleiro específico
int verificarEmpateTabuleiro(char tab[TAMANHO][TAMANHO]); // Verifica empate em um tabuleiro específico
// Algoritmos de IA
int minimax(bool maximizando); // Minimax padrão
int minimaxPuro(bool maximizando); // Minimax sem poda
Jogada mcts(void); // Monte Carlo Tree Search
int forcaBrutaAI(bool maximizando); // IA por força bruta
int heuristicaAI(bool maximizando); // IA com heurística
int simularJogoAleatorio(void); // Simula um jogo aleatório
int simularJogo(bool maximizando); // Simula um jogo com IA
int avaliarFitness(bool maximizando); // Avalia o fitness para algoritmos genéticos
int algoritmoGeneticoAI(bool maximizando); // IA com algoritmo genético
int negamax(bool maximizarJogador); // Algoritmo Negamax
int iterativeDeepening(bool maximizando); // Busca com aprofundamento iterativo
// Funções de jogabilidade
void melhorJogada(void); // Determina a melhor jogada da IA
void jogar(void); // Loop principal do jogo
void mostrarPlacar(void); // Exibe o placar atual
void zerarPlacar(void); // Zera o placar
void draw_menu(char *text[], int count); // Desenha o menu no display
// Definições para o buzzer
#define BUZZER_PIN 18 // Pino GPIO onde o buzzer está conectado
/**
* @brief Configura e toca uma frequência no buzzer por uma duração específica.
* @param frequency Frequência em Hz a ser tocada.
* @param duration Duração do som em milissegundos.
*/
void play_tone(uint frequency, uint duration) {
gpio_set_function(BUZZER_PIN, GPIO_FUNC_PWM); // Define o pino como PWM
uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN); // Obtém o slice PWM associado ao pino
// Calcula o divisor e o wrap para a frequência desejada
uint32_t clock = 125000000; // Frequência do clock do Pico (125 MHz)
float divider = (float) clock / (frequency * 1000);
// Divisor para alcançar a frequência (usando float para precisão)
if (divider < 1.0f) divider = 1.0f; // Garante divisor mínimo
pwm_set_clkdiv(slice_num, divider); // Define o divisor do clock
pwm_set_wrap(slice_num, 999); // Define o período do PWM (1000 ciclos - 1)
pwm_set_chan_level(slice_num, pwm_gpio_to_channel(BUZZER_PIN), 500); // Duty cycle de 50%
pwm_set_enabled(slice_num, true); // Habilita o PWM
sleep_ms(duration); // Toca o tom pela duração especificada
pwm_set_enabled(slice_num, false); // Desliga o PWM
}
/**
* @brief Simula o som de uma tecla sendo pressionada no buzzer.
* Toca um tom curto e agudo típico de clique de teclado.
*/
void play_keypress_sound() {
play_tone(1000, 50); // Toca um tom de 1000 Hz por 50ms (clique agudo)
sleep_ms(10); // Pequena pausa para evitar sobreposição de sons
}
/**
* @brief Simula o som de uma conexão dial-up no buzzer.
*/
void play_dialup_sound() {
// Sequência inicial de tons de handshake (tons variados entre 300 Hz e 2000 Hz)
uint handshake_tones[] = {300, 600, 900, 1200, 1800, 1500, 1000, 400};
int num_tones = sizeof(handshake_tones) / sizeof(handshake_tones[0]);
for (int i = 0; i < num_tones; i++) {
play_tone(handshake_tones[i], 100); // Toca cada tom por 100ms
sleep_ms(50); // Pausa curta entre tons para simular transição
}
// Simula ruído de estática com tons rápidos e aleatórios
for (int i = 0; i < 30; i++) {
uint freq = 500 + (rand() % 1500); // Frequência aleatória entre 500 Hz e 2000 Hz
play_tone(freq, 50); // Toca tons curtos de 50ms
sleep_ms(20); // Pausa mínima entre tons
}
// Finaliza com um tom descendente
play_tone(1000, 100);
play_tone(800, 100);
play_tone(600, 150);
}
/**
* @brief Simula o som da vitória de Ayrton Senna no buzzer.
* Reproduz uma versão simplificada do tema da vitória da Fórmula 1.
*/
void play_senna_victory_sound() {
// Sequência baseada no tema da vitória (notas aproximadas de "Aquarela do Brasil")
uint victory_notes[] = {
523, // C5 (Dó)
659, // E5 (Mi)
784, // G5 (Sol)
880, // A5 (Lá)
784, // G5 (Sol)
659, // E5 (Mi)
523, // C5 (Dó)
392 // G4 (Sol)
};
int num_notes = sizeof(victory_notes) / sizeof(victory_notes[0]);
// Toca a sequência de notas triunfantes
for (int i = 0; i < num_notes; i++) {
play_tone(victory_notes[i], 150); // Cada nota toca por 150ms
sleep_ms(50); // Pausa curta entre notas para clareza
}
// Finaliza com um tom longo e vibrante
play_tone(784, 300); // G5 (Sol) por 300ms para um final marcante
}
/**
* @brief Executa o loop principal de um jogo da velha entre jogador ('O') e computador ('X').
*/
/**
* @brief Escreve um byte no barramento I2C para o LCD.
* @param val Byte a ser enviado ao dispositivo I2C.
*/
void i2c_write_byte(uint8_t val) {
i2c_write_blocking(i2c_default, addr, &val, 1, false); // Envia o byte via I2C sem manter controle
}
/**
* @brief Alterna o bit de habilitação do LCD para processar um comando ou dado.
* @param val Byte base que será modificado com o bit de enable.
*/
void lcd_toggle_enable(uint8_t val) {
sleep_us(600); // Aguarda 600µs para sincronização do LCD
i2c_write_byte(val | LCD_ENABLE_BIT); // Ativa o bit de enable para processar o dado/comando
sleep_us(600); // Aguarda o LCD processar o sinal
i2c_write_byte(val & ~LCD_ENABLE_BIT); // Desativa o bit de enable
sleep_us(600); // Aguarda estabilização
}
/**
* @brief Envia um byte ao LCD, dividido em dois nibbles (superior e inferior).
* @param val Byte a ser enviado (caractere ou comando).
* @param mode Modo de envio (1 para caractere, 0 para comando).
*/
void lcd_send_byte(uint8_t val, int mode) {
uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT; // Prepara nibble superior com modo e luz de fundo
uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT; // Prepara nibble inferior com modo e luz de fundo
i2c_write_byte(high); // Envia o nibble superior
lcd_toggle_enable(high); // Processa o nibble superior no LCD
i2c_write_byte(low); // Envia o nibble inferior
lcd_toggle_enable(low); // Processa o nibble inferior no LCD
}
/**
* @brief Limpa o display do LCD.
*/
void lcd_clear() {
lcd_send_byte(0x01, 0); // Comando para limpar o display (0x01)
sleep_ms(2); // Pequena espera para o comando ser processado pelo LCD
}
/**
* @brief Define a posição do cursor no LCD.
* @param line Linha do display (0 para primeira, 1 para segunda).
* @param position Posição na linha (0 a 15).
*/
void lcd_set_cursor(int line, int position) {
int val = (line == 0) ? 0x80 + position : 0xC0 + position;
// Calcula endereço DDRAM (0x80 = linha 1, 0xC0 = linha 2)
lcd_send_byte(val, 0); // Envia comando para posicionar o cursor
}
/**
* @brief Escreve uma string no LCD, caractere por caractere.
* @param s Ponteiro para a string a ser exibida.
*/
void lcd_string(const char *s) {
while (*s) {
// Enquanto houver caracteres na string
lcd_send_byte(*s++, 1); // Envia cada caractere como dado (mode = 1)
}
}
/**
* @brief Inicializa o LCD no modo 4 bits, 2 linhas e fonte 5x8.
*/
void lcd_init() {
lcd_send_byte(0x03, 0); // Passo 1 da sequência de inicialização
lcd_send_byte(0x03, 0); // Passo 2 da sequência de inicialização
lcd_send_byte(0x03, 0); // Passo 3 da sequência de inicialização
lcd_send_byte(0x02, 0); // Define modo de operação 4 bits
lcd_send_byte(0x28, 0); // Configura 2 linhas e fonte 5x8 pontos
lcd_send_byte(0x0C, 0); // Liga o display, sem cursor ou piscar
lcd_send_byte(0x06, 0); // Define modo de entrada (incremento à direita, sem deslocamento)
lcd_send_byte(0x01, 0); // Limpa o display no final da inicialização
}
// Função para adicionar uma string formatada ao array
/**
* @brief Adiciona uma string formatada ao array global de strings.
* @param formato String de formato (compatível com printf).
* @param ... Argumentos variáveis para o formato.
*/
void adicionar_string(const char *formato, ...) {
if (count >= MAX_STRINGS) {
// Verifica se o limite de strings foi atingido
printf("Erro: Limite de strings atingido!\n");
return;
}
// Aloca memória temporária para armazenar a string formatada
char buffer[MAX_TAMANHO];
va_list args;
va_start(args, formato); // Inicia lista de argumentos variáveis
vsnprintf(buffer, MAX_TAMANHO, formato, args); // Formata a string de forma segura no buffer
va_end(args); // Finaliza a lista de argumentos
// Aloca memória para armazenar a string final
text[count] = (char *) malloc(strlen(buffer) + 1); // Aloca espaço para a string + terminador nulo
if (text[count] == NULL) {
// Verifica falha na alocação
printf("Erro de alocação de memória!\n");
return;
}
strcpy(text[count], buffer); // Copia a string formatada para o array
count++; // Incrementa o contador de strings
}
// Função para concatenar todas as strings do array
/**
* @brief Concatena todas as strings armazenadas em uma única string.
* @return Ponteiro para a string concatenada (ou NULL em caso de erro/falta de strings).
*/
char *concatenar_strings() {
if (count == 0) return NULL; // Retorna NULL se não houver strings
// Calcula o tamanho total necessário
int tamanho_total = 0;
for (int i = 0; i < count; i++) {
tamanho_total += strlen(text[i]); // Soma o comprimento de cada string
}
// Aloca memória para a string concatenada
char *resultado = (char *) malloc(tamanho_total + 1); // +1 para o terminador nulo
if (resultado == NULL) {
// Verifica falha na alocação
printf("Erro de alocação de memória!\n");
return NULL;
}
resultado[0] = '\0'; // Inicia string vazia
// Concatena todas as strings
for (int i = 0; i < count; i++) {
strcat(resultado, text[i]); // Adiciona cada string ao resultado
}
return resultado; // Retorna a string concatenada (deve ser liberada pelo chamador)
}
// Função para limpar todas as strings armazenadas
/**
* @brief Libera a memória de todas as strings no array e zera o contador.
*/
void zerar_strings() {
for (int i = 0; i < count; i++) {
free(text[i]); // Libera a memória alocada para cada string
text[i] = NULL; // Define o ponteiro como NULL para evitar dangling pointers
}
count = 0; // Zera o contador de strings
}
/**
* @brief Desenha o menu inicial no display OLED.
* @param text Array de strings contendo as linhas do menu.
* @param count Número de strings no array a serem exibidas.
*/
void draw_menu(char *text[], int count) {
memset(ssd, 0, ssd1306_buffer_length); // Limpa o buffer do display OLED, preenchendo com zeros
int y = 5; // Variável para a posição vertical inicial no display (em pixels)
for (int i = 0; i < count; i++) {
ssd1306_draw_string(ssd, 5, y, text[i]); // Desenha cada string no buffer na posição (5, y)
y += 8; // Incrementa a posição vertical em 8 pixels para a próxima linha
}
render_on_display(ssd, &frame_area); // Atualiza o display OLED com o conteúdo do buffer
}
/**
* @brief Reseta o tabuleiro para o estado inicial.
*/
void resetarTabuleiro() {
for (int i = 0; i < TAMANHO; i++)
for (int j = 0; j < TAMANHO; j++)
tabuleiro[i][j] = ' '; // Preenche todas as posições do tabuleiro com espaço vazio (' ')
mostrarNumeros = true; // Ativa a exibição dos números das posições na próxima impressão do tabuleiro
}
/**
* @brief Imprime o tabuleiro do jogo da velha no display.
*/
#define TAMANHO 3 // Tamanho do tabuleiro (3x3)
#define MAX_LINHAS 100 // Tamanho máximo do vetor de strings para armazenar as linhas do tabuleiro
char *str[MAX_LINHAS]; // Vetor de strings para armazenar as linhas a serem exibidas
int linha_atual = 0; // Contador para controlar a linha atual no vetor de strings
/**
* @brief Adiciona uma nova linha ao vetor de strings.
* @param texto String a ser adicionada ao vetor.
*/
void adicionarLinha(const char *texto) {
if (linha_atual < MAX_LINHAS) {
// Verifica se há espaço no vetor
str[linha_atual] = (char *) malloc(strlen(texto) + 1); // Aloca memória para a nova linha
strcpy(str[linha_atual], texto); // Copia o texto para a linha alocada
linha_atual++; // Incrementa o contador de linhas
} else {
printf("Erro: Número máximo de linhas atingido.\n"); // Exibe erro se o limite for atingido
}
}
/**
* @brief Libera a memória alocada para o vetor de strings.
*/
void liberarMemoria() {
for (int i = 0; i < linha_atual; i++) {
free(str[i]); // Libera a memória de cada linha alocada dinamicamente
}
linha_atual = 0; // Reseta o contador para zero após liberar a memória
}
/**
* @brief Imprime o tabuleiro no display, mostrando números ou símbolos conforme o estado.
*/
void imprimirTabuleiro() {
adicionarLinha("\n"); // Adiciona uma linha em branco antes do tabuleiro
for (int i = 0; i < TAMANHO; i++) {
char linha[100] = ""; // Buffer para construir a linha atual do tabuleiro
for (int j = 0; j < TAMANHO; j++) {
int posicaoB = i * TAMANHO + j + 1; // Calcula o número da posição (1 a 9) no tabuleiro
if (mostrarNumeros && tabuleiro[i][j] == ' ') {
// Se mostrarNumeros está ativo e a posição está vazia
char temp[10];
sprintf(temp, " %d ", posicaoB); // Formata o número da posição (ex.: " 1 ")
strcat(linha, temp); // Concatena o número à linha atual
} else {
// Caso a posição tenha 'X' ou 'O'
char temp[10];
sprintf(temp, " %c ", tabuleiro[i][j]); // Formata o símbolo (ex.: " X ")
strcat(linha, temp); // Concatena o símbolo à linha atual
}
if (j < TAMANHO - 1) {
// Se não for a última coluna
strcat(linha, "|"); // Adiciona um separador vertical entre as colunas
}
}
adicionarLinha(linha); // Adiciona a linha construída ao vetor de strings
if (i < TAMANHO - 1) {
// Se não for a última linha
adicionarLinha("---+---+---"); // Adiciona um separador horizontal entre as linhas
}
}
adicionarLinha("\n\n"); // Adiciona duas linhas em branco após o tabuleiro
int strCount = sizeof(str) / sizeof(str[0]);
// Calcula o número total de elementos no vetor (pode não refletir linhas reais)
draw_menu(str, strCount); // Desenha o tabuleiro no display usando o vetor de strings
liberarMemoria(); // Libera a memória alocada para as linhas
//zerar_strings();
mostrarNumeros = false; // Desativa a exibição dos números nas próximas impressões
}
/************/
/**
* @brief Verifica se um jogador venceu no tabuleiro atual.
* @param jogador Caractere representando o jogador ('X' ou 'O').
* @return bool Verdadeiro se o jogador venceu, falso caso contrário.
*/
bool verificarVitoria(char jogador) {
// Verifica linhas e colunas
for (int i = 0; i < TAMANHO; i++) {
if (tabuleiro[i][0] == jogador && tabuleiro[i][1] == jogador && tabuleiro[i][2] == jogador)
return true; // Verifica se o jogador venceu em uma linha
if (tabuleiro[0][i] == jogador && tabuleiro[1][i] == jogador && tabuleiro[2][i] == jogador)
return true; // Verifica se o jogador venceu em uma coluna
}
// Verifica diagonais
if (tabuleiro[0][0] == jogador && tabuleiro[1][1] == jogador && tabuleiro[2][2] == jogador)
return true; // Verifica se o jogador venceu na diagonal principal (esquerda superior -> direita inferior)
if (tabuleiro[0][2] == jogador && tabuleiro[1][1] == jogador && tabuleiro[2][0] == jogador)
return true; // Verifica se o jogador venceu na diagonal secundária (direita superior -> esquerda inferior)
return false; // Retorna falso se não houver vitória
}
/**
* @brief Verifica se o jogo terminou em empate no tabuleiro atual.
* @return bool Verdadeiro se todas as posições estão ocupadas, falso se há pelo menos uma vaga.
*/
bool verificarEmpate() {
for (int i = 0; i < TAMANHO; i++)
for (int j = 0; j < TAMANHO; j++)
if (tabuleiro[i][j] == ' ')
return false; // Retorna falso se houver pelo menos uma posição vazia
return true; // Retorna verdadeiro se todas as posições estiverem ocupadas (sem espaços vazios)
}
/**
* @brief Limpa o buffer de entrada para evitar leituras indesejadas.
*/
void limparBufferEntrada() {
int c;
while ((c = getchar()) != '\n' && c != EOF); // Lê e descarta caracteres até encontrar nova linha ou fim de arquivo
}
/**
* @brief Verifica se um jogador venceu em um tabuleiro específico.
* @param tab Matriz 3x3 representando o tabuleiro a ser analisado.
* @param jogador Caractere representando o jogador ('X' ou 'O').
* @return int 1 se o jogador venceu, 0 caso contrário.
*/
int verificarVitoriaTabuleiro(char tab[TAMANHO][TAMANHO], char jogador) {
// Verifica linhas e colunas
for (int i = 0; i < TAMANHO; i++) {
if ((tab[i][0] == jogador && tab[i][1] == jogador && tab[i][2] == jogador) || // Verifica linhas
(tab[0][i] == jogador && tab[1][i] == jogador && tab[2][i] == jogador)) {
// Verifica colunas
return 1; // Retorna 1 se o jogador venceu
}
}
// Verifica diagonais
if ((tab[0][0] == jogador && tab[1][1] == jogador && tab[2][2] == jogador) || // Diagonal principal
(tab[0][2] == jogador && tab[1][1] == jogador && tab[2][0] == jogador)) {
// Diagonal secundária
return 1; // Retorna 1 se o jogador venceu
}
return 0; // Retorna 0 se não houver vitória
}
/**
* @brief Verifica se um tabuleiro específico terminou em empate.
* @param tab Matriz 3x3 representando o tabuleiro a ser analisado.
* @return int 1 se houve empate, 0 se ainda há posições vazias.
*/
int verificarEmpateTabuleiro(char tab[TAMANHO][TAMANHO]) {
for (int i = 0; i < TAMANHO; i++)
for (int j = 0; j < TAMANHO; j++)
if (tab[i][j] == ' ')
return 0; // Retorna 0 se houver pelo menos uma posição vazia
return 1; // Retorna 1 se todas as posições estiverem ocupadas
}
/**
* @brief Simula um jogo aleatório a partir do estado atual do tabuleiro.
* @return int 1 se 'X' (computador) vencer, -1 se 'O' (jogador) vencer, 0 em caso de empate.
*/
int simularJogoAleatorio() {
char copiaTabuleiro[TAMANHO][TAMANHO];
// Copia o tabuleiro atual para a simulação, preservando o estado original
for (int i = 0; i < TAMANHO; i++)
for (int j = 0; j < TAMANHO; j++)
copiaTabuleiro[i][j] = tabuleiro[i][j];
char jogadorAtual = 'X'; // O computador começa como 'X'
while (1) {
// Verifica se há vitória ou empate no tabuleiro simulado
if (verificarVitoriaTabuleiro(copiaTabuleiro, 'X'))
return 1; // Vitória do computador ('X')
if (verificarVitoriaTabuleiro(copiaTabuleiro, 'O'))
return -1; // Vitória do jogador ('O')
if (verificarEmpateTabuleiro(copiaTabuleiro))
return 0; // Empate
// Escolhe uma jogada aleatória válida (posição vazia)
int linha, coluna;
do {
linha = rand() % TAMANHO; // Gera uma linha aleatória (0 a 2)
coluna = rand() % TAMANHO; // Gera uma coluna aleatória (0 a 2)
} while (copiaTabuleiro[linha][coluna] != ' '); // Repete até encontrar uma posição vazia
// Faz a jogada no tabuleiro de cópia
copiaTabuleiro[linha][coluna] = jogadorAtual;
// Alterna entre 'X' e 'O' para simular a vez do próximo jogador
jogadorAtual = (jogadorAtual == 'X') ? 'O' : 'X';
}
}
/**
* @brief Implementação do Algoritmo Minimax.
* Avalia o estado atual do tabuleiro e retorna a pontuação ótima.
* @param maximizando Verdadeiro se o jogador atual ('X') está maximizando, falso se 'O' está minimizando.
* @return int Pontuação do estado: 1 (vitória de 'X'), -1 (vitória de 'O'), ou 0 (empate).
*/
int minimax(bool maximizando) {
// Verifica se o jogo terminou e retorna a pontuação correspondente
if (verificarVitoria('X'))
return 1; // Vitória de 'X' (computador)
if (verificarVitoria('O'))
return -1; // Vitória de 'O' (jogador)
if (verificarEmpate())
return 0; // Empate
if (maximizando) {
// Caso esteja maximizando ('X' tentando maximizar a pontuação)
int melhorPontuacao = -1000; // Inicializa com um valor baixo para maximização
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Faz a jogada para 'X'
int pontuacao = minimax(false); // Chama recursivamente para o próximo jogador ('O')
tabuleiro[i][j] = ' '; // Desfaz a jogada
if (pontuacao > melhorPontuacao)
melhorPontuacao = pontuacao; // Atualiza a melhor pontuação se a atual for maior
}
}
}
return melhorPontuacao; // Retorna a melhor pontuação encontrada
} else {
// Caso esteja minimizando ('O' tentando minimizar a pontuação)
int melhorPontuacao = 1000; // Inicializa com um valor alto para minimização
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'O'; // Faz a jogada para 'O'
int pontuacao = minimax(true); // Chama recursivamente para o próximo jogador ('X')
tabuleiro[i][j] = ' '; // Desfaz a jogada
if (pontuacao < melhorPontuacao)
melhorPontuacao = pontuacao; // Atualiza a melhor pontuação se a atual for menor
}
}
}
return melhorPontuacao; // Retorna a melhor pontuação encontrada
}
}
/**
* @brief Implementação do Algoritmo Monte Carlo Tree Search (MCTS).
* Simula várias jogadas aleatórias para determinar a melhor jogada para 'X'.
* @return Jogada Estrutura contendo a linha e columna da melhor jogada encontrada.
*/
Jogada mcts() {
Jogada melhorJogada = {-1, -1}; // Inicializa a melhor jogada com valores inválidos
int melhorPontuacao = -1000; // Inicializa a melhor pontuação com um valor baixo
// Número de simulações por jogada
int numSimulacoes = 1000;
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
int pontuacaoTotal = 0;
// Simula várias jogadas para avaliar a qualidade da jogada atual
for (int k = 0; k < numSimulacoes; k++) {
tabuleiro[i][j] = 'X'; // Faz a jogada para 'X'
pontuacaoTotal += simularJogoAleatorio(); // Simula o jogo até o fim e acumula a pontuação
tabuleiro[i][j] = ' '; // Desfaz a jogada
}
// Atualiza a melhor jogada se a pontuação total for maior
if (pontuacaoTotal > melhorPontuacao) {
melhorPontuacao = pontuacaoTotal; // Atualiza a pontuação máxima
melhorJogada.linha = i; // Armazena a linha da melhor jogada
melhorJogada.coluna = j; // Armazena a coluna da melhor jogada
}
}
}
}
// Retorna a melhor jogada encontrada
return melhorJogada;
}
/**
* @brief Implementação do Minimax sem otimizações (versão pura).
* Similar ao Minimax padrão, mas sem podas ou heurísticas adicionais.
* @param maximizando Verdadeiro se 'X' está maximizando, falso se 'O' está minimizando.
* @return int Pontuação do estado: 1 (vitória de 'X'), -1 (vitória de 'O'), ou 0 (empate).
*/
int minimaxPuro(bool maximizando) {
if (verificarVitoria('X'))
return 1; // Vitória de 'X'
if (verificarVitoria('O'))
return -1; // Vitória de 'O'
if (verificarEmpate())
return 0; // Empate
if (maximizando) {
// Caso esteja maximizando ('X')
int melhorPontuacao = -1000; // Valor inicial baixo para maximização
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Faz a jogada
int pontuacao = minimaxPuro(false); // Avalia recursivamente
tabuleiro[i][j] = ' '; // Desfaz a jogada
if (pontuacao > melhorPontuacao)
melhorPontuacao = pontuacao; // Atualiza a melhor pontuação
}
}
}
return melhorPontuacao; // Retorna a pontuação máxima
} else {
// Caso esteja minimizando ('O')
int melhorPontuacao = 1000; // Valor inicial alto para minimização
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'O'; // Faz a jogada
int pontuacao = minimaxPuro(true); // Avalia recursivamente
tabuleiro[i][j] = ' '; // Desfaz a jogada
if (pontuacao < melhorPontuacao)
melhorPontuacao = pontuacao; // Atualiza a melhor pontuação
}
}
}
return melhorPontuacao; // Retorna a pontuação mínima
}
}
/**
* @brief Implementação de uma IA de força bruta.
* Similar ao Minimax, explora todas as possibilidades sem otimizações.
* @param maximizando Verdadeiro se 'X' está maximizando, falso se 'O' está minimizando.
* @return int Pontuação do estado: 1 (vitória de 'X'), -1 (vitória de 'O'), ou 0 (empate).
*/
int forcaBrutaAI(bool maximizando) {
// Verifica se o jogo terminou e retorna a pontuação correspondente
if (verificarVitoria('X'))
return 1; // Vitória de 'X'
if (verificarVitoria('O'))
return -1; // Vitória de 'O'
if (verificarEmpate())
return 0; // Empate
if (maximizando) {
// Caso esteja maximizando ('X')
int melhorPontuacao = -1000; // Valor inicial baixo
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Faz a jogada
int pontuacao = forcaBrutaAI(false); // Chama recursivamente
tabuleiro[i][j] = ' '; // Desfaz a jogada
if (pontuacao > melhorPontuacao)
melhorPontuacao = pontuacao; // Atualiza a melhor pontuação
}
}
}
return melhorPontuacao; // Retorna a pontuação máxima
} else {
// Caso esteja minimizando ('O')
int melhorPontuacao = 1000; // Valor inicial alto
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'O'; // Faz a jogada
int pontuacao = forcaBrutaAI(true); // Chama recursivamente
tabuleiro[i][j] = ' '; // Desfaz a jogada
if (pontuacao < melhorPontuacao)
melhorPontuacao = pontuacao; // Atualiza a melhor pontuação
}
}
}
return melhorPontuacao; // Retorna a pontuação mínima
}
}
/**
* @brief Implementação de uma IA com heurística simples.
* Avalia o tabuleiro recursivamente com base em vitórias, derrotas ou empates.
* @param maximizando Verdadeiro se 'X' está maximizando, falso se 'O' está minimizando.
* @return int Pontuação do estado: 1 (vitória de 'X'), -1 (vitória de 'O'), ou 0 (empate).
*/
int heuristicaAI(bool maximizando) {
// Verifica se o jogo terminou e retorna a pontuação correspondente
if (verificarVitoria('X'))
return 1; // Vitória de 'X'
if (verificarVitoria('O'))
return -1; // Vitória de 'O'
if (verificarEmpate())
return 0; // Empate
// Inicializa a melhor pontuação dependendo se está maximizando ou minimizando
int melhorPontuacao = maximizando ? -1000 : 1000;
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
// Faz uma jogada temporária
tabuleiro[i][j] = maximizando ? 'X' : 'O'; // 'X' se maximizando, 'O' se minimizando
// Avalia a pontuação da jogada recursivamente
int pontuacao = heuristicaAI(!maximizando);
// Desfaz a jogada temporária
tabuleiro[i][j] = ' ';
// Atualiza a melhor pontuação dependendo se está maximizando ou minimizando
if (maximizando) {
if (pontuacao > melhorPontuacao)
melhorPontuacao = pontuacao; // Maximiza a pontuação
} else {
if (pontuacao < melhorPontuacao)
melhorPontuacao = pontuacao; // Minimiza a pontuação
}
}
}
}
return melhorPontuacao; // Retorna a pontuação ótima encontrada
}
/**
* @brief Simula um jogo completo a partir do estado atual do tabuleiro.
* Faz jogadas aleatórias até o fim do jogo para determinar o resultado.
* @param maximizando Verdadeiro se 'X' está jogando, falso se 'O' está jogando.
* @return int 1 se 'X' vencer, -1 se 'O' vencer, 0 em caso de empate.
*/
int simularJogo(bool maximizando) {
// Verifica se o jogo terminou e retorna a pontuação correspondente
if (verificarVitoria('X'))
return 1; // Vitória de 'X'
if (verificarVitoria('O'))
return -1; // Vitória de 'O'
if (verificarEmpate())
return 0; // Empate
// Simula uma jogada aleatória
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = maximizando ? 'X' : 'O'; // Faz a jogada ('X' se maximizando, 'O' se minimizando)
int resultado = simularJogo(!maximizando); // Alterna jogadores e continua a simulação
tabuleiro[i][j] = ' '; // Desfaz a jogada
return resultado; // Retorna o resultado da simulação
}
}
}
return 0; // Retorna 0 se não houver jogadas disponíveis (caso teórico, pois o empate é verificado antes)
}
/**
* @brief Avalia o "fitness" (aptidão) do estado atual do tabuleiro para um jogador.
* Calcula uma pontuação heurística baseada na ocupação de linhas, colunas, diagonais e centro.
* @param maximizando Verdadeiro se avalia para 'X', falso se avalia para 'O'.
* @return int Pontuação total do tabuleiro (positiva favorece o jogador, negativa favorece o oponente).
*/
int avaliarFitness(bool maximizando) {
// Define os símbolos do jogador e do oponente com base na estratégia (maximizando ou minimizando)
char jogador = maximizando ? 'X' : 'O'; // Símbolo do jogador atual
char oponente = maximizando ? 'O' : 'X'; // Símbolo do adversário
int fitness = 0; // Pontuação total do tabuleiro para o jogador atual
// Avaliação por linhas
for (int i = 0; i < TAMANHO; i++) {
int pontosJogador = 0, pontosOponente = 0;
// Conta quantos símbolos do jogador e do oponente existem na linha atual
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == jogador) pontosJogador++;
if (tabuleiro[i][j] == oponente) pontosOponente++;
}
// Ajusta a pontuação com base no progresso da linha
if (pontosJogador > 0 && pontosOponente == 0) fitness += pontosJogador; // Linha favorável ao jogador
if (pontosOponente > 0 && pontosJogador == 0) fitness -= pontosOponente; // Linha favorável ao oponente
}
// Avaliação por colunas (mesma lógica das linhas)
for (int j = 0; j < TAMANHO; j++) {
int pontosJogador = 0, pontosOponente = 0;
// Conta quantos símbolos do jogador e do oponente existem na coluna atual
for (int i = 0; i < TAMANHO; i++) {
if (tabuleiro[i][j] == jogador) pontosJogador++;
if (tabuleiro[i][j] == oponente) pontosOponente++;
}
// Ajusta a pontuação com base no progresso da coluna
if (pontosJogador > 0 && pontosOponente == 0) fitness += pontosJogador; // Coluna favorável ao jogador
if (pontosOponente > 0 && pontosJogador == 0) fitness -= pontosOponente; // Coluna favorável ao oponente
}
// Avaliação das diagonais
int pontosJogadorDiagonal1 = 0, pontosOponenteDiagonal1 = 0;
// Diagonal principal (esquerda superior -> direita inferior)
int pontosJogadorDiagonal2 = 0, pontosOponenteDiagonal2 = 0;
// Diagonal secundária (direita superior -> esquerda inferior)
// Conta símbolos na diagonal principal
for (int i = 0; i < TAMANHO; i++) {
if (tabuleiro[i][i] == jogador) pontosJogadorDiagonal1++;
if (tabuleiro[i][i] == oponente) pontosOponenteDiagonal1++;
}
// Conta símbolos na diagonal secundária
for (int i = 0; i < TAMANHO; i++) {
if (tabuleiro[i][TAMANHO - i - 1] == jogador) pontosJogadorDiagonal2++;
if (tabuleiro[i][TAMANHO - i - 1] == oponente) pontosOponenteDiagonal2++;
}
// Ajusta a pontuação com base nas diagonais
if (pontosJogadorDiagonal1 > 0 && pontosOponenteDiagonal1 == 0) fitness += pontosJogadorDiagonal1;
// Diagonal principal favorável
if (pontosOponenteDiagonal1 > 0 && pontosJogadorDiagonal1 == 0) fitness -= pontosOponenteDiagonal1;
// Diagonal principal desfavorável
if (pontosJogadorDiagonal2 > 0 && pontosOponenteDiagonal2 == 0) fitness += pontosJogadorDiagonal2;
// Diagonal secundária favorável
if (pontosOponenteDiagonal2 > 0 && pontosJogadorDiagonal2 == 0) fitness -= pontosOponenteDiagonal2;
// Diagonal secundária desfavorável
// Valorização do centro (se o tabuleiro tiver um centro)
if (TAMANHO % 2 == 1) {
// Verifica se o tamanho é ímpar (ex.: 3x3 tem centro)
int meio = TAMANHO / 2; // Calcula a posição central
if (tabuleiro[meio][meio] == jogador) fitness += 2; // Controle do centro é vantajoso para o jogador
if (tabuleiro[meio][meio] == oponente) fitness -= 2; // Bloquear o centro é valioso para o oponente
}
return fitness; // Retorna a pontuação total do tabuleiro
}
/**
* @brief Implementa um algoritmo genético para escolher a melhor jogada.
* Usa evolução para encontrar a jogada com maior fitness para 'X' ou menor para 'O'.
* @param maximizando Verdadeiro se 'X' está maximizando, falso se 'O' está minimizando.
* @return int Fitness da melhor jogada encontrada.
*/
int algoritmoGeneticoAI(bool maximizando) {
// Parâmetros do algoritmo genético
const int TAMANHO_POPULACAO = 20; // Tamanho da população de indivíduos (jogadas candidatas)
const int NUMERO_GERACOES = 30; // Número de gerações para evolução da população
const float TAXA_MUTACAO = 0.1; // Taxa de mutação para diversificar a população
// Estrutura que representa um indivíduo (uma jogada candidata)
typedef struct {
int x, y; // Coordenadas da jogada no tabuleiro
int fitness; // Pontuação da jogada (quanto maior, melhor para 'X'; menor, melhor para 'O')
} Individuo;
Individuo populacao[TAMANHO_POPULACAO]; // População de indivíduos (jogadas candidatas)
Individuo melhorIndividuo = {-1, -1, maximizando ? -1000 : 1000};
// Melhor indivíduo encontrado, inicializado com valores inválidos
int indice = 0; // Índice para controlar o preenchimento da população
// Criação da população inicial com posições válidas no tabuleiro
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ' && indice < TAMANHO_POPULACAO) {
// Verifica se a posição está vazia e cabe na população
populacao[indice++] = (Individuo){i, j, 0}; // Adiciona posição vazia à população com fitness inicial 0
}
}
}
/**
* @brief Função interna para simular uma jogada e calcular o fitness resultante.
* @param x Coordenada da linha.
* @param y Coordenada da coluna.
* @param adversario Verdadeiro se simula para o adversário, falso se para o jogador.
* @return int Fitness após a jogada simulada.
*/
int simularProximaJogada(int x, int y, bool adversario) {
// Simula a jogada no tabuleiro
tabuleiro[x][y] = adversario ? 'O' : 'X'; // O jogador adversário é 'O'
// Avalia o fitness após a jogada simulada
int fitness = avaliarFitness(!adversario);
tabuleiro[x][y] = ' '; // Restaura o estado original do tabuleiro
return fitness;
}
// Loop de gerações do algoritmo genético
for (int geracao = 0; geracao < NUMERO_GERACOES; geracao++) {
// Avaliação da população atual
for (int k = 0; k < indice; k++) {
int x = populacao[k].x;
int y = populacao[k].y;
// Ignora posições inválidas ou já ocupadas
if (x < 0 || x >= TAMANHO || y < 0 || y >= TAMANHO || tabuleiro[x][y] != ' ') {
continue;
}
// Simula a jogada e calcula o fitness inicial
tabuleiro[x][y] = maximizando ? 'X' : 'O'; // Faz a jogada para o jogador atual
int fitnessBase = avaliarFitness(maximizando);
// Simula a resposta do oponente para penalizar jogadas que favorecem o adversário
int fitnessAdversario = 0;
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica posições disponíveis para o adversário
fitnessAdversario = simularProximaJogada(i, j, true); // Calcula o fitness do adversário
}
}
}
// Atualiza o fitness do indivíduo
populacao[k].fitness = fitnessBase - fitnessAdversario; // Penaliza jogadas que favorecem o oponente
tabuleiro[x][y] = ' '; // Reverte a jogada
// Atualiza o melhor indivíduo encontrado
if ((maximizando && populacao[k].fitness > melhorIndividuo.fitness) ||
// Se maximizando, busca maior fitness
(!maximizando && populacao[k].fitness < melhorIndividuo.fitness)) {
// Se minimizando, busca menor fitness
melhorIndividuo = populacao[k];
}
}
// Cruzamento: combina características de dois indivíduos para gerar um novo
for (int k = 0; k < indice / 2; k++) {
int parceiro = rand() % indice; // Escolhe um parceiro aleatório na população
int x = (populacao[k].x + populacao[parceiro].x) / 2; // Média das coordenadas X
int y = (populacao[k].y + populacao[parceiro].y) / 2; // Média das coordenadas Y
// Verifica se a nova posição é válida
if (x >= 0 && x < TAMANHO && y >= 0 && y < TAMANHO && tabuleiro[x][y] == ' ') {
populacao[k].x = x; // Atualiza a coordenada X do indivíduo
populacao[k].y = y; // Atualiza a coordenada Y do indivíduo
} else {
// Gera uma nova posição aleatória se a anterior for inválida
populacao[k].x = rand() % TAMANHO;
populacao[k].y = rand() % TAMANHO;
}
}
// Mutação: altera aleatoriamente algumas posições para diversificar a população
for (int k = 0; k < indice; k++) {
if ((float) rand() / RAND_MAX < TAXA_MUTACAO) {
// Verifica se a mutação ocorre com base na taxa
int novoX = rand() % TAMANHO; // Gera uma nova coordenada X aleatória
int novoY = rand() % TAMANHO; // Gera uma nova coordenada Y aleatória
if (tabuleiro[novoX][novoY] == ' ') {
// Verifica se a nova posição está vazia
populacao[k].x = novoX; // Atualiza a coordenada X
populacao[k].y = novoY; // Atualiza a coordenada Y
}
}
}
}
// Retorna o fitness da melhor jogada encontrada
return melhorIndividuo.fitness;
}
/**
* @brief De termina a melhor jogada para o computador usando diferentes algoritmos de IA.
* Seleciona aleatoriamente um algoritmo e aplica-o para encontrar a melhor jogada no tabuleiro.
*/
void melhorJogada() {
lcd_clear(); // Limpa o display LCD
lcd_set_cursor(0, 0); // Posiciona o cursor na linha 0, coluna 0
int pensamento = rand() % 400; // Gera um tempo aleatório entre 0 e 399 ms
sleep_ms(pensamento); // Pausa para simular o "pensamento" do computador
int pontuacao; // Armazena a pontuação calculada para cada jogada
int melhorLinha = -1; // Linha da melhor jogada encontrada (-1 indica nenhuma jogada válida ainda)
int melhorColuna = -1; // Coluna da melhor jogada encontrada (-1 indica nenhuma jogada válida ainda)
int melhorPontuacao = -1000; // Inicializa a melhor pontuação com um valor baixo para maximização
// Define uma semente para o gerador de números aleatórios baseada no placar do jogo
unsigned int semente = vitoriasJogador * 100 + vitoriasComputador * 10 + empates*2+1;
srand(semente); // Inicializa o gerador aleatório com a semente
// Gera um número aleatório entre 0 e 5 para selecionar o algoritmo de IA
algoritmoIA = rand() % 5;
// printf("Número gerado: %d\n", algoritmoIA); // Exibe o número gerado para depuração
// Escolhe o algoritmo de IA com base no valor gerado
switch (algoritmoIA) {
case 1: {
lcd_string("Monte Carlo Tree Search (MCTS)\n"); // Exibe o nome do algoritmo no LCD
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Simula uma jogada do jogador 'X'
pontuacao = forcaBrutaAI(false); // Calcula a pontuação com Força Bruta
tabuleiro[i][j] = ' '; // Desfaz a jogada simulada
if (pontuacao > melhorPontuacao) {
// Verifica se a pontuação é a melhor até agora
melhorPontuacao = pontuacao;
melhorLinha = i;
melhorColuna = j;
}
}
}
}
break;
}
case 2: {
// Minimax Puro
lcd_string("Minimax Puro\n"); // Exibe o nome do algoritmo no LCD
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Simula uma jogada do jogador 'X'
pontuacao = forcaBrutaAI(false); // Calcula a pontuação com Força Bruta
tabuleiro[i][j] = ' '; // Desfaz a jogada simulada
if (pontuacao > melhorPontuacao) {
// Verifica se a pontuação é a melhor até agora
melhorPontuacao = pontuacao;
melhorLinha = i;
melhorColuna = j;
}
}
}
}
break;
break;
}
case 3: {
lcd_string("Heuristica AI\n"); // Exibe o nome do algoritmo no LCD
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Simula uma jogada do jogador 'X'
pontuacao = algoritmoGeneticoAI(true); // Calcula a pontuação com Algoritmo Genético
tabuleiro[i][j] = ' '; // Desfaz a jogada simulada
if (pontuacao > melhorPontuacao) {
// Verifica se a pontuação é a melhor até agora
melhorPontuacao = pontuacao;
melhorLinha = i;
melhorColuna = j;
}
}
}
}
break;
}
case 4: {
lcd_string("Forca Bruta\n"); // Exibe o nome do algoritmo no LCD
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Simula uma jogada do jogador 'X'
pontuacao = forcaBrutaAI(false); // Calcula a pontuação com Força Bruta
tabuleiro[i][j] = ' '; // Desfaz a jogada simulada
if (pontuacao > melhorPontuacao) {
// Verifica se a pontuação é a melhor até agora
melhorPontuacao = pontuacao;
melhorLinha = i;
melhorColuna = j;
}
}
}
}
break;
}
case 5: {
// Algoritmo Genético
lcd_string("Algoritmo Genetico\n"); // Exibe o nome do algoritmo no LCD
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Simula uma jogada do jogador 'X'
pontuacao = algoritmoGeneticoAI(true); // Calcula a pontuação com Algoritmo Genético
tabuleiro[i][j] = ' '; // Desfaz a jogada simulada
if (pontuacao > melhorPontuacao) {
// Verifica se a pontuação é a melhor até agora
melhorPontuacao = pontuacao;
melhorLinha = i;
melhorColuna = j;
}
}
}
}
break;
}
default: {
lcd_string(" Minimax\n"); // Exibe o nome do algoritmo padrão no LCD
for (int i = 0; i < TAMANHO; i++) {
for (int j = 0; j < TAMANHO; j++) {
if (tabuleiro[i][j] == ' ') {
// Verifica se a posição está vazia
tabuleiro[i][j] = 'X'; // Simula uma jogada do jogador 'X'
pontuacao = forcaBrutaAI(false); // Calcula a pontuação com Força Bruta
tabuleiro[i][j] = ' '; // Desfaz a jogada simulada
if (pontuacao > melhorPontuacao) {
// Verifica se a pontuação é a melhor até agora
melhorPontuacao = pontuacao;
melhorLinha = i;
melhorColuna = j;
}
}
}
}
break;
break;
}
}
// Faz a melhor jogada encontrada
if (melhorLinha != -1 && melhorColuna != -1) {
// Verifica se uma jogada válida foi encontrada
tabuleiro[melhorLinha][melhorColuna] = 'X'; // Marca a posição escolhida com 'X'
}
}
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/gpio.h"
#include "ssd1306.h"
/**
* @brief Executa o loop principal de um jogo da velha entre jogador ('O') e computador ('X').
* O jogador usa um teclado matricial para escolher posições, e o computador usa IA para jogar.
*/
void jogar() {
liberarMemoria(); // Libera memória e reseta linha_atual antes de iniciar
int posicao; // Posição escolhida pelo jogador (1 a 9)
bool vezDoJogador = true; // Controla a vez (true = jogador, false = computador)
bool flag = true; // Controla o loop do jogo
flagLiberaMain = false; // Bloqueia o menu principal durante o jogo
resetarTabuleiro(); // Reseta o tabuleiro para o estado inicial
liberarMemoria(); // Garante que o contador de linhas esteja zerado antes de imprimir
imprimirTabuleiro(); // Exibe o tabuleiro inicial
while (flag) {
// Loop principal do jogo
if (vezDoJogador) {
// Vez do jogador
char pressedKey;
bool entradaValida = false;
// Loop até que o jogador forneça uma entrada válida
while (!entradaValida) {
pressedKey = getPressedKey(); // Captura a tecla pressionada no teclado matricial
// Verifica se a tecla é um número de 1 a 9
if (pressedKey >= '1' && pressedKey <= '9') {
posicao = pressedKey - '0'; // Converte char para int (ex.: '1' -> 1)
int linha = (posicao - 1) / TAMANHO; // Calcula a linha (0 a 2)
int coluna = (posicao - 1) % TAMANHO; // Calcula a coluna (0 a 2)
if (tabuleiro[linha][coluna] != ' ') {
// Verifica se a posição já está ocupada
// Posição inválida; continua no loop para nova entrada
} else {
tabuleiro[linha][coluna] = 'O'; // Marca a jogada do jogador
entradaValida = true; // Sai do loop de entrada
}
}
}
} else {
// Vez do computador
melhorJogada(); // Computador escolhe e executa a melhor jogada
}
liberarMemoria(); // Reseta o contador e libera memória antes de atualizar o tabuleiro
imprimirTabuleiro(); // Atualiza a exibição do tabuleiro
// Verifica o resultado do jogo
if (verificarVitoria('O')) {
// Jogador venceu
sleep_ms(1000); // Pausa para exibir a mensagem
char *str[] = {
" ",
" ",
" Voce",
" ",
" Venceu"
};
int strCount = sizeof(str) / sizeof(str[0]);
draw_menu(str, strCount); // Exibe mensagem de vitória
vitoriasJogador++; // Incrementa o placar do jogador
play_senna_victory_sound(); // Toca o som da vitória de Ayrton Senna
sleep_ms(1000); // Pausa antes de voltar ao menu
sleep_ms(1000); // Pausa antes de voltar ao menu
flagLiberaMain = true; // Libera o menu principal
flag = false; // Encerra o jogo
} else if (verificarVitoria('X')) {
// Computador venceu
sleep_ms(1000); // Pausa para exibir a mensagem
char *str[] = {
" ",
" ",
" Computador",
" Venceu",
" "
};
int strCount = sizeof(str) / sizeof(str[0]);
draw_menu(str, strCount); // Exibe mensagem de vitória do computador
vitoriasComputador++; // Incrementa o placar do computador
sleep_ms(1000); // Pausa antes de voltar ao menu
flagLiberaMain = true; // Libera o menu principal
flag = false; // Encerra o jogo
} else if (verificarEmpate()) {
// Empate
sleep_ms(1000); // Pausa para exibir a mensagem
char *str[] = {
" ",
" ",
" Empate",
" ",
" "
};
int strCount = sizeof(str) / sizeof(str[0]);
draw_menu(str, strCount); // Exibe mensagem de empate
empates++; // Incrementa o contador de empates
sleep_ms(1000); // Pausa antes de voltar ao menu
flagLiberaMain = true; // Libera o menu principal
flag = false; // Encerra o jogo
}
vezDoJogador = !vezDoJogador; // Alterna a vez entre jogador e computador
}
}
/**
* @brief Configura os pinos GPIO para o teclado matricial e LEDs.
*/
void initializeHardware(void) {
// Configura os pinos das linhas do teclado matricial como saída
for (int i = 0; i < 4; i++) {
gpio_init(rows[i]); // Inicializa o pino da linha
gpio_set_dir(rows[i], GPIO_OUT); // Define como saída
gpio_put(rows[i], 0); // Define o estado inicial como baixo
}
// Configura os pinos das colunas do teclado matricial como entrada com pull-down
for (int i = 0; i < 4; i++) {
gpio_init(cols[i]); // Inicializa o pino da coluna
gpio_set_dir(cols[i], GPIO_IN); // Define como entrada
gpio_pull_down(cols[i]); // Ativa resistor pull-down interno
}
// Configura os pinos dos LEDs como saída
gpio_init(ledGold); // Inicializa o pino do LED dourado
gpio_set_dir(ledGold, GPIO_OUT); // Define como saída
gpio_put(ledGold, 0); // Desliga o LED dourado inicialmente
gpio_init(ledGreen); // Inicializa o pino do LED verde
gpio_set_dir(ledGreen, GPIO_OUT); // Define como saída
gpio_put(ledGreen, 0); // Desliga o LED verde inicialmente
gpio_init(ledBlue); // Inicializa o pino do LED azul
gpio_set_dir(ledBlue, GPIO_OUT); // Define como saída
gpio_put(ledBlue, 0); // Desliga o LED azul inicialmente
// Configura os LEDs da GPIO 11 e GPIO 12 com base nos flags
gpio_put(ledBlue, blueLedFlag); // Define o estado inicial do LED azul (GPIO 11)
gpio_put(ledGreen, greenLedFlag); // Define o estado inicial do LED verde (GPIO 12)
}
/**
* @brief Captura a tecla pressionada no teclado matricial 4x4.
* @return char Caractere correspondente à tecla pressionada ou 0 se nenhuma tecla for detectada.
*/
char getPressedKey(void) {
for (int i = 0; i < 4; i++) {
// Varre as linhas do teclado
gpio_put(rows[i], 1); // Ativa a linha atual
for (int j = 0; j < 4; j++) {
// Verifica as colunas
if (gpio_get(cols[j])) {
// Se a coluna estiver alta, uma tecla foi pressionada
gpio_put(rows[i], 0); // Desativa a linha após detectar a tecla
play_keypress_sound(); // Toca o som de tecla pressionada
return keyboardLayout[i][j]; // Retorna o caractere correspondente
}
}
gpio_put(rows[i], 0); // Desativa a linha antes de passar para a próxima
}
return 0; // Retorna 0 se nenhuma tecla for pressionada
}
/**
* @brief Exibe o placar atual no display OLED.
*/
void mostrarPlacar() {
static char jogadorTexto[20], computadorTexto[20], empatesTexto[20];
snprintf(jogadorTexto, sizeof(jogadorTexto), "Jogador: %d", vitoriasJogador); // Formata texto do jogador
snprintf(computadorTexto, sizeof(computadorTexto), "Computador: %d", vitoriasComputador);
// Formata texto do computador
snprintf(empatesTexto, sizeof(empatesTexto), "Empates: %d", empates); // Formata texto dos empates
char *menu_text[] = {
" ",
jogadorTexto,
" ",
computadorTexto,
" ",
empatesTexto
};
int menu_text_count = sizeof(menu_text) / sizeof(menu_text[0]);
draw_menu(menu_text, menu_text_count); // Exibe o placar no display
sleep_ms(100); // Pausa para visualização
}
/**
* @brief Zera o placar do jogo e exibe uma mensagem de confirmação.
*/
void zerarPlacar() {
vitoriasJogador = 0; // Reseta vitórias do jogador
vitoriasComputador = 0; // Reseta vitórias do computador
empates = 0; // Reseta empates
char *menu_text[] = {
" ",
" ",
" Placar",
" ",
" Zerado"
};
int menu_text_count = sizeof(menu_text) / sizeof(menu_text[0]);
draw_menu(menu_text, menu_text_count); // Exibe mensagem de placar zerado
sleep_ms(100); // Pausa para visualização
}
/**
* @brief Função principal que inicializa o hardware e gerencia o menu do jogo.
* @return int Código de saída (0 para sucesso).
*/
int main(void) {
stdio_init_all(); // Inicializa a comunicação serial
// Exibe o manual do jogo no terminal serial
printf("\n=== Manual do Jogo da Velha ===\n"
"Bem-vindo ao Jogo da Velha no Raspberry Pi Pico!\n\n"
"REGRAS:\n"
"- Você joga com 'O' e o computador joga com 'X'.\n"
"- O tabuleiro é 3x3. Escolha posições de 1 a 9 usando o teclado matricial.\n"
"- O objetivo é alinhar 3 símbolos iguais em linha, coluna ou diagonal.\n"
"- O jogo termina com vitória, derrota ou empate.\n\n"
"CONTROLES DO TECLADO MATRICIAL:\n"
"- Teclas 1 a 9: Escolhe a posição para jogar (1=topo-esquerda, 9=baixo-direita).\n"
"- Tecla '*': Liga o LED dourado (GPIO 13) como lanterna.\n"
"- Tecla '#': Desliga o LED dourado.\n"
"- Tecla 'A': Inicia um novo jogo.\n"
"- Tecla 'B': Mostra o placar atual.\n"
"- Tecla 'C': Encerra o programa.\n"
"- Tecla 'D': Exibe informações sobre o autor.\n"
"- Tecla '0': Zera o placar.\n\n"
"MENU PRINCIPAL:\n"
"- Aparece ao iniciar ou após um jogo.\n"
"- Use as teclas acima para navegar.\n\n"
"LEDs:\n"
"- LED Dourado (GPIO 13): Controlado por '*' e '#', ilumina o teclado.\n"
"- LED Verde (GPIO 12): Estado inicial definido por greenLedFlag.\n"
"- LED Azul (GPIO 11): Estado inicial definido por blueLedFlag.\n\n"
"DISPLAYS:\n"
"- OLED: Mostra o tabuleiro, mensagens de vitória/empate e menus.\n"
"- LCD: Mostra o algoritmo de IA usado pelo computador durante sua vez.\n\n"
"IA DO COMPUTADOR:\n"
"- Escolhe entre Minimax, MCTS, Minimax Puro, Heurística, Força Bruta ou Algoritmo Genético.\n"
"- A escolha é aleatória a cada jogada, simulando diferentes níveis de dificuldade.\n\n"
"PLACAR:\n"
"- Mantém contagem de vitórias do jogador, computador e empates.\n"
"- Pode ser zerado com a tecla '0'.\n\n"
"INICIANDO:\n"
"- Pressione 'A' para começar ou explore o menu!\n"
"===============================\n\n");
sleep_ms(1000); // Pausa de 1 segundo antes de tocar o som
play_dialup_sound(); // Toca o som de conexão dial-up
sleep_ms(1000); // Pausa de 1 segundo após o som
initializeHardware(); // Configura os pinos GPIO para teclado e LEDs
// Inicialização do I2C e displays (OLED e LCD)
i2c_init(i2c1, 400000); // Configura I2C1 a 400kHz para OLED
gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); // Define SDA para I2C
gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); // Define SCL para I2C
gpio_pull_up(I2C_SDA); // Ativa pull-up no SDA
gpio_pull_up(I2C_SCL); // Ativa pull-up no SCL
ssd1306_init(); // Inicializa o display OLED
i2c_init(i2c_default, 100 * 1000); // Configura I2C padrão a 100kHz para LCD
gpio_set_function(16, GPIO_FUNC_I2C); // Define GPIO16 como SDA
gpio_set_function(17, GPIO_FUNC_I2C); // Define GPIO17 como SCL
gpio_pull_up(16); // Ativa pull-up no GPIO16
gpio_pull_up(17); // Ativa pull-up no GPIO17
lcd_init(); // Inicializa o LCD
// Configura a área de renderização do display OLED
frame_area.start_column = 0;
frame_area.end_column = ssd1306_width - 1;
frame_area.start_page = 0;
frame_area.end_page = ssd1306_n_pages - 1;
calculate_render_area_buffer_length(&frame_area);
// Exibe o menu principal se liberado
if (flagLiberaMain) {
// Correção: usa == em vez de = para comparação
char *menuMain[] = {
"A - Jogar ", // Inicia um novo jogo
"B - Placar", // Exibe o placar atual
"C - Sair ", // Encerra o programa
"D - About", // Mostra informações sobre o autor
"0 - Zerar ", // Reseta o placar
"Escolha " // Solicita a escolha do usuário
};
int menuMainCount = sizeof(menuMain) / sizeof(menuMain[0]);
draw_menu(menuMain, menuMainCount); // Exibe o menu principal
sleep_ms(100); // Pausa para visualização
}
for(;;) {
// Loop infinito do menu principal
char pressedKey = getPressedKey(); // Captura a tecla pressionada
switch (pressedKey) {
case '*': // Liga o LED dourado
gpio_put(ledGold, 1); // Acende o LED dourado (GPIO 13)
break;
case '#': // Desliga o LED dourado
gpio_put(ledGold, 0); // Apaga o LED dourado (GPIO 13)
break;
case 'B': // Mostra o placar
mostrarPlacar();
break;
case 'C': // Encerra o programa
{
char *menu_text[] = {
" ",
"Programa",
"Finalizado",
" ",
};
int menu_text_count = sizeof(menu_text) / sizeof(menu_text[0]);
draw_menu(menu_text, menu_text_count); // Exibe mensagem de saída
return 0; // Encerra o programa
}
break;
case 'D': // Exibe informações sobre o autor
{
char *menuTextAutor[] = {
" ",
" ",
" Autor",
" ",
" Patrik Lima"
};
int menuTextAutorCount = sizeof(menuTextAutor) / sizeof(menuTextAutor[0]);
draw_menu(menuTextAutor, menuTextAutorCount); // Exibe informações do autor
sleep_ms(100); // Pausa para visualização
}
break;
case '0': // Zera o placar
zerarPlacar();
break;
case 'A': // Inicia o jogo
jogar();
break;
default:
break; // Ignora outras teclas
}
// Atualiza os LEDs da GPIO 11 e GPIO 12 com base nos flags
gpio_put(ledBlue, blueLedFlag); // Atualiza o LED azul (GPIO 11)
gpio_put(ledGreen, greenLedFlag); // Atualiza o LED verde (GPIO 12)
sleep_ms(100); // Aguarda 100ms antes de verificar novamente
}
}
LED dourado esta sem resistor propositalmente ...
Na prática, o projeto funcionaria como um relé
acionando um conjunto de LEDs mais
potentes para a iluminação do teclado ou
com um teclado matricial com
retroiluminação ou backlight.
Ademais teria a funcao efeito colateral de lanterna
Mostra os algoritmos de IA de minha autoria,
que estao sendo exercutados no momento