sexta-feira, outubro 05, 2018

Pirate's Adventure

Este é um jogo sobre o qual eu já comentei antes (aqui e aqui). Neste post vou falar das dificuldades que eu tive para adaptar para o Yabasic o fonte publicado na revista Byte (em dezembro de 1980), como parte de meu esforço para criar um mini-servidor para executar programas antigos.


Pirate's Adventure foi o segundo jogo criado por Scott Adams (não confundir com o autor do Dilbert). Foi escrito originalmente em BASIC para o TRS-80, inspirado nos clássicos Colossal Cave Adventure (o jogo) e Ilha do Tesouro (o livro). Para conseguir fazer o jogo rodar em um micro com 16K de Ram, Scott criou um interpretador que processa uma série de tabelas que definem verbos, nomes, objetos, locais, mensagens e ações.

Em dezembro de 1980, a revista Byte publicou um artigo com o código em BASIC do interpretador e das tabelas que definem o Pirate's Adventure. A imagem abaixo dá uma ideia do código: múltiplos comandos por linha, sem espaços entre os elementos e transbordando de GOTOs.


Em 1981 (movido por aquela animação típica da juventude) eu decifrei a maior parte do código e traduzi o interpretador para COBOL, para rodar no único computador a que eu tinha acesso (no estágio). No ano seguinte eu já tinha acesso (no trabalho) a um micro CP/M-80 e não tive dificuldade em adaptar para rodar no MBASIC.

No final dos anos 90, já existindo a internet, encontrei várias informações adicionais, como o site do Scott Adams, o if-archive e iFiction. Nestes sites você encontra tanto interpretadores como os dados para as várias aventuras publicadas pela Adventure International.

Portanto não existe dificuldade em rodar este jogo hoje em dia... a não ser que você seja um saudosista como eu e queira rodar algo bem próximo do que foi publicado na revista Byte. O interpretador BASIC que eu venho utilizando no Raspberry Pi é o Yabasic, que tem a vantagem adicional de também poder ser usado sob Windows. A sua grande desvantagem é que não possui um editor integrado, você edita o código fora dele e depois tenta rodar. Também não está disponível este "ópio do programadores" que é o depurador interativo.

Felizmente eu ainda tinha o arquivo que rodei em 1982, que de alguma forma migrou ao longo dos anos do disquete CP/M-80 de 8 polegadas para um HD externo (provavelmente passando por disquetes de 5 1/4 e 3 1/2 polegadas e um CD-Rom). Este arquivo é quase igual à listagem da revista; a principal alteração é ter espaço entre os tokens.

O primeiro passo foi um tedioso acerto das pequenas diferenças sintáticas. O maior volume foi colocar aspas nos textos dos comandos DATA que contem os dados:
6390 DATA TOO DRY. FISH VANISH.,PIRATE AWAKENS. SAYS -AYE MATEY WE BE CASTING OFF SOON- HE THEN VANISHES
6400 DATA AFTER A MONTH AT SEA WE SET ANCHOR OFF OF A SANDY BEACH. ALL ASHORE WHO'S GOING ASHORE...

6390 DATA "TOO DRY. FISH VANISH.","PIRATE AWAKENS. SAYS -AYE MATEY WE BE CASTING OFF SOON- HE THEN VANISHES"
6400 DATA "AFTER A MONTH AT SEA WE SET ANCHOR OFF OF A SANDY BEACH. ALL ASHORE WHO'S GOING ASHORE..."
Uma outra alteração chata foi ter que colocar ENDIF no final dos IFs com THEN.
370 IF NV(0) <> V THEN GOTO 980 ELSE N=INT(CA(X,0)-V*150)
380 IF NV(0)=0 THEN F=0 : IF 100*RND(1) <= N THEN 400 ELSE GOTO 980
390 IF N<>NV(1) AND N<>0 THEN GOTO 980

370 IF NV(0) <> V THEN GOTO 980 ELSE N=INT(CA(X,0)-V*150) ENDIF
380 IF NV(0)=0 THEN F=0 : IF RAN(100) <= N THEN GOTO 400 ELSE GOTO 980 ENDIF : ENDIF
390 IF N<>NV(1) AND N<>0 THEN GOTO 980 ENDIF
Algumas mudanças foram mais simples, como a ordem dos parâmetros no OPEN (acabei trocando o comando OPEN pela função OPEN para poder testar se deu erro). Os comandos INPUT e PRINT tem pequenas diferenças, particularmente a necessidade de usara vírgula ao invés de ponto-e-vírgula. A manipulação de bits também exigiu algumas mudanças.

Por fim teve a parte mais complicada: o abuso dos comandos FOR/NEXT. Na teoria, estes comandos deveriam ser usados em par e aninhados corretamente. Por exemplo:
FOR X=1 TO 10
FOR Y=1 TO 5
PRINT X+Y
NEXT Y
NEXT X
Observe a linha abaixo e tente entender o que ela faz (e o que ela deixa de fazer):
410 F1=-1 : FOR Z=0 TO IL : IF IA(Z)=-1 THEN 550 ELSE NEXT : F1=0 : GOTO 550
Vamos decifrar isto:
  • F1 é usado como uma variável lógica, no BASIC do TRS-80 -1 é verdadeiro. Para ser mais preciso, -1 é o resultado de uma expressão verdadeira, no IF qualquer valor diferente de 0 é verdadeiro. O YABASIC usa 1 no resultado, mas isto não atrapalhou o programa.
  • IA é um vetor que armazena o local onde está cada objeto. -1 indica que o objeto está sendo carregado pelo jogador
  • IL é o número de objetos (contando a partir de zero, é claro)
Portanto o código percorre o vetor (FOR Z=0 TO IL), examinado se cada objeto está sendo carregado. Se não, ele passa para o próximo objeto (NEXT), ao final dos objetos coloca falso (0) em F1 e prossegue na linha 550. Se ele achar um objeto que esteja sendo carregado, ele interrompe o loop pulando direto para a linha 550. O resultado final é que F1 indica se o jogador está carregando algum objeto. O efeito colateral é que o FOR ficou "pendurado pela brocha". De alguma forma o BASIC do TRS-80 e o MBASIC do CPM/M-80 sabem conviver com isso. O Yabasic não.

Neste caso dá para usar o comando BREAK para fazer uma saída limpa:
410 F1=0
412 FOR Z=0 TO IL
414 IF IA(Z)=-1 THEN F1 = -1 : BREAK : ENDIF
416 NEXT Z
418 GOTO 550
Existem vários casos de FOR "abandonados" através de GOTOs e RETURNs no código original. Em alguns casos foi possível usar o BREAK em outros eu substituí o FOR X=0 TO N / NEXT X por X=0 / X=X+1 : IF X <= N THEN GOTO xxx ENDIF.

Apesar de todo o trabalho, parece que o programa está funcionando. Você pode baixá-lo do github..

Nenhum comentário: