Tribulações de programador

O lançamento do The Spectrum levou-me a meter no projeto de reeditar, de forma atualizada um jogo antigo. Mas os problemas estão a ser muitos.

Creio que devo ter tido o meu Zx Spectrum em 1985, tinha eu 14 anos. Apesar de, nessa altura, já ter contacto com o Zx81, e ter-me iniciado na programação com ele, o Zx Spectrum era um mundo à parte, passando a memória de 1Kb para 48 Kb, e acrescentando velocidade, gráficos e cores.

Foi com o Zx Spectrum que basicamente aprendi a programar. E foi com ele que todo o bichinho da informática que tenho hoje, apareceu.

Devido à paixão que a máquina nos trouxe, eu e o meu primo Tiago, juntavamos-nos e fazíamos jogos para o Spectrum. Apesar de ser para uso pessoal, e não comercial, os jogos não só tinham alguma qualidade, como eram muitos, motivo pelo qual resolvemos assinar os mesmos. Assim surge a SOFTIMAR, abreviatura de Software Tiago e Mário, e aprovada entre muitas outras propostas.

Ora um dos jogos realizados nessa altura no âmbito da Softimar, ficou imortalizado, estando ainda hoje disponível no World of Spectrum. Chama-se Treinador de Futebol. E devido a esse jogo, a Softimar teve recentemente direito a uma referência num livro chamado Jovens Programadores Portugueses, e que fala sobre programadores Portugueses que, neste primórdio da informática, criaram software que de destacava para o seu tempo.



Ora como sabem, recentemente adquiri o The Spectrum. Uma reedição do antigo Zx Spectrum, adaptada aos nossos dias, e resolvi fazer uma biblioteca de tudo o que existe disponível para a máquina. Naturalmente o Treinador de Futebol não podia deixar de fazer parte desse lote.

E foi então que me passou algo pela cabeça. E se eu refizesse esse jogo, mais adaptado aos dias de hoje, melhorado, e a explorar melhor o Spectrum.

E assim fiz… Tendo começado o projecto Treinador de Futebol 2025 para o ZX Spectrum.

Confesso que voltar a programar no velhinho spectrum foi fascinante. O sentir de novo as suas teclas de borracha foi uma nostalgia fabulosa, que me deu uma pica tremenda para avançar.

Assim peguei no meu spectrum original, ainda funcional, no meu velhinho Sanyo DR-101, o meu leitor da altura, igualmente funcional, e corri a minha biblioteca de cassettes à procura do código do antigo Treinador de Futebol, que certamente estaria algures.

Após vários dias e muito trabalho, finalmente encontrei-o. Foi uma questão de ligar o gravador ao PC, e gravar o jogo em WAV para carregar no The Spectrum, e criar um ficheiro .TAP.



O primeiro passo foi analisar o código… e ver o que poderia aproveitar.

O que me apercebi foi que o jogo estava programado de uma forma extremamente simplista. Natural dado que na altura que tive o meu Zx Spectrum eu e o meu primo éramos meras crianças. Código mal feito, mal optimizado, e mesmo com algumas bugs ao não prever certas situações. Basicamente o que aproveitei do jogo original foi… o nome Treinador de Futebol. 🙂

Bem, na realidade aproveitei mais alguma coisa, as ideias para as animações das jogadas. Essas anotei e re-criei de novo, de forma bem mais optimizada.

Primeiro problema

Mal começo a programar, algo imediatamente me preocupou. O Zx Spectrum não faz o input dos comandos, teclando os mesmos letra a letra. Ele tem os comandos associados às teclas e eles são acessíveis ou diretamente ou por combinações de teclas. É algo completamente arcaico, e que, por muito saudosismo que tenha da máquina, não consegui continuar a fazer.



Resolvi assim usar o Basic do Spectrum 128. Ele é diferente na medida que os comandos aqui são teclados letra a letra, e reconhecidos quando se pressiona enter. Um grande passo face ao Spectrum original!

E assim, superei este problema.

Segundo problema

Ao programar apercebi-me que a sintaxe do Spectrum é mesmo retrograda. A sintaxe do Basic foi melhorada ao longo dos tempos, mas ali no spectrum ainda é muito primária.

Para começar os comandos tem de ser introduzidos em linhas numeradas. E isso cria o problema que um erro de digitação pode apagar uma linha já existente, obrigando a ter de reler o código todo e a perceber o que foi apagado, refazendo. Não há UNDO, nem possibilidade de procura, tendo de se fazer scroll do texto todo à procura do que se quer.



Depois ciclos IF THEN ELSE não existem no Spectrum. Existe apenas o IF THEN, pelo que o ELSE obriga a novo IF THEN. E isto por vezes é um problema obrigando a linhas extra de código pois determinados ciclos tornam-se problemáticos de serem encaixados, obrigando a repetir codigo.

Outro problema é que não existe o comando END IF. Isso quer dizer que no spectrum o IF aplica-se a tudo o que se encontra na mesma linha de código, a seguir ao comando IF. Ora se a ideia for o IF alterar radicalmente algo, isso implica ou linhas enormes, ou o uso de subrotinas chamadas dentro dos IF. Não há outra forma.

E comandos IF dentro de outros IF tornam-se complexos.

O meu problema mais frequente foram ciclos FOR NEXT, com IF inseridos, onde por vezes metia tudo numa única linha, mas o NEXT não ocorria por aparecer depois de um IF. Vi-me grego com algumas linhas de código para me aperceber que este era o erro que estava a ter.

Outra situação arcaica é que as variaveis tem de ser definidas usando o comando LET.



Onde normalmente eu escreveria:

Avaria=1

dando à variável avaria o valor 1, no Spectrum tenho de escrever

LET Avaria1=1

O mesmo com arrays, se eu quiser a$(1)=”Mário”, e a$(2)=”Armão”, no ZX Spectrum tenho de dimensionar a$ usando



DIM a$(2)

Ainda por cima, o Spectrum não possui o operador MOD, e muitos outros comandos que se tornaram comuns nos dias de hoje, sendo que me obriga a programa-los à unha. Por exemplo, um comando:

IF N MOD D=0 THEN,

não pode ser usado. Basicamente MOD retorna-me o resto da divisão de N por D, pelo que caso esta for 0, então N é múltiplo de D.

Sou obrigado a fazer algo do género



LET resto=N-INT(N/D)*D

IF resto=0 então estou perante um multiplo.

Enfim, são várias situações ligadas a sintaxe, muitas delas limitativas e que me obrigam a perder tempo para as contornar, ou a ter de programar usando comandos mais básicos aquilo que funções mais avançadas e inexistentes fazem.

O Spectrum, felizmente tem os comandos DEF FN e FN que me permitem criar funções que me permitem definir esses comandos uma única vez, e chama-los quando preciso. Um comando que se está a revelar valioso.

Terceiro Problema



Estando a programar em casa com o meu PC com 64 GB, não tenho qualquer problemas como o uso de meros bytes ou KBytes, mas no Spectrum, eu apenas tenho 48K, sendo que desses apenas 42 KB estão disponíveis para o BASIC

Ora naturalmente que, sem me aperceber, rapidamente bati no teto, e ao executar o meu programa obtive o erro: OUT OF MEMORY

Era altura de otimizar!

A optimização demorou muito tempo, mas funcionou e muito bem. Basicamente suprimi muitos comandos ao fazer a coisa automatizar em vez de ser manual. Foram ciclos maiores, mas que no global libertaram mais de 500 linhas de código, que elas próprias estavam a gastar RAM.

Outra ferramenta super útil já falei em cima, foi o comando DEF FN e consequente FN. Permitiu poupar mais de 0,5 KB em uso de comandos extensos, que foram assim associados à função.



E da mesma forma tornei os ciclos mais eficientes. Cada comando de escrita para o ecrã gasta-me memória, pelo que em vez de usar escritas seguidas, meter as escritas dentro de um ciclo FOR NEXT, apesar de fazer o mesmo, poupa-me RAM. No entanto cria-me efeitos visuais de escrita que não são visualmente os melhores, mas por serem temporários são sacrifícios que necessito de realizar.

Mas a real optimização teve de surgir no uso das variáveis, e isso levou-me a ter de aprender muito sobre como o Spectrum gere a sua RAM nas variáveis.

A primeira solução que me ocorreu foi fazer o jogo exclusivo do Spectrum 128KB, mas o problema é que esse Spectrum, apesar de ter a RAM adicional disponível, ela só pode ser usada como armazenamento, que troca dados nos 48 KB originais. Ora se meu jogo tivesse níveis, isso resolver-me-ia o problema, mas como se trata de um único núcleo de programação, estou na mesma preso aos 48 KB como limite. Daí que me restavam as variáveis.

Repensar as variáveis foi complexo. Mas algumas situações foram até simples.

Por exemplo, se eu quiser guardar os valores “Guarda-Redes, Defesa, Médio, Atacante”, numa variavel p$(i), eu vou precisar de 4 posições dentro da variável p$, onde p$(1)=”Guarda-Redes”, p$(2)=”Defesa”, p$(3)=”Médio”, e p$(4)=”Atacante”.



E normalmente, para reservar RAM, e porque o Spectrum o exige antes de introduzir este tipo de arrays multidimensionais, faria um

DIM p$(4)

O que eu vim a descobrir é que este dimensionamento cria-me uma variavel com 4 posições, mas com uma dimensão de 255 caracteres cada uma. Ou seja, dado que a minha maios String é “Guarda-Redes”, que apenas contem 12 letras, eu estava a reservar memória para 243 caráteres adicionais que não vou usar.

Isso resolvo com uma alteração ao DIM

DIM p$(4,12)



Desta forma, estou a limitar o numero de caracteres em cada posição a 12, o que me, aplicado a todas as variáveis, me poupou imensa memória.

Mas haviam variáveis com grande dimensão. Por exemplo, eu estava a guardar os resultados todos do campeonato, assim como o calendário de jogos todo na RAM.

Estamos a falar, num campeonato de 18 equipas, de um total de 306 valores para cada variável.

Ora isto levou-me a estruturar melhor a situação. A ideia de guardar todos os resultados tinha a ver com o moral das equipas, onde eu iria verificar se haviam ciclos de três vitorias seguidas, ou três derrotas seguidas, alterando o moral de acordo com isso. Consegui fazer isso com uma única variavel com 18 entradas, que para cada equipa quando há uma vitória a variavel toma o valor 1, e soma 1 unidade sempre que ganha. Caso alcance 3, houveram 3 vitorias. Da mesma forma, uma derrota atira o valor para-1, subtraindo um a cada nova derrota, e ao valor -3 temos 3 derrotas.

Só com esta alteração, poupei quase 3KB no uso da RAM.

Da mesma forma, dado que o campeonato é em duas mãos, sendo que a segunda mão tem os mesmos jogos da primeira, apenas alterando o factor casa, reduzi o guardar do calendário de 306 entradas para 153, poupando metade da memória.

Claro que tudo isto obrigou a ajustes no código, mas os ganhos foram compensadores, e ando aqui à caça de tudo o que seja byte que possa poupar.

Neste momento o jogo está-me a ocupar 35 KB, e o seu grosso está implementado, tendo conseguido poupar mais de 12 KB. Acredito conseguir meter o que falta nos 48KB de RAM, mas só poderei cantar de galo quando tudo estiver terminado.

Quarto Problema

A ambição nas alterações face ao jogo original, e as optimizações pela RAM, levaram a que muito fosse automatizado. Ora se isso é bom porque me poupa memória, tem o seu lado mau, pois o ZX Spectrum não é um exemplo de velocidade.

Por esse motivo ponderei a certa altura passar o jogo para Assembler, mas tal como com o Basic, esbarrei com questões de sintaxe arcaica, o que seria voltar quase à estaca zero, uma noção que me levou a suspender essa ideia. Irei no entanto, assim que o jogo estiver terminado e funcional, tentar novamente a passagem, de forma a ganhar velocidade de execução.

Um dos motivos pelos quais a passagem era desejada é que certas situações requerem testar o código a meio da temporada, ou no final desta. E naturalmente estar a simular tudo a baixa velocidade é desesperante, até porque uma eventual bug pode obrigar a repetir a situação umas boas dezenas de vezes.

Resolvi o problema deixando de programar no The Spectrum, e passando a programar no PC. Com o uso de um emulador, posso alterar a velocidade do Spectrum para se equivaler à velocidade do meu PC, e assim tornar instantâneas situações que no espectro demorariam largos minutos, simulando assim as partes a meio e fim do jogo que preciso para caça às bugs.

Quinto Problema

Infelizmente o ZX Spectrum só tem duas maneiras de lidar com grafismo. Uma é carregar um ecrã com gráficos para a RAM, e guardar os endereços de RAM com a posição do gráfico para uso quando precisar. Uma metodologia que no meu caso não me agrada pois um ecrã de carga gasta 6 KB de RAM, e não tenho onde o carregar. Basicamente isso poderia ser uma hipótese, se o meu jogo não estivesse tão exigente. Mas ele é mais dependente de matrizes e métodos estatísticos, do que realmente de gráficos, pelo que não olho para esta possibilidade como interessante.

A outra alternativa é o uso de grafismo personalizável. O Spectrum permite definir gráficos, sem uso real de memória pois ele substitui caracteres pré existentes pelos novos.  Uma solução que me interessa aqui, mas que infelizmente tem limites. Isso porque os caracteres personalizáveis estão associados às teclas A a U, sendo que os caracteres presentes em T e U não estão acessíveis para alteração direta requerendo mexer em endereços de memória que me podem perturbar parte do código.

Assim, fico limitado aos caracteres A a S, ou seja, 19 caracteres de 8×8 pixels.  E isto pode parecer suficiente, mas infelizmente, eu quis apostar um pouquinho em grafismo, pelo que gastei tudo muito rapidamente.

E isto coloca um problema, pois o Spectrum não explica nos seus manuais, como superar esta limitação, especialmente dada a forma como os comandos READ acedem os comandos DATA.

Basicamente quando fazemos um READ para ir buscar dados, o ZX Spectrum procura por uma linha com comandos DATA, que contem os dados a ler. A questão é que ele faz isto sequencialmente, ou seja, ele ignora a numeração do código e vai procurar do inicio para o fim do código a primeira linha DATA que encontra para dar os dados ao READ. Caso faça um novo READ, a primeira linha DATA, está marcada como lida, o que quer dizer que ele vai procurar a linha DATA seguinte para ler.

O que isto implica é que se eu quiser voltar a ler a primeira linha DATA, não posso, ou seja, se eu redefinir o grafismo usando comandos DATA ou DATA BIN, o gráfico é lido, mas para ser re-escrito teria de ler outra linha DATA com os novos valores. E se quisesse voltar aos valores iniciais, teria de ter uma outra linha DATA, igual à primeira, para reler.

O que isto implica é que substituições sequencias de dados não são possíveis, e se eu quiser usar os 19 caracteres para algo, precisando de substituir os mesmos para mais, os comandos DATA são inúteis.

Após muito pensar, consegui resolver isto atribuindo os valores de DATA a variáveis, que leio e re-leio as vezes que quiser. Uma solução de recurso que me ocupa RAM em permanência, mas que me permite trocar o grafismo à vontade.

No entanto nem tudo são rosas.

Isto porque estes dados tem de ser guardados em strings multidimensionais, que requerem dimensionamento. E o Spectrum tem um severo limite nesse aspecto.

Por exemplo, se eu quisesse isto:

DIM graficoa(8): DIM graficob(8): DIM graficoc(8)

No Spectrum não posso.

Isto porque variáveis multidimensionais, definidas com o DIM, sejam elas numéricas ou alfa numéricas, apenas podem ter uma letra.

Assim teria de fazer algo do genero:

DIM a(8): DIM b(8): DIM c(8)

Até aqui nada de mais, não fosse o simples facto que só tenho 24 letras no alfabeto… E rapidamente, pelo uso que faço com várias matrizes de transformação, fico sem letras. Neste momento creio que só tenho uma letra por usar, e não sei se não precisarei dela ainda.

Por exemplo, se eu pudesse ter o seguinte comando:

DIM goloscasa(153): DIM golosfora(153)

Seria fácil perceber o que cada uma das variáveis é. Mesmo que fosse gc(i) ou gf(i), seria igualmente facil.

Mas com uma letra se eu fizer

DIM g(8)

para os golos casa, que letra uso para golos fora? Percebam que o problema não é usar uma outra letra, é depois, ao ver o código perceber facilmente o que a variavel que está nessa letra faz. Ou seja se eu usar um DIM y(8) para golos fora, o que é que Y tem a ver com golos fora? Como é que eu vou facilmente perceber, ao rever o código que o que Y guarda são os golos fora?

Esta situação torna o código complexo, e problemático de percepção quando se volta atrás a rotinas pré existentes já realizadas à algum tempo.

A solução são os REMARKS ou REM, mas eles gastam memória e não fazem nada senão dar indicações, pelo que estou a evitar os mesmos. Infelizmente remover os mesmos só mesmo no fim.

Problemas de última hora

O jogo está quase finalizado, faltando apenas algumas linhas de código extra, mas ontem vou a executar o código após acrescentar mais umas rotinas e… OUT OF MEMORY.

Alterei algumas rotinas extras, e poupei 171 bytes. Nada de extraordinário. Ou seja, não consigo acabar o jogo por falta de memória.

Existe uma uma solução de ultimo recurso. Alterar a chamada RAMTOP do ZX SPECTRUM. Basicamente isso implica pegar numa memória reservado do sistema para cálculos internos e processamento gráfico e torná-lo acessível para uso. Normalmente não é algo muito desejável de ser feito, pois pode criar instabilidade no sistema. Mexendo na mesma percebo que consigo ir buscar mais 1 KB, o que deveria acabar  o jogo. Mas a verdade é os testes mostram que o sistema se torna instável e o Spectrum crasha com facilidade. A alternativa poderá ser puxar apenas metade dessa memória. Terei de ver.

A verdade é que o mercado aínda não está completo, e não tenho nada para as sequências de fim (descer de divisão, ir à falência ou ser campeão). A memória está a ser um problema, ando a espremer bytes, e poderá não chegar. É um grande receio que me obrigaria a cortes que não queria fazer.

Conclusão

Apesar de o projeto estar a ir muito bem, este não está livre de problemas. Sinceramente nos dias de hoje não é fácil conter um código a 48K. Estar preocupado com os bytes usados na execução não é algo que se faça nos dias que correm, mas é um problema que estou a ter aqui, e estou a ter todos os cuidados para poupar todos os bytes que puder, bem como reutilizar todas as variaveis que puder, de forma a não ter de reservar espaço para mais.

É um desafio… mas está a ser interessante.

Como referido, o jogo está quase finalizado, e neste mo então, face ao jogo de 1989 eis as alterações acrescentadas.

  • Grafismo renovado.
  • Mais grafismo.
  • Possibilidade de escolha da equipa a treinar.
  • Gestão da equipa com mais do que um suplente. [o jogo original só permitia um suplente]
  • Gestão financeira (É agora possível levar o clube à bancarrota)
  • Salários [Melhores jogadores implicam mais despesa]
  • A idade começa a pesar nas performances [Novidade]
  • Sorteio do calendário alternando jogos em casa e fora [no jogo original os jogos eram em sequencia, todos em casa na primeira volta, todos fora na segunda volta]
  • Possibilidade de lesões [Não existiam]
  • Novas e mais jogadas animadas nos resumos do jogo. [3 no jogo original, 5 agora]
  • Fator casa [Novidade – Jogar em casa aumenta as probabilidades de um bom resultado]
  • Noticias semanais. [Novidade – Tudo o que aconteceu de relevante na jornada, será relatado aqui]
  • Existência de multas e novos patrocínios. [Novidade]
  • Possibilidade de cartões vermelhos. [Novidade]
  • Mercado de transferências renovado, com várias ofertas de compra e de venda semanais. Só é possível vender-se um jogador se houver interessados. [o jogo original permitia vender qualquer jogador a qualquer altura, e oferecia um jogador aleatoriamente para compra por jornada]
  • O mercado de transferências é a única parte por implementar, mas é essencial ao jogo.

Os sacrifícios de RAM levaram a abdicar de certos conceitos, mas irei ainda tentar manter na versão 48K o seguinte:

  • Audio (Está música poderá ser mantida em RAM no spectrum 128K para reprodução em caso de vencer o campeonato, mas no 48K tenho de a reproduzir antes de executar o corpo principal do programa, removendo-a da RAM quando for para o executar)
  • Imagem de vitória de campeonato – Apenas versão 128K [a versão 48K terá ou um mero texto de vitória, ou uma animação, mas está última está difícil de ser realizável dada a falta de RAM]

Curiosidade

Eu já não me lembrava disto, até porque na altura não tinha conhecimentos para me aperceber dessa situação. Como alguns saberão, o Spectrum não tem verdadeiramente uma placa gráfica e apenas pode manda caracteres (definidos pelo utilizador), pedaços de ecrã previamente guardados na RAM, e linhas para o ecrã, nada mais. Ora isto é extremamente limitativo, pois escrever uma nova coisa sobre outra, apaga a anterior. E é aqui que reside a curiosidade, pois o Spectrum possui o comando OVER, que ativa uma funcionalidade XOR no que escreve no ecrã.

O que isso quer dizer é que se eu escrever algo, e mandar escrever por cima com o OVER ativado, o novo caráter sobrepõem-se ao outro, criando um símbolo composto. Isto é útil, claro, mas o relevante é como o XOR funciona. Basicamente ele verifica o binário do que está no ecrã, e do que se escrever, e faz a operação de soma. Assim, um pixel apagado (0), se sobreposto por um acesso (1) passará a aceso (1) (acontece igualmente no caso contrário, em que é o pixel acesso que é sobreposto por um apagado). Se ambos os pixels, do existente e do que se escreve, estiverem acessos, ele apaga.

Está situação uso-a extensivamente nas animações de jogo, pois escrever um caráter sobre outro, não apaga o anterior, e reescrevendo o mesmo caráter novamente, retorna o que estava inicialmente no ecrã, sem alterações. É extremamente engraçado e interessante, permitindo sobreposições simples e animações que não destroem o que estava já escrito.

 



Subscreva
Notify of
guest
0 Comentários
Antigos
Recentes
Inline Feedbacks
Ver todos os comentários
error: Conteúdo protegido
0
Queríamos ouvir a tua opinião. Comenta!x