IA-725:
Computação gráfica I
JOGO RESTA-UM
Faculdade
de Engenharia Elétrica e de Computação
ÍNDICE
Introdução.
1. Gerenciamento do jogo.
2. Parte gráfica do jogo.
3. Arquitetura geral do programa.
Conclusão.
Anexos. I. Referências.
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.
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:
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:
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.
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.
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:
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
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
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á. |