1 LET s$="PEACE":INK7:PAPER7:CLS:RESTORE1:DATA 27,9,47,39,55,82,18
2 LET m=22528:FOR i=1 TO 7:READ a:FOR j=1 TO 96:POKE m,a:LET m=m+1:NEXT j:NEXT i
3 PRINT INK3;PAPER3;AT 0,0;s$:FOR j=0 TO 7:FOR i=0 TO LEN s$*8-1:IF POINT(i,(175-j)) THEN GO SUB 9
4 NEXT i:NEXT j:PAUSE 0:STOP
9 FOR k=0 TO 5:FOR l=0 TO 6:PLOT 8+i*6+k, 119-j*7-l:NEXT l:NEXT k:RETURN
Spiegazione del programma
L’istruzione DATA alla linea 1 contiene i valori degli attributi (colore di “inchiostro” e “carta” e flag di luminosità) corrispondenti alle 7 strisce colorate. Il ciclo FOR della linea 2 utilizza questi valori per disegnare lo sfondo della bandiera, scrivendoli direttamente nellla meoria degli attributi video, a partire dall’indirizzo 22528 (5800hex). Nella linea 3, la stringa “PEACE”, contenuta nella variabile s$, è stampata in modo invisibile (utilizzando inchiostro magenta su carta magenta) nell’angolo in alto a sinistra dello schermo. Mediante l’istruzione POINT, vengono identificati i singoli pixel che costituiscono la scritta e per ciascuno di essi viene richiamata la subroutine alla linea 9, che ne effettua lo zoom e la stampa al centro dello schermo.
Fishie – Keep the sea plastic free! è un piccolo videogioco con un grande messaggio ecologista. Aiuta Fishie a mantenere il mare pulito dai rifiuti plastici trasportati dalle onde, catturando le bolle di ossigeno che emergono dall’acqua, prima che esplodano. Ogni volta che Fishie riesce ad acchiappare una bolla, la spazzatura che si trova sulla stessa linea scomparirà e il punteggio sarà incrementato. Il contatto con i rifiuti è dannoso e ti farà perdere una vita.
Puoi sia scaricareFishie – Keep the sea plastic free! (il download include il codice sorgente del programma e la documentazione dettagliata), sia giocare online sul tuo web browser (i dispositivi mobili non sono attualmentesupportati), semplicemente accedendo alla pagina del progetto.
Si sono da poco concluse le votazioni per gli ZX Online Awards 2022, evento a cui qualsiasi gioco per ZX Spectrum rilasciato nel 2021 poteva partecipare.
Avevo candidato le mie realizzazioni del 2021 senza troppe pretese, trattandosi di piccoli progetti realizzati per i vari contest a cui ho partecipato. Proprio per questo motivo sono stao piacevolmente sorpreso nell’apprendere che Project: RE.VE.LA.TION, il mio videogioco in stile Lunar Lander, si è aggiudicato il premio speciale Best Well-polished Classics!
Consiglio di provare i vincitori di tute le categorie, in quanto lo scorso anno è stato ricco di uscite interessanti e di eccelsa qualità!
Bastilude (BASIC Hastilude) è un videogioco d’azione, programmato in sole 10 righe in linguaggio BASIC per il computer Sinclair ZX Spectrum. Il gioco trae ispirazione del classico Joust, sviluppato da Williams e rilasciato nel 1982.
Bastilude: schermata del titolo.
Il giocatore controlla, con un po’ d’immaginazione, un cavaliere che cavalca un drago verde. Lo scopo del gioco è disarcionare i cavalieri avversari, che montano draghi color magenta, in una sorta di giostra fantastica. Il vincitore del duello è il cavaliere che si trova in posizione più elevata al momento dell’impatto. La velocità dei cavalieri avversari aumenta man mano che questi sono sconfitti, determinando un incremento della difficoltà.
SierpChaos (versione CHIP-8) in esecuzione su CHIP-OTTO per Z88 (2017)
Avendo già dimestichezza con l’argomento, che reputo molto interessante e stimolato dall’adesione di persone di rilievo nel mondo della retroprogrammazione, ho deciso di partecipare, ponendomi anche l’obiettivo di imparare qualcosa di nuovo. Mi sono focalizzato quindi sull’implementazione in linguaggio C per ZX Spectrum con z88dk.
Per questo motivo, ho praticamente riscritto il programma, avvalendomi della nuova libreria C (newlib) e affidandomi al compilatore sdcc. La nuova libreria non contiene primitive grafiche ad alto livello, per cui per disegnare i punti del frattale ho fatto ricorso alle funzioni per manipolare gli indirizzi della memoria video, definendo la seguente macro:
Ho ottenuto un’ulteriore riduzione dei tempi di esecuzione modificando la generazione dei di numeri pseudocasuali utilizzati per selezionare il vertice ad ogni iterazione, rimuovendo la chiamata alla funzione rand() della libreria standard e utilizzando i valori dei byte della ROM dello ZX Spectrum come sequenza “pseudocasuale”. Con queste modifiche ed altri piccoli accorgimenti (tra cui lo shift a destra di 1 bit per implementare la divisione per 2 e l’utilizzo di 2 cicli annidati con variabili di controllo a 8 bit anziché di un unico ciclo con variabile di controllo a 16 bit), sono riuscito ad abbassare i tempi di esecuzione, su uno ZX Spectrum +2, emulato con Fuse, a:
1.66 secondi per 3000 iterazioni e
5.42 secondi per 10000 iterazioni.
Tornando alla macro plot definita prima, è interessante notare che la memoria video dello ZX Spectrum non è gestita in modo lineare. Questo significa che, per poter disegnare ciascun punto del frattale, è necessario svolgere alcuni calcoli per determinare la locazione di memoria il cui contenuto deve essere modificato per “accendere” il pixel corrispondente.Una tecnica utilizzata per ridurre queste operazioni è quella di precalcolare alcuni valori e memorizzarli in un array, in modo da poterli recuperare istantaneamente. Nella fattiscpecie, ho modificato il programma per precalcolare gli indirizzi delle locazioni di memoria corrispondenti al primo byte di ciascuna delle 192 linee dello schermo:
In questo modo, per “accendere” un pixel, è sufficiente reperire l’indirizzo precalcolato della linea, determinare e sommare l’offset del byte da modificare in base all’ascissa e settare il bit corrispondente al punto da disegnare:
Con questa modifica, il tempo di esecuzione per 10000 iterazioni è sceso a 5.04 secondi.
Nonostante la riduzione ottenuta, il tempo di esecuzione risultava ancora troppo elevato. Facendo alcune prove, ho appurato che la lentezza era dovuta all’operazione di modulo 3, utilizzata in ciascuna iterazione per ricondurre il numero casuale nell’intervallo degli indici dei 3 possibili vertici, compreso tra 0 e 2. Ho quindi sostituito il modulo con l’operazione di AND bit a bit e aggiunto un po’ di logica per selezionare indici validi nel caso in cui il risultato fosse 3. Con l’eliminazione del modulo, sono riuscito ad abbassare il tempo di esecuzione a 1.84 secondi per 10000 iterazioni. La qualità dell’immagine generata è leggermente peggiorata, a causa della logica aggiunta, tutt’altro che casuale.
Senza la pretesa di fare meglio degli autori di z88dk, ma per il puro gusto della programmazione, ho sostituito la chiamata alla funzione zx_py2saddr() con la mia implementazione, constatando con piacere di non aver incrementato il tempo di esecuzione:
#pragma output CRT_ORG_CODE = 0x8184 // sposto ORG per lasciare spazio per la tabella IM2
#pragma printf "%f" // utilizzo printf solo per stampare il tempo di esecuzione (float)
#include <arch/zx.h>
#include <cpu.h>
#include <im2.h>
#include <intrinsic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Contatore frame (utilizzato per tenere traccia del tempo di esecuzione).
uint8_t frames = 0;
// IM2 service routine. Incrementa il contatore ad ogni frame.
IM2_DEFINE_ISR_8080(isr)
{
++frames;
}
// Utilizzato per "accendere" i pixel nella memoria video: il pixel 0 è quello più a sinistra.
uint8_t m[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
// Ascisse dei vertici del triangolo
uint8_t a[] = { 0, 127, 255 };
// Ordinate dei vertici del triangolo
uint8_t b[] = { 191, 0, 191 };
// Coordinate del punto da disegnare
uint8_t x,y;
// Puntatore ai dati della ROM (utilizzati come sequenza "pseudocasuale")
uint8_t *p = 0;
// Valore pseudocasuale nell'intervallo 0..2, utilizzato come indice per selezionare il vertice del triangolo ad ogni iterazione.
uint8_t k;
// Variabile d'appoggio per la generazione dei numeri "pseudocasuali"
uint8_t h = 0;
// Indici per i loop.
uint8_t i;
uint8_t j;
// Puntatori al primo byte della memoria video per ciscuna delle 192 linee dello schermo.
uint8_t *screen_line_address[192];
// Tempo di esecuzione.
float es;
void main()
{
// Inizializza interrupt IM2.
im2_init((void*)0x8000);
memset((void*)0x8000, 0x81, 257);
cpu_bpoke(0x8181, 0xc3);
cpu_wpoke(0x8182, (unsigned int)isr);
// Abilita interrupt.
intrinsic_ei();
// Precalcola puntatori alla memoria video per ciscuna linea dello schermo.
for (y=0; y<192; y++)
{
screen_line_address[y] = (uint8_t *) ((((y & 0x07) | ((y & 0xC0) >> 3) | 0x40) << 8) | ((y & 0x38) << 2));
}
x = a[0];
y = b[0];
zx_cls(PAPER_WHITE);
// 10000 iterazioni
for (i=0; i<100; i++)
{
for (j=0; j<100; j++)
{
// k = numero "casuale" compreso tra 0 e 2, utilizzato come indice per selezionare il vertice del triangolo.
k = (*p++ & 0x03);
if (k == 3)
{
k = h;
if (++h == 3)
h = 0;
}
// Calcola coordinate del punto da disegnare.
x=(x+a[k])>>1;
y=(y+b[k])>>1;
// Disegna punto (x, y).
*(screen_line_address[y] + (x >> 3)) |= m[x & 0x07];
}
}
// Calcola e stampa tempo trascorso.
es = (float)frames / 50;
printf("%f", es);
// Fin.
for(;;);
}
Sicuramente c’è ancora molto margine di miglioramento, ma per ora posso ritenermi soddisfatto del risultato raggiunto.
Concludo con un ringraziamnento allo staff di RetroProgramming Italia per l’opportunità di partecipare a questa interessantissima iniziativa!