In attesa che sia pronto il prossimo articolo sugli strumenti messi a disposizione dal compilatore FreeBASIC per sviluppare applicazioni per la console (console application), visto che è domenica, rilassiamoci ascoltando dalla viva voce di Richard Stallman – il fondatore della Free Software Foundation – la Canzone del Software Libero. 🙂
FreeBASIC – configurare la console
Esiste ancora tutto un mondo di applicazioni per console (console application), alcune delle quali si permettono anche il lusso di fare a meno del mouse.
Per esempio ne (the nice editor) è un noto editor di testo per console sviluppato da Sebastiano Vigna dell’Università degli Studi di Milano.
Potrà sembrare una cosa da inguaribili nostalgici – e forse lo è – ma in questo blog trovano posto anche questo tipo di applicazioni.
Seguendo il metodo imparare facendo (learning by doing), approfondiremo passo passo le procedure per la console messe a disposizione dal compilatore FreeBASIC cercando di realizzare un semplice editor di testo per console che avrà quindi una interfaccia utente basata sul solo testo (text-based user interface). Un editor di testo che magari possa anche interagire con il compilatore FreeBASIC in modo tale che possa essere utilizzato anche come semplice IDE (Integrated Development Environment).
Il nostro piccolo progetto si chiamerà CLUMSY che in inglese suona più o meno come oggetto mal costruito. 😉
Per scaricare il file sorgente di CLUMSY fai clic qui.
Configurare la console
Come prima cosa dobbiamo informare il compilatore FreeBASIC che abbiamo intenzione di aprire una finestra grafica in modalità console. Per farlo dobbiamo impostare il valore della modalità grafica (parametro mode della istruzione screen) al valore 0 (zero).
Imposteremo poi il colore del primo piano e dello sfondo con l’istruzione color sfruttando la tavolozza dei colori di default.
Faremo uso dell’istruzione cls per pulire lo schermo e rendere attiva l’impostazione dei colori precedentemente scelta.
L’istruzione cls può essere fatta seguire da un parametro che può assumere i seguenti tre valori:
- 0 -> pulisce l’intero schermo;
- 1 -> pulisce l’area grafica se definita con l’istruzione view (se lo schermo è stato settato in modalità console questa istruzione non ha alcun effetto);
- 2 -> pulisce l’area di stampa se definita con l’istruzione view print.
Se si utilizza l’istruzione cls senza alcun parametro, nel caso sia stata definita un’area grafica o di stampa, questa verra pulita, altrimenti viene pulito l’intero schermo.
Utilizzeremo poi l’istruzione width in combinazione con le istruzioni hiword e loword, per conoscere rispettivamente il numero massimo di righe e di colonne messe a disposizione di default dalla nostra finestra terminale (o prompt dei comandi in ambiente Windows).
Per posizionare il testo sfrutteremo l’istruzione locate.
Infine, per definire l’area di testo su cui andremo a scrivere utilizzaremo l’istruzione view print specificando la riga iniziale e quella finale.
''project: clumsy | pre-alpha version
''description: text editor for console
''author: ciuco informatico
''last update: 3th February 2020
dim as uinteger max_rows, max_cols
screen 0 ''console-mode functionality
max_rows = hiword(width) ''max number of rows
max_cols = loword(width) ''max number of columns
color 7, 0 ''text grey, background black
cls 0 ''clears the entire screen
''footer info
locate max_rows, 1
print "clumsy is a silly text editor for console"; _
" | pre-alpha version" _
" | ^H for Help";
''sets the printable area of the console screen
view print 1 to (max_rows - 1)
color 0, 7 ''text black, background grey
cls 2 ''clear only the text viewport
''text
print "Hello, world!";
sleep
Nel prossimo articolo vedremo come ottenere informazioni talla tastiera sfruttando la funzione getkey.
Advent of Code 2019 – Day 2
I due problemi del Day 2 proposti da Eric Wastl su Advent of Code cominciano ad essere veramente impegnativi.
Per risolverli ho utilizzato diversi strumenti messi a disposizione da FreeBASIC:
- le istruzioni per la lettura di un file di testo
- il contenitore indicizzato (array)
- la struttura di controllo select case
- il ciclo do-loop
- il ciclo for-next
- la struttura di controllo if-then-else
- la subroutine
Più sotto il codice relativo alle due soluzioni.
File 02a.bas
''programma: Quiz 1 del Day 2 di Advent of Code 2019
''autore: ciuco informatico
''data: 12 dicembre 2019
''web: https://ciucoinformatico.home.blog/
dim as integer array() ''array vuoto
dim as integer position
dim as integer value
position = 0
value = 0
''lettura del file e creazione del contenitore indicizzato (array)
open "02_input.txt" for input as #1
do until eof(1)
input #1, value ''lettura di un singolo valore
redim preserve array (0 to position) ''ridimensionamento array
array(position) = value
position += 1
loop
close #1
''restore the gravity assist program
array(1) = 12
array(2) = 2
''intcode program
position = 0
do
value = array(position)
select case value
case 1 ''somma
array(array(position +3)) = array(array(position + 1)) + array(array(position + 2))
case 2 ''moltiplica
array(array(position +3)) = array(array(position + 1)) * array(array(position + 2))
case 99 ''esce dal programma intcode
exit do
case else ''caso non previsto
print "opcode non previsto"
end select
position += 4
loop until position >= ubound(array)
''risposta del quiz 1 del Day 2
print "Il valore in posizione zero e':"; array(0)
File 02b.bas
''programma: Quiz 2 del Day 2 di Advent of Code 2019
''autore: ciuco informatico
''data: 12 dicembre 2019
''web: https://ciucoinformatico.home.blog/
dim shared array() as integer ''array vuoto
dim shared address as integer
dim shared value as integer
dim as integer opcode
''dichiarazione procedura intcode
declare sub reset_intcode
address = 0
opcode = 0
value = 0
''ciclo per il parametro "verbo" (verb)
for i as integer = 0 to 99
''ciclo per il parametro "sostantivo" (noon)
for j as integer = 0 to 99
''chiamata della procedura intcode reset input)
reset_intcode
array(2) = i ''verb
array(1) = j ''noon
address = 0
do
opcode = array(address)
select case opcode
case 1 ''somma
array(array(address +3)) = array(array(address + 1)) + array(array(address + 2))
case 2 ''moltiplica
array(array(address +3)) = array(array(address + 1)) * array(array(address + 2))
case 99 ''esce dal programma intcode
exit do
case else ''caso non previsto
print "opcode non previsto"
end select
address += 4
loop until address > ubound(array)
if array(0) = 19690720 then
''domanda del quiz 2 del Day 2
print "What is 100 * noun + verb?"; 100 * array(1) + array(2)
else
''non fare nulla
end if
next j
next i
''definizione procedura intcode
sub reset_intcode
address = 0
''lettura del file e creazione del contenitore indicizzato (array)
open "02_input.txt" for input as #1
do until eof(1)
input #1, value ''lettura di un singolo valore
redim preserve array (0 to address) ''ridimensionamento array
array(address) = value
address += 1
loop
close #1
end sub
Attenzione però!
L’algoritmo utilizzato per scrivere il codice contenuto nel file sorgente 02b.bas contiene una parte che lo rende altamente inefficiente.
Lascio a voi trovarla. 😉
Quando progettiamo i nostri algoritmi non dobbiamo mai dimenticarci di curare la fase di ottimizzazione.
Advent of Code 2019 – Day 1
Il testo completo in inglese lo trovate qui.
Puzzle n.1
In questo primo puzzle Eric, l’ideatore di Advent of Code, ci chiede di calcolare la quantità di carburante necessario per lanciare nello spazio dei moduli spaziali.
Il puzzle è molto semplice: ci viene detto che per calcolare la quantità di carburante per ciascun modulo occorre basarsi sulla sua massa e applicare questa formula:
Dividi la massa per 3, approssima il risultato per difetto e quindi sottrai il valore 2.
I valori delle masse dei moduli ci vengono forniti sotto forma di lista numerica.
Definizione dell’algoritmo
In questo caso i passi del nostro algoritmo sono praticamente già descritti in chiaro nel testo:
- Leggi il valore della massa (m) del primo modulo.
- Applica la formula data per ricavare la quantità di carburante necessario (f) per il lancio di un singolo modulo.
- Somma tutti i valori ottenuti per ottenere il totale del carburante (f_tot) necessario per tutti i moduli.
Scrittura del codice
Prima di iniziare a scrivere il codice, salviamo la lista delle masse in un file di testo che nomineremo input.txt e che salveremo per semplicità nella stessa cartella dove andremo a salvare il nostro file sorgente che possiamo nominare per esempio 01a.bas.
''nome file: 01a.bas dim m as string ''variabile per la lettura della massa dim as integer f, f_tot ''variabili per il carburante ''reset variabili m = "" f = 0 f_tot = 0 ''apertura del file open "input.txt" for input as #1 line input #1, m ''lettura della prima massa ''ciclo per la lettura di tutte le masse e ''calcolo del carburante necessario do until m = "" f = int(val(m) / 3) - 2 ''carburante per un modulo f_tot = f_tot + f ''carburante totale line input #1, m ''lettura di un'altra massa loop ''stampa il risultato del puzzle n.1 print "Totale carburante necessario = "; f_tot close #1 ''chiusura del file
Ovviamente questo è soltanto un esempio di possibile risoluzione: potremmo infatti decidere di creare un array per memorizzare tutti i valori delle masse, oppure potremmo creare una funzione ad hoc per il calcolo del carburante necessario.
Insomma, nella programmazione esistono sempre più modi di risolvere un problema. E questa è proprio una delle cose che la rendono così interessante.
Puzzle n.2
Il secondo puzzle del Day 1 comincia ad essere un po’ più impegnativo.
Eric ci fa notare che anche il carburante di un modulo è a sua volta una massa, e quindi anche questa massa per essere lanciata nello spazio ha bisogno di carburante. Ma, attenzione! Anche il carburante necessario per la massa del carburante è a sua volta una massa…
Insomma, lo avete capito, qui bisogna escogitare un algoritmo iterativo un po’ più raffinato.
Come tutte le iterazioni, anche questa deve avere una fine, altrimenti sono guai. Nel testo del puzzle n.2 ci viene detto che, applicando la stessa formula che abbiamo visto per il puzzle n.1, una volta che otteniamo una quantità di carburante negativa, allora dobbiamo fermarci.
Definizione dell’algoritmo
L’idea è quella di utilizzare un altro ciclo do-loop all’interno del ciclo principale che itera le masse dei moduli.
In questo nuovo clico interno il valore del carburante necessario dovrà rientrare come input alla formula di calcolo. Nella condizione di controllo del ciclo andremo quindi a verificare per ogni iterazione che il carburante necessario non sia negativo. Infine la variabile del carburante necessario al carburante dovrà incrementarsi soltanto se i valori del carburante calcolato sono positivi.
Scrittura del codice
Riprandendo il codice del puzzle n.1, andiamo ad aggiungere, nella parte dedicata alla dichiarazione delle variabili, una nuova variabile che conterrà la somma del carburante necessario al carburante.
dim as integer f, f_tot, f_tot2 ''variabili per il carburante
Poi andremo ad inserire all’interno del primo ciclo un secondo ciclo dedicato proprio al calcolo del carburante necessario al carburante.
do until m = ""
f = int(val(m) / 3) - 2 ''carburante per un modulo
f_tot = f_tot + f ''carburante totale
''puzzle n.2
do until f <= 0
f = int(f / 3) - 2
if f > 0 then f_tot2 = f_tot2 + f
loop
line input #1, m ''lettura di un'altra massa
loop
Ed infine possiamo inserire una riga per la stampa del risultato.
''stampa il risultato del puzzle n.1 print "Puzzle n.1 - Totale carburante necessario = "; f_tot ''stampa il risultato del puzzle n.2 print "Puzzle n.2 - Totale carburante necessario = "; f_tot + f_tot2
Bene. Il Day 1 è fatto! 🙂
FreeBASIC – gestione degli errori
La gestione degli errori è un aspetto molto importante che un programmatore deve conoscere per scrivere del codice affidabile.
Può capitare per esempio di dover aprire un file, ma, per qualche motivo, il file è stato cancellato, spostato o rinominato. In questi casi è bene prevedere un controllo per intercettare l’eventuale errore ed informare così l’utente del problema.
A tale scopo FreeBASIC mette a disposizione la funzione err che ritorna lo specifico codice di errore del problema riscontrato.
In FreeBASIC esiste una lista dei codici di errore che il programmatore può gestire.
Vediamo un semplice esempio:
dim err_code as integer
open "file.txt" for input as #1
err_code = err
select case err_code
case 0
close #1 'nessun errore riscontrato
case 1
print "Illegal function call"
case 2
print "File not found signal"
case 3
print "File I/O error"
case 4
print "Out of memory"
case 5
print "Illegal resume"
case 6
print "Out of bounds array access"
case 7
print "Null Pointer Access"
case 8
print "No privileges"
case 9
print "Interrupted signal"
case 10
print "Illegal instruction signal"
case 11
print "Floating point error signal"
case 12
print "Segmentation violation signal"
case 13
print "Termination request signal"
case 14
print "Abnormal termination signal"
case 15
print "Quit request signal"
case 16
print "Return without gosub"
case 17
print "End of file"
end select
sleep
Abbastanza semplice direi. 😉
In FreeBASIC esistono anche altre istruzioni e procedure che permettono una gestione degli errori avanzata.
A rileggerci al prossimo articolo.
FreeBASIC – libreria dinamica
A differenza della libreria statica che abbiamo visto nel precedente articolo, la libreria dinamica viene caricata soltanto quando il nostro programma è in esecuzione (runtime).
Altra importante caratteristica è che la libreria dinamica può essere condivisa (shared) tra più programmi in esecuzione.
Passiamo subito alla pratica e scriviamo quattro files.
Modulo libreria
''nome del file: funzioni_utili.bas
''funzione per il calcolo dell'area di un quadrato
public function area_quadrato(byval l as integer) _
as integer
return l * l
end function
Una volta salvato il file dobbiamo compilarlo specificando al compilatore FreeBASIC che vogliamo creare proprio una libreria dinamica:
fbc -dll [eventuale percorso]\funzioni_utili.bas
Se tutto è andato in porto troveremo nella stessa cartella contenente il file sorgente la nostra libreria.
Sui sistemi operativi Linux l’estensione del file sarà .so, mentre sul sistema operativo Windows l’estensione del file sarà .dll.
A questo punto il file della libreria dinamica va spostato in specifiche cartelle a seconda del sistema operativo:
- su Linux: /usr/lib
- su Windows: la cartella di sistema (p.e. C:\Windows\System32)
Interfaccia
Come abbiamo già fatto per rendere facilmente disponibile la libreria statica, anche per la libreria dinamica creiamo la nostra interfaccia (API) sfruttando un file di intestazione:
''nome file: funzioni_utili.bi
#inclib "funzioni_utili" ''include il modulo libreria
''dichiara la funzione
declare function area_quadrato(byval l as integer) _
as integer
Test
Creiamo adesso due programmi sostanzialmente identici che, una volta mandati in esecuzione, condivideranno la medesima libreria dinamica.
Programma 1
''nome file: test1.bas #include "funzioni_utili.bi" ''include l'interfaccia dim a as integer ''stampa l'area del quadrato do input "Test 1: Inserisci la lunghezza del lato: "; a print "L'area del quadrato vale: "; area_quadrato(a) loop until a = 0
Programma 2
''nome file: test2.bas #include "funzioni_utili.bi" ''include l'interfaccia dim a as integer ''stampa l'area del quadrato do input "Test 2: Inserisci la lunghezza del lato: "; a print "L'area del quadrato vale: "; area_quadrato(a) loop until a = 0
Bene! Con questo articolo possiamo per ora ritenere la nostra conoscenza dell’uso delle librerie in FreeBASIC sufficiente.
Ricordiamoci però sempre di ben commentare le nostre librerie per aiutare chi volesse utilizzarle a capire velocemente come funzionano.
A rileggerci al prossimo articolo. 🙂
FreeBASIC – libreria statica
Nel precedente articolo abbiamo avuto modo di dare un primo sguardo alla teoria che riguarda le librerie.
Oggi passeremo alla pratica realizzando la nostra prima libreria statica.
Una libreria statica è un file contenente del codice compilato che svolge diversi compiti. Questo codice può essere utilizzato nel proprio progetto e quindi aggiunto staticamente durante la creazione del file eseguibile.
Facciamo quindi un semplice esempio creando una libreria che contiene un unico modulo con un’unica funzione:
Modulo libreria
''nome del file: libreria.bas
''funzione per il calcolo dell'area di un triangolo
public function area(byval b as integer, _
byval h as integer) as integer
return b * h / 2
end function
A questo punto dobbiamo compilare il file specificando al compilatore FreeBASIC che vogliamo creare una libreria e non un eseguibile:
fbc -lib libreria.bas
Noteremo che nella stessa cartella del modulo compare ora il file liblibreria.a che è il file archivio contenente la nostra libreria.
Un archivio può contenere più moduli.
Interfaccia
Per facilitare l’uso della nostra libreria è bene creare una interfaccia (API) che permetta ad un qualsiasi programmatore interessato di accedere facilmente alle varie funzioni messe a disposizione.
In FreeBASIC per creare una interfaccia, non facciamo altro che scrivere un file di intestazione.
''nome del file: libreria.bi
#inclib "libreria" ''include il modulo libreria
declare function area(byval b as integer, _
byval h as integer) as integer
Programma
Immaginiamo ora di essere un programmatore che voglia utilizzare la funzione area della nostra libreria.
Scriviamo quindi questo semplice programma:
''nome file: test.bas #include "libreria.bi" ''include il file di intestazione 'stampa l'area del triangolo print "L'area del triangolo vale: "; area(4, 5)
A questo punto si compila il modulo del programma (test.bas) e si ottiene il file eseguibile che ingloba anche la libreria.
Nel prossimo articolo creeremo invece una libreria dinamica.
Spunti – matematica, geometria e musica
Ogni tanto è bene fermarsi, prendere una pausa per riflettere o, più semplicemente, per mettersi in ascolto, osservare…
FreeBASIC – salvare e caricare immagini
Dopo aver imparato come creare una immagine, oggi vedremo come salvarla in un file utilizzando il formato bitmap e quindi come poterla caricare per usi successivi.
Come salvare una immagine
L’istruzione per salvare una qualsiasi immagine è la funzione bsave.
La funzione restituisce il valore 0 (zero) se il salvataggio è andato a buon fine, altrimenti viene restituito un codice di errore.
Vediamo l’uso di questa istruzione con un semplice esempio:
screen 19, 32, 1
''dimensiona la variabile per gestire il
''valore restituito dalla funzione bsave
dim risultato as long
''dimensiona la variabile di tipo puntatore
''per l'indirizzo di memoria
dim immagine1 as any ptr
''crea l'immagine
immagine1 = imagecreate(200, 200, rgb(255,0,0))
if immagine1 = 0 then
print "Creazione dell'immagine non riuscita!"
sleep
else
''disegna un cerchio verde al centro
circle immagine1, (100, 100), 50, rgb(0,255,0)
''inserisce l'immagine all'interno
''della finestra grafica
put(150, 150), immagine1
end if
print "Premi un tasto per salvare l'immagine."
sleep
''salva l'immagine
risultato = bsave("immagine.bmp", immagine1)
if risultato <> 0 then
print "Salvataggio dell'immagine non riuscito!"
else
print "L'immagine e' stata correttamente salvata."
end if
sleep
Ora, se il salvataggio è riuscito, andiamo a rinominare il file della nostra immagine modificandolo poi con un qualsiasi programma di grafica prima di provare a caricarlo.
Come caricare una immagine
L’istruzione per caricare le immagini è bload.
Aggiungiamo ora questo blocco di codice in fondo al programma che abbiamo appena provato:
print "Premi un tasto per caricare l'immagine"
print "modificata in una nuova posizione."
sleep
risultato = bload("immagine2.bmp", immagine1)
if risultato <> 0 then
print "Caricamento dell'immagine non riuscito!"
sleep
else
print "L'immagine e' stata correttamente caricata."
''inserisce l'immagine all'interno
''della finestra grafica
put(300, 50), immagine1
print "Premi un tasto per uscire."
sleep
''distrugge l'immagine per liberare la memoria
imagedestroy immagine1
end if
Se tutto è andato a buon fine dovremmo essere riusciti a visualizzare, in una diversa posizione dello schermo, la nostra immagine modificata.
Bene. Anche per oggi possiamo ritenerci soddisfatti. Un altro piccolo passo è stato fatto nel percorso di apprendimento del FreeBASIC. 🙂
FreeBASIC – creare una immagine
Spesso può rivelarsi utile creare una immagine in memoria invece che disegnarla direttamente sullo schermo grafico.
Poi, una volta creata, sarà possibile richiamarla attraverso il suo indirizzo di memoria.
Questo procedimento, oltre a velocizzare il disegno delle immagini, rende possibile vari effetti grafici che possono migliorare l’efficacia e la qualità dei nostri programmi.
Possiamo immaginare ciascuna immagine come una tela contenuta all’interno di una cornice avente delle specifiche dimensioni (larghezza e altezza); la tela potrà essere colorata o trasparente.
Come creare una immagine
La funzione che ci permette di creare un’immagine è imagecreate disponibile in due versioni:
screen 19, 32, 1 ''dimensiona due variabili di tipo puntatore ''per gli indirizzi di memoria dim immagine1 as any ptr dim immagine2 as any ptr ''I versione con i seguenti argomenti: ''- larghezza ''- altezza ''- colore dello sfondo immagine1 = imagecreate(200, 200, rgb(255,0,0)) if immagine1 = 0 then print "Creazione dell'immagine 1 non riuscita!" sleep else ''disegna un cerchio verde al centro circle immagine1, (100, 100), 50, rgb(0,255,0) ''inserisce l'immagine all'interno ''della finestra grafica put(150, 150), immagine1 ''distrugge l'immagine per liberare la memoria imagedestroy immagine1 end if ''II versione con l'aggiunta di un quarto argomento: ''- profondità di colore immagine2 =imagecreate(200, 200, rgb(0,255,0), 32) if immagine2 = 0 then print "Creazione dell'immagine 2 non riuscita!" sleep else ''disegna un cerchio rosso al centro circle immagine2, (100, 100), 50, rgb(255,0,0) ''inserisce l'immagine all'interno ''della finestra grafica put(300, 300), immagine2 ''distrugge l'immagine per liberare la memoria imagedestroy immagine2 end if sleep
Note sulla istruzione imagecreate
- L’istruzione va utilizzata soltanto dopo aver definito la modalità grafica: e quindi sia la risoluzione dello schermo che la risoluzione grafica.
- Se il colore dello sfondo non viene specificato, il valore predefinito sarà trasparente e quindi in pratica sarà visibile il colore di sfondo della finestra grafica.
- Se l’istruzione dovesse fallire, viene restituito il valore null (0), altrimenti viene restituito l’indirizzo di memoria dell’immagine.
Per fare le cose per bene, occorre anche ricordarsi di distruggere le immagini create in modo tale da liberare la memoria. Per farlo si utilizza l’istruzione imagedestroy.
Bene. Per oggi possiamo fermarci qui. Nel prossimo articolo impareremo a salvare e caricare le immagini.









