Apresentação do Resta 1

1.Introdução

O objetivo do presente relatório é discutir como foi feita a implementação do jogo "resta 1". Para isto, adotou-se a seguinte metodologia: primeiro foi feito um levantamento geral sobre os problemas que tinham que ser resolvidos, como modelagem geométrica, interface com o usuário, a lógica do jogo e alguns detalhes na aparência final do aplicativo. E em segundo lugar, feito o levantamento dos problemas, foram propostas soluções para resolvê-los. As soluções preferenciais foram soluções com implementação mais simples e diretas. A implementação do jogo "resta 1" foi feita utilizando a linguagem C++ e a biblioteca gráfica OpenGL.

O relatório está organizado da seguinte forma: na seção 2 são feitos os levantamentos dos problemas a serem resolvidos. Na seção 3 são abordadas as soluções adotadas para a resolução dos problemas levantados na seção 2. E na seção 4, são feitos alguns comentários a respeito das dificuldades encontradas na implementação do jogo "resta 1".

2. Problemas a Resolver

Os problemas descritos nesta seção foram encontrados conforme a necessidade de implementação do jogo e suas soluções são descritas na seção 3. O primeiro nível de problema é referente à modelagem geométrica: como desenhar as peças, como modelar e desenhar o tabuleiro. A segunda classe de problemas engloba a interface com o usuário: como possibilitar o movimento das peças do jogo. O terceiro nível de problemas trata da parte lógica: quais movimetos são permitidos e quais não são, estes movimentos são determinados pelas regras do jogo, as quais permitem apenas movimentos na vertical e na horizontal. E finalmente, quais melhorias podem ser feitas para que o aplicativo fique com uma boa aparência visual.

3. Resolução de Problemas

Um dos maiores desafios na solução dos problemas descritos anteriormente é propor soluções simples e eficientes para os mesmos. Para isto, torna-se necessário o conhecimento das linguagens de programação que serão utilizadas, no caso OpenGL e C++,  para que as soluções possam se tornem fáceis. Assim como um conhecimento razoável em técnicas de programação e um entendimento completo a respeito do domínio do problema que está sendo abordado. Nas subseções a seguir são descritos os problemas abordados na seção anterior e os respectivos códigos utilizados para solucioná-los.

3.1. Modelagem Geométrica

Para as peças do jogo foi escolhido um formato cilíndrico e em sua extremidade foi colocado uma pequena esfera, para dar um forma arredondada a mesma. Para consegui esta forma foram utilizadas as seguintes funções do OpenGL:

void desenhar_peca() /*procedimento para desenhar as peças*/

  GLfloat cor_peca[] = {.9, .8, .7, 0.};

  GLUquadric * quad = gluNewQuadric();

  glMaterialfv(GL_FRONT, GL_DIFFUSE ,cor_peca);

  glColor3f(1., 1., 1.);

  gluCylinder(quad, .3, .3, .5, 10, 10);

  glTranslatef(0., 0., .5);

  gluSphere(quad, .3, 12, 12);

  gluDeleteQuadric(quad);

O tabuleiro do jogo é composto por uma matriz 7x7, entretanto, nem todas as regiões dele são utilizadas. Para se conseguir o efeito desejado, foram utilizados três cubos com escalas diferentes posicionados nas mesmas coordenadas e sobrepostos entre si. Os efeitos das divisões nos cubos foram conseguidos com o traçado de linhas sobre os dois cubos superiores. O código abaixo descreve o desenho dos cubos  e das linhas:

void linhas(float max, float x) /*formatuo das linhas*/

  glVertex3f(x, max*LARG,  .01);

  glVertex3f(x, -max*LARG, .01);

  glVertex3f(max*LARG, x, .01);

  glVertex3f(-max*LARG, x, .01);

glBegin(GL_LINES); /* looping para desenhar as linhas*/

  for (float x = -3.5*LARG; x <= 3.5 * LARG; x += LARG)

    linhas(1.5, x);

  for (float x = -1.5*LARG; x <= 1.5 * LARG; x += LARG)

    linhas(3.5, x);

/* funções para desenhar os cubos e as peças */

glTranslatef(0., 0., -.25);

glScalef(LARG*3., LARG*7., .5);

glutSolidCube(1.); /*cubo com linhas*/

glScalef(7./3., 3./7., 1.);

glutSolidCube(1.); /*cubo com linhas*/

glTranslatef(0., 0., -.75);

glScalef(2.5, 2.5, .1);

glColor3f(.5, .5, .5);

glutSolidCube(5.); /*cubo sem linhas*/

glColor3f(0, 0, 0);

glBegin(GL_LINES);

  for (float x = -3.5*LARG; x <= 3.5 * LARG; x += LARG)

    linhas(1.5, x);

  for (float x = -1.5*LARG; x <= 1.5 * LARG; x += LARG)

    linhas(3.5, x);

glEnd();

A Figura 1 mostra o formato do tabuleiro assim como as peças sobre o mesmo.

 

3.2. Interface com o Usuário

O que procurou-se fazer nesta etapa foi facilitar a interativiade do usuário com o aplicativo através de comandos de mouse e teclado. Para tal, foram disponibilizadas operações de rotação sobre e aproximação/distanciamento do tabuleiro, assim como a movimentação de peças.

Para a visualização do tabuleiro de diversos ângulos, foi utilizada a seguinte chamada de função:

gluLookAt(raio*cos(teta)*cos(fi), -raio*sin(teta)*cos(fi), raio*sin(fi), 0., 0., 0., -sin(fi)*cos(teta), sin(fi)*sin(teta), cos(fi));

em que o usuário tem o controle sobre os ângulos fi e teta através do pressionamento do botão direito do mouse e movimentando-o ou utilizando as setas do teclado. O controle sobre a distância do tabuleiro é feito utilizando as teclas "+ " e " - ". Os trechos de código abaixo apresentam como isso foi feito no OpenGL:

void especial(int key, int x, int y) {

  bool redesenhar(true);

  switch(key) {

  case GLUT_KEY_LEFT: teta += .09; break;

  case GLUT_KEY_RIGHT: teta -= .09; break;

  case GLUT_KEY_UP: fi += .09; break;

  case GLUT_KEY_DOWN: fi -= .09; break;

  ...

} }

void teclado(unsigned char tecla, int x, int y) {

  bool redesenhar(true);

  switch (tecla) {

  ...

  case '+': raio -= .5; break;

  case '-': raio += .5; break;

  ...

} }

void mouse(int button, int state, int x, int y) {

  switch (button) {

  case GLUT_RIGHT_BUTTON:

    if (rodando = state == GLUT_DOWN) {

      xant = x;

      yant = y;

    break;

  ...

} } }

void movimento(int x, int y) {

  if (rodando) {

    fi += (y-yant)/500.;

    teta += (x-xant)/500.;

} }

Para a movimentação das peças, quando o mouse fosse pressionado, havia a necessidade de determinar sua posição no espaço para que fosse possível checar se o mesmo estava sendo pressionado sobre alguma peça. Para conseguir isto, foi utilizada a função gluUnProject, que mapeia um ponto do volume canônico para o espaço, e a função glReadPixels para a leitura da coordenada " z " (no volume canônico) do ponto " x, y " sobre o qual o mouse foi pressionado. O trecho abaixo mostra a função de mapeamento:

void mapear(int x, int y, double& x1, double& y1, double& z1) {

  GLfloat z;

  glReadPixels(x, H-y-1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);

  gluUnProject (x, H-y-1, z, matrizModelview, matrizProjecao, matrizViewport, &x1, &y1, &z1);

}

3.3. Lógica do Jogo

A " lógica " do jogo foi implementada em dois arquivos separados da interface, jogo.cpp e jogo.hpp, para que os dois, interface e lógica, tivessem o mínimo de acoplamento possível. Com isso, permite-se que a lógica do jogo seja utilizada por outras interfaces e que a interface gráfica desenvolvida possa utilizar outras lógicas, dependendo da aplicação. No trecho de código mostrado abaixo, é feito um comentário sobre como está implementada a parte lógica. O código completo pode ser encontrado em: código fonte. Para isto, foi necessário um conhecimento sobre as regras do jogo " resta 1 ". As regras básicas são as seguintes: as peças só podem se mover na horizontal e na vertical, as peças só podem de movimentar de duas em duas casas, desde que exista uma peça na frente e a proxima casa esteja vazia, ou seja, as jogadas válidas são{x+2, x-2, y+2, y-2}, existem também algumas posições no tabuleiro que nao sao válidas. O jogo é iniciado com a posição central do tabuleiro vazia.

A função char inicial[7][8]{}, /*inicializa o tabuleiro com as seguintes características: "#" indica posição inválida, "P" indica Peça, "B" posição em Branco " sem peça "*/

void reiniciar(); /*reinicia o jogo*/

bool pode_mover(int dx, int dy, int px, int py); /*verifica se a peça pode ser movevida de (dx, dy) para (px, py)*/

bool pode_origem(int i, int j); /*verifica se a posição (i,j) é uma origem de movimento válida*/

bool mover(int dx, int dy, int px, int py); /*se for possivel, executa o movimento de (dx,dy) para (px,py)*/

bool operator()(int i, int j); /*verifica se a posicao (i,j) tem uma peça*/

bool valida(unsigned i, unsigned j); /*verifica se a posição (i,j) é uma posição válida*/

bool Jogo::pode_mover(int dx, int dy, int px, int py); /*Verifica se a jogada de (dx,dy) para (px,py) é válida. É assumido que a posição (dx,dy) já tenha sido verificada com "pode_origem".*/

bool Jogo::mover(int dx, int dy, int px, int py); /*Verifica o movimento de (dx,dy) para (px,py), e o executa caso seja possivel, atualizando o tabuleiro.*/

bool Jogo::pode_jogar(); /*// Verifica se o jogador ainda tem algum movimento possivel, no pior caso (quando o jogador nao tem mais movimentos) testa todas as possibilidades de jogada.*/

3.4. Aparência Visual

Para a renderização de imagens mais agradáveis do tabuleiro e das peças, optou-se por disponibilizar iluminação difusa, posicionando o ponto de luz sobre o tabuleiro. A utilização ou não da mesma fica a critério do usuário, que pode habilitar/desabilitar pressionando a tecla " l". Os trechos abaixo mostram como foi implemenetada a iluminação no jogo:

GLfloat luz[] = { 1.0, 1.0, 1.0, 0.0};

GLfloat pos1[] = { 0., 0., 10., 0.};

...

glLightfv(GL_LIGHT1, GL_DIFFUSE, luz);

glLightfv(GL_LIGHT1, GL_POSITION, pos1);

4. Conclusões

O OpenGL mostrou-se uma ferramenta eficiente para a renderização de cenas, implementando e disponibilizando operações complexas como a iluminação e a transformação de projeção. Por implementar as operações de acordo com a teoria

abordada na computação gráfica, o mesmo torna-se intuitivo para quem conhece a mesma.

Apesar de mostrar-se uma ferramenta poderosa para a renderização de cenas, o OpenGL também mostrou-se uma ferramenta pobre para a interação com o usuário, mesmo utilizando em conjunto com bibliotecas como o GLU e o GLUT. O mesmo pôde ser observado pelas dificuldades encontradas para a implementação da interação com usuário, onde era necessário disponibilizar um \emph{picking} 3D.