Franklin Cesar Flores (RA/ 006010)
Romaric Audigier (RA/ 015030)

 

 

 

IA-725: Computação gráfica I
Projeto OpenGL

 

 

JOGO “RESTA-UM”

 

 

 

 

Faculdade de Engenharia Elétrica e de Computação
Universidade Estadual de Campinas
– Julho 2002 –

 

 

 

 

 

 

 

ÍNDICE

Introdução.

1. Gerenciamento do jogo.

a. Estrutura do tabuleiro.
b. Variáveis globais utilizadas.
c. Funções principais de gerenciamento do jogo.

2. Parte gráfica do jogo.

a. Modelagem do tabuleiro e das peças.
b. Funções gráficas para o display das peças.

3. Arquitetura geral do programa.

a. Inicializações.
b. Recursos para aumentar a velocidade de execução.
c. Laço principal do programa permitindo interatividade.

Conclusão.

Anexos.

I. Referências.
II. Como executar o programa.

 

 

 

INTRODUÇÃO

O objetivo desse trabalho é desenvolver um jogo “resta-um” com a biblioteca gráfica OpenGL, a fim de colocar em prática os conhecimentos teóricos adquiridos em computação gráfica. O projeto foi programado em C e foi usado o gerenciador de janela glut.

O resta-um é um jogo solitário cujo objetivo é, no final, conseguir deixar apenas uma peça sobre um tabuleiro que comportavam inicialmente várias. Há regras que permitem a qualquer peça no tabuleiro “eliminar” uma de sua “vizinha imediata” (uma peça que está numa posição adjacente horizontalmente ou verticalmente), pulando em cima desta. A posição alcançada é a posição vizinha da peça eliminada que se situa no oposto da posição de partida. Chamamos essa posição de destino de “vizinha não imediata” da posição de origem, por oposição à “vizinha imediata” de onde vai ser retirada a peça eliminada. A peça sendo retirada, o número de peças sobrando no tabuleiro diminui, e assim por diante até não poder mais jogar. É possível eliminar as peças uma por uma até sobrar uma só se jogar com táctica.

Numa primeira parte é apresentada a parte lógica do jogo, isto é as funções de gerenciamento do jogo resta-um que garantem a boa aplicação das regras do jogo. Numa segunda parte são descritas as funções gráficas do jogo que determinam o que tem que mostrar na tela em função das saídas das funções de gerenciamento. Finalmente a terceira seção explicita como todas essas funções são utilizadas no laço principal do programa para permitir a interatividade do jogo.

 


1. GERENCIAMENTO DO JOGO

Começamos essa seção pela descrição da estrutura lógica do tabuleiro no qual o jogo é baseado. Em seguida veremos as variáveis globais utilizadas no programa assim como as principais funções de gerenciamento que garantem o bom funcionamento do jogo

 

a. Estrutura do tabuleiro

Definimos o tabuleiro como um vetor (conjunto) de 33 buracos. Cada um desses buracos é de tipo tpBuraco, um novo tipo estruturado que contém 5 atributos:

  • status pode ter os valores 0, 1 ou 2 quando o buraco estiver respectivamente vazio, ocupado por uma peça ou que a peça pertencendo a este buraco está sendo movido;
  • njogs é o número de buracos alcançáveis a partir desse buraco. Este número tem um valor entre 1 e 4 e só depende da topologia do tabuleiro que é fixa. Por exemplo, um buraco no canto do tabuleiro tem teoricamente 2 posições onde ir, um buraco mais central pode ter 4 posições onde ir;
  • jogadas1 é um vetor contendo os índices dos buracos vizinhos imediatos (a serem pulados);
  • jogadas2 é um vetor contendo os índices dos buracos vizinhos não imediatos (a serem alcançados);
  • c_x e c_y são as coordenadas do centro do buraco (no sistema normalizado).

Esses atributos são consultados e atualizados durante o jogo. Eles são responsáveis pelo respeito de certas regras de jogo.

 

b. Variáveis globais utilizadas

Ao longo do programa são utilizados 11 variáveis globais:

  • tabuleiro é nosso tabuleiro com 33 buracos definidos anteriormente;
  • buracoOrigem é o buraco de onde o jogador selecionou a peça para movê-la (a cada clique pode mudar);
  • buracoPula, é o buraco vizinho imediato do buracoOrigem que vai ser eliminado no final de um passo bem sucedido;
  • buracoDestino é o buraco onde a peça está sendo soltada pelo jogador;
  • pressed corresponde à posição do botão do mouse, verdadeira quando o botão está apertado;
  • Xglobal e Yglobal são as coordenadas 2D (na tela) apontadas pelo ponteiro do mouse;
  • npecas, é o número de peças sobre o tabuleiro (atualizado a cada passo);
  • tabul_config dá o número da configuração inicial das peças sobre o tabuleiro (escolhida pelo jogador);
  • jogando é falso quando o jogador ganhou ou perdeu e verdadeiro durante o jogo;
  • theDrawTab é uma lista de display cujo papel é explicado na última sessão (3.b).

 

c. Funções principais de gerenciamento do jogo

Um passo do jogo consiste na seleção de uma peça pelo jogador, no deslocamento desta e termina quando a peça soltada.

Primeiro a função indexSelecionada retorna o índice do buraco (de 0 a 32) apontado pelo mouse durante o clique de seleção. Na verdade é calculada uma distância euclidiana entre o centro de cada buraco e a posição apontada. Se esta distância for menor que o raio de um buraco, a peça deste buraco é considerada selecionada. Se o buraco está sem peça o valor 255 é retornado.

A função indexBuracoDestino retorna o índice (relativo ao buraco de origem) do buraco onde está soltada a peça. É verificado se a posição fica dentro do perímetro de um buraco, se este buraco faz parte dos buracos de destino potenciais (cf. jogada2) e se ele está ocupado por uma peça.

A função vizinhoOcupado retorna o índice do buraco de onde a peça vai ser retirada. Esse buraco fica entre o buraco de origem e o de destino quando esses dois forem válidos. Caso o buraco que está sendo pulado já esteja vazio, o valor 255 é retornado.

A cada fim de passo avalia-se o número de peças sobrando no tabuleiro (variável npecas). Em função deste a função fimdejogo determina se o jogador ganhou (uma peça só), se ele perdeu (várias peças sobrando) ou se ele ainda pode jogar.

 

 

2. PARTE GRÁFICA DO JOGO

Agora que foi explicado o desenvolvimento lógico de um passo básico, vamos ver como o display dos objetos da cena é tratado.

 

a. Modelagem do tabuleiro e das peças

Decidimos oferecer só uma vista de cima do tabuleiro sem possibilidade de mover o tabuleiro, isto é uma vista 2D.

As peças são modeladas por “esferas” (glutSolidSphere) de baixa resolução (6 faces longitudinalmente e 6 latitudinalmente), de cor vermelha (glColor3f). É por isso que elas parecem rubis. A função desenhaPeca se encarrega de desenhar uma tal peça numa posição dada (no sistema de coordenadas normalizadas) usando glTranslatef.

Os buracos nos quais são colocadas as peças, também são modelados por esse tipo de “esfera”. Essas esferas dão a ilusão de serem buracos, pois elas estão situadas atrás das peças e porque, do fato de serem esferas, têm uma tonalização que dá a impressão de relevo. A função desenhaBasePeca desenha então este falso buraco na posição especificada no sistema normalizado.

Finalmente a função desenhaTabuleiro desenha o tabuleiro completo: suporte e buracos. Começa por ressetar o framebuffer (planos de bits codificando as cores) com glClear. Desenha de cyan (glColor3f), dois retângulos (glRectf) que se cruzam e formam assim a plataforma suporte do tabuleiro. E em seguida chama a função desenhaBasePeca 33 vezes para desenhar cada buraco.

 

b. Funções gráficas para o display das peças

Uma vez nossos objetos modelados, temos que saber quais peças desenhar sobre este tabuleiro. A função distribuiPecas manda desenhar uma peça em cada buraco que está no estado ocupado (status=1), chamando a função desenhaPeca que acabamos de descrever na sessão anterior.

Quando uma peça é selecionada e está sendo rastreada com o mouse (status=2), ela tem que aparecer no ponteiro do mouse (cf. fig. 1). Por isso a função desenhaPecaMouse desenha uma peça (chamando desenhaPeca) nas coordenadas apontadas pelo mouse (Xglobal, Yglobal). Observa-se que é preciso converter as coordenadas do ponteiro do mouse (sistema de coordenadas da janela, viewport) em coordenadas normalizadas.



Figura 1. Jogo em andamento onde uma peça está sendo rastreada e pula sobre a vizinha para eliminá-la
(o jogador perdeu com 6 peças na última figura).

 

 

3. ARQUITETURA GERAL DO PROGRAMA

Agora que vimos a lógica básica do jogo e o display do tabuleiro e das peças para cada passo do jogo, nós vamos ver a estrutura geral do programa que permite juntar as funções já descritas e os recursos utilizados para alcançar uma boa interatividade.

 

a. Inicializações

O programa começa pela escolha de uma configuração (escolheconfiguracao) específica das peças no tabuleiro. São propostas 7 configurações iniciais diferentes (cruz, mais, lareira, seta, pirâmide, diamante, paciência) como mostram as figuras seguintes.


"cruz"


"mais"

"lareira"

"seta"

"pirâmide"

"diamante"


"paciência" (default)


Figura 2. As diversas configurações iniciais do tabuleiro.

Uma vez a configuração escolhida, é preciso inicializar todas as variáveis e atributos do jogo. A função inicia_tabul chama então preencheData para inicializar os valores dos atributos de cada buraco (status, njogs, jogadas1, jogadas2, c_x, c_y) independentemente da configuração escolhida. Logo depois, inicia_tabul chama preencheStatus que informa o estado de ocupação dos buracos (seta status=1 para os buracos ocupados) em função da configuração das peças escolhida. Finalmente incia_tabul inicializa as variáveis globais (pressed, buracoOrigem, buracoPula e buracoDestino).

Depois das inicializações dos parâmetros lógicos (invisíveis) do jogo, a função init procede à inicialização dos parâmetros de visualização. Assim inicializa-se a cor do fundo (glClearColor) em preto, a matriz de projeção (GL_PROJECTION) e ativa-se a cor do material (GL_COLOR_MATERIAL). Chama-se a função initlights para inicializar os parâmetros de iluminação (glLightfv) definindo as luzes ambiente, difusa e especular assim como o brilho do material (glMaterialfv).

 

b. Recursos para aumentar a velocidade de execução

Na função init apresentada acima, esquecemos falar da lista de display que é criada logo no início. Com efeito, é criada a display list chamada theDrawTab com uso de glGenLists. Esta display list pré-compila todas as funções especificadas (desenhaTabuleiro no nosso caso) para aumentar a velocidade de execução destas.

Para cada seleção de peça com o mouse, durante cada rastreamento de peça e depois de cada passo do jogo (botão do mouse soltado), é chamada a função display para atualizar a cena na janela. Essa função chama a lista de display (glCallList) criada na inicialização que desenha rapidamente o suporte e os 33 buracos do tabuleiro. Manda redesenhar as peças por cima do tabuleiro (distribuiPecas) e uma peça no ponteiro do mouse se esta está sendo rastreada. Pode-se notar que utilizamos o double buffering para aumentar a velocidade de display e portanto a interatividade do jogo (jogo em tempo real). Chaveia-se de um buffer para outro usando glutSwapBuffers.

 

c. Laço principal do programa permitindo interatividade

A função principal (main) do programa é constituída pela inicialização do gerenciador de janela glut e da janela, pela inicialização geral do programa (função init já vista) e pelo laço principal que é executado sem parar a partir do comando glutMainLoop.

A inicialização do gerenciador de janela é feita através de glutInit. É especificado o modo de uso do frame buffer para o display com glutInitDisplayMode (GLUT_DOUBLE para o double buffering, GLUT_RGB para um display colorido e GLUT_DEPTH para habilitar a profundidade ou depth buffer).

Após a especificação do tamanho (glutInitWindowSize) e da posição (glutInitWindowPosition) iniciais da janela, esta é criada com glutCreateWindow.

O laço principal gerencia os eventos, e funciona então como uma máquina de estados. Assim se liga cada tarefa de interação com uma função de callback adequada:

  • display é chamada por glutDisplayFunc quando é preciso redesenhar a cena;
  • MouseMove por glutMotionFunc para cada movimento do mouse;
  • MousePoint por glutMouseFunc para cada clique do mouse;
  • Keyb por glutKeyboardFunc para levar em conta a interação do usuário com o programa pelo teclado.

MouseMove atualiza as variáveis globais de posição do ponteiro do mouse (Xglobal e Yglobal) enquanto o mouse está sendo movido com o botão apertado.

MousePoint fica atento à posição do botão do mouse. Se este for apertado, chama-se a função indexSelecionada para verificar que uma peça foi selecionada e atualizar a variável buracoOrigem. Se o botão for liberado, verifica-se que o buraco de destino é válido (funções indexBuracoDestino e vizinhoOcupado), retira-se a peça eliminada (status=0) e verifica-se que não é o fim do jogo (fimdejogo).

Keyb só tem um efeito uma vez o jogo terminado (jogando=0) e leva em conta as teclas apertadas pelo usuário se forem entre 1 e 7. Estes números correspondem à escolha da configuração da próxima partida. Depois da escolha chama-se as funções inica_tabul e display para reiniciar a partida, carregando novos dados no tabuleiro e redesenhando-o.

O seguinte diagrama mostra a arquitetura geral do programa que acabamos de apresentar e detalha alguns testes e alocações.



 

CONCLUSÃO

 

Este projeto permitiu aplicar os conhecimentos teóricos em computação gráfica (como a modelagem, o modelo de iluminação, os problemas de visibilidade e de mudança de sistemas de coordenadas) e aprender os princípios de base de OpenGL para a visualização de objetos 3D em tempo real. O fato de o projeto ser um jogo interativo nos obrigou a estudar mais as partes de interatividade pelo mouse o pelo teclado e a integrá-las num programa que respeite regras de jogo bem particulares.

 

ANEXOS

 

I. Referências

  1. Woo M., Neider J., Davis T., “OpenGL Programming Guide”. Addison Wesley Developers Press. 1997.
  2. Foley et al. “Computer Graphics: Principles and Pratice”. Addison Wesley. 1996

 

II. Como executar o programa

 

1. Requisitos do sistema

O código-fonte escrito em C é genérico e poderia ser compilado no windows mas nós utilizamos gcc com OpenGL (versão MESA que é disponível na rede) para rodar nas estações SUN.

 

2. Como gerar o executável

  • Copiar o código-fonte em dois arquivos.(restaum.c e tabuleiro.c)
  • Compilar este arquivo para gerar o executável com o comando make restaum (ambiente UNIX)
  • Para rodar o programa, só precisa digitar: restaum (num terminal)
  • Para saber quais são as opções na linha de comando, digitar: restaum help
  • Para rodar o resta-um com uma configuração inicial específica xxx, digitar: restaum xxx

 

3. Código-fonte

Faça o download do código-fonte aqui: restaum.c

 

4. Manual do usuário

Você tem a escolha da configuração inicial do tabuleiro, digitando o comando restaum help aparece o menu de palavras-chaves xxx: cruz, mais, lareira, seta, pirâmide, diamante, paciência.Você precisa digitar restaum xxx para começar o jogo. Se digitar só restaum, a configuração paciência será automaticamente ativada.

Uma janela de jogo se abre e você pode começar a jogar selecionando uma peça com um clique e rastreando-a aonde quiser mantendo-se o botão esquerdo do mouse apertado. Uma vez a posição de destino atingida, é só liberar o botão do mouse. Se a jogada for impossível, a peça voltará ao seu lugar anterior. Caso contrário, ela ficará nesta posição e a peça pulada sumirá.

No fim do jogo aparecerá na janela do terminal uma mensagem: você ganhou ou você perdeu com X peças. A janela de jogo será inativa, mas ficará aberta. Selecionando-se a janela de jogo e digitando-se o código correspondente à configuração desejada (de 1 a 7 para cruz até paciência), uma nova partida começará.