Tribulações de programador – Parte 2

A Saga de programar um jogo no ZX Spectrum continua.

Como sabem, resolvi reeditar o antigo jogo Treinador de Futebol, perpetuado no World of Spectrum e editado pela Softimar, abreviatura de software Tiago e Mário (o meu primo e eu), em 1989. Jogo. Aliás que teve direito a referência recente no livro jovens programadores Portugueses.

A minha ambição levou-me a melhorias enormes face ao jogo original, mas rapidamente, e apesar de toda uma optimização de código que não existiu no jogo original, atingi os limites da máquina. E ao programar num Spectrum 128K pela facilidade de digitação (a digitação é feita letra a letra como nas máquinas modernas, e não com combinações de teclas como no 48K), sem me aperceber rapidamente alcancei os 64 KB de RAM, eliminando assim a possibilidade de suporte ao modelo 48K.

Como referi na primeira parte deste artigo, não é fácil nos dias de hoje programar com tão pouca RAM, tendo de se ter o cuidado com cada byte usado. Mas o certo é que acredito que em 1989 não nos apercebemos do quão perto o treinador de futebol ficou no limite de esgotar os recursos do 48Kb.

A realidade foi que, perante as melhorias, precisei de 64Kb, mas infelizmente atingi o limite da RAM, antes de acabar o jogo.



Dado que o que falta é muito pouco decidi explorar formas de obter mais memória. A primeira foi otimizar o código ao extremo, o que me deu a possibilidade de acabar o mercado de transferências que estava em falta. Mas os finais de época, as descidas e subidas de divisão, e o ecrã de vitória do campeonato, ficaram de fora. Precisava de mais RAM.

Ora dado que o Spectrum 48k estava excluído, porque não usar toda a RAM do 128K?

Infelizmente a coisa não é assim tão simples. O Z80 apenas endereça no máximo 64KB, pelo que aceder a mais RAM é problemático e requer paginação de memória. Uma situação complexa e avançada que é um problema de ser feita com o Basic.

Mas mesmo assim, tentei. E consegui! Mas infelizmente o método não servia. Apesar de só ter copiado para a RAM estendida um bloco de 7 KB recuperar o mesmo para a RAM vídeo era um processo de mais de 1 minuto.

Basicamente tinha de aceder à RAM adicional, ler todos os bytes um a um, e copiar de volta. Um ciclo For Next que no Basic é lento. Super lento.

Apesar de tudo consegui aumentar a capacidade de leitura para 256 bytes de cada vez, reduzindo o tempo de leitura de forma significativa, mas mesmo assim… demasiadamente lento.



A solução que me restava era só uma. Assembler.

Ora o Assembler, vocês já ouviram falar dele. É o código máquina, a linguagem de mais baixo nível que existe. E também a mais rápida que existe. Uma linguagem sobre a qual os meus conhecimentos são muito parcos.

Mas entre parcos e zero ainda vai uma grande diferença, e eu sabia que neste caso apenas precisava de dominar uma pequena parte do assembler: A leitura e cópia de dados. E nesse sentido, dediquei-me a estudar o mesmo, e após alguns dias tinha conseguido copiar uma imagem de uma RAM para a outra. O tempo? Bem, varia dependendo do banco, pois alguns são mais lentos que outros, mas mesmo assim, o caso pior, é uma ridicularia: 41 ms.

O problema com a RAM parecia ultrapassado.

Mas infelizmente não, pois eu precisava de meter na memória extra mais do que um bloco. A ideia era meter os dados para um menu, a criação do estádio onde vemos o jogo, e o ecra de celebração de vitória no campeonato. Um total de 28 KB que não seriam um problema para os restantes 64Kb. Ou seriam?



O certo é que o que funcionava com uma imagem… Não funcionava com as restantes. E descobrir os motivos, foi um berbicacho. Até porque estava perante vários problemas e não apenas um, o que demorou a perceber.

O que eu descobri foi que a paginação de memória não me troca dos 64KB originais para os outros. Dado que o Spectrum está no limite, uma mudança dessas implicava perder os valores todos das variáveis, e mesmo parte do código presente na RAM.

O que o Spectrum permite, para resolver isso, é abrir uma ranhurazinha na “parede da sala” de 64KB, para se ver a “sala do lado”, e passar dados por ela.

Mas ranhurinha ou não, com o uso de assembler, o problema estava resolvido pois a transferência era rápida, e 41ms era aceitável. Agora porque motivo não conseguia meter mais do que um bloco de dados?

O problema é que os restantes 64KB não existem. Se nos primeiros 64KB olhamos para eles como um todo pois tudo é invisível ao utilizador, os restantes 64 Kb são na realidade 4x16KB, pelo que não basta abrir a ranhura na parede. É preciso definir onde a fazemos para que se veja o compartimento que se pretende aceder.



Ou seja, a paginação é mais complexa ainda do que inicialmente parecia pois não se acede a mais 64 KB, mas sim a 4x 16 Kb.

Mas essa parte eu também acabei por conseguir superar, e consegui definir qual dos bancos de ram de 16 KB quero ver.

Mas o Spectrum com 128KB tem 8 bancos de 16 KB. A dúvida agora era saber quais são os que já estão usados e os que estão livres.

Poderiam ser de se esperar que a coisa fosse linear, mas não é. E como linear era que o Basic usasse os 4 primeiros e os restantes 4 estivessem livres (gênero, banco 0, 1, 2 e 3 usados, e 4, 5, 6 e 7 livres). Mas não! E descobrir em quais eu podia escrever sem causar danos ao meu programa foi um problema. Mas depois de descobrir, passei a usar dois bancos adicionais, o 1 e o 6. Dado que cada um tem 16 KB, e cada bloco de dados ocupa perto de 7KB, cada um pode conter dois blocos, e precisando de 4 deles, esta memória chegaria.

Mas depois tive outro problema. Dado que os bancos não são sequenciais, quais os endereços de memória de cada um?



E esta parte ainda hoje me mata. Porque há duas formas de lhes aceder,  sendo que basta um pequeno erro de código, e ambas misturam-se, com resultados desagradáveis.

A primeira forma é usando os endereços reais. Basicamente os primeiros 64KB usam os endereços do zero até ao 65535, e os restantes bancos vão usar do 65536 até ao 131070. A questão aqui é que banco contém que parte desses endereços?

Ora isso é possível saber-se, mas para usar endereços reais eu preciso que o Z80 lhes aceda, e como já referi, isso obriga a largar parte da RAM base, com consequências desagradáveis para a estabilidade do programa (na maior parte dos testes, parte do meu código que estava na RAM do Basic, ficou corrompido ou desapareceu). A alternativa era usar endereços lógicos.

O que isso quer dizer? Basicamente tratar cada um dos bancos como se fosse único, com endereços iguais em todos. E isso trabalha bem melhor com a janelinha de abertura pois não causa problemas com a ram base.

Ora os endereços lógicos funcionam muito bem, mas tem um inconveniente. Algo que me demorou duas semanas a perceber. A questão é que quando eu acedo a um banco de memória, mesmo que mude para outro, o anterior mantem-se acessível, algo que eu não sabia, e que as IAs que eu consultava para auxilio me garantiam que não acontecia. Ora o que acontecia é que eu carregava dados para o banco 1, por exemplo nos endereços 40000 e 46912. Tudo corria bem, e elas estavam lá. Mas quando copiava para o banco 6, para os mesmos endereços, a janelinha para o banco 1 mantinha-se aberta, e o comando ia também para o banco 1, copiando os dados para ambos, e escrevendo por cima do que já existia no banco 1. O resultado é que só obtinha metade dos dados, e eu não estava a compreender porque. A resposta é que o que estava nos dois bancos era igual pois eu estava a escrever por cima.



Infelizmente, dado que o que pretendo passar para cada banco são dois blocos de dados que ocupam 7 KB cada um (total de 14 KB), e os bancos são de 16 KB cada um, eu enchia basicamente os bancos, pelo que não tinha possibilidade de usar endereços que não se sobrepusessem. E ao manterem-se os dois bancos ativos, a copia para o segundo banco, escreve por cima do primeiro.

Perceber a causa do problema foi um berbicacho. As IAs são uma vergonha. O Deepseek não percebe a ponta de um chavelho de ZX Spectrum, e insiste comigo que o meu código está todo mal e que não funciona. Para lhe explicar as coisas perco horas, sendo que ele acaba por reconhecer que estou correto, mas passado um bocado, volta à carga insistindo que está tudo mal.

O Gemini é sem dúvida o que mais entende de Spectrum, mas é impressionante como ele está tão atrás dos outros, cometendo sucessivamente erros básicos.

O ChatGPT é o mais avançado, mas insiste nos erros e também os comete, não tendo um conhecimento da arquitetura do Spectrum ao nível do Gemini.

O que me tem valido são duas janelas, uma com o Chat, outra com o Gemini, onde os confronto com as afirmações um do outro, até chegarem a um consenso. Mas mesmo assim há muita ignorâncias nestas IAs que falam como se tivessem certeza do que dizem.



Como auxiliares para código são os dois miseráveis, mas para me explicarem a arquitetura do Spectrum, tem sido uma ajuda preciosa, apesar de os ter de confrontar regularmente com as incongruências. Mas mesmo elas não foram capazes de me dizer o que estava a acontecer. E apenas quando eu formulei de forma insistente a hipótese de estar a escrever em simultâneo para os dois bancos é que elas reconheceram a situação. Apesar de andarem à duas semanas a negaram que isso pudesse estar a acontecer.

A realidade é que, devido a isto, até ao momento ainda não consegui um código funcional que me recupere 4 blocos de dados. Recorri a fóruns de programação do Spectrum, mas em vez de ajuda tudo o que me disseram foi que me meti num caminho que poucos conseguiram trilhar com sucesso.

E infelizmente, devido a isso decidi que vou ter de dar uma pausa ao jogo. Afastar a cabeça por uns dias a ver se vem ideias frescas. Porque estou à beira de conseguir o que pretendo, mas devo estar a cair em algum erro básico que me está a entupir e entretanto estou perto de um esgotamento.

Acréscimo de ultima hora.

Basicamente o que aconteceu foi que desisti do jogo. Estava de tal forma cansado que resolvi mandar tudo às urtigas.

O facto é que perante isso, sem stresses resolvi meter-se a ler sobre a arquitetura interna do Zx Spectrum. Basicamente deixar de acreditar em IAs e ir eu mesmo à busca da informação.



O resultado foi que ao fim de 10 minutos de leitura, estava entusiasmado com o que lia e voltei a programar. E à primeira tentativa com um novo método, estava com sucesso. Acedi a três bancos, guardei 6 blocos de código, e retornei os mesmos com sucesso.

Por outras palavras, o jogo não está morto, e o problema resolvido tendo agora acesso a toda a RAM onde leio e escrevo à vontade. Irei agora, com calma, acabar o que falta, e resolver um ou outro problema que me parece menor que ainda existe, acreditando que dentro em breve o jogo estará publicado.  Dado que agora acedo à totalidade dos 128 KB, talvez tire partido disso e melhore o grafismo ainda mais um bocadinho.

Só lamento ter matado a cabeça por duas semanas à procura de uma solução recomendada pelas IAs, que se revelou um beco sem saída. Tudo devido à Estupidez artificial das mesmas.

IMAGENS DO JOGO



21 Comentários
Antigos
Recentes
Inline Feedbacks
Ver todos os comentários
Felipe Leite
Felipe Leite
12 de Fevereiro de 2025 9:57

Que aventura!!

Está a ser muito divertido e interessante de acompanhar, Mário!

Felipe Leite
Felipe Leite
Responder a  Mário Armão Ferreira
12 de Fevereiro de 2025 12:07

Pouco importa se é um grande jogo ou não, o projeto e o caminho é que estão a ser muito interessantes!
E acredito que muito satisfatório para si!

Tiohildo
Tiohildo
12 de Fevereiro de 2025 21:25

Meus parabéns. Realmente você entrou fundo na programação do spectrum.
Ficou muito bem feito o jogo.

Juca
Juca
12 de Fevereiro de 2025 22:19

Bacana demais, Mário! Parabéns!

Juca
Juca
Responder a  Mário Armão Ferreira
13 de Fevereiro de 2025 14:18

É impressionante o que vc conseguiu usando bem menos do que um equivalente de uma jpeg de 200x200plxs de memória! Ótimo resultado e o jogo me remete a um grande saudosismo de uma época em que eu ainda gostava de jogar games de futebol, como no atari.

Last edited 1 mês atrás by Juca
Fernando Cardoso
Fernando Cardoso
12 de Fevereiro de 2025 22:51

Inspirador! Os programadores de nova geração ainda teriam muito a aprender contigo em matéria de optimização de código! O velhinho speecy assim o obrigava! Conhecendo as limitações da máquina e os jogos que eram feitos, sem dúvida que os programadores de spectrum são verdadeiros magos!
Parabéns pelo projeto!

Rui Teixeira
Rui Teixeira
13 de Fevereiro de 2025 16:53

Parabéns! Alegro-me que tenhas conseguido superar todos os obstáculos, e que não tenhas desistido de terminar o jogo.
Tem sido muito interessante seguir esta história!
Se não for muito incomodo, seria possível saber qual a documentação que usaste?
Tenho andado a ver alguns tutoriais de programação em assembler para o spectrum, mas às vezes é um pouco difícil entender algumas coisas. Obrigado.

Rui Teixeira
Rui Teixeira
Responder a  Mário Armão Ferreira
13 de Fevereiro de 2025 23:05

Muito obrigado pela extensa explicação 🙂
Achei curioso que tenhas de colocar os POKEs para todas as linhas de assembler, teria suposto que o código seria executado em sequência até encontrar o RET, mas pelos vistos não é assim, muito interessante.
Também não sabia que o spectrum tinha interpretador de assembler. Eu tenho usado o compilador sjasmplus que cria um ficheiro SNA (creio que também suporta outros formatos como o TAP), tem sido bastante fácil de usar, só ainda não sei se haverá alguma forma de fazer debug ou algo.
Mais uma vez, muito obrigado pela explicação!

Rui Teixeira
Rui Teixeira
Responder a  Mário Armão Ferreira
15 de Fevereiro de 2025 16:59

8 páginas! Para entender o código deve ter sido uma aventura por si só 🙂
Muito interessante ver todo o processo que fizeste para poupar uns bytes aqui e ali. Um teste que fiz para comparar a velocidade a preencher o ecrã com pixels, a versão do basic era mesmo penosa hehe.
Mais uma vez, obrigado pela explicação. Imagino que depois desta odisseia, não vais voltar a fazer mais nenhum jogo no spectrum hehe.

Bruno
Bruno
13 de Fevereiro de 2025 17:30

Boas . Li e gostei é possível jogar esse jogo ? Fiquei curioso .

error: Conteúdo protegido