Italo Info


Jogo da velha (C++)

Atenção: Esta página é melhor visualizada em um computador com dimensão da tela maior ou igual a 800x600 por conter imagens grandes.

Eu estava com vontade de praticar um pouco a linguagem de programação C++, pois, tenho pouca experiência na linguagem. Então, decidi implementar um Jogo da Velha em C++. E assim fiz. Projetei o sistema para ser flexível a mudanças, o que facilita a possibilidade de um trabalho em equipe. No entanto, foi mais viável implementar o Jogo da Velha em C (Uma linguagem de programação estruturada), por tratar-se de um sistema relativamente simples e por ter sido feito apenas por uma pessoa: Eu. Inclusive, caso queira, você pode visitar a página onde escrevi sobre o desenvolvimento de um Jogo da Velha em C clicando aqui, onde, disponibilizei, também, versões em Java e Android.

Jogo da velha em execução
Jogo da velha em execução

Pular para a sessão de downloads

O código fonte está distribuído entre os pacotes: modelo, gui, controlador, grafico. No pacote modelo ficam as classes que representam a lógica do sistema. Abaixo, o diagrama de classes que representa o módulo "modelo":

Módulo modelo do Jogo da Velha
Módulo "modelo"

De acordo com o modelo acima, os métodos das coordenadas e dimensões dos objetos desenhados pelo jogo, ficam na classe Tabuleiro. Na classe Placar, ficam armazenados os valores das quantidades de vitórias, empates e derrotas. Na classe Jogo ficam as funções da lógica do sistema, inclusive, nesta classe, há uma implementação de um algoritmo de inteligência artificial: O MINIMAX

.

No pacote “gui” ficam as classes de interface gráfica com o usuário, onde, foi implementado o tratamento de eventos do mouse, teclas e janela. Abaixo o diagrama de classes que representa o módulo "gui":

Módulo gui do Jogo da Velha
Módulo "gui"

No pacote "gui" existe uma classe chamada GUI onde fica o loop principal do jogo. Nesse loop, são tratados os eventos e os devidos métodos implementados na classe GUI_Controlador são chamados. Para que isso funcione, é claro, é necessário que a classe GUI_Controlador implemente a interface GUI_Listener e seus métodos e seja registrada na classe GUI, através do método setGUIListener. Por exemplo, veja o código abaixo:


GUI_Controlador* guiCtrl = new GUI_Controlador( jogo, gui );
gui->setGUIListener( guiCtrl );

Veja o loop principal na classe GUI:


fim = false;
    
while( !fim ) {                                
    grafico->desenha( renderizador );                            
    if ( listener != NULL )        
        listener->desenhou();
                    
    while( SDL_PollEvent( &evento ) != 0 ) {
        if ( listener != NULL )    {                            
            switch( evento.type ) {
                case SDL_QUIT:
                    listener->janela_fechada();
                    fim = true;
                    break;
                case SDL_MOUSEBUTTONDOWN:
                    listener->mouse_pressionado( evento.motion.x, evento.motion.y );                        
                    break;
                case SDL_KEYDOWN:
                    listener->tecla_pressionada( evento.key.keysym.sym );                        
                    break;
            }
        }
    }
    
    SDL_Delay( Consts::DELAY );        
}    

No exemplo acima, repare nos métodos do objeto listener. São eles: desenhou, janela_fechada, mouse_pressionado, tecla_pressionada. A classe GUI_Controlador implementa esses métodos, porque, estão definidos na interface GUI_Listener (Arquivo cabeçalho .h). Esta é uma implementação do padrão de projetos Observer aplicado para desacoplar a lógica do jogo do código de interface gráfica. Repare também que existe um objeto de nome grafico, onde o método desenha é chamado. Assim como o registro de uma implementação da interface GUI_Listener, existe também um registro como a seguir:


Grafico* grafico = new JogoGrafico( jogo, gui );
gui->setGrafico( grafico );

Veja abaixo o diagrama que ilustra a relação de implementação entre GUI_Listener e GUI_Controller:

Módulo controlador do Jogo da Velha
Módulo "controlador"

Na classe GUI_Controlador:


GUI_Controlador::GUI_Controlador( Jogo* jogo, GUI* gui ) {
    this->jogo = jogo;
    this->gui = gui;
}

void GUI_Controlador::mouse_pressionado( int x, int y ) {
    ... // implementação omitida para simplificar o exemplo
}
    
void GUI_Controlador::tecla_pressionada( char key ) {    
    ... // implementação omitida para simplificar o exemplo
}

void GUI_Controlador::desenhou() {    
    ... // implementação omitida para simplificar o exemplo
}
    
void GUI_Controlador::janela_fechada() {
        
}

Repare nos métodos implementados: mouse_pressionado, tecla_pressionada, desenhou, janela_fechada. São todos chamados no loop principal presente na classe GUI.

Outra estratégia parecida (mas não igual) é aplicada para separar a implementação dos gráficos da interface gráfica com usuário. Na classe GUI existe, além do método setGUIListener, o método setGrafico. Onde, se pode passar como parâmetro uma implementação da interface (grafico.h) conforme foi explicado mais atrás. Abaixo o diagrama de classes que ilustra o relacionamento entre a classe JogoGrafico que implementa a interface grafico (grafico.h) e outras classes de desenho na tela do jogo:

Módulo gráfico do Jogo da Velha
Módulo "grafico"

O MINIMAX

O algoritmo de inteligência artificial simbólica MINIMAX foi implementado para a inteligência do jogador computador. Conforme a fonte WIKIPEDIA (pt.wikipedia.org), o algoritmo MINIMAX pode ser representado em pseudocódigo conforme a seguir:


ROTINA minimax(nó, profundidade, maximizador)    
	SE nó é um nó terminal OU profundidade = 0 ENTÃO        
		RETORNE o valor da heurística do nó            
	SENÃO SE maximizador é FALSE ENTÃO
		α ← +∞
		PARA CADA filho DE nó
			α ← min(α, minimax(filho, profundidade-1,true))
		FIM PARA
		RETORNE α
	SENÃO
		α ← -∞
		PARA CADA filho DE nó
			α ← max(α, minimax(filho, profundidade-1,false))
		FIM PARA
		RETORNE α
	FIM SE
FIM ROTINA

Veja abaixo como eu implantei o algoritmo MINIMAX:


int Jogo::_minimax( char js[3][3], int nivel, bool maximizador ) {                            
    if ( verificaSeVenceu( js, O ) )
        return 1;                                                        

    if ( verificaSeVenceu( js, X ) ) 
        return -1;        
            
    if ( verificaSeEmpate( js ) ) 
        return 0;        
        
    if ( nivel <= 0 )
        return 0;    
        
    if ( maximizador ) {
        int min = INT_MAX;
        for( int i = 0; i <= 2; i++ ) {
            for( int j = 0; j <= 2; j++ ) {    
                if ( js[i][j] == V ) {                     
                    char aux[3][3];                                
                    copiaJogadas( aux, js );
                    
                    aux[i][j] = X;
                                            
                    int p = _minimax( aux, nivel-1, false );
                    if ( p <= min )
                        min = p;                                    
                }
            }
        }
        return min;
    } else {    
        int max = INT_MIN;
        for( int i = 0; i <= 2; i++ ) {
            for( int j = 0; j <= 2; j++ ) {                                
                if ( js[i][j] == V ) {                
                    char aux[3][3];                
                    copiaJogadas( aux, js );
                    
                    aux[i][j] = O;
                                            
                    int p = _minimax( aux, nivel-1, true );
                    if ( p >= max )
                        max = p;        
                }
            }
        }
        return max;
    }                
}

void Jogo::minimax( int *posX, int *posY ) {        
    int pontos = INT_MIN;        
    
    int vet[9][3];
    int cont = 0;
            
    for( int i = 0; i <= 2; i++ ) {
        for( int j = 0; j <= 2; j++ ) {
            if ( jogadas[i][j] == V ) {
                char aux[3][3];        
                copiaJogadas( aux, jogadas );
                
                aux[i][j] = O;
                    
                if ( verificaSeVenceu( aux, O ) || verificaSeEmpate( aux ) ) {
                    *posX = i;
                    *posY = j;
                                                                                
                    return;
                }
                    
                int p = _minimax( aux, 9-contaJogadas( aux ), true ); 
                if ( p >= pontos ) {                                        
                    vet[cont][0] = i;
                    vet[cont][1] = j;
                    vet[cont][2] = p;
                    cont++;
                                                                                
                    pontos = p;                
                }
            }
        }
    }        
    
    int vet2[9][2];
    int cont2 = 0;
    
    for( int k = 0; k < cont; k++ ) {
        if ( vet[k][2] == pontos ) {
            vet2[cont2][0] = vet[k][0];
            vet2[cont2][1] = vet[k][1];
            cont2++;
        }
    }
        
    int i = rand() % cont2;
    
    *posX = vet2[ i ][ 0 ];
    *posY = vet2[ i ][ 1 ];
}

Onde, js é uma matriz de jogadas que representa uma possibilidade no tabuleiro. Ex: com três jogadas no tabuleiro, uma possibilidade é a celula (2, 2) igual a "X", a célula (1, 1) igual a "O" a célula (1,2) igual a "X" e o restante, igual a VAZIO. Inicialmente, a matriz js é uma cópia da matriz jogadas da classe Jogo.

Exemplo de matriz js:

       

char js[3][3] = {
    { ' ', ' ', ' '},
    { ' ', 'O', ' '},
    { ' ', 'X', 'X'}        
};


Concluindo...

O desenvolvimento do Jogo da Velha em C++ foi para mim uma ótima oportunidade de aprender uma nova linguagem de programação. Tive a oportunidade de aplicar padrões de projeto para separar (desacoplar) os módulos do sistema de modo a tornar possível o trabalho em equipe. Embora, eu tenha construido o sistema sozinho. Inclusive, gostaria de ter a oportunidade de desenvolver algum software em equipe para por em prática o conhecimento e experiência que desenvolvi até agora na área de analise de sistemas.

Downloads

Programa executável: jogodavelha.rar.

Projeto no dev-c++: jogodavelha-src.rar.

Caso alguem queira compilar o código fonte, entre em contato comigo (Pode deixar um comentário). Que eu oriento sobre como fazer. Posso adiantar que o sistema depende da biblioteca SDL para funcionar. Por isso, é necessário configurar a biblioteca no ambiente de desenvolvimento escolhido.

Até o próximo pessoal!