Mostrando postagens com marcador programação jogos. Mostrar todas as postagens
Mostrando postagens com marcador programação jogos. Mostrar todas as postagens

segunda-feira, 7 de janeiro de 2013

Conclusão

Chegamos ao final deste blog. Para acessar de uma forma fácil todas as postagens utilize o SUMÁRIO.

Se você estiver interessado em conhecer mais sobre a área de desenvolvimento de jogos dê uma olhada em diversos materiais didáticos que eu já produzi nesta área. Vou comentar sobre alguns deles.

  • Game Design Compendium
A série Game Design Compendium contém 20 apresentações voltada para iniciantes na área de desenvolvimento de jogos e aborda diversos conceitos fundamentais de Game Design.

As apresentações estão disponíveis neste link: Game Design Compendium.



  • MRDX
O MRDX é uma biblioteca para programação de jogos em C/C++ que eu implementei em 2001 com o objetivo de facilitar o ensino nesta área. O MRDX foi apresentado no 1º Workshop Brasileiro de Desenvolvimento de Jogos (WJogos) que ocorreu em Fortaleza no ano de 2002.

Os textos que acompanham o MRDX são bem didáticos e apresentam noções básicas de programação de jogos que são úteis independente da linguagem de programação que você estiver utilizando atualmente.

O MRDX pode ser baixado neste link: MRDX.



  • UnrealScript
UnrealScript é uma linguagem de programação orientada a objetos utilizada para programar jogos 3D na Unreal Engine. Em novembro/2009, a empresa Epic Games lançou o Unreal Development Kit (UDK) que pode ser usado livremente para fins não comerciais.

Criei uma série de 40 artigos em português ensinando a programação de jogos no UDK com UnrealScript. Os artigos podem ser acessados neste link: Sumário UnrealScript.


sexta-feira, 4 de janeiro de 2013

Uso de Arquivo para os Recordes


Os recordes das fases do Mina 2 são gravados em um arquivo quando o programa é finalizado para que estes recordes possam ser recuperados na próxima execução do Mina 2. Se este arquivo não fosse usado, os valores dos Recordes sempre retornariam ao valor padrão em uma nova execução do jogo.

A variável com o nome "frec" é usada para referenciar o arquivo que guarda os recordes no Mina 2. Ela foi definida da seguinte forma:
type tiporecorde = record
                      nome:string[10];
                      score:integer;
                   end;

var frec: file of tiporecorde;

Como pode ser visto no Laço Principal, no começo da execução do programa, a variável "frec" é associada ao nome de arquivo "recordes.mi2" através da procedure assign():
assign(frec,'recordes.mi2');

Logo após é chamada a procedure verificarecordes que irá procurar pelo arquivo "recordes.mi2" e carregar os valores de recordes caso o arquivo seja encontrado. Se o aquivo não existir, serão utilizados os valores padrões para os recordes.

O código da procedure verificarecordes é o seguinte:
procedure verificarecordes;            
var texto1:PathStr;
    ind:integer;
begin
     texto1:=FSearch('recordes.mi2','.');
     if texto1 = '' then
        for ind:=1 to QF do
          begin
            recordes[ind].nome:='Computador';
            recordes[ind].score:=100;
          end
     else
        begin
          reset(frec);
          for ind:=1 to QF do
              read(frec,recordes[ind]);
        end;
end;

A procedure gravarecordes é chamada quando for escolhida a opção "Finalizar" na procedure menu. Esta procedure é responsável por gravar os recordes atuais das fases no arquivo "recordes.mi2".

Código da procedure gravarecordes:
procedure gravarecordes;            
var ind:integer;
begin
  rewrite(frec);
  for ind:=1 to QF do
      write(frec,recordes[ind]);
  close(frec);
end;

A tela de recordes é acessada a partir do Menu Principal do Mina 2. A procedure procrecorde controla esta tela que é apenas informativa. É feito um conjunto de movimentações com os textos para montar a tela. O trecho de código abaixo é responsável por mover os textos que contém o recorde de cada fase da direita para esquerda até sua posição correta.
 {trecho da procedure procrecorde}

  for rec:= 1 to QF do
      begin
           str(recordes[rec].score:3,s);
           str(rec:2,strfase);
           s:= 'FASE '+ strfase+' : '+ s + ' - '+ recordes[rec].nome;
           for ind:=80 downto 25 do
               begin
                    x:=81-ind;
                    if x>26 then x:=26;
                    gotoxy(ind,(rec*2)+6);
                    write(copy(s,1,x));
                    escondecursor;
                    textcolor(14);
                    delay(20);
                    if ind <> 25 then
                       apagalinha((rec*2)+6);
               end;
      end;

quinta-feira, 3 de janeiro de 2013

Procedure fimdejogo


A procedure fimdejogo é chamada a partir da procedure jogo quando o Tempo da fase chega a zero. Esta procedure mostra o Placar da partida com os pontos dos jogadores e indica se o recorde atual da fase foi superado.

O código da procedure fimdejogo foi dividido em três partes nesta postagem para facilitar a explicação.

A 1ª parte realiza uma pequena animação que mostra os textos "PLACAR" e "MINA 2" descendo lentamente a partir do topo da tela.
procedure fimdejogo;
var ind,col,tecla_res:integer;
    s,strfase:string;
begin
  apagatela;
  textcolor(15);

  for ind:=1 to 5 do
      begin
           gotoxy(36,ind);
           write('PLACAR');
           escondecursor;
           textcolor(15);
           delay(100);
           if ind <> 5 then
                  apagalinha(ind);
      end;

  for ind:=1 to 3 do
      begin
           gotoxy(36,ind);
           write('MINA 2');
           escondecursor;
           textcolor(15);
           delay(100);
           if ind <> 3 then
                  apagalinha(ind);
      end;

A 2ª parte escreve os nomes e os pontos dos jogadores na tela. Caso esteja no modo de 2 jogadores é verificado a pontuação de cada jogador para determinar o vencedor ou se a partida acabou em Empate.
  for ind:=1 to qjog do   
   begin
    textcolor(jog[ind].cor);
    if ind=1 then
       col:=47
    else
       col:=23;
    gotoxy(col,8);
    write(extrainfo[ind].nome);
    gotoxy(col+2,10);
    write(extrainfo[ind].score:3);
   end;
   textcolor(15);
   ind:=1;
   if qjog=2 then
    if extrainfo[1].score = extrainfo[2].score then
     begin
       gotoxy(36,12);
       write('EMPATE');
     end
    else
     begin
       if extrainfo[1].score > extrainfo[2].score then
          ind:=1
       else
          ind:=2;
       gotoxy(30,12);
       write('VENCEDOR : '+ extrainfo[ind].nome);
     end;

A 3ª parte verifica se os pontos do vencedor foram maior do que o recorde atual da fase. Se isto acontecer será exibido na tela o texto "NOVO RECORDE!" junto com as informações relacionadas ao recorde. O nome e os pontos do jogador serão armazenados no vetor "recordes[]" que guarda os recordes de todas as fases. No final do código a procedure enfeite é chamada para fazer a animação da borda até que a tecla ENTER seja pressionada.
   if extrainfo[ind].score > recordes[fase].score then
    begin
     textcolor(15+blink);
     gotoxy(32,14);
     write('NOVO RECORDE!');
     textcolor(15);
     recordes[fase].score := extrainfo[ind].score;
     recordes[fase].nome := extrainfo[ind].nome;
     str(recordes[fase].score:3,s);
     str(fase:2,strfase);
     s:= 'FASE '+ strfase+' : '+ s + ' - '+ recordes[fase].nome;
     gotoxy(28,16);
     write(s);
    end;
  gotoxy(25,20);
  write('Pressione ENTER para continuar.');
  repeat
   enfeite;
   tecla_res:=ord(upcase(readkey));
   if tecla_res=0 then tecla_res:=ord(readkey)+255;
  until tecla_res=ENT;
end;

quarta-feira, 2 de janeiro de 2013

Procedures utilitárias

Existem diversas procedures utilitárias no Mina 2 que serão analisadas nesta postagem.

A procedure informa escreve no topo da tela o tempo restante e qual jogador está vencendo. Nas laterais são escritos os nomes e pontos dos jogadores de acordo com a cor de cada jogador como pode ser visto nesta imagem.


Os nomes e os pontos dos jogadores estão armazenados no vetor "extrainfo[]". Este é o código da procedure informa:
procedure informa;
var ind,col:integer;
begin
 for ind:=1 to qjog do   {nome e ponto do jog}
  begin
    apagalinha(5);
    textcolor(jog[ind].cor);
    if ind=1 then
       col:=67
    else
       col:=3;
    gotoxy(col,10);
    write(extrainfo[ind].nome);
    gotoxy(col+2,12);
    write(extrainfo[ind].score:3);
  end;
  textcolor(15);
  if qjog=2 then
    if extrainfo[1].score = extrainfo[2].score then
       msg('     Empate')
    else
     begin
       if extrainfo[1].score > extrainfo[2].score then
          ind:=1
       else
          ind:=2;
       msg(extrainfo[ind].nome + ' esta vencendo.');
     end;
  gotoxy(35,3);
  textcolor(15);
  if tempo<=10 then textcolor(15+blink);
  write('TEMPO: ',tempo:2);
end;

Existe outra procedure chamada mostra que é responsável por exibir na tela os caracteres que representam os jogadores e seus objetos, além das bolas que ameaçam os jogadores. Esta procedure e a procedure informa são chamadas a partir da procedure jogo.

As informações necessárias para a exibição de todos os itens estão nos vetores "jog[]", "obj[]" e "bola[]". Estes vetores são do tipo "geral" como pode ser visto na postagem Definição das variáveis.
procedure mostra;
var ind:integer;
begin
  for ind:=1 to qjog do
    begin
      textcolor(jog[ind].cor);

      gotoxy(obj[ind].col + difcol, obj[ind].lin + diflin);
      write(chr(obj[ind].cara));

      gotoxy(jog[ind].col + difcol, jog[ind].lin + diflin);
      write(chr(jog[ind].cara));
    end;

  if qbola > 0 then
   for ind:= 1 to qbola do
    begin
      textcolor(15); {branco}
      gotoxy(bola[ind].col + difcol, bola[ind].lin + diflin);
      write('0');
    end;
end;

A procedure inicia procura uma posição aleatória na fase que não contenha um bloco. Esta procedure utiliza passagem de parâmetros por referência. Isto é feito usando a palavra "var" na frente dos nomes dos parâmetros. Na passagem por referência, as variáveis originais que foram passadas para a procedure tem os seus valores modificados.
procedure inicia(var nlin, ncol:integer);
begin
   repeat
     nlin := random( altura ) + 1;
     ncol := random( largura) + 1;
   until mapa[ nlin, ncol ] = 0;
end;

A procedure morte é chamada quando ocorre algum tipo de colisão do jogador. Ela desenha um "*" na posição atual do jogador e exibe uma mensagem no topo da tela com o nome do jogador que explodiu. Depois faz uma pequena pausa e subtrai 5 pontos do jogador.
procedure morte(x:integer);
begin
  apagalinha(5);
  sound(50);
  textcolor(jog[x].cor);
  gotoxy(jog[x].col + difcol, jog[x].lin + diflin);
  write('*');
  msg(extrainfo[x].nome + ' explodiu.');
  delay(300);
  nosound;
  delay(400);
  apagalinha(5);
  extrainfo[x].score := extrainfo[x].score - 5;
  jog[x].direcao := 0;
end;

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.