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!