sexta-feira, 21 de dezembro de 2012

Movimento e Colisão

Nesta postagem vamos comentar sobre diversas procedures que são responsáveis por movimentar os jogadores e as bolas e testar a colisão do jogador com diversos elementos do jogo. Estas procedures são chamadas a partir da procedure jogo.

A procedure movecara movimenta os jogadores baseado no valor da variável "direcao" que recebe seu valor atual na procedure controle de acordo com as teclas pressionadas pelos jogadores.

O trecho de código abaixo mostra a lógica do movimento para a direção 1 (para cima). Para as outras direções o código é bem parecido, por isso foram omitidos.
procedure movecara;
var ind:integer;
begin
  for ind:= 1 to qjog do
    begin
      apaga(jog[ind].lin,jog[ind].col);
      case jog[ind].direcao of
        1:
           if mapa[jog[ind].lin-1,jog[ind].col]=1 then
                       morte(ind)
           else
                 jog[ind].lin:=jog[ind].lin-1;

        2: {...}
        3: {...}
        4: {...}
end;

A imagem do jogador em sua posição atual é apagada. Depois, baseado na direção que o jogador está se movendo é verificado se existe um bloco no caminho. Se o caminho estiver livre, a posição atual do jogador será modificada, mas se houver um bloco o jogador irá colidir, perderá pontos e será exibido um "*" na posição do jogador representando a colisão conforme a imagem abaixo. Essas ações estão na procedure morte().



O movimento das bolas é feito pela procedure movebola. As bolas se movimentam em diagonal e mudam de direção quando colidem com os blocos. O trecho de código abaixo mostra a lógica do movimento das bolas para a direção 1.
procedure movebola;
var ok,ind:integer;
begin
  for ind:= 1 to qbola do
   begin
     apaga(bola[ind].lin,bola[ind].col);
     ok:=0;
     repeat
        case bola[ind].direcao of
           1:begin
              if mapa[bola[ind].lin-1,bola[ind].col+1]=1 then
               begin
                 if((mapa[bola[ind].lin-1,bola[ind].col]=1) and
                    (mapa[bola[ind].lin,bola[ind].col+1]=1)) or
                    ((mapa[bola[ind].lin-1,bola[ind].col]=0) and
                    (mapa[bola[ind].lin,bola[ind].col+1]=0)) then
                      bola[ind].direcao:=3    {direcao oposta}
                 else
                  if(mapa[bola[ind].lin-1,bola[ind].col]=1) and
                    (mapa[bola[ind].lin,bola[ind].col+1]=0) then
                      bola[ind].direcao:=2    {muda movimento vertical}
                  else
                   if(mapa[bola[ind].lin-1,bola[ind].col]=0) and
                     (mapa[bola[ind].lin,bola[ind].col+1]=1) then
                       bola[ind].direcao:=4    {muda movimento horizontal}
               end
              else
                begin
                 bola[ind].lin:=bola[ind].lin-1;
                 bola[ind].col:=bola[ind].col+1;
                 ok:=1;
                end;
            end;

           2: {...}
           3: {...}
           4: {...}
end;

Os valores de 1 a 4 representam as seguintes direções para as bolas:


Se houver um bloco no local para onde a bola deveria ir será preciso decidir de que forma a bola irá mudar sua direção. Caso a direção da bola seja 1, o primeiro teste feito é para verificar se as posição acima e a direita possuem blocos ou se as duas posições estão sem blocos. Em ambos os caso a bola moverá na direção oposta.

O segundo teste verifica se a posição acima tem bloco e a direita não. Neste caso a bola muda apenas seu movimento vertical.

O terceiro teste verifica se a posição acima não tem bloco e a direita tem. Neste caso a bola muda apenas seu movimento horizontal.


A procedure testaobj verifica se os jogadores conseguiram pegar os seus itens. O código completo da procedure é este:
procedure testaobj;
var ind:integer;
begin
  for ind:=1 to qjog do
   begin
     if (jog[ind].col=obj[ind].col) and (jog[ind].lin=obj[ind].lin) then
        begin

          extrainfo[ind].score := extrainfo[ind].score + pontoobj;

          if qjog=2 then
            repeat
             inicia(obj[ind].lin,obj[ind].col);
            until (obj[1].lin<>obj[2].lin) or (obj[1].col<>obj[2].col)
          else
             inicia(obj[ind].lin,obj[ind].col);
        end;
   end;
end;

O vetor "obj" guarda as informações relacionadas aos itens que os jogadores devem pegar. Caso a posição do jogador e de seu item seja igual, o jogador ganhará a quantidade de pontos que está armazenada na variável "pontoobj". O valor desta variável é definido na procedure pegadados e depende da velocidade do jogo e da quantidade de bolas.

Depois o item deve reaparecer em outra posição. Isto é feito através da procedure inicia(). Caso 2 jogadores estejam jogando é feito um laço de repetição para garantir que o item não fique no mesmo lugar do item do outro jogador.

A procedure testamina verifica se os jogadores estão na mesma posição de uma das 5 minas que estão escondidas na área do jogo. Se estiver, será chamada a procedure morte() para o jogador e a mina será movida para outra posição. O código da procedure testamina é muito parecido com o da procedure testaobj.
        
A procedure testabola apenas verifica a colisão do jogador com as bolas do jogo e chama a procedure morte() caso ocorra. O seu código é bem simples e está abaixo.
procedure testabola;
var ind,bo:integer;
begin
  for ind:=1 to qjog do
    for bo:= 1 to qbola do
       if (jog[ind].col=bola[bo].col) and (jog[ind].lin=bola[bo].lin) then
              morte(ind);
end;

quinta-feira, 20 de dezembro de 2012

Procedure controle

A procedure controle é chamada quando o jogador pressiona uma tecla durante uma partida do Mina 2. O seu conteúdo é o seguinte:
procedure controle;
begin
  case tecla of
    CI1:jog[1].direcao:=1;
    DI1:jog[1].direcao:=2;
    BA1:jog[1].direcao:=3;
    ES1:jog[1].direcao:=4;
    CI2:jog[2].direcao:=1;
    DI2:jog[2].direcao:=2;
    BA2:jog[2].direcao:=3;
    ES2:jog[2].direcao:=4;
    ESC:sair:=1;

     PA:begin
          apagalinha(5);
          textcolor(15+blink);
          gotoxy(36,5);
          write('PAUSA');
          escondecursor;
          repeat
            tecla:=ord(upcase(readkey));
            if tecla=0 then tecla:=ord(readkey)+255;
          until tecla=PA;
          textcolor(15);
          apagalinha(5);
        end;
  end;
end;

As contantes CI1, DI1, BA1 e ES1 contém os valores ASCII das setas do teclado que são usadas pelo 1º jogador. O 2º jogador usas as teclas W, D, S, e A. Os valores ASCII destas teclas estão armazenadas nas contantes CI2, DI2, BA2 e ES2. Todas essas contantes estão definidas no início do código fonte do Mina 2 como pode ser visto na postagem Definição das variáveis. É importante ressaltar que os códigos ASCII das letras minúsculas e maiúsculas são diferentes. Para evitar este problema na leitura das teclas pressionadas as letras sempre são convertidas para maiúsculas através do uso da função "upcase()".

O pressionamento dessas teclas alteram a direção em que se move um determinado jogador. Após isso a procedure controle é encerrada e o fluxo voltará para a procedure jogo. São usados os valores de 1 a 4 para representar a direção do jogador conforme a imagem abaixo.


Para encerrar a partida basta pressionar a tecla "ESC". É possível também pausar o jogo pressionando a tecla "P". O valor ASCII da tecla "P" está armazenado na constante "PA". Existe um código que é executado quando o jogo está em pausa. Este código escreve na área de mensagem da tela a palavra "PAUSA" piscando e entra em um laço que só será encerrado quando o jogador pressionar a tecla "P" de novo. Para fazer o texto piscar é precisa adicionar o atributo "blink" na chamada da procedure "textcolor()". A imagem abaixo mostra a mensagem de Pausa.


quarta-feira, 19 de dezembro de 2012

Procedure jogo


A procedure jogo gerencia uma partida do Mina 2. A partir dela são chamadas as procedures que movimentam os itens do jogo e testam a colisão do jogador.

A procedure contém um laço while que mantém sua execução enquanto não for pressionada alguma tecla. Quando isso acontecer o fluxo do programa sai da procedure jogo e passa para a procedure controle para depois retornar à procedure jogo. Isto pode ser visto no Laço Principal.

Foi criada uma variável inteira chamada "cont" que serve para contar as iterações do laço while. Esta variável é usada para sincronizar diversas ações durante uma partida.

As variáveis "tempo" e "espera" também são usadas na procedure jogo. Seus valores são determinados na procedure pegadados de acordo com os valores informados pelo jogador. O valor da variável "tempo" é exibida na parte superior da tela e representa o tempo que falta para encerrar a partida. O valor da variável "espera" é o tempo em milissegundos que deve durar uma pausa que é feita no final do laço while.

O código completo da procedure jogo está abaixo.
procedure jogo;
begin
  while not(keypressed) do         {1ª parte}
    begin
      if((tempo mod 5) = 1) then
        for i:= 1 to QM do
          inicia(mina[i].lin,mina[i].col);

      if((cont mod 5) = 1) then     {2ª parte}
        begin
          movecara;
          if qbola > 0 then movebola;
          mostra;
          testaobj;
          testamina;
          if qbola > 0 then testabola;
          informa;
          escondecursor;
        end;

      if((cont mod 50) = 1) then     {3ª parte}
       begin
        tempo:=tempo-1;
        if tempo = 0 then
          begin
            fimdejogo;
            sair:=1;
            exit; {sai da procedure jogo}
          end;
       end;

      delay(espera);             {4ª parte}
      cont:=cont+1;
      if cont = 30000 then cont:=0;
    end;  {fim do while}

    tecla:=ord(upcase(readkey));
    if tecla=0 then tecla:=ord(readkey)+255;
end;

A 1ª parte do código muda a posição das minas a cada 5 unidades de tempo. Isto é feito usando o operador "mod" do Pascal que retorna o resto de uma divisão entre inteiros. No código usamos a expressão (tempo mod 5) que poderá resultar nos valores de 0 a 4. A procedure inicia() procura uma posição vazia na fase e armazena essa posição nas variáveis do item que foi passado como parâmetro.

Na 2ª parte do código, a cada 5 iterações do laço while, são chamadas diversas procedures que realizam a movimentação do jogador e das bolas e que testam a colisão do jogador com os objetos, as minas e as bolas.

A 3ª parte do código é responsável por diminuir a unidade de tempo do jogo que é exibida na tela. Isto ocorre a cada 50 iterações. Quando o valor da variável "tempo" for igual a zero a partida é encerrada e o fluxo do programa vai para a procedure fimdejogo.

Na 4ª parte temos a função "delay(espera)" que faz uma pausa na execução do código baseado no valor da variável "espera". Após isso a variável "cont" é adicionado de 1. Quando a variável "cont" estiver com o valor 30000 ela é zerada para evitar que chegue no valor limite de 32767 (inteiro 16 bit) e se torne um valor negativo.

O código ASCII da tecla que foi pressionada é armazenada na variável "tecla" para ser analisado na procedure controle e executar alguma ação caso exista.

segunda-feira, 10 de dezembro de 2012

Procedure tela

A Procedure tela é responsável por desenhar a tela relacionada a fase que o jogador escolheu e inicializar todos os itens desta fase. Esta procedure é executada apenas uma vez antes de começar uma partida.

O código desta procedure é extenso mas é simples. As ações que são executadas dentro desta procedure são as seguintes:
procedure tela;
var i:integer;
    s,strfase:string;
begin
    { Inicia dados dos jogadores... }

    { Inicia dados das bolas... }

    { Limpa a matriz mapa que representa a fase atual... }

    { Preenche as bordas da fase na matriz mapa... }

    { Preenche a matriz mapa de acordo com a fase escolhida... }

    { Desenha a fase de acordo com os valores da matriz mapa... }

    { Inicia as minas da fase... }

    { Inicia os objetos que os jogadores tem de pegar... }

    { Monta a string de Recorde e exibe no topo da tela... }

end;

O código abaixo inicia os dados do jogador. Lembre-se que as variáveis "jog" e "obj" são arrays de duas posições do tipo "geral". O tipo "geral" é definido no início do programa e possui as variáveis col, lin, cara, cor e direcao
  for i:= 1 to qjog do
    begin
      jog[i].cara:=2;
      jog[i].direcao:=0;
      extrainfo[i].score:=0;
      if i = 1 then
        begin
          jog[i].col:=28;
          jog[i].lin:=11;
          jog[i].cor:=10;
          obj[i].cara:=4;
          obj[i].cor:=10;
        end
      else
        begin
          jog[i].col:=23;
          jog[i].lin:=7;
          jog[i].cor:=14;
          obj[i].cara:=6;
          obj[i].cor:=14;
        end;
    end;

As variáveis lin e col definem a posição na área do jogo. A variável direcao pode conter os valores de 0 a 4, sendo que 0 (zero) significa que o jogador está parado e os outros valores indicam a direção atual que o jogador está se movendo.

A variável cara contém o valor ASCII do símbolo que deve ser desenhado na tela. Os valores utilizados no Mina 2 são:
☻ (ASCII = 2), ♦ (ASCII = 4) e ♠ (ASCII = 6)

O 1º jogador utiliza a cor verde claro (valor = 10) e o 2º jogador utiliza a cor amarelo (valor = 14). A cor também é usada nos objetos que os jogadores devem pegar.


Em relação as bolas do jogo, elas iniciam em posições predeterminadas na área de jogo mas com um pequeno ajuste aleatório que é obtido usando a função "random()".

A função "random()" recebe como parâmetro um valor inteiro X e retorna um valor aleatório que vai de 0 até X-1. Por exemplo, random(3) pode retornar os valores 0, 1 ou 2.

O trecho de código abaixo mostra como é definida a posição da 1ª bola na área de jogo. As outras bolas são feitas de forma semelhante.
  if qbola > 0 then
    for i:= 1 to qbola do
     begin
      case i of
        1: begin
             bola[i].col:=3+random(3);
             bola[i].lin:=2+random(3);
             bola[i].direcao:=2;
           end;

    ...

Esta imagem mostra como ficaram as posições iniciais das 8 bolas na fase 2.


A matriz mapa[lin,col] guarda o valor "1" para as posições da fase que contém um bloco. Todas as fases possuem o mesmo tipo de borda feito com os blocos. A parte interior da área do jogo é preenchida de acordo com a fase escolhida.

O código abaixo mostra o algoritmo de preenchimento das fases de 2 a 5. A fase 1 não está presente porque não existe nenhum bloco na área central da fase 1.

Por exemplo, a fase 2 que está na imagem acima consiste apenas das bordas e de uma linha central que inicia na posição (lin=9, col=12) e vai até a posição (lin=9, col=39) da matriz mapa.
  case fase of
       2: for i:= 12 to 39 do
              mapa[9,i]:=1;
       3: for i:= 5 to 13 do
            begin
              mapa[i,15]:=1;
              mapa[i,36]:=1;
            end;
       4: for i:= 12 to 39 do
            begin
              mapa[5,i]:=1;
              mapa[13,i]:=1;
              if (i=25) or (i=26) then
                begin
                  mapa[8,i]:=1;
                  mapa[9,i]:=1;
                  mapa[10,i]:=1;
                end;
            end;
       5: begin
            i:=12;
            repeat
              if(odd(i)) then
                for j:=4 to 12 do
                  mapa[j,i]:=1
              else
                for j:=6 to 14 do
                  mapa[j,i]:=1;
              i:=i+9;
            until i>39;
          end;
  end;

O código que desenha os blocos na tela baseado nos valores da matriz mapa é o seguinte:
  textcolor(12);   {cor vermelha}
  for i:= 1 to altura do
    for j:= 1 to largura do
      if mapa[i,j]=1 then
        begin
          gotoxy(j+difcol,i+diflin);
          write('█');
        end;

As diferenças entre as coordenadas da área do jogo e as coordenadas da tela estão definidas nas constantes "diflin = 6" e "difcol = 15", sendo assim a posição (1,1) da matriz mapa será desenhada na coordenada de tela (7,16).

Todas as variáveis que definem as posições do jogador, objetos e bolas, são referentes a posição dentro da área de jogo. Para encontrar a posição real de tela onde serão desenhados esses itens é preciso adicionar os valores das constantes diflin e difcol.

terça-feira, 27 de novembro de 2012

Procedure pegadados


A procedure pegadados é executada antes de iniciar uma partida do Mina 2. Através desta procedure é possível especificar os nomes dos jogadores, o número da fase, a velocidade do jogo e a quantidade de bolas. Estas informações são guardadas em variáveis que serão acessadas em outras procedures.

O código completo da procedure está abaixo.
procedure pegadados;
var resp1:char;
    code,ind:integer;
    resplong:string;

begin
    apagatela;
    for ind:= 1 to qjog do
      begin
        gotoxy(5,3+(2*ind));
        write('Digite o NOME do jogador ');
        if qjog=2 then
         begin
           if ind = 1 then write('da DIREITA ');
           if ind = 2 then write('da ESQUERDA ');
         end;
        write('(max. 10 letras) : ');
        readln(resplong);
        extrainfo[ind].nome:=copy(resplong,1,10);
      end;

    repeat
      gotoxy(5,9);
      write('Digite o N§ da FASE (1 a 5) : ');
      readln(fase);
    until (fase>=1) and (fase<=5);

    repeat
      gotoxy(5,11);
      write('Digite o N§ da VELOCIDADE (1 a 5) : ');
      readln(vel);
    until (vel>=1) and (vel<=5);

    repeat
      gotoxy(5,13);
      write('Digite o N§ de BOLAS (0 a 8) : ');
      readln(qbola);
    until (qbola>=0) and (qbola<=8);

    espera:=35-(5*vel);
    tempo:=49+(10*vel);
    pontoobj:=vel+qbola;
end;

A variável "qjog" contém o número de jogadores. Ela é preenchida na procedure menu de acordo com a opção selecionada.

Os nomes dos jogadores são armazenados na variável extrainfo cuja definição é feita da seguinte forma:
extrainfo: array[1..2] of tiporecorde;

O "tiporecorde" é um novo tipo definido no início do programa para armazenar o nome e a pontuação de um jogador.

As demais informações são guardadas nas seguintes variáveis:
  • fase: Número da fase.
  • vel: Velocidade do jogo
  • qbola: Número de bolas na fase. 

A partir dessas variáveis outras são calculadas:
  • espera: Tempo de pausa em milisegundos de cada quadro do jogo. Quanto maior a velocidade menor será o valor de "espera".
  • tempo: Tempo de duração de uma partida do Mina 2. Quanto maior a velocidade maior será o valor de "tempo".
  • pontoobj: Pontos que o jogador irá ganhar ao pegar um objeto do jogo. Seu valor é igual a soma das variáveis "vel" e "qbola".
     

segunda-feira, 26 de novembro de 2012

Procedure menu


A procedure menu exibe o título do jogo e mostra diversas opções que podem ser selecionadas pelo jogador utilizando as teclas de setas e pressionando ENTER.

As opções que o jogador pode escolher estão armazenadas em um array de string, que é preenchida logo no início da procedure menu.
procedure menu;
 var escolha,sair:integer;
     opcoes: array[1..5] of string[20];

 begin
      sair:=0;
      opcoes[1]:='   1 JOGADOR    ';
      opcoes[2]:='   2 JOGADORES  ';
      opcoes[3]:='   RECORDES     ';
      opcoes[4]:='   INSTRUCOES   ';
      opcoes[5]:='   FINALIZAR    ';
      ...

O trecho de código abaixo é responsável pela lógica da seleção e da exibição em cor diferente da escolha atual. A opção que está selecionada é exibida com a cor branca (cor=15) e o fundo vermelho escuro (cor=4). As demais opções são exibidas com a cor verde claro (cor=10) e fundo preto (cor=0). A variável inteira "escolha" guarda o valor da opção selecionada.
      repeat
            textbackground(4);
            textcolor(15);
            gotoxy(28,10 + (2 * escolha));
            write(opcoes[escolha]);
            enfeite;
            tecla:=ord(readkey);
            if tecla=0 then tecla:=ord(readkey)+255;
            textbackground(0);
            textcolor(10);
            gotoxy(28,10 + (2 * escolha));
            write(opcoes[escolha]);
            case tecla of
                 CI1: begin
                           escolha:=escolha-1;
                           if escolha=0 then escolha:=5;
                      end;
                 BA1: begin
                           escolha:=escolha+1;
                           if escolha=6 then escolha:=1;
                      end;
                 ENT: case escolha of
                           1: begin
                               qjog:=1;
                               sair:=1;
                              end;
                           2: begin
                               qjog:=2;
                               sair:=1;
                              end;
                           3: procrecorde;
                           4: instrucoes ;
                           5: begin 
                               gravarecordes;
                               textcolor(15);
                               textbackground(0);
                               clrscr;
                               halt;              {finaliza o programa}
                              end;
                      end;
            end;
      until tecla=ENT;

No início do bloco "repeat / until" a opção atual é desenhada em destaque e depois a procedure enfeite é chamada. Quando uma tecla for pressionada, a opção atual é redesenhada com a cor padrão igual as demais opções. As únicas teclas que fazem alguma ação no menu são: seta para cima, seta para baixo e tecla ENTER. Os valores dessas teclas estão armazenados nas constantes CI1, BA1 e ENT.

As setas apenas alteram o valor da variável "escolha". Caso a tecla ENTER seja pressionada, será verificada o valor atual da variável "escolha" para decidir o fluxo do programa.

sexta-feira, 23 de novembro de 2012

Procedure enfeite


A procedure enfeite é usada para fazer a animação da borda da tela de uma forma semelhante aqueles antigos placares eletrônicos. Esta procedure é usada em várias telas do Mina 2. O código fonte da procedure está abaixo.
procedure enfeite;
 var asteriscos:string;
     a,b,c:integer;

 begin
      asteriscos:='*    *    *    *    *    *    *    *    *    *    *    *    *    *    *    *    *    ';
      textcolor(11);
      textbackground(0);
      repeat
            for a:=1 to 5 do
             begin
                  gotoxy(1,1);
                  write(copy(asteriscos,a,80));
                  gotoxy(1,22);
                  write(copy(asteriscos,6-a,80));
                  escondecursor;
                  textcolor(11);
                  textbackground(0);
                  delay(60);
                  for b:=2 to 21 do
                   begin
                     c:=(a+b) mod 5;
                     if c = 1 then
                      begin
                        gotoxy(80,b);
                        write('*');
                        gotoxy(1,23-b);
                        write('*');
                      end
                     else
                      begin
                        gotoxy(80,b);
                        write(' ');
                        gotoxy(1,23-b);
                        write(' ');
                      end;
                   end;
             end;
      until keypressed;
 end;

Antes de explicar a lógica utilizada nesta procedure, vou comentar sobre algumas procedures que aparecem neste código:
  • copy(string,inicio,tamanho): A função copy() retorna uma parte de uma string especificada pelos parâmetros inicio e tamanho. 
  • delay(tempo): Esta procedure é usada para fazer uma pausa na execução do programa que dure o tempo especificado em milisegundos.
  • escondecursor: Esta é uma procedure utilitária que fiz para esconder o cursor durante a execução do Mina 2. Ela define a cor de frente e de fundo como preto em uma posição da tela e coloca o cursor lá. No tempo do DOS essa solução funcionava corretamente, mas quando o Mina 2 é executado em uma janela do Windows é possível ver o cursor aparecendo em várias partes da tela.

A animação das bordas é feita de duas formas, sendo uma para as bordas horizontais e a outra para as bordas verticais.

Para as bordas horizontais, criei uma longa string que contém todos os asteriscos que devem ser exibidos na parte superior e inferior da borda. Entre os asteriscos existem 4 espaços em branco.

Utilizando a função copy() eu desloco a string com asteriscos para a esquerda na borda superior e para a direita na borda inferior. O trecho de código relacionado está abaixo:
for a:=1 to 5 do
 begin
   gotoxy(1,1);     {borda superior}
   write(copy(asteriscos,a,80));

   gotoxy(1,22);    {borda inferior}
   write(copy(asteriscos,6-a,80));
   ...

Para as bordas verticais, um asterisco é desenhado a cada 5 posições levando em conta a posição atual das bordas horizontais. Nas outras posições é colocado um espaço em branco para apagar os asteriscos antigos.

Quando uma tecla for pressionada a procedure enfeite é encerrada e o fluxo do programa volta para a procedure anterior que chamou a procedure enfeite.

quinta-feira, 22 de novembro de 2012

Procedure instrucoes


A tela de Instruções do Mina 2 é a mais simples de todas. Ela apenas exibe um texto para o usuário e aguarda que seja pressionada a tecla ENTER.

Evitei usar acentuação no texto de Instruções porque haviam muitos PCs da época que não estavam configurados corretamente e acabavam mostrando caracteres estranhos.

O código completo da procedure instrucoes é o seguinte:
procedure instrucoes;
 var tecla_res:integer;
 begin
   apagatela;
   textcolor(14);
   textbackground(0);
   gotoxy(1,2);
   writeln('                            INSTRUCOES  ');
   writeln('    MINA 2 eh um jogo para um ou dois jogadores. O seu objetivo eh pegar ');
   writeln('  o maximo de objetos que aparecem na tela em um tempo limitado. Voce pode');
   writeln('  alterar a velocidade, o n§ de bolas e a fase onde quer jogar. No campo');
   writeln('  do jogo existem 5 minas escondidas. Se encontrar uma mina, bater na');
   writeln('  parede ou ser atingido por uma bola, voce morre e perde 5 pontos. ');
   writeln('    A cada 5 unidades de tempo as minas mudam de posicao.');
   writeln('  Cada vez que pegar um objeto ganhara pontos de acordo com a seguinte');
   writeln('  formula : pontos = (n§ da velocidade) + (n§ de bolas).');
   writeln('    Por isso quanto maior for a velocidade e o n§ de bolas , mais pontos');
   writeln('  voce podera ganhar. Se estiver no modo de 2 jogadores, o jogador da');
   writeln('  direita pega o objeto ',chr(4),' e o da esquerda pega o ',chr(6),' .');
   writeln('  Controles do jogo:  P - pausa/despausa    ESC - encerra uma partida');
   writeln('                      1§ jogador: usa as setas p/ se movimentar.');
   writeln('                      2§ jogador:      W');
   writeln('                                     A S D');
   writeln('  OBS: Voce nao precisa segurar a tecla para mover o personagem. Basta');
   writeln('  da um toque na direcao desejada que ele ira.');
   writeln('  Ass: Marcos Romero G. de Almeida. E-mail: maromero@zaz.com.br');
   writeln('                    PRESSIONE ENTER P/ CONTINUAR');
   repeat
    enfeite;
    tecla_res:=ord(readkey);
    if tecla_res=0 then tecla_res:=ord(readkey)+255;
   until tecla_res=ENT;
 end;

A procedure auxiliar apagatela foi feita para limpar a tela devagar e é utilizada em várias partes do Mina 2 para fazer a transição entre telas.

O texto é escrito a partir da 2ª linha da tela usando a cor amarela (valor 14) e o fundo preto (valor 0).

A procedure writeln(texto) escreve um texto e depois coloca o cursor no início da próxima linha.

No código fonte, onde aparece o símbolo § deveria aparecer º. Na execução do programa o símbolo º aparece corretamente.

Para escrever os símbolos ♦ (ASCII = 4) e ♠ (ASCII = 6), utilizei a função chr() na seguinte linha:
writeln('  direita pega o objeto ',chr(4),' e o da esquerda pega o ',chr(6),' .');

Para fazer a leitura das teclas pressionadas no Mina 2, utilizo as seguintes funções do Pascal:
  • keypressed: Esta função retorna True caso alguma tecla tenha sido pressionada.
  • readkey: Esta função retorna o caractere associado a tecla que foi pressionada.  
  • ord(char): Esta função retorna o código ASCII de um caractere.

Uma observação sobre a função readkey. Algumas teclas especiais como as de funções (F1, F2...) e as Setas do teclado possuem dois Bytes. Por isso a primeira leitura do readkey retornará 0 (zero), sendo necessário chamar o readkey de novo para pegar o valor desta tecla especial. Para diferenciar as teclas especiais das teclas normais, eu adiciono o valor 255 quando identifico que é uma tecla especial.

Esta lógica está no código abaixo:
tecla_res := ord(readkey);
if tecla_res = 0 then tecla_res := ord(readkey) + 255;

A procedure enfeite, que se encontra dentro do bloco repeat / until, é usada para fazer a animação da borda da tela até que uma tecla seja pressionada. Dentro dela está a chamada para a função keypressed. A procedure enfeite será analisada no próximo artigo.

Se a tecla pressionada for o ENTER, cujo código ASCII está armazenado na constante ENT, então a procedure instrucoes encerrará e o programa voltará para o menu.

quarta-feira, 21 de novembro de 2012

Modo Texto em Pascal

O Mina 2 é executado em modo texto no qual a tela é dividida em 25 linhas e 80 colunas. Cada posição da tela pode ser ocupada por um caractere ou símbolo.

As procedures abaixo são muito utilizadas no Mina 2:
  • textcolor(cor): Define a cor do texto que será utilizada.
  • textbackground(cor): Define a cor de fundo do texto.
  • gotoxy(coluna,linha): Coloca o cursor na posição especificada.
  • write(texto): Escreve um texto na posição atual do cursor usando as cores que estão definidas.  

Existem 16 cores que podem ser utilizadas nas procedures textcolor(cor) e textbackground(cor). Basta passar o número da cor como parâmetro. A imagem abaixo mostra as cores disponíveis. Para saber o número da cor, conte da esquerda para a direita começando em 0. Exemplos: preto = 0, vermelho escuro = 4, verde claro = 10 e branco = 15.


Como exemplo, o código abaixo escreve o texto "1 JOGADOR" na posição (coluna = 30, linha = 15) usando a cor branca com a cor de fundo vermelho escuro.


textcolor(15);
textbackground(4);
gotoxy(30,15);
write('   1 JOGADOR    ');

Para usar o Modo Texto de uma forma efetiva é preciso conhecer o código ASCII que é uma representação numérica dos caracteres usados no computador. O padrão ASCII define o código de 128 caracteres baseado no alfabeto inglês. Existe a versão estendida do ASCII que incluem mais 128 caracteres. Esta versão estendida era usada principalmente em países que utilizam acentuação como aqui no Brasil. Consulte a página www.asciitable.com para ver os símbolos existentes no padrão ASCII normal e estendido.

O problema é que na codificação atualmente usada pelos computadores foi mantida a compatibilidade apenas com o código ASCII original, dessa forma os caracteres que utilizavam a versão estendida do ASCII aparecem trocados. Por exemplo, no código fonte do Mina 2 eu utilizo muito o símbolo █ que representa um bloco do jogo. Este símbolo possui o código ASCII 219. No padrão atual, o código 219 representa o caracter Û.

Por isso que no código da Procedure menu do Mina 2 aparecem diversos caracteres estranhos, como pode ser visto abaixo.
write('Ü   Ü   Ü   Ü   Ü   ÜÜÜÜÜ     ÜÜÜÜÜ'); gotoxy(20,4);
write('ÛÛ ÛÛ   Û   ÛÛ  Û   Û   Û         Û'); gotoxy(20,5);
write('Û Û Û   Û   Û Û Û   ÛßßßÛ     Ûßßßß'); gotoxy(20,6);
write('Û   Û   Û   Û  ßÛ   Û   Û     ÛÜÜÜÜ');

O que deveria aparecer era algo assim:





Mas na hora da execução do programa, os caracteres são interpretados corretamente de acordo com a tabela ASCII.

Outra forma possível de utilizar os caracteres ASCII estendidos é através da função chr() que recebe um código ASCII e retorna o caractere equivalente. Como exemplo a linha abaixo escreverá o símbolo █ na tela.
write(chr(219));

terça-feira, 20 de novembro de 2012

Laço Principal

O código que contém o laço principal do Mina 2 está no fim do arquivo MINA2.PAS. O seu conteúdo é o seguinte:
begin
  clrscr;           {1ª parte}
  randomize;
  assign(frec,'recordes.mi2');
  verificarecordes;

  repeat            {2ª parte}
   sair:=0;
   cont:=0;
   menu;
   pegadados;
   tela;

   while sair = 0 do    {3ª parte}
    begin
      jogo;
      controle;
    end;

  until 1=0;
end.

Para facilitar a explicação, dividi este código em 3 partes.

A 1ª parte contém algumas procedures de inicialização. Segue uma breve descrição de cada uma delas:
  • clrscr: Limpa a tela. 
  • randomize: Inicializa o gerador de números aleatórios.  
  • assign(frec,'recordes.mi2'): Associa uma variável a um arquivo externo chamado recordes.mi2. 
  • verificarecordes: Verifica a existência do arquivo com Recordes e carrega no programa. 

A 2ª parte contém um laço infinito (repeat / until 1=0). Este laço contém todo o fluxo do jogo, que se inicia no Menu. A partir do Menu o jogador pode ir para as telas de Instruções e de Recordes, ou pode iniciar o jogo. O encerramento do programa também é feito a partir do Menu principal.

A procedure "pegadados" é usada para permitir que o jogador especifique alguns dados para o jogo como seu nome, número da fase e velocidade. A procedure "tela" desenha na tela a fase que foi escolhida pelo jogador.

A 3ª parte contém um laço while relacionado a execução do jogo. Quando o tempo do jogo acaba, a variável "sair" recebe o valor 1 fazendo com que o programa volte para o Menu principal.

A imagem abaixo mostra a ordem de execução das diversas procedures do Mina 2.


segunda-feira, 19 de novembro de 2012

Definição das variáveis

O código fonte em Pascal do Mina 2 está no arquivo MINA2.PAS. Logo no começo do arquivo temos as definições das variáveis e constantes utilizadas no jogo Mina 2 que são as seguintes:
program MINA2;
uses crt,dos;

const CI1 = 327;     {setas}
      BA1 = 335;
      ES1 = 330;
      DI1 = 332;
      CI2 = 87;      {w, a, s, d}
      BA2 = 83;
      ES2 = 65;
      DI2 = 68;
      PA  = 80;
      ESC = 27;
      ENT = 13;
      QM  = 5;  {qt de minas}
      QF  = 5;  {qt de fases}
      altura  = 17;
      largura = 50;
      diflin = 6;
      difcol = 15;

type
     geral = record
              col,lin,cara,cor,direcao:integer;
             end;
     tiporecorde = record
                      nome:string[10];
                      score:integer;
                   end;

var
     mina: array[1..QM] of geral;
     bola: array[1..8] of geral;
     jog,obj: array[1..2] of geral;
     extrainfo: array[1..2] of tiporecorde;
     recordes: array[1..QF] of tiporecorde;
     mapa: array[1..altura,1..largura] of integer;
     frec: file of tiporecorde;
     i,j,qjog,qbola,vel,fase,espera,tecla,cont: integer;
     sair,pontoobj,tempo: integer;

Vamos começar analisando a variável "mapa" que é responsável por representar a área do jogo.

A definição da variável "mapa" é feita da seguinte forma:
mapa: array[1..altura,1..largura] of integer;

Isto significa que "mapa" é uma matriz cujas dimensões são definidas pelas constantes "altura" e "largura". A constante "altura" está definida com o valor 17 e a constante "largura" com o valor 50. Cada posição desta matriz pode guardar um valor inteiro. No Mina 2, cada posição da variável "mapa" só guarda 0 ou 1, sendo que o valor 1 significa que existe um bloco nesta posição.

A imagem abaixo mostra a área de jogo do Mina 2. Coloquei uma grade por cima da imagem para que fique fácil de visualizar como a matriz representa a imagem. Cada posição da grade equivale a uma posição da matriz.


Agora vamos analisar as variáveis que representam a imagem dos jogadores e dos objetos que precisam ser coletados. A definição é feita desta forma:
jog,obj: array[1..2] of geral;

As variáveis "jog" e "obj" são do mesmo tipo, que consiste em um vetor de duas posições do tipo "geral". Cada posição do vetor representa um jogador.

Eu declarei o tipo "geral" usando o registro do Pascal para agrupar diversas variáveis comuns que são usados por vários itens no jogo Mina 2. A declaração foi feita desta forma:
type geral = record
              col,lin,cara,cor,direcao:integer;
             end;
         
Como exemplo de uso destas variáveis, o código abaixo define que o objeto que deve ser pego pelo 2º jogador irá aparecer na posição (linha: 10, coluna: 10) da área do jogo:
obj[2].lin := 10;
obj[2].col := 10;

As demais variáveis e constantes serão analisadas em outros artigos de acordo com o uso delas.

sexta-feira, 16 de novembro de 2012

Exemplo de um Jogo em Pascal


O objetivo deste blog é explicar o funcionamento do código fonte do jogo Mina 2 que eu programei com o Turbo Pascal há muito tempo atrás. Apesar de Pascal ser uma linguagem de programação antiga, muitas escolas e faculdades ainda continuam utilizando Pascal para ensinar algoritmos e lógica de programação.

Eu possuo um blog sobre desenvolvimento e programação de jogos chamado Romero Games. A postagem mais popular desse blog é uma chamada "Jogos em Pascal", o que mostra que existe um público interessado neste assunto aqui no Brasil. Provavelmente são pessoas que perceberam que é bem mais interessante aprender a programar fazendo um jogo.

O jogo Mina 2 com código fonte pode ser baixado no link: Mina2.zip

Para facilitar a leitura dos artigos na ordem correta, criei uma página de Sumário que pode ser acessada na barra lateral direita do blog.