Crucisberla

enigmistica in python



intro

Ma salve, eccomi tornato con un bell’articolo lungherrimo in cui racconterò un progettino carino carino terminato giusto ieri.

A parte il titolo un pochino manipolato, oggi vi illustrerò passo passo il mio primo cruciverba scritto in python. Non vi nascondo che usciranno aggiornamenti e nuove funzionalità sul mio profilo github.

l’idea

Perchè un cruciverba? Ho 17 anni, non 71, non ho bisogno di fare la settimana enigmistica per combattere l’alzheimer; ciò nonostante ne ho programmato uno, ora vi racconto come mai.

Una dozzina di giorni fa mio nonno si ritrovò positivo al COVID, nulla di preoccupante, giusto un po’ di tosse e basta. Un pubblicazione mi chiese di fare un salto all’edicola del paese per comprargli la nuova pubblicazione dell’enigmistica. Io, da bravo nipotino obbediente, eseguii i comandi.

Arrivato in edicola una domanda mi devastò: quanto sarebbe complicato “digitalizzare” un’enigmistica?

Cioè dai, soluzioni, tabelle, quiz, errori… Sono un sacco di robe a cui pensare. Arrivai a casa e ne parlai a mio nonno, il quale lavorò come programmatore per IBM negli anni 60. La sua risposta fu: “oh bello, programmatela te tutta l’enigmistica in assembly, in cobol o in basic, io sto bene con quella di carta”.

Io gli risposi: “nonno, va che adesso ci sono molti più linguaggi di programmazione rispetto ai tuoi tempi, non sarà così complesso fare un cruciverba in python”.

“Eha, per me, ti vuoi friggere la testa… Vai al mare piuttosto”. Disse lui. “La prendo come una sfida, domani te lo porto fatto e finito”.

E con quest’ultima affermazione conclusi il discorso, gli posai l’enigmistica sul tavolo dell’orto, andai su in casa e mi misi a lavorare.

Coding time

Non ci crederete mai, ma il pubblicazione dopo gli portai davvero un prototipo di “cruciverba” scritto in python la sera prima. Stranamente impiegai anche poco tempo per realizzarlo… Avevo gia delle ottime basi da cui partire: mi ero immaginato le caselle del cruciverba come una “lista”. Mi spiego meglio: da un punto di vista logico, immaginare una sola linea molto lunga di caselle è molto più semplice che utilizzare un sistema di assi cartesiani per distinguere la cella tramite le coordinate. Quindi, invece che giocare a battaglia navale, dovevo solo programmare un “editor di testo” che modificasse le caselle vuote. Però il cruciverba ha sia le definizioni orizzontali che verticali, come fare per queste ultime? Nulla di più stupido! Mi basta modificare le caselle ad intervalli regolari (che sono noti poichè uguali alla larghezza di una riga orizzontale). Bene, ora che abbiamo un’idea generale, possiamo finalmente analizzare il modo in cui l’ho programmato.

Variabili globali e moduli

Per prima cosa importiamo i seguenti moduli:

import os
import time

OS ci servirà per agire sulla finestra del terminale, time per aggiungere dei ritardi all’esecuzione de lcodice per rendere tutto più fruibile… dopo capirete meglio cosa intendo.

Per quanto riguarda le variabili… beh, definiamo il cruciverba e il cruciverba “completato”

cruciverba = [
    " ",    " ",    " ",    " ",    " ",    " ",    " ",    " ",    " ",    " ",    "#",  " ",
    " ",    " ",    " ",    "#",   " ",    " ",    " ",    " ",    "#",   " ",    " ",   " ",
    " ",    "#",   " ",    " ",    "#",   " ",    " ",    " ",    " ",    "#",   " ",   " ",
    " ",    " ",    " ",    " ",    " ",    " ",    "#",   "#",   " ",    " ",    " ",   " ",
    " ",    " ",    " ",    "#",   " ",    "#",   " ",    " ",    " ",    " ",    " ",   " ",
]
crucicorretto = [
# Lezzooo, guarda che faccia, non se l'aspettava... pensavi di trovare gia le soluzioni eh...
]

Come vedete, ho utilizzato due liste per agevolarmi la modifica delle celle tramite gli indici delle stesse. Definiamo ora due variabili utili per orientarci nel nostro programma: il numero di celle in una riga e la quantità di celle del nostro cruciverba

maxline = 12
maxcells = 60

Infine, creiamo i 2 dizionari che conterranno le nostre definizioni:

definizioniV = {
    "0)":   "Si indossa in moto (ma non a Napoli)",
    "1)":   "Sigla di Rovigo",
    "2)":   "Qualcosa che serve",
    "4)":   "192.168.1.120",
    "5)":   "Isola delle Fiji",
    "6)":   "Rimbomba nelle grotte",
    "7)":   "Random Access Memory",
    "9)":   "Preposizione inglese",
    "11)":   "Unione in inglese",
    "22)":   "Può essere a tutto sesto",
    "27)":   "Sodio",
    "32)":   "Azienda  Provinciale Trasporti",
    "37)":   "Gas nobile del 3° periodo",
    "40)":   "Sigla di Torino",
    "45)":   "Esclamazione di stupore"
}

definizioniO = {
    "0)":   "Lo stai facendo ora",
    "12)":   "Attack On Titan",
    "16)":   "Il contrario di 'tanta'",
    "21)":   "Sin/Cos",
    "26)":   "Di, A, Da, {...}, Con, Su, Per, Tra, Fra",
    "29)":   "Museum of Modern Art",
    "34)":   "Rio senza 'o'",
    "36)":   "Il contrario di 'elevare'",
    "44)":   "Se non è tanto è {...}",
    "48)":   "Le segna l'orologio",
    "55)":   "Il 'serpentese' per i programmatori"
}

Bene, passiamo ora a due variabili “inutili” al funzionamento del programma ma che possono essere delle chicche divertenti da mostrare all’utente: intendo il numero di tentativi per risolvere il cruciverba e quanto tempo si ha impiegato.

tentativi = 0

tic = time.perf_counter()

Loading screen

Sono un maniaco, ho programmato un loading screen in cui appare il nome del programam e il mio nickname, lo so, potevo evitarlo, ma sono troppo egocentrico..

def loading():
    for i in range(6):
        os.system('cls' if os.name == 'nt' else 'clear')
        print("\n")
        print("Caricamento")
        print("° "*(i+1))
        time.sleep(0.5)
    
    time.sleep(1)
    
    os.system('cls' if os.name == 'nt' else 'clear')
    
    print("\n\n")
    print("  ______                             __          __                         __          \n /      \                           |  \        |  \                       |  \         \n|  ▓▓▓▓▓▓\ ______  __    __  _______ \▓▓ _______| ▓▓____   ______   ______ | ▓▓ ______  \n| ▓▓   \▓▓/      \|  \  |  \/       \  \/       \ ▓▓    \ /      \ /      \| ▓▓|      \ \n| ▓▓     |  ▓▓▓▓▓▓\ ▓▓  | ▓▓  ▓▓▓▓▓▓▓ ▓▓  ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓\  ▓▓▓▓▓▓\  ▓▓▓▓▓▓\ ▓▓ \▓▓▓▓▓▓\ \n| ▓▓   __| ▓▓   \▓▓ ▓▓  | ▓▓ ▓▓     | ▓▓\▓▓    \| ▓▓  | ▓▓ ▓▓    ▓▓ ▓▓   \▓▓ ▓▓/      ▓▓\n| ▓▓__/  \ ▓▓     | ▓▓__/ ▓▓ ▓▓_____| ▓▓_\▓▓▓▓▓▓\ ▓▓__/ ▓▓ ▓▓▓▓▓▓▓▓ ▓▓     | ▓▓  ▓▓▓▓▓▓▓\n \▓▓    ▓▓ ▓▓      \▓▓    ▓▓\▓▓     \ ▓▓       ▓▓ ▓▓    ▓▓\▓▓     \ ▓▓     | ▓▓\▓▓    ▓▓\n  \▓▓▓▓▓▓ \▓▓       \▓▓▓▓▓▓  \▓▓▓▓▓▓▓\▓▓\▓▓▓▓▓▓▓ \▓▓▓▓▓▓▓  \▓▓▓▓▓▓▓\▓▓      \▓▓ \▓▓▓▓▓▓▓", end="\n\n")
    print("-------------------")
    print("╔═══╗    ╔═══╗╔═══╗\n║╔═╗║    ║╔═╗║║╔═╗║\n║║ ╚╝╔╗╔╗║╚══╗║║ ║║\n║║ ╔╗║║║║╚══╗║║║ ║║\n║╚═╝║║╚╝║║╚═╝║║╚═╝║\n╚═══╝╚══╝╚═══╝╚═══╝")
    print("-------------------")
    time.sleep(2.4)

    os.system('cls' if os.name == 'nt' else 'clear')

Come avrete visto, ho utilizzato os per “pulire” il terminale, questo perchè il cruciverba sarà visualizzabile come “output” del programma, proprio dalla finestra del terminale, e non volevo che fosse pieno di scritte del tipo “c://user/namUser/desktop/crucisberla.py»»» python3.9 »»»” e robe così, diamo una bella pulita.

Ovviamente ho eliso gran parte del print del loading screen perchè sarebbe diventato impossibile da leggere su smartphone, vi ricordo pertanto che il codice è disponibile sul mio profilo github.

Che altro dire sulla funzione loading()? A dire il vero non saprei, printa robe e basta, passiamo alla prossima

Tutorial

Da bravo gamer so quanto un tutorial possa fare la differenza… ok sto mentendo, piuttosto imparo i comandi di un gioco da solo, i tutorial sono robe per bambini stupidi. Ma non in questo caso, infatti il terminale di python non è sta gran cosa a livello di interfaccia, quindi una piccola introduzione a ciò che l’utente dovrà inserire mi sembrava doverosa.

def askTuto():
    print("\n")
    print("*--------------------------------------------------------------------------------------------------------------------------*")
    print("|                                                                                                                          |")
    print("| Benvenuto su 'Crucisberla', un cruciverba giocabile tramite il terminale di Python, desideri avere un piccolo tutorial?  |")
    print("|                                                                                                                          |")
    print("*--------------------------------------------------------------------------------------------------------------------------*")
    
    y_n = ""

    while not (y_n == "si" or y_n == "no" or y_n == "sì"):
        y_n = input("Risposta [si/no]: \t")
        y_n = y_n.lower()

    if y_n == "sì" or y_n == "si":
        print("Iniziamo il tutorial!")

        os.system('cls' if os.name == 'nt' else 'clear')
        
        print("Ti verrà mostrata una situazione di questo tipo: \n")
        
        for i in range(0, maxcells, maxline):
            print("|", end="")
            for j in range(maxline):
                print(cruciverba[i+j], end="")
                print("|", end="")
            print("\n")
        
        print("Le caselle vuote le dovrai riempire con delle parole in base alla definizione corrispondente, gli # sono caselle annerite, quindi non potrai modificarli.")
        time.sleep(6)
        
        print("Ti verrà chiesto il numero della casella/definizione e il verso in cui inserire la parola (veritcale o orizzontale).")
        time.sleep(4)

        print("E questo è quanto, divertiti!")
        time.sleep(2.4)
        
        game()

    else:
        print("Va bene, divertiti!")
        time.sleep(3)
        
        game()

Anche qui tutto comprensibile, una casuale domanda a cui rispondere sì o no, un controllo sull’input per evitare errori e dei print intervallati per rendere leggibili le scritte.

Infine, un richiamo alla funzione game() che vedremo più tardi.

Visualizzare il cruciverba

per mostrare il fantastico cruciverba con le sue definizioni abbiamo bisogno di una funzione apposita, eccola qui:

def view():

    os.system('cls' if os.name == 'nt' else 'clear')

    for i in range(0, maxcells, maxline):
        print("|", end="")
        for j in range(maxline):
            print(cruciverba[i+j], end="")
            print("|", end="")
        print("\n")

    print("\n")

    print("VERTICALI")
    for ele in definizioniV:
        print(ele, end="\t")
        print(definizioniV[ele])

    print("\n")

    print("ORIZZONTALI")
    for ele in definizioniO:
        print(ele, end="\t")
        print(definizioniO[ele])

    print("\n")

Vi ricordate “maxcells” e “maxline”? Bene, sono tornate per evitare di scrivere a mano i numeri ed incappare in errori di calcolo, che altro non farebbero se non bloccare il programma. Ritroviamo poi il nostro caro OS per pulire il terminale e un paio di for annidati per mostrare il cruciverba a mo’ di tabella.

Infine, stampiamo a schermo i dizionari con le definizioni.

askO / askV

Bene bene bene, ora entriamo in qualche funzione più caruccia: gli “ask”. Questi ci serviranno per modificare in maniera capillare le caselle del cruciverba, vediamo come.

def askO(n):

    c = 0

    for i in range(n, maxcells+1):
        if cruciverba[i] == "#":
            c = i-n
        else:
            c = (((n//maxline)+1)*maxline)-n

    print(f"Lunghezza della parola {c} caratteri")

    p = input("inserisci la parola:\n")

    if len(p) < c:
        print("Parola troppo corta, reinserire")
        askO(n)
    elif len(p) > c:
        print("Parola troppo lunga :/")
        askO(n)
    else:
        p = p.upper()
        for i in range(len(p)):
            cruciverba[n+i] = p[i]

#----------------------------------------------------------------------#


def askV(n):

    if n % maxline == 0 and n!=0:
        c = -1
    else:
        c = 0

    for i in range(n, maxcells, maxline):
        if cruciverba[i] == "#":
            break
        else:
            c += 1

    print(f"Lunghezza della parola {c}caratteri")

    p = input("Inserisci la parola:\n")

    if len(p) < c:
        print("Parola troppo corta, reinserire")
        askV(n)
    elif len(p) > c:
        print("Parola troppo lunga :/")
        askV(n)
    else:
        p = p.upper()
        for i in range(len(p)):
            cruciverba[n+i*maxline] = p[i]


Al contrario delle altre funzioni viste finora, queste due necessitano di un parametro “n”, che vedremo essere il numero della cella da cui si vuole partire per scrivere la parola, da qui il programma conterà quanti sazi vuoti ci restano fino alla fine della riga o quanto dista la prossima casella annerita. Come potrete notare, tra le due funzioni cambia il come vengono modificate le celle: nella prima notiamo un cambiamento in successione diretta, il secondo tramite intervalli dettati dalla variabile “maxline”.

correzione del cruciverba

Ora vediamo due funzioni che vengono richiamate nella fase finale del gioco: correggi() e viewFin().

def correggi():

    if cruciverba == crucicorretto:
        toc = time.perf_counter()
        print(
            f"Complimenti! \n Hai completato il cruciverba in maniera corretta in {tentativi} tentativi impiegando {tic-toc}  secondi")
        viewfin()
    else:
        print("Mi dispiace, hai commesso degli errori, riprova :( ")
        view()
        
#----------------------------------------------------------------------#

def viewfin():

    for i in range(0, maxcells, maxline):
        print("|", end="")
        for j in range(maxline):
            print(cruciverba[i+j], end="")
            print("|", end="")
        print("\n")

La prima corregge il cruciverba e ci dice se abbiamo commesso degli errori, in caso negativo ci invia un messaggio di congratulazioni mostrandoci il numero di tentativi ed il tempo impiegato. (tramite le variabili supplementari che abbiamo inizializzato poco prima.

La seconda è una versione ridotta della funzione view(), vista prima. Infatti mostra solamente il cruciverba completato senza le definizioni.

Il gioco completo

Ora che abbiamo visto le funzioni principali, direi che sia il momento di mostrarvi come vengono sfruttate.

def game():

    while True:

        view()

        n = -1

        while not (0 <= n <= maxcells-1):
            n = int(input("Inserisci la cella:\n"))
            if cruciverba[n] == "#":
                print("Hey, non puoi cambiare le caselle annerite")
                n = -1

        t = ""
        while not(t == "o" or t == "v" or t == "orizzontale" or t == "verticale"):
            t = input("Verticale o orizzontale:\n")
            t = t.lower()

        if t == "orizzontale" or t == "o":
            askO(n)
        elif t == "verticale" or t == "v":
            askV(n)

        if cruciverba.count(" ") == 0:
            risp = input("Cruciverba completato, correggerlo? (sì/no) \n")
            risp = risp.lower()
            if risp == "sì":
                tentativi += 1
                correggi()

Come vedete, c’è un ciclo infinito che richiama la funzione view(), che mostra il cruciverba con le definizioni, poi c’è la richiesta della cella “n” in cui inserire la parola, segue la decisione di inserire la parola in orizzontale o verticale. In questo punto vengono richiamate le funzioni “ask”, viste antecedentemente che chiederanno la parola da inserire nel cruciverba.

Ultimo ma non per importanza, un piccolo controllo per verificare quando il cruciverba viene completato (ovvero quando non ci sono più caselle vuote), che rimanda alla funzione di correzione illustrata appena sopra.

Mettiamo tutto insieme

Come avrete notato, anche game() è una funzione, quindi va richiamata, bene: ecco come ho fatto io:


def main():
    loading()
    askTuto()

#----------------------------------------------------------------------#
#----------------------------------------------------------------------#

if __name__ == "__main__":
    main()

Considerato che tutto gira attorn oad un loop della funzione game, e le altre funzioni “ask” e di controllo vengono eseguite da quella non ci sarà bisogno di richiamarle. Per richiamare game() basta eseguire “askTuto”, funzione che vi ho spiegato subito dopo a quella del loading screen. Se vi ricordate, alla fine c’era appunto la stringa di codice che richiamava l’avvio del gioco, nulla di più semplice. Potremmo dire che il “programma” è solo composto da 2 linne di codice: il comando

` if name==’main’: `;

questo perchè è l’unico al di fuori di una qualsiasi funzione.

Conclusioni

Che ne dite di questo nuovo progetto? Siete curiosi dei suoi possibili aggiornamenti?

Io sì, quindi anche se a voi non interessano li porterò ugualmente :)

Ah… ora sto lavorando sul “digitalizzare” una partita di scopa…. prossimo articolo?