Obrigado a Julio Marchi pelo espaço cedido na MSX All
 

Curso de Jogos



Batalha Naval

  English

  Jogo de estratégia, onde um jogador deve derrubar toda a frota do adversário antes que o adversário elimine toda a sua frota.


1. Regras do jogo
  Há diversas variações do jogo, tanto o tamanho do tabuleiro, quanto à quantidade de navios e seus formatos.
  Nessa versão, o tabuleiro possui o tamanho de 10x10, onde as coordenadas são representadas por uma letra para o eixo horizontal e um número para o eixo vertical. Um exemplo de coordenada seria "B7". Além disso, possui 10 navios com os seguintes tipos, formatos e quantidades de cada um:
 Tipo         | Formato | Quantidade
 -------------+---------+-----------
 Porta-aviões | PPPP    | 1
 Cruzador     | CCC     | 2
 Hidro-avião  | HH      | 3
 Submarino    | S       | 4

  Inicialmente, cada jogador irá posicionar sua frota dentro do tabuleiro, respeitando o formato do navio e sem exceder as dimensões do tabuleiro.
Válido  Inválido
.....   .....
.HH..   ....HH
.....   .....
.....   .....
.....   .....

  Não poderá haver superposição de navios, além de que os navios deverão estar distantes entre si, pelo menos, uma casa em qualquer direção.
Válido  Inválido  Inválido
.....   .....     ..S...
.HH..   .HS..     .HH..
.....   .....     .....
...S.   .....     .....
.....   .....     .....

  O objetivo do jogo é encontrar todos os navios do adversário, antes que ele descubra os seus.
  O jogo é composto de dois tabuleiros para cada jogador: um para anotar o posicionamento de sua própria frota, e outro para anotar o que for sendo descoberto do adversário.
  Cada jogador informa uma coordenada ao adversário e ele responde "água", se nenhum navio estiver naquela posição, ou "acertou um ...", caso haja encontrado uma parte de um navio.


2. Rotinas do jogo
  Rotina que desenha o tabuleiro

  O jogo será desenhado na screen 1 para maior legibilidade.
5 ' Rem MarMSX 2018
10 COLOR 15,0,0:SCREEN 1:WIDTH 32:KEY OFF
...
200 '
201 ' Desenha tabuleiro
202 '
210 DIM PX(2):PX(1)=0:PX(2)=18
220 FOR I=1 TO 2
230 LOCATE PX(I)+2,0:?"ABCDEFGHIJ"
240 NEXT I
250 FOR Y=1 TO 10:FOR I=1 TO 2
260 LOCATE PX(I),Y:PRINT RIGHT$(" "+STR$(Y),2);".........."
270 NEXT I,Y
275 LOCATE 4,12:PRINT"Voce":LOCATE 24,12:PRINT"Eu"
280 LOCATE 0,17
290 FOR I=1 TO 5
300 LOCATE 0:READ A$:PRINT A$;
310 LOCATE 15:READ A$:PRINT A$;
320 LOCATE 25:READ A$:PRINT A$
325 IF I=1 THEN PRINT
330 NEXT I
340 RETURN
...
2000 '
2001 ' Dados do jogo
2002 '
...
2020 DATA Navio, Formato, Total, Porta-avioes, PPPP, 1,Cruzador, CCC, 2,
Hidro-aviao, HH, 3, Submarino, S, 4


  Rotina que posiciona os navios do jogador e computador

  Há duas maneiras de posicionar os navios do jogador no tabuleiro: a primeira, criando uma rotina que permita o próprio jogador posicionar seus navios; a segunda, criando uma rotina que faça o posicionamento automaticamente para ele.
  Nesse jogo, iremos posicionar automaticamente os navios do jogador e do computador. Desse modo, podemos aproveitar a mesma rotina para criar ambos os jogos.
  Para maior facilidade de acesso aos tabuleiros, vamos criar uma matriz tridimensional com os tabuleiros "superpostos". Assim, a matriz "TJ" teria as dimensões 10x10x2.
20 BT=10:DIM TJ(BT,BT,2):DIM N$(4)
  Obs: o tamanho do tabuleiro é indicado pela variável BT, que dá maior flexibilidade à rotina de criação do jogo.

  Por exemplo, quando desejássemos acessar a posição 5,4 do jogador, faríamos TJ(5,4,1) e para a mesma posição do computador, faríamos TJ(5,4,2), ou seja, basta indicar o valor "1" ou "2" da última coordenada de "TJ" para escolher um dos tabuleiros.
  O vetor "N$" irá armazenar os símbolos dos 4 tipos de navios:
  • P - Porta-aviões
  • C - Cruzador
  • H - Hidro-avião
  • S - Submarino
  Na área de dados dos jogo (linhas 2000 a 2020), iremos armazenar as informações quanto ao símbolos dos navios, o comprimento e a quantidade de cada um. Nessa versão, todos os navios serão desenhados na horizontal.
2010 DATA P,4,1,C,3,2,H,2,3,S,1,4
  Obs: como esses dados serão lidos primeiro que os dados utilizados pela rotina que desenha o tabuleiro, eles deverão aparecer primeiro na listagem do programa.

  Uma vez tendo todas as informações necessárias, podemos enfim posicionar os navios nos tabuleiros.

  Estratégia básica para posicionar os navios:
var TJ : vetor[1..10, 1..10, 1..2] de inteiro;
    N : string;
    C,T,X,Y : inteiro;
    i, it, p : inteiro;

para i ← 1 até 4 faça
  leia(N, C, T);
  para it ← 1 até T faça
    para p ← 1 até 2 faça
      X ← aleatorio(10-C+1);
      Y ← aleatorio(10);
      insira_navio(i, X, Y);
    fim_para
  fim_para
fim_para

  Onde:

para i ← 1 até 4 faça
  leia(N, C, T);
  para it ← 1 até T faça
    para p ← 1 até 2 faça
      X ← aleatorio(10-C+1);
      Y ← aleatorio(10);
      insira_navio(i, X, Y);
    fim_para
  fim_para
fim_para
para i ← 1 até 4 faça
  leia(N, C, T);
  para it ← 1 até T faça
    para p ← 1 até 2 faça
      X ← aleatorio(10-C+1);
      Y ← aleatorio(10);
      insira_navio(i, X, Y);
    fim_para
  fim_para
fim_para
para i ← 1 até 4 faça
  leia(N, C, T);
  para it ← 1 até T faça
    para p ← 1 até 2 faça
      X ← aleatorio(10-C+1);
      Y ← aleatorio(10);
      insira_navio(i, X, Y);
    fim_para
  fim_para
fim_para
Para cada um dos 4 tipos de navio, leia o símbolo, o comprimento e a quantidade. Para cada um do mesmo tipo de navio, repita a quantidade T vezes. Para cada jogador, obtenha as coordenadas X e Y aleatoriamente e insira o navio ali.

  Como garantir que o valor aleatório gerado não faz com que o objeto de comprimento "C" não ultrapasse os limites do tabuleiro?

 

  Para objetos de tamanho igual a 1, bastaria criar um número aleatório de 1 a 10 e problema estaria resolvido. Entretanto, os objetos possuem comprimento diferente de 1 no eixo X.
  Para contornar esse problema, podemos criar um pivô na menor coordenada de X do objeto e delimitar o valor máximo de X em relação ao comprimento "C".

 
  Pivô do navio em verde

  Para um tabuleiro de 10x10, os valores aleatórios seriam calculados da seguinte maneira:
Y: mínimo=1, máximo=10
X: mínimo=1, máximo=10-C+1
  Para um cruzador de comprimento "C" igual a 3, teríamos o valor em X variando de 1 a 8, e o valor em Y de 1 a 10, o que corresponde ao retângulo vermelho da figura anterior.

  Escolhidas as coordenadas X e Y, colocaremos o código do navio na matrix "TJ". Entretanto, devemos escrever o código em "C" posições consecutivas. Assim, temos:
  para ic ← 0 até C-1 faça
    TJ[X+ic,Y,P] ← i;
  fim_para
  Obs: uma vez que o tabuleiro é de valores inteiros, vamos armazenar o código do navio e não a string.

  O código em Basic fica:
400 '
401 ' Cria jogo
402 '
410 FOR I=1 TO 4
413 LOCATE 0,2:PRINT"Criando:";INT(100*(I-1)/4);"%"
415 READ N$(I),C,T
420 FOR IT=1 TO T
425 FOR P=1 TO 2
430 X=INT((BT-C+1)*RND(-TIME))+1
435 Y=INT(BT*RND(-TIME))+1
440 IF I>1 THEN GOSUB 500 ELSE 460
445 IF FV=1 THEN 460 
450 X=X+1:IF X+C-1>BT THEN X=1:Y=Y+1
453 IF Y>BT THEN Y=1
455 GOTO 440
460 FOR IC=0 TO C-1:TJ(X+IC,Y,P)=I:NEXT 
465 NEXT P,IT,I
470 RETURN

  As linhas 440-455 irão tratar da superposição dos objetos. Para o primeiro objeto, um "porta-aviões", não é necessário rodá-la, pois o tabuleiro está vazio.
  A rotina que trata especificamente da superposição está na linha 500. Ela retorna o valor em "FV" igual a 0, caso o objeto atual superponha algum objeto existente.
  Caso haja superposição, podemos adotar duas estratégias diferentes:
  • Repetir as linhas 430 e 435 e realizar um novo sorteio para X e Y; ou
  • Mover X e Y para a próxima posição dentro do retângulo vermelho.
  Dependendo do número de espaços vazios, o sorteio de uma nova posição X,Y válida pode levar um tempo considerável. Assim, foi adotada a segunda estratégia (linhas 450-455).


  Rotina que analisa a superposição de objetos

  Uma vez obtidas as coordenadas X,Y do pivô do navio, devemos verificar se ele se sobrepõe a um objeto existente.
  Lembrando que não poderemos ter objetos com distância menor que uma casa, deveremos verificar se na área do retângulo em torno do objeto existe algum navio.

 

  O exemplo da figura acima mostra a área de verificação (em verde) para um cruzador.

  Código:
500 '
501 ' Verifica superposicao
502 '
510 FV=1
520 FOR VY=Y-1 TO Y+C:FOR VX=X-1 TO X+C
530 IF VX<1 OR VX>BT OR VY<1 OR VY>BT THEN 550 
540 IF TJ(VX,VY,P)<>0 THEN FV=0:RETURN
550 NEXT VX,VY
560 RETURN


  Jogo do jogador

  Esta rotina apresenta o tabuleiro do jogador logo que a tela é desenhada.

1000 '
1001 ' Mostra jogo do jogador
1002 '
1010 FOR Y=1 TO 10:FOR X=1 TO 10
1020 LOCATE X+1,Y:IF TJ(X,Y,1)<>0 THEN PRINT N$(TJ(X,Y,1)) ELSE PRINT " "
1025 'LOCATE X+19,Y:IF TJ(X,Y,2)<>0 THEN PRINT N$(TJ(X,Y,2)) ELSE PRINT "X"
1030 NEXT X,Y
1040 RETURN


  Loop principal

  Após posicionar os navios e desenhar a tela, criamos o loop principal que irá controlar as ações do jogador e do computador.

30 PRINT "Batalha Naval - MarMSX 2018"
40 GOSUB 400:CLS:GOSUB 200:GOSUB 1000
50 CX=20:CY=1:PJ=0:PC=0
100 '
101 ' Loop principal
102 '
110 LOCATE CX,CY:A$=INPUT$(1):A=ASC(A$)
120 IF A=29 THEN CX=CX-1:IF CX<20 THEN CX=20
130 IF A=28 THEN CX=CX+1:IF CX>29 THEN CX=29
140 IF A=30 THEN CY=CY-1:IF CY<1 THEN CY=1
150 IF A=31 THEN CY=CY+1:IF CY>10 THEN CY=10
160 IF A=32 THEN GOSUB 600:IF V=1 THEN GOSUB 650
170 GOTO 110

  O código controla o movimento do cursor no mapa do computador visto pelo jogador.
  A barra de espaços abre uma casa do tabuleiro do computador, revelando o que havia ali. A rotina (linha 600) retorna um flag "V" informando se esse procedimento é valido. Se for, passa a vez para o computador jogar (linha 650).


  Analisa a jogada

  Esta rotina primeiramente verifica se a casa escolhida pelo jogador não havia sido aberta antes. Isto é feito, verificando se naquela coordenada da tela existe um ponto ".".
  Ao abrir a casa, ela imprime vazio " " caso o valor da coordenada X,Y da matriz código seja 0. Caso seja diferente de 0, ela imprime o símbolo relativo ao código encontrado (N$). Além disso, incrementa o valor de "PJ", informando que um objeto foi encontrado para os "pontos do jogador". Ao atingir 20, o jogador ganha a partida.
600 '
601 ' Analisa o comando do jogador
602 '
610 V=1:IF VPEEK(6144+CX+CY*32)<>46 THEN V=0:RETURN
620 LOCATE CX,CY:IF TJ(CX-19,CY,2)<>0 THEN PRINT N$(TJ(CX-19,CY,2)):PJ=PJ+1 ELSE PRINT " "
630 IF PJ=20 THEN LOCATE 0,14:PRINT"** Voce venceu o jogo !! **":END
640 RETURN


  O computador joga

  A coordenada é escolhida aleatoriamente. Então, ele verifica se a posição escolhida já havia sido aberta. Em caso positivo, ele passa para a próxima posição do tabuleiro até encontrar uma posição vazia.
  Diferente do outro tabuleiro, esse é marcado com um "X" nas casas visitadas.
  Caso encontre um objeto, incrementa "PC", o placar do computador.
650 '
651 ' O computador joga
652 '
660 X=INT(BT*RND(-TIME))+1
665 Y=INT(BT*RND(-TIME))+1
670 IF VPEEK(6144+(X+1)+Y*32)<>88 THEN 685
675 X=X+1:IF X>10 THEN Y=Y+1:X=1:IF Y>10 THEN Y=1
680 GOTO 670
685 LOCATE X+1,Y:PRINT"X":IF TJ(X,Y,1)<>0 THEN PC=PC+1
690 IF PC=20 THEN LOCATE 0,14:PRINT"** Eu venci o jogo !! **":END
695 RETURN

  Importante: a consulta à tabela do adversário poderá ser somente realizada a partir do palpite feito pelo jogador ou pelo computador em uma coordenada X,Y. Qualquer outro acesso fora do palpite seria "roubar" o adversário. Por isso, as linhas 670 da rotina do computador e 610 da rotina do jogador fazem acesso à informação visualizada na tela.


3. Melhorando a inteligência do computador
  A rotina do computador (linhas 650-695) é bem simples e sempre dá "um tiro no escuro", uma vez que não modifica seu comportamento após identificar um navio e calcula X e Y sempre aleatoriamente.
  Podemos modificar isso, fazendo com que o computador busque pelas casa adjacentes, sempre que encontrar um objeto de tamanho maior que 1.
  O primeiro passo é criar um vetor "N" para armazenar o tamanho de cada navio, pois agora será necessário obter essa informação.
20 BT=10:DIM TJ(BT,BT,2):DIM N$(4):DIM N(4)
...
415 READ N$(I),C,T:N(I)=C

  Agora, serão duas opções para o computador: uma é a coordenada X,Y obtida aleatoriamente, e a outra coordenada obtida através de uma perseguição ao navio encontrado.
  Um flag deverá ser criado, de forma que indique à rotina se ela deverá escolher aleatoriamente uma posição, ou continuar a busca pelo objeto encontrado.
50 CX=20:CY=1:PJ=0:PC=0:OE=0

  O código fica:
655 IF OE=1 THEN 690
656 ' Rotina aleatoria
660 X=INT(BT*RND(-TIME))+1
665 Y=INT(BT*RND(-TIME))+1
670 IF VPEEK(6144+(X+1)+Y*32)<>88 THEN 685
675 X=X+1:IF X>10 THEN Y=Y+1:X=1:IF Y>10 THEN Y=1
680 GOTO 670
685 IF TJ(X,Y,1)<>0 THEN OE=1:CR=N(TJ(X,Y,1)):C=CR:DX=1
690 LOCATE X+1,Y:PRINT"X":IF TJ(X,Y,1)<>0 THEN PC=PC+1
695 IF PC=20 THEN LOCATE 0,14:PRINT"** Eu venci o jogo !! **":END
700 IF OE=0 THEN RETURN
705 ' Rotina de perseguicao
710 IF TJ(X,Y,1)=0 THEN DX=-DX:GOTO 760
720 CR=CR-1:IF CR=0 THEN OE=0:RETURN
730 X=X+DX
740 IF X<1 OR X>10 THEN DX=-DX:GOTO 760
750 IF VPEEK(6144+(X+1)+Y*32)<>88 THEN 770 ELSE DX=-DX
760 X=X+DX:IF VPEEK(6144+(X+1)+Y*32)=88 THEN 760
770 RETURN

  A linha 685 analisa se a posição X,Y aleatória é um navio. Se for, seta o flag "OE", indica o comprimento do objeto em "CR" (comprimento restante) e ajusta o deslocamento inicial "DX" como sendo positivo.
  A partir das rodadas restantes, a rotina de perseguição (linha 705) é executada até que CR=0.
  Se o alvo foi água, modifique o sentido "DX" da perseguição. Desloque X até que encontre uma posição não visitada (linha 760).
  Na rodada atual, o valor da próxima posição de X já é calculado. Se a posição X já tiver sido visitada, mude o sentido e desloque X até que encontre uma posição não visitada (linha 760).

  Situação 1:

 

  Obs: o retângulo verde corresponde às coordenadas X,Y da jogada atual, e o retângulo cinza claro aos X,Y futuros.

  Situação 2:

 

  A linha 690 corresponde a um palpite do computador tanto da rotina aleatória, como da rotina de perseguição.

  Podemos também sinalizar o entorno do objeto encontrado, para minimizar o esforço do computador.
720 CR=CR-1:IF CR=0 THEN OE=0:GOSUB 800:RETURN
...
800 ' Sinaliza entorno
810 IF X>0 THEN IF TJ(X,Y,1)<>0 THEN X=X-1:GOTO 810
820 Y=Y-1
830 FOR PY=Y TO Y+2:FOR PX=X TO X+C+1
840 IF PX<1 OR PY<1 OR PX>10 OR PY>10 THEN 850 ELSE LOCATE PX+1,PY:PRINT"X"
850 NEXT PX,PY
860 RETURN


4. O jogo completo
  Download:

  batalha.zip - jogo no formato Basic e Basic ASCII.


Marcelo Silveira
Engenheiro de Sistemas e Computação
Especialista em Processamento de Imagens e Inteligência Artificial
© MarMSX 1999-2024