Se qualcuno mi chiedesse qual è il computer che ha maggiormente segnato l'home computing in Italia, non esiterei a rispondere: il Commodore 64!
A metà degli anni ottanta, macchine come il Vic 20 ed il Commodore 64 erano le protagoniste indiscusse del panorama informatico italiano di quegli anni.
Anzi, per dirla tutta, il Commodore 64 non veniva considerato come UN computer ma come IL computer (con buona pace per tutti i fan Sinclair!), più vicino ad un oggetto di cult che ad una semplice macchina.
Inoltre, dato l'enorme successo riscosso, fiorivano riviste mitiche quali:
Superfloppy 64,
Commodore Computer Club,
Commodore Gazzette,
Bit,
MC Micro Computer,
Papersoft e tante altre.
Insomma la Commodore ha scritto la storia dell'informatica in Italia (e, forse, nel mondo), facendo appassionare all'informatica tantissime persone, contribuendo anche a realizzare il sogno di Bill Gates: un computer in ogni casa.
Oggi, ad oltre trent'anni di distanza, il ricordo di quel periodo è sempre vivo in chi, come il sottoscritto, lo ha vissuto.
Me ne rendo conto navigando su internet dove è possibile scaricare in modo gratuito tanti emulatori del Commodore 64 e moltissimo software ad esso dedicato.
Ancora oggi ci sono tantissimi appassionati che si dilettano a realizzare programmi per questo meraviglioso computer.
Un'ottima testimonianza è costituita dall'
intervento del bravo Giovanni Simotti che, al codemotion 2012, ha efficacemente illustrato come ancora oggi, ad oltre trent'anni dalla sua nascita, sia possibile programmare il Commodore 64 con strumenti moderni.
Così anch'io mi sono detto: perchè non riprovarci? Ed allora, di buona lena, ho deciso di mettermi all'opera ed ho deciso di scrivere un'implementazione dell'algoritmo per lo sviluppo di sistemi integrali del totocalcio, presentato nel primo topic di questo blog.
La prima cosa che ho fatto è stata quella di procurarmi un buon emulatore.
La scelta è caduta sull'ottimo
VICE che, oltre ad emulare diversi tipi di macchina di casa Commodore, dispone anche di un ottimo debbugger le cui modalità di utilizzo sono descritte abbastanza bene su questo
link.
La seconda è stata quella di procurarmi un buon IDE che semplificasse la scrittura del programma da eseguire con l'emulatore.
La scelta è caduta sull'ottimo
CBM .prg Studio di Arthur Jordison che permette di scrivere in modo ottimale programmi per vari tipi di computer di casa Commodore quali: VIC 20, Commodore 64, Commodore 128, Commodore 16, Plus/4 e Pet.
Una volta avuti a disposizione tali strumenti di sviluppo, ho provveduto a scrivere una prima stesura in basic dell'algoritmo che desideravo implementare.
Il codice che ho sviluppato è il seguente:
10 input"{clear}quante parite";n
20 if n<1 or n>16 then 10
30 dim bs(n): cs=1: pr$="": print: for i=1 to n
40 print"partita n.";i;"{left}";: input p$
50 cs=cs*len(p$): bs(i)=len(p$): pr$=pr$+p$
60 next: print: print"{down}colonne da sviluppare:"; cs
70 print: for i=1 to cs: gosub 80: print cl$: next: end
80 cl$="": pt=len(pr$)-bs(n): nc=i-1: for j=n to 1 step -1
90 cl$=mid$(pr$, nc-int(nc/bs(j))*bs(j)+pt+1, 1)+cl$
100 nc=int(nc/bs(j)):if j>1 then pt=pt-bs(j-1)
110 next: return
Tale programma, sebbene sia di dimensioni molto ridotte ed eseguibile praticamente su tutte le macchine di casa Commodore, ha il difetto di non essere facilmente "traducibile" in assembler.
Il motivo di tale difficoltà deriva dal fatto che non è semplice eseguire un conteggio per numeri maggiori di 255 su macchine a 8bit.
Per tale motivo, per ovviare a tale difficoltà, ho riscritto il programma in modo tale che il conteggio non sia più eseguito con un ciclo ma mediante operazioni più "semplici".
Per fare ciò, mi sono "ispirato" ad un contatore eseguito su una macchina di Turing. Chiunque voglia vedere come questo funzioni su una tale macchina, può visitare il
sito, oppure scaricare il simulatore dal
sito.
Il nuovo codice sviluppato, di cui lascio ai volenterosi il compito di capirne i dettagli implementativi, è il seguente:
10 input"{clear}quante parite";n
20 if n<1 or n>16 then 10
30 dim a(n), bs(n): cs=1: pr$="": print: for i=1 to n
40 print"partita n.";i;"{left}";: input p$
50 cs=cs*len(p$): bs(i)=len(p$): pr$=pr$+p$
60 next: print: print"{down}colonne da sviluppare:"; cs
70 ps=n: dr=0: print: for i=1 to n: a(i)=0: next
80 gosub 170
90 a(ps)=a(ps)+1
100 if a(ps)
110 dr=1
120 ps=ps-1: if ps>0 then a(ps)=a(ps)+1
130 if ps>0 and a(ps)=bs(ps) and dr=1 then 120
140 if a(1)=bs(1) then end
150 for i=ps+1 to n: a(i)=0: next
160 ps=n: dr=0: goto 100
170 pt=0: for i=1 to n: print mid$(pr$, a(i)+pt+1, 1);: pt=pt+bs(i): next
180 print: return
A questo punto, mi proposi di "tradurre" tale programma in assembler, ma desideravo che il codice che sarei andato a realizzare fosse multipiattaforma; ossia che potesse essere eseguito non solo su Commodore 64 ma anche su altre macchine quali: Vic 20, Commodore 128, Commodore 16 e Plus4.
Per fare ciò, ho fatto largo utilizzo dell'indirizzamento indiretto indicizzato basandolo sulle locazioni 251($FB) e 252($FC) di pagina zero. La scelta è caduta su di esse in quanto le locazioni da 251 a 254 in pagina zero sono libere su tutte le macchine cbm.
Inoltre, ho dovuto rinunciare all'utilizzo dell'istruzione JMP basando i salti all'interno del codice sulle istruzioni CLC e BCC.
Ho cercato di eseguire la "traduzione" in assembler, per quanto possibile, riga per riga e commentando il codice indicando anche l'inizio della corrispettiva riga in basic (relativa al codice precedente).
Il codice che ho scritto, che anche in questo caso lascio ai volenterosi il compito di capirne i dettagli implementativi, è il seguente:
; Tot16
*=$C000
start LDY #$0
LDA ($FB),Y
TAY
LDA #$0
loop1 STA ($FB),Y
DEY
BNE loop1
LDA ($FB),Y
TAY ; y=n
LDX #$0 ; x=0
cllprt JSR subprt ; Chiama la subroutine per stampare la colonna ** Riga 80 **
CLC
LDA ($FB),Y ; a(y)=a(y)+1
ADC #$1
STA ($FB),Y
loop2 PHA ; tmp=a(y)
TYA ; y=y+n
CLC
LDY #$0
ADC ($FB),Y
TAY
PLA ; a=tmp
CMP ($FB),Y ; if a(y)==b(y) || x==1 then chgdr ** Riga 100 **
BEQ chgdr
CPX #$1
BEQ chgdr
PHA ; tmp=a
TYA ; y=y-n
SEC
LDY #$0
SBC ($FB),Y
TAY
PLA ; a=tmp
CLC ; JMP cllprt
BCC cllprt
chgdr LDX #$1 ; x=1 ** Riga 110 **
PHA ; tmp=a
TYA ; y=y-n
SEC
LDY #$0
SBC ($FB),Y
TAY
PLA ; a=tmp
loop3 DEY ; y-- ** Riga 120 **
CPY #$0
BEQ chkend
CLC ; ** Riga 130 **
LDA ($FB),Y ; a=a(y)+1
ADC #$1
STA ($FB),Y
PHA ; tmp=a
TYA ; y=y+n
LDY #$0
ADC ($FB),Y
TAY
PLA ; a=tmp
CMP ($FB),Y ; if a!=b(y)
BNE chkend
CPX #$1
BNE chkend
PHA ; tmp=a
TYA ; y=y-n
LDY #$0
SEC
SBC ($FB),Y
TAY
PLA ; a=tmp
CLC ; JMP loop3
BCC loop3
jmplp2 CLC ; JMP loop2
BCC loop2
chkend TYA ; y=y-n ** Riga 140 **
LDY #$0
SEC
SBC ($FB),Y
TAY ; y=a
PHA ; tmp=a
LDY #$0
LDA ($FB),Y
TAY
INY
LDA ($FB),Y ; a=b[1]
LDY #$1
CMP ($FB),Y
BEQ end
PLA ; a=tmp
TAY
loop4 INY ; ** Riga 150 **
TYA ; a=y
PHA ; tmp=a
LDA #$0
STA ($FB),Y
PLA ; a=tmp
LDY #$0
CMP ($FB),Y
BEQ endres
TAY ; y=a
CLC ; JMP loop4
BCC loop4
endres LDX #$0 ; ** Riga 160 **
TXA
TAY
LDA ($FB),Y
TAY
LDA ($FB),Y ; a=a[n]
CLC ; JMP loop2: è necessario un salto più "piccolo" a jmpl2
BCC jmplp2
end PLA ; Svuota lo stack
RTS
; Subroutine per stampare la colonna
subprt PHA ; Salva l'accumulatore nello stack
TXA
PHA ; Salva il registro x nello stack
TYA
PHA ; Salva il registro y nello stack
LDX #$0 ; x=0
LDY #$0 ; y=0
TYA ; tmp=y
PHA
loop5 PLA ; y=tmp
TAY
INY ; y=y+1
TYA ; tmp=y
PHA
TXA ; a=x+a(y)+1+n*2
CLC
ADC ($FB),Y
ADC #$1
LDY #$0
ADC ($FB),Y
ADC ($FB),Y
TAY ; y=a
LDA ($FB),Y ; stampa il carattere
JSR $FFD2
PLA ; a=y(originario corrispondente allo i-esimo)
PHA
LDY #$0 ; x=x+a(y+n)
ADC ($FB),Y
TAY
LDA ($FB),Y
loop6 INX
SEC
SBC #$1
BNE loop6
LDY #$0 ; Compara l'y originario con n
PLA
PHA
CMP ($FB),Y
BNE loop5 ; Se non coincide continua con la stampa
LDA #$D
JSR $FFD2
PLA ; Svuota lo stack
PLA ; Recupera il registro y dallo stack
TAY
PLA ; Recupera il registro x dallo stack
TAX
PLA ; Recupera l'accumulatore dallo stack
RTS ; Esce dalla subroutine
Una volta scritto tale codice, restava da scriverne il loader che si occupasse di caricarlo in memoria, rilocandolo a seconda della macchina su cui viene eseguito.
Gli unici due problemi da risolvere erano i seguenti: individuare il tipo di macchina in cui il codice viene eseguito e determinare la posizione dove allocare il programma assembler nella memoria di tale macchina.
In merito al primo problema, su internet ho trovato la seguente
discussione da cui ho estratto lo snippet che ho utilizzato nel mio codice.
Per quanto riguarda il secondo, invece, ho pensato di allocare il codice 2k dopo l'inizio dell'area dedicata ad ospitare il codice basic che su tutte le macchine cbm può essere individuata grazie alle locazioni 43 e 44.
Per il Commodore 64, sebbene non necessario, ho preferito scegliere in modo deterministico l'area di memoria che inizia all'indirizzo 49152($C000).
Infine, faccio esplicitamente osservare che, limitatamente alla sola istruzione "JSR subprt", si è reso necessario modificare 2 byte del codice assembler in funzione dell'indirizzo di memoria determinato per l'inizio del programma.
Il codice che ho scritto per fare ciò è il seguente:
10 cbm=peek(65534)
20 if peek(1177)=63 then poke1177,62: cbm=peek(65534): poke1177,63
30 if cbm=72 or cbm=114 or cbm=23 or cbm=179 then 50
40 print "solo per: c64, vic20, c128, c16, plus4": end
50 if cbm=72 then sa=49152: goto 70
60 sa=peek(44)*256+peek(43)+1024*2
70 o=235: for i=0 to o-1: read a: poke sa+i,a: next
80 input"{clear}quante parite";n
90 if n<1 or n>16 then 80
100 poke sa+o,n: cs=1: pr$="": print
110 for i=1 to n
120 print"partita n.";i;"{left}";: input p$
130 cs=cs*len(p$): poke sa+o+n+i,len(p$): pr$=pr$+p$
140 next: print: print"{down}colonne da sviluppare:"; cs
150 for i=1 to len(pr$): poke sa+o+n*2+i,asc(mid$(pr$, i, 1)): next
160 hi=int((sa+o)/256): lo=(sa+o)-hi*256: poke 251,lo: poke 252,hi
170 rem modifica nel codice l'indirizzo della chiamata a subprt
180 hi=int((sa+166)/256): lo=(sa+166)-hi*256: poke sa+18,lo: poke sa+19,hi
190 sys sa
200 end
210 data 160,0,177,251,168,169,0,145
220 data 251,136,208,251,177,251,168,162
230 data 0,32,166,192,24,177,251,105
240 data 1,145,251,72,152,24,160,0
250 data 113,251,168,104,209,251,240,16
260 data 228,1,240,12,72,152,56,160
270 data 0,241,251,168,104,24,144,217
280 data 162,1,72,152,56,160,0,241
290 data 251,168,104,136,192,0,240,38
300 data 24,177,251,105,1,145,251,72
310 data 152,160,0,113,251,168,104,209
320 data 251,208,19,224,1,208,15,72
330 data 152,160,0,56,241,251,168,104
340 data 24,144,216,24,144,173,152,160
350 data 0,56,241,251,168,72,160,0
360 data 177,251,168,200,177,251,160,1
370 data 209,251,240,32,104,168,200,152
380 data 72,169,0,145,251,104,160,0
390 data 209,251,240,4,168,24,144,238
400 data 162,0,138,168,177,251,168,177
410 data 251,24,144,199,104,96,72,138
420 data 72,152,72,162,0,160,0,152
430 data 72,104,168,200,152,72,138,24
440 data 113,251,105,1,160,0,113,251
450 data 113,251,168,177,251,32,210,255
460 data 104,72,160,0,113,251,168,177
470 data 251,232,56,233,1,208,250,160
480 data 0,104,72,209,251,208,210,169
490 data 13,32,210,255,104,104,168,104
500 data 170,104,96
Tale codice ha il notevole pregio di poter essere eseguito senza alcuna modifica su molte macchine di casa Commodore.
Che altro dire?
Anche a distanza di anni il Commodore 64 continua ad affascinare ed interessare tanti suoi fan, per cui non mi resta che augurare buona programmazione a tutti coloro che vorranno cimentarsi ancora una volta a realizzare programmi per quella che è stata, e resta, una macchina mitica!