Batalha Naval 1.0
Autores:
Diego Ferreira dos Santos (santos@ceb.unicamp.br)
Amarildo Martins (amartins@inatel.br)

A primeira versão do jogo Batalha Naval pode ser visualizada no seguinte Código Fonte. Essa versão foi desenvolvida em OpenGL no ambiente Linux e possui as seguintes características:

  • Três tipos distintos de Navios
  • Versão em Wireframe
  • Usuário entra com a posição dos Navios pelo teclado
  • Diferentes posições de Visualização das Matrizes
  • Os navios foram desenhados utilizando o software Maya 6.0. Abaixo podem ser vistos os três tipos de navios criados no Maya 6.0.

    Navio2



    Os navios foram gravados no formato .OBJ. Esse formato pode ser lido e usado em opengl através da biblioteca glm que faz parte do GLUT. Abaixo estão os comandos que são utilizados para ler os arquivos contendo as imagens 3D.

    GLMmodel *model2;
    GLMmodel *model3;
    GLMmodel *model4;
    GLuint model_list2;
    GLuint model_list3;
    GLuint model_list4;


    model2 = glmReadOBJ ("navio2.obj");
    model3 = glmReadOBJ ("navio3.obj");
    model4 = glmReadOBJ ("navio4.obj");


    model_list2 = glmList(model2,GLM_FLAT);
    model_list3 = glmList(model3,GLM_FLAT);
    model_list4 = glmList(model4,GLM_FLAT);

    Lógica do Jogo

    O jogo é composto basicamente de 2 matrizes 15x15. Uma matriz do usuário e outra destinada ao computador. Na matriz do computador os navios serão distribuídos aleatoriamente, enquanto que na outra matriz o usuário entrará com a posição dos navios manualmente via teclado.

    As matrizes são formadas por um tipo criado denominado "navio" que contém as seguintes variáveis:


    typedef struct NAVIO{
    int tipo;
    int visivel;
    int orientacao;
    int posicaoNavio;
    } navio;

    Onde:

  • tipo = Tipo do Navio. Na atual versão estão disponíveis 3 tipos numerados de 2 a 4, de acordo com o número de casas na matriz que o navio ocupa. Se o tipo for zero, então não existe navio na posição.
  • visivel. Indica se é para desenhar a posição atual na matriz ou não.
  • orientacao. Indica orientacao do navio (horizontal ou vertical).
  • posicaoNavio. Indica a posicao do tipo do navio na matriz. Se for 0 indica que esta é a posição inicial do navio. Util para descobrir onde se inicia o desenho do navio.

    Ao se iniciar o jogo, o computador sorteia a posição onde serão armazenados os navios através do seguinte código:

    for (Navio.tipo=2; Navio.tipo<=4; Navio.tipo++)
    do{
    gettimeofday(&tm,NULL);
    srand(tm.tv_usec);
    srand(tm.tv_usec);
    x = (int) (15.0*rand()/(RAND_MAX+1.0));
    gettimeofday(&tm,NULL);
    srand(tm.tv_usec);
    y = (int) (15.0*rand()/(RAND_MAX+1.0));
    NumeroTentativas++;
    if (NumeroTentativas > 100)
    break;
    }while (!putNavio(0,x,y,Navio));

    A função "putNavio" tem como argumentos a matriz que se deseja colocar o navio, a posição onde o navio será inserido e o tipo de navio. Essa função retornará falso caso a posição em que se deseja adicionar um navio já esteja ocupada ou então caso o navio não consiga preencher todas as posições da matriz (de acordo com o seu tamanho).

    O Tamanho inicial da janela escolhido é de 800x600. O volume de visualização escolhido é de:

  • Posicao X: -300, 300
  • Posicao Y: -100, 100
  • Posicao Z: -100, 100
  • As duas matrizes foram definidas através dos seguintes vértices: GLfloat vertices[8*3] = {
    -30.0,0.0,7.0,
    -3.0,0.0,7.0,
    -3.0,2.5,7.0,
    -30.0,2.5,7.0,
    -30.0,0.0,-8.0,
    -3.0,0.0,-8.0,
    -3.0,2.5,-8.0,
    -30.0,2.5,-8.0
    };

    GLfloat vertices2[8*3] = {
    3.0,0.0,7.0,
    30.0,0.0,7.0,
    30.0,2.5,7.0,
    3.0,2.5,7.0,
    3.0,0.0,-8.0,
    30.0,0.0,-8.0,
    30.0,2.5,-8.0,
    3.0,2.5,-8.0
    };

    Após o usuário entrar com a posição dos navios (função putNavio) para ser armazenado na matriz o jogo começa. As jogadas são alternadas entre o usuário e o computador. A matriz em que o usuário fará as jogadas é a da esquerda (pode ser visto na seção de Telas do Jogo mais abaixo). O computador atribui posições aleatoriamente para suas jogadas e nessa primeira versão não foi utilizada nenhuma heurística caso o computador acerte uma parte do navio.

    Para cada jogada, tanto do computador quanto do usuário, o seguinte algoritmo é utilizado:

    Verificar se Acertou Navio

    No fluxograma acima está demonstrado a jogada do usuário, mas o código para a jogada do computador é o mesmo.

    Após cada jogada, a tela do sistema é re-pintada e as posições são desenhadas de acordo com as informações das matrizes (através da função desenhaJogo). A função desenhaJogo percorre as duas matrizes inteiras e verifica:

  • Se variável matriz.visivel = 0, não desenha nenhuma ação na tela, pois significa que essa posição ainda não foi escolhida pelo usuário (ou pelo computador)
  • Se a matriz.visivel for igual a 1 (significa que o usuário (ou computador) já escolheu essa posição anteriormente. Se a variável tipo da matriz for igual a 0, então desenha na posição um X, significando que não existe um navio nessa posição. Se a variável tipo for diferente de 0, então um navio foi acertado. Desenha-se então na tela um quadrado (caso o navio ainda não foi completamente destruído (identificado através da função criada pecaEliminada) ou, caso contrário, desenha-se o navio inteiro (de acordo com o tipo lido). A função que desenha os navios é a seguinte:
  • glPushMatrix();
    glTranslatef(((x-1)*1.8)+deslocaX,2.6,6.6-(y-1));
    if (tipo == 2)
    {
    glScalef(0.85,1.0,1.0);
    glCallList(model_list2);
    }
    else if (tipo == 3)
    glCallList (model_list3);
    else if (tipo == 4)
    glCallList (model_list4);
    glPopMatrix();

    A variável deslocaX terá um valor diferente para cada tipo de navio, fazendo com que o navio seja transladado para a posição correta da matriz

    O fluxograma abaixo demonstra o algoritmo utilizado para o desenho do jogo

    Após cada jogada, é verificado se foi atingido o final do jogo. Isso é feito através da função acabouJogo(int matriz). Essa função percorre toda a matriz e verifica se todas as peças possuem o valor visible = 1. Se essa informação se confirmar, então o jogo está encerrado.

    Algumas Telas do Jogo - Parte 1

    Parte 2: Versão com Tonalização de Gouraud e chaveamento do cenário entre Diurno e Noturno onde navios terão luzes acesas

    Foram utilizados os seguintes valores para simular um ambiente diurno no jogo.

    GLfloat dia_amb[]={0.7, 0.7, 0.7, 1.0};
    GLfloat dia_dif[]={1.0, 1.0, 1.0, 1.0};
    GLfloat dia_spe[]={1.0, 1.0, 1.0, 1.0};

    A fonte de luz está posicionada em:


    GLfloat lightpos[4]={-100.0,10.0,10.0,1.0};

    Para simular um abiente noturno, foram utilizados os seguintes valores:


    GLfloat noite_amb[]={0.0, 0.0, 0.0, 1.0};
    GLfloat noite_dif[]={0.1, 0.1, 0.1, 1.0};
    GLfloat noite_spe[]={0.0, 0.0, 0.0, 1.0};

    Para simular luzes acesas nos navios quando o ambiente for noturno, foi utilizado o parâmetro GL_EMISSION do objeto, com as seguintes características:


    Caso ambiente Noturno: GLfloat emission[4]={0.6, 0.7, 0.4, 1.0};
    Caso ambiente Diruno: GLfloat without_emission[4]={0.0, 0.0, 0.0, 1.0};

    A tecla 'd' ativa o ambiente diurno enquanto a 'n' ativa o noturno

    Algumas telas do Jogo - Parte 2

    Parte 3: Cenas texturizadas e o jogador seleciona a posição através do cursor

    Para selecionar a posição através do cursor foi necessário criar um ID para cada quadrado que identifica a posição da jogada no tabuleiro. Através da função glLoadName é então atribuído um nome para cada objeto. Para saber qual objeto o usuário clicou, foi criada uma função denominada PICK, que retorna o objeto clicado pelo usuário.


    int pick(int x, int y)
    {
    int hit=0;

    glGetIntegerv(GL_VIEWPORT, viewport);
    (void) glRenderMode (GL_SELECT);
    glInitNames();
    glPushName(-1);

    glMatrixMode (GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluPickMatrix (x, height - y, 4, 4, viewport);
    gluPerspective (80.0,(GLfloat)width/(GLfloat)height,1.0,128.0);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt (0.0,32.0,12.0,0.0,0.0,0.0,0.0,1.0,0.0);

    glMultMatrixd(rotacao);

    desenhaQuadradosTabuleiro();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glFlush();
    hit = glRenderMode (GL_RENDER);

    if (hit)
    return selectBuffer[3];
    else
    return 0;
    }

    Para a parte de texturas, foram feitos 5 tipos de texturas: de metal, de rocha, de madeira, de água e de concreto. As texturas são lidas de um arquivo BMP. A biblioteca utilizada nesse projeto, a GLM, possui um método para atribuir as coordenadas de textura. Portanto, não é necessário fazer essa atribuição para os objetos lidos dos arquivos .OBJ.

    Primeiro, indica-se a quantidade de texturas que será utilizada através do comando glGenTextures, nesse caso são 5. O comando glTexImage2D foi utilizado para armazenar os dados RGB de cada textura. Para selecionar cada textura separadamente, utiliza-se o comando glBindTexture.

    Algumas telas de exemplo da última parte do jogo

    Observações:

    Para compilar o código fonte é utilizada a seguinte linha:
    cc naval.c glm.c -o naval -lGL -lGLU -lglut