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 momento, 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 ainda está 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. Isto se for possível.)
- 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 esta ú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 mandar para o écran caracteres (definidos pelo utilizador), pedaços de ecrã previamente guardados na RAM, e linhas. 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 aceso (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.
Realmente é muito pouca memória para suas tantas ambições de melhoria Mário.
Entendi os diversos problemas que enfrenta e enfrentou e torço pra que engenhosamente os resolva como já fez com muitos. Vamos ver se algum outro colega que entenda de Basic possa sugerir algo pra encurtar as suas linhas de programação e fazê-las não estourarem a RAM.
Infelizmente tive de eliminar os DEF FN e FN todos. Apesar de com isso poupar 0,5 KB, a realidade é que o impacto na performance era brutal.
Muito interessante! Parabéns pela dedicação!
Obrigado Mário por contares detalhadamente a tua aventura com o spectrum!
Dá uma vista de olhos a este compilador de basic para spectrum https://www.boriel.com/pages/the-zx-basic-compiler.html
Parece que há diferenças entre o basic original do spectrum e este, mas talvez consigas adaptar o código. Também deveria de resolver o problema da performance, talvez até ganhos de memória!? Não sei, nunca usei isto!
Também estive a pensar uma forma de poupar um pouco de ram na parte da moral da equipa, mas depois de uma pesquisa não vi forma de implementar esta ideia em basic.
Basicamente a ideia seria esta (que nem sei se realmente funcionaria ou não):
Como a moral muda depois de 3 vitórias/derrotas, deverias de poder representar isso em apenas 2 bits, mais um bit extra para representar se é positivo ou negativo. Isto permitiria guardar em 1 byte as vitórias/derrotas de 4 equipas diferentes, mais 1 byte extra para guardar se está em vitórias ou derrotas para 8 equipas.
Gostaria de te poder dar algum código em como implementar isto, mas os meus conhecimentos são muito limitados, nem sei se isto é possível de implementar ou se traria verdadeiros ganhos. Seja como for, fica a ideia.
Continuação de bom trabalho 😉
Obrigada pelo teu feedback Rui.
O Boriel já o usei e deu-me milhares de erros. A sua sintaxe é diferente. Quem sabe o possa usar, mas depois de acabado o jogo, perdendo um pouco de tempo nele.
Quanto ao moral, acabei com ele. O Boost que dava não podia ser significativo e perdia quer para o fator casa, quer para o algoritmo que uso que dá sempre algumas hipóteses às equipas mais fracas de fazerem resultados surpresa.
Ontem estive a brincar um pouco com esse basic e realmente há muitas diferenças, também estive a ver se conseguia implementar a ideia que comentei ontem. Deixo aqui o código caso te possa ser de utilidade, mesmo que já não uses a moral.
https://pastecode.io/s/49ngymgb
Atualização… O corpo do jogo está terminado. Desencantei mais uns bytes e acabei o jogo.
O que me falta? Fazer finais, e subidas e descidas de divisão.
As descidas de divisão poderei ter de não as fazer. Mas os finais, precisava de 2, sendo que o ser campeões merecia algo mais do que mero texto. Mas vai ser um problema fazer algo, pelo menos no 48K.
Colocar tanta coisa em apenas 48K deve dar um trabalhão, uma pena que não consigas incluir a subida/descida de divisão.
PS: no código que coloquei no outro dia, há um bug no IF que está na função inc_2bit. Tinha-me apercebido dele na altura, mas acabei por me esquecer de o corrigir.
Se te lembras de jogos como o Football manager, o input era feito introduzindo números ou letras.
No meu jogo não quis isso. Uso seletores que se movem entre as diversas escolhas. Isso gasta muita RAM, mas não abdico disso.
Impressionante as coisas a que tens de ter atenção para poupar uns bytes aqui ou ali, até o método de seleção! Muito interessante, possivelmente frustrante, mas interessante.
Mais novidades.
Pensei que o ZX Spectrum 48K e o 128 K tinham a mesma memória RAM disponivel para o basic, mas isso não é verdade, e o 128K tem mais. O que isso implica é que o meu jogo não vai correr no 48 KB, pois já passei o seu limite.
Mesmo assim estou com problemas de memória e estou a tentar estudar o uso de memória adicional, mas isto está complicado de se perceber.
Entretanto estou a criar e a executar um pequeno programa que me vai verificar a memória byte a byte para saber o que está ocupado e o que não está, de forma a perceber que memória tenho efetivamente livre.
Se o 48K te estava a dar tantas dores de cabeça e a limitar demasiado a ideia que tinhas para o jogo, acho que mudar para o 128K foi a melhor opção.
Espero que consigas entender o uso da memória. Boa sorte e continuação de bom trabalho!
Estou a falar com alguns programadores de Spectrum, e o consenso é que a paginação de memória do Spectrum é das maiores dores de cabeça que existem, tornando a máquina super instável. Eles dizem que funciona muito bem em assembler, mas que é muito problemática em Basic.
Seja como for, eu estou com um problema, pois segundo eles, seja em que linguagem for, a paginação só funciona bem se o código base não passar dos 48 KB, e eu experimentei um código de teste, e as rotinas que uso, que me crasham o Spectrum, realmente funcionam em pleno se não passar os 48K.
O problema é que o código está atualmente a gastar quase 64 KB que é o limite de endereçamento do Z80. E dessa forma a paginação é super instável.
Uma possível solução que me deram foi o uso da RAM disk do Spectrum 128K+2 e superiores. Basicamente é uma metodologia que só é suportada nessas máquinas onde o mapeamento dos bancos é automático, permite acessos e trocas de dados sem ter de criar rotinas de mudanças de bancos. Mas mesmo aí estou com dois problemas.
O primeiro é que tudo o que entre para os restantes 64 KB, tem de passar primeiro pelos 64 KB base, e eu estou com essa memória cheia, sendo que mesmo sem executar jogo e atribuir variáveis só o código usa perto de 60 KB. Ou seja, não consigo passar grandes blocos, e nem sequer gráficos que são blocos de 6 KB.
A questão é que se eu passar gráficos, eu posso revolucionar o jogo. Posso apagar imenso código e substituir por gráficos. Gastaria mais RAM, mas como ficaria com acesso a 64KB adicionais, compensaria, e permita um aspeto visual bem superior que não tenho neste momento, daí que estou disposto a apagar código e a substituir por grafismo, tendo apagado 6KB de código para conseguir carregar imagens na RAM superior. Ou seja, este é um problema, mas superável.
Mas existe uma segunda questão. Que talvez se supere… Mas que a ser superado não estou a conseguir fazê-lo com performance aceitável. A questão é que a ramdrive, realmente automátiza as passagens, e nem sequer tenho de me preocupar com endereços de memória pois é tudo automático. Mas a gravação para lá, e a leitura para cá, por defeito… É um save ou um load para a RAM. Ora o problema disto é que, apesar de um pouco mais rápido, isto é igual a ler da cassete, com os habituais “risquinhos” laterais no ecrã. E isso é algo indesejável. É lento e não instantâneo como julgava, e obriga a loads constantes. Pior ainda são os saves que me colocam o texto “Please insert a tape and press any key” antes de gravar. Agora percebo o motivo pelo qual o art studio na versão 128k faz gravações que não existem. Ele está a usar a ramdrive para gravar dados na RAM superior.
Eu penso no entanto que poderá existir uma alternativa. Dizem-me que poderá funcionar, mas ninguem me garante. É a leitura byte a byte da RAM superior e a cópia direta para a memória video. Mas pessoalmente tenho sérias dúvidas sobre se funciona… E se funcionar, qual a performance.
Se funciona porque para fazer a leitura tenho de aceder aos endereços de memória. Ora sei ZIP não as mapeia, como é que as leio? Mas acreditam que possa ser possível, definindo um parametro adicional nos pokes e peeks. Só testando!
Mas o maior problema é que ler 6146 bytes (creio que é esta a dimensão da RAM video do spectrum), byte a byte, num ciclo for Next parece ser uma tarefa desesperante para um CPU a 3,5 MHz. A maior parte das pessoas não tem essa noção pois nos dias de hoje trabalha-se com Ghz. É apenas 1000 vezes mais rápido em velocidade pura e não falando que a ram apenas responde a 2.5 Mhz.
No meu jogo tenho umas leituras de comandos DATA com 175 ciclos que já soam a uma eternidade, agora imagino 6146, e repetidos varias vezes pelo código.
Preciso me informar mais, mas neste momento não consigo meter musica e nem um ecrã de fim, pelo que para eles existirem teria de criar blocos de load separados, algo que eu não queria.
Lembrei-me agora de uma possibilidade. Vou tentar simular em Basic o código assembler de acesso à ram que evita a sequência de load e save.
Yippee-Ki-Yay Motherfucker… Creio ter conseguido.
Acima de tudo vou dar uma pausa ao jogo, pois ando super arrasado com isto, mas a simulação do ASSEMBLER em basic funcionou. E consigo carregar imagens para a RAMDRIVE e Retornar a mesma em 27,1ms. Nada mau!
A questão é que para meter no bloco 2, tenho de carregar no bloco 1. E o bloco 1 está cheio com o meu programa. Logo quando testo isto, a imagem passa, mas quando executo o meu programa… ele foi-se! Porque escrevi por cima dele! 🙁
Mas eu acho que tenho solução para isso. Faço um loader onde, com a memória ainda toda vazia, carrego o que preciso para os restantes 64 KB, e só depois é que carrego o meu codigo de jogo, fazendo nele apenas as chamadas para retorno da informação dos restantes 64 KB
Isto se funcionar vai revolucionar o aspeto do jogo. Irei usar mais grafismo do que esperava, especialmente nos menus onde posso apagar todo o código de desenho no ecrã.
A realidade é que ando esgotado, pelo que vou ter de dar uma pausa ao jogo, mas pelo menos pauso com a ideia que a solução existe.
Muito interessante! Obrigado por continuares a falar do processo e as peripécias que estás a ter com o spectrum.
Curiosamente ontem vi um vídeo onde comparava a velocidade do basic (do spectrum) com assembler em preencher o ecrã todo com um padrão. A diferença foi de cerca de 40 segundos no basic e praticamente instantâneo no assember. Hoje fiz um teste no boriel-basic/zxbasic e demora cerca de 1 ou 2 segundos, não sendo tão rápido como o assembler mas mesmo assim muito mais rápido que o basic do spectrum.
Espero que essa solução que encontraste funcione. Bom descanso!