Benvenuto Visitatore(Log In|Registrati)

6 Pagine V  « < 4 5 6  
Reply to this topicStart new topic
> Traduzione e ampliamento di "MANOR" in BASIC (MANIERO), Ultimo della serie "the sumer game": DEVLOG
TheRuler
messaggio26 Sep 2021, 16:24
Messaggio #126



Gruppo icone

Gruppo: SMod
Messaggi: 11.677
Iscritto il: 20 January 06
Da: Bologna
Utente Nr.: 2.653
Entropologo part-time

BGE AG: Indy3
Arcade: Silpheed
Free Roaming: Shadow of the Colossus
RTS: Praetorians

Playing Magari...
SO Windows7

OGI Supporter




La funzione di disegno della mappa è una di quelle che ho cambiato più volte mantenendo sempre lo stesso layout, simboli, colori e posizione delle terre, ma con l'obiettivo di velocizzare l'output a schermo.

Il metodo originale è curioso e molto furbo.
Per rappresentare la mappa di 22x40 Leon Baradat ha assegnato ad ogni casella una variabile T(Y,X) con valori che vanno da T(1,1) a T(22,40).
All'inizio del gioco lui associa poi ad ognona di queste variabili T un numero che corrisponde ad un particolare carattere della tabella ascii che poi stamperà a schermo al momento opportuno col comando PRINT CHR$([0-255])
Ad esempio se la prima casella in alto a sx fosse del Duca Cinturanera, alla variabile verrebbe assegnato il valore 219:
T(1,1)=219
di modo che se dovesse stampare quella casella basterebbe il comando:
PRINT CHR$(T(Y,X))
ovvero
PRINT CHR$(219)

questa è la definizione delle variabili ad inizio gioco, ovvero il layout della mappa:
CODICE
130 FOR A=1 TO 10:FOR B=1 TO 23:T(A,B)=176:NEXT:NEXT:FOR A=17 TO 22:FOR B=1 TO 40:T(A,B)=178:NEXT:NEXT
140 FOR A=1 TO 5:FOR B=24 TO 40:T(A,B)=8:NEXT:NEXT:FOR A=9 TO 18:FOR B=30 TO 40:T(A,B)=15:NEXT:NEXT
150 FOR A=9 TO 16:FOR B=16 TO 29:T(A,B)=219:NEXT:NEXT:FOR A=11 TO 18:FOR B=1 TO 15:T(A,B)=177:NEXT:NEXT:FOR A=10 TO 12:T(A,15)=219:NEXT:FOR A=1 TO 10:T(11,A)=176:NEXT
160 FOR A=12 TO 15:T(18,A)=178:NEXT:FOR A=7 TO 8:FOR B=24 TO 31:T(A,B)=8:NEXT:NEXT:FOR A=38 TO 40:T(8,A)=15:NEXT:FOR A=24 TO 40:T(6,A)=8:NEXT:FOR A=16 TO 27:T(17,A)=219:NEXT
170 FOR A=7 TO 8:FOR B=32 TO 40:T(A,B)=15:NEXT:NEXT:FOR A=32 TO 40:T(19,A)=15:NEXT:T(7,32)=8:T(17,28)=15:T(17,29)=15:FOR A=36 TO 40:T(20,A)=15:NEXT:FOR A=38 TO 40:T(21,A)=15:NEXT

Già il codice è abbastanza efficiente perché usa dei cicli FOR-NEXT per assegnare lo stesso valore a caselle contigue, invece di assegnare il valore ad ogni variabile singolarmente, perché 22x40 sarebbero 880 definizioni.

esempio per una linea contigua:
FOR A=16 TO 27:T(17,A)=219:NEXT
Alle caselle dalla T(17,16) alla T(17,27) assegna il valore 219.

esempio per blocco (due cicli FOR nested)
FOR A=1 TO 10:FOR B=1 TO 23:T(A,B)=176:NEXT:NEXT
Alle caselle dalla T(1,1) alla T(10,23) assegna il valore 176.

piccola digressione sui cicli NESTED:
il seguente codice non fa nulla se non far passare del tempo ma è COME lo fa che è interessante per capire questa diavoleria dei cicli INNESTATI uno dentro l'altro. Molto comodi in programmazione perché evitano codice e fanno risparmiare tempo sia al programmatore che alla macchina.

FOR A=1 TO 10:FOR B=1 TO 8:NEXT:NEXT

la macchina comincia a contare dal ciclo più interno per poi uscire verso gli esterni. Nota che possono essrci ben più di due cicli innestati.
Comincia ad assegnare ad A il valore 1, poi a B il valore 1, dunque assegna a B il valore 2 e così via fino al valore 8, dopo di che dichiara finito il primo ciclo B (di 8) ed anche il primo ciclo A (di 10).
In seguito associa ad A il valore 2, poi a B il valore 1 (sovrascrivendo l'ultimo valore che era 8) e così via fino a B=8 dichiarando concluso anche il secondo ciclo B.
Dopo di che continua fino a che non è giunto a A=10 e B=8 e dichiara concluso anche il ciclo A.


Per creare il minor numero di comandi Baradat ha disegnato dei rettangoli poi ha aggiunto blocchetti e linee qua e là sovrascrivendo variabili già definite.
Io l'ho voluto riscrivere col numero minore di comandi ma senza sovrapposizioni:

CODICE
100 FOR A=1 TO 8:FOR B=1 TO 23:T(A,B)=176:NEXT:NEXT:FOR B=1 TO 15:T(9,B)=176:NEXT:FOR B=1 TO 14:T(10,B)=176:NEXT:FOR B=1 TO 10:T(11,B)=176:NEXT
101 FOR B=11 TO 14:T(11,B)=237:NEXT:FOR B=1 TO 14:T(12,B)=237:NEXT:FOR A=13 TO 17:FOR B=1 TO 15:T(A,B)=237:NEXT:NEXT:FOR B=1 TO 11:T(18,B)=237:NEXT
102 FOR A=1 TO 6:FOR B=24 TO 40:T(A,B)=8:NEXT:NEXT:FOR B=24 TO 32:T(7,B)=8:NEXT:FOR B=24 TO 31:T(8,B)=8:NEXT
103 FOR A=33 TO 40:T(7,A)=15:NEXT:FOR A=8 TO 19:FOR B=32 TO 40:T(A,B)=15:NEXT:NEXT:FOR A=9 TO 18:FOR B=30 TO 40:T(A,B)=15:NEXT:NEXT:FOR B=28 TO 29:T(17,B)=15:NEXT:FOR B=36 TO 40:T(20,B)=15:NEXT:FOR B=38 TO 40:T(21,B)=15:NEXT
104 FOR A=9 TO 16:FOR B=16 TO 29:T(A,B)=219:NEXT:NEXT:FOR A=10 TO 12:T(A,15)=219:NEXT:FOR B=16 TO 27:T(17,B)=219:NEXT:
105 FOR B=11 TO 29:T(18,B)=178:NEXT:FOR B=1 TO 31:T(19,B)=178:NEXT:FOR B=1 TO 35:T(20,B)=178:NEXT:FOR B=1 TO 37:T(21,B)=178:NEXT:FOR B=1 TO 40:T(22,B)=178:NEXT:T(1,1)=206

La quantità di codice è maggiore ma è più veloce perché non fa cose superflue.


Veniamo ora alla messa a schermo originale della mappa:
CODICE
1240 CLS:CL=2:FOR A=1 TO 22:FOR B=1 TO 40:IF T(A,B)=T(A,B-1) THEN 1250 ELSE IF T(A,B)=176 THEN CL=13 ELSE IF T(A,B)=177 THEN CL=11 ELSE IF T(A,B)=178 THEN CL=14 ELSE IF T(A,B)=15 THEN CL=12 ELSE IF T(A,B)=8 THEN CL=15 ELSE CL=2
1245 COLOR CL,0
1250 PRINT CHR$(T(A,B));:NEXT:PRINT:NEXT


I due cicli nested non sono altro che l'area della mappa: FOR A=1 TO 22:FOR B=1 TO 40

Dopo di che viene una condizionale IF THEN dove associa al volo il colore alla casella. SE T(A,B)=176 ALLORA CL=13, variabile CL che viene passata subito dopo a COLOR CL che in questo caso diventerà COLOR 13 ovvero porpora.
Stampa poi CHR$(176) e se non è arrivato alla fine della riga (A,40) allora continua con il ciclo delle B. Quando arriva a T(A,40) e terminato il primo dei 22 cicli A, passa alla A successiva andando a capo come definisce quel PRINT fra i due NEXT.

Questa stampa dura circa 2.6 secondi su un 386.


Questa invece è la mia ultima iterazione:
CODICE
210 FOR A=1 TO 22:FOR B=1 TO 40:IF T(A,B)=T(A,B-1) THEN 213
212 COLOR CF(CINT(T(A,B)/7.9))
213 ?CHR$(T(A,B));:NEXT:?:NEXT


Si può comprimere ancora un pelo togliendo la condizione che fa saltare la definizione del colore nel caso si trovino valori uguali contigui, ma poi rallenta. Su harware 386 le performance sono addirittura peggiori della versione originale.
Ad ogni modo questo sarebbe la versione in una sola linea:
CODICE
210 FOR A=1 TO 22:FOR B=1 TO 40:COLOR CF(CINT(T(A,B)/7.9)):?CHR$(T(A,B));:NEXT:?:NEXT


Stessi due cicli nested ma questa volta definisco il colore con una singola condizione invece di 6.
Questo approccio però necessita di una ulteriore definizione ad inizio programma ovvero CF

quello che sostanzialmente cambia nel mio codice è questo:
COLOR CF(CINT(T(A,B)/7.9))

io voglio che per ogni valore di T(A,B) che mi si presenta corrisponda un determinato numero di modo che quando devo stampare il valore 219 questo sia collegato alla variabile CF del COLOR con il valore 2, COLOR 2 ovvero verde.
la definizione è questa:
CF(28)=2
CF(22)=13
CF(30)=11
CF(23)=14
CF(02)=12
CF(01)=15
CF(26)=9

perché CF(28), CF(22)... ?
Ad inizio programma è necessario definire le dimensioni massime degli ARRAY, in GW BASIC normalmente settati a 10.
DIM T(23,41),CF(30)
Questa definizione alloca già la memoria necessaria a queste variabili e la riempie di zeri, questo per evitare che durante l'esecuzione il programma non si debba trovare ad immagazzinare un nuovo valore per una variabile e non avere più memoria, il dannato "out of memory" error.
NOTA, ogni variabile non definita ha valore ZERO. Se si vuole che una certa variabile cominci con ZERO è inutile definirla, si risparmia così codice e memoria.

Qui entra in scena il trucco di dividere per 7.9, un valore non a caso ed è il più alto che mi sono potuto permettere.

l'assegnazione delle variabili CF senza questo trucco sarebbe stata:
CF(219)=2 (ricordate l'associazione che devo fare 219 - 2?)
CF(176)=13
CF(237)=11
CF(178)=14
CF(015)=12
CF(008)=15
CF(206)=9

avrei dovuto quindi definire come dimensione massima dell'ARRAY almeno il valore massimo di CF ovvero 237.
Ma ciò avrebbe comportato tanta memoria sprecata (per ogni posizione dell'array il GWBASIC richiede 4Bytes, quindi quasi 1KB a fronte di sole 7 posizioni occupate realmente nell'array).
NOTA 1: GW BASIC alloca lo stesso anche tutte le altre variabili mancanti tipo CF(1) o CF(100) anche se non le utilizzi
NOTA 2: Essendo 60300Bytes la memoria che può gestire il GW BASIC, Il valore massimo definibile per un array è DIM A(15071) (ma lascerebbe soli 3 Bytes per il programma).

Da qui la divisione per 7.9 (il valore più vicino al valore minimo dei valori che mi servono, che è 8).
La funzione CINT è come INT ma arrotonda all'INTero più vicino, mentre INT all'intero più basso. Se il risultato è 1,6 INT lo arrotonda a 1, mentre CINT a 2.
Ho usato CINT per via del fatto che ho due valori nell'array molto vicini (176 e 178) che se divisi per 8 danno la stessa cifra, mentre con CINT e dividendo per 7.9 danno rispettivamente 22 e 23.

Tirando le somme, mi sono dilungato anche troppo, la nuova funzione di stampa della mappa dura ora 1.4 secondi, sempre su stesso hardware 386.
Lo si nota anche via PCBASIC. Meno su GWBASIC sotto dosbox, lì è istantaneo.

Alla prossima!


--------------------
 

6 Pagine V  « < 4 5 6
Reply to this topicStart new topic
1 utenti stanno leggendo questa discussione (1 visitatori e 0 utenti anonimi)
0 utenti:

 

Modalità di visualizzazione: Normale · Passa a: Lineare · Passa a: Outline


Versione Lo-Fi Oggi è il: 28th September 2021 - 19:51