1. Home
  2. iThink
  3. Programmazione per Ragazzi
  4. Battaglia navale in Python: liste, cicli e logica di gioco
Programmazione per Ragazzi Divento Programmatore · Capitolo 3

Battaglia navale in Python: liste, cicli e logica di gioco

Codice Python sullo schermo di un computer
Foto: JohnsonMartin · Pixabay · Licenza dei contenuti Pixabay

Divento Programmatore — Capitolo 3: Battaglia Navale Testuale

Obiettivi di Apprendimento

Al termine di questo capitolo avrai costruito un gioco completo di Battaglia Navale in modalità testuale e imparerai a:

  • Creare e gestire matrici (liste di liste) per rappresentare griglie bidimensionali
  • Accedere a singoli punti su un piano cartesiano tramite coordinate (riga, colonna)
  • Usare cicli for nidificati per scorrere e stampare griglie bidimensionali a schermo
  • Posizionare oggetti in modo casuale su una griglia senza andare fuori dai bordi
  • Gestire le risposte dell'utente convalidando l'input per evitare errori o crash

Termini Fondamentali

| Termine | Simbolo | Significato | | --------- | --------- | ------------- | | Matrice (matrix) | 📊 | Struttura dati organizzata in righe e colonne, creata in Python inserendo liste dentro altre liste | | Cicli nidificati (nested loops) | 🔁 | Struttura in cui un ciclo (interno) viene eseguito interamente per ogni iterazione di un altro ciclo (esterno) | | Riga (row) | ↔️ | La linea orizzontale in una griglia | | Colonna (column) | ↕️ | La linea verticale in una griglia | | Coordinate (coordinates) | 🗺️ | Coppia di numeri (riga, colonna) che individua in modo univoco una cella in una griglia | | join() | 🔗 | Metodo Python che unisce gli elementi di una lista in una singola stringa, usando un separatore a scelta |


La Storia: La battaglia navale dai fogli di carta ai pixel

Prima di essere uno dei giochi da tavolo più famosi della Milton Bradley (e persino un film hollywoodiano!), la Battaglia Navale era un semplice gioco con carta e matita. Fu inventato dai soldati francesi e russi durante la Prima Guerra Mondiale, col nome di L'Attaque. I soldati disegnavano due griglie sui loro taccuini per segnare le proprie navi e quelle nemiche, urlando le coordinate sotto le tende dei campi militari.

Nel 1930, la Milton Bradley pubblicò la prima versione commerciale cartacea, ma la vera rivoluzione avvenne nel 1977 con la commercializzazione di Battleship, una valigetta di plastica con pioli rossi e bianchi che emetteva suoni elettronici realistici di esplosioni quando colpita.

Nello stesso periodo, i programmatori dei primi mainframe (i computer giganti che occupavano intere stanze nelle università) scrissero le prime versioni testuali del gioco. Non c'erano grafica o animazioni 3D: il computer stampava caratteri di testo per mostrare l'oceano (~), i colpi andati a vuoto (*) e le navi affondate (X). Quello che stai per programmare è un pezzo di storia dell'informatica!


Concept 1: Liste di Liste (La Griglia di Gioco)

Fino a ora hai usato le liste come una fila ordinata di oggetti. Ma cosa succede se vuoi rappresentare qualcosa che si sviluppa in due dimensioni, come una scacchiera o la griglia del gioco del tris? Semplice: inseriamo una lista dentro un'altra lista! Questo concetto prende il nome di Matrice o Lista di liste.

Immagina un condominio: ogni piano è una lista che contiene diversi appartamenti.

Vediamo come creare una griglia 3x3 in Python:

griglia = [
    ["~", "~", "~"],  # Riga 0
    ["~", "~", "~"],  # Riga 1
    ["~", "~", "~"]   # Riga 2
]

Per leggere cosa c'è in una cella specifica, dobbiamo prima selezionare la riga (la lista esterna) e poi la colonna (la posizione dentro la lista interna):

# Leggiamo la riga 1, colonna 2
valore = griglia[1][2]

Ricorda: gli indici in Python partono sempre da 0. Quindi griglia[1][2] indica il terzo appartamento al secondo piano!


Concept 2: Coordinate (Riga e Colonna)

Quando giochiamo a Battaglia Navale, diciamo coordinate come "B4" o "C2". Nel nostro programma testuale useremo numeri sia per le righe sia per le colonne.

Ad esempio, se l'utente vuole sparare sulla cella alla riga 3 e colonna 4, in Python dovremo sottrarre 1 a entrambi i numeri per ottenere gli indici corretti (0-based):

riga_indice = 2
colonna_indice = 3

E poi scriveremo:

griglia[riga_indice][colonna_indice] = "X"

Concept 3: Il Ciclo Nidificato (Un loop dentro l'altro)

Come facciamo a stampare la nostra griglia sullo schermo in modo ordinato, riga dopo riga? Dobbiamo usare un ciclo for nidificato (ovvero un ciclo dentro l'altro).

for riga in griglia:
    for cella in riga:
        print(cella, end=" ")
    print()  # Va a capo alla fine di ogni riga

Come funziona questo meccanismo?

  1. Il ciclo esterno seleziona la prima riga di griglia.
  2. Il ciclo interno prende ogni cella di quella riga e la stampa seguita da uno spazio (end=" ").
  3. Quando il ciclo interno finisce tutte le celle di quella riga, il print() vuoto manda il testo a capo.
  4. Il ciclo esterno passa alla riga successiva, e il processo si ripete.

Costruiamo il Gioco: Versione Base

Creiamo una versione minimale in cui c'è una sola nave da 1 cella nascosta in una griglia 5x5.

import random

# Creiamo la griglia 5x5 vuota
griglia = []
for riga in range(5):
    griglia.append(["~"] * 5)

# Posizioniamo la nave nemica in una coordinata casuale
nave_riga = random.randint(0, 4)
nave_colonna = random.randint(0, 4)

print("🚢 BENVENUTO A BATTAGLIA NAVALE (BASE) 🚢\n")

# Mostriamo la griglia iniziale
for riga in griglia:
    print(" ".join(riga))

# Chiediamo le coordinate al giocatore
scelta_riga = int(input("\nScegli la riga (1-5): ")) - 1
scelta_colonna = int(input("Scegli la colonna (1-5): ")) - 1

# Verifichiamo se ha colpito la nave
if scelta_riga == nave_riga and scelta_colonna == nave_colonna:
    print("💥 COLPITO E AFFONDATO! Hai vinto! 🎉")
    griglia[scelta_riga][scelta_colonna] = "X"
else:
    print("💦 ACQUA! La nave era in un altro punto.")
    griglia[scelta_riga][scelta_colonna] = "*"

# Mostriamo la griglia finale
for riga in griglia:
    print(" ".join(riga))

Sfide: Fai salire di livello la tua Battaglia Navale

Sfida 1 — Munizioni Limitate

Una partita in cui puoi sparare all'infinito è troppo facile. Aggiungiamo un contatore di turni (munizioni): il giocatore ha a disposizione solo 10 colpi. Se finisce i colpi senza affondare la nave, ha perso!

Sfida 2 — Mappa di Gioco Dinamica

Il gioco deve mostrare la griglia aggiornata a ogni turno, così che il giocatore possa ricordare dove ha già sparato ed evitare di sprecare munizioni nello stesso punto.

Sfida 3 — Flotta di 3 Navi

Invece di una sola nave, nascondiamo 3 navi da 1 cella ciascuna. Il giocatore vince solo quando le trova tutte e tre! Dobbiamo assicurarci che il computer non posizioni accidentalmente due navi sulla stessa identica cella.

Sfida 4 — La Corazzata (Nave Multi-Cella)

Invece di navi da 1 cella, inseriamo una corazzata lunga 3 celle adiacenti. La corazzata può essere posizionata in orizzontale o in verticale. Il giocatore dovrà colpire tutte e 3 le parti per vincere!


Il Codice Completo con Tutte le Sfide

Salva questo codice in Thonny come battaglia_navale.py per provarlo con i tuoi amici!

import random
import os

dimensione_griglia = 5
max_munizioni = 10
lunghezza_corazzata = 3

def crea_griglia():
    """Crea una griglia vuota (lista di liste di stringhe onde '~')."""
    griglia = []
    for riga in range(dimensione_griglia):
        griglia.append(["~"] * dimensione_griglia)
    return griglia

def mostra_griglia(griglia):
    """Mostra la griglia a schermo con intestazione di colonne e righe."""
    print("\n   1 2 3 4 5")  # Intestazione colonne
    print("  " + "-" * 11)
    for riga_idx, riga in enumerate(griglia, 1):
        riga_str = " ".join(riga)
        print(f"{riga_idx} | {riga_str}")
    print()

def genera_corazzata():
    """Sfida 4: Genera le coordinate di una nave lunga 3 celle adiacenti
    (in orizzontale o in verticale)."""
    orientamento = random.choice(["orizzontale", "verticale"])
    coordinate_nave = []
    
    if orientamento == "orizzontale":
        riga = random.randint(0, dimensione_griglia - 1)
        colonna_inizio = random.randint(0, dimensione_griglia - lunghezza_corazzata)
        for i in range(lunghezza_corazzata):
            coordinate_nave.append((riga, colonna_inizio + i))
    else:
        riga_inizio = random.randint(0, dimensione_griglia - lunghezza_corazzata)
        colonna = random.randint(0, dimensione_griglia - 1)
        for i in range(lunghezza_corazzata):
            coordinate_nave.append((riga_inizio + i, colonna))
            
    return coordinate_nave

def leggi_coordinata(messaggio):
    """Legge una coordinata numerica validata (1-5) per prevenire crash."""
    while True:
        try:
            valore = int(input(messaggio))
            if 1 <= valore <= dimensione_griglia:
                return valore - 1  # Convertiamo in indice 0-based per il codice
            else:
                print(f"Valore fuori range! Inserisci un numero da 1 a {dimensione_griglia}.")
        except ValueError:
            print("Devi inserire un numero intero!")

def gioca():
    griglia = crea_griglia()
    corazzata = genera_corazzata()
    colpite = []
    colpi_sparati = set()
    munizioni = max_munizioni
    
    print("╔══════════════════════════════════════╗")
    print("║      🚢 BATTAGLIA NAVALE 🚢          ║")
    print("║  Trova e distruggi la corazzata!     ║")
    print("║  La nave occupa 3 celle adiacenti    ║")
    print("╚══════════════════════════════════════╝")
    
    # Ciclo di gioco principale
    while munizioni > 0 and len(colpite) < lunghezza_corazzata:
        mostra_griglia(griglia)
        print(f"Munizioni rimaste: {munizioni} 💣 | Nave colpita: {len(colpite)}/{lunghezza_corazzata} 💥")
        
        print("\n--- Inserisci le coordinate del colpo ---")
        riga = leggi_coordinata("Scegli la riga (1-5): ")
        colonna = leggi_coordinata("Scegli la colonna (1-5): ")
        
        coordinata = (riga, colonna)
        
        # Evitiamo di far sprecare colpi ripetuti nello stesso posto
        if coordinata in colpi_sparati:
            print("⚠️ Hai già sparato in quel punto! Scegli altre coordinate.")
            continue
            
        colpi_sparati.add(coordinata)
        munizioni -= 1
        
        # Sfida 2: Verifichiamo ed aggiorniamo la griglia con X o *
        if coordinata in corazzata:
            print("🔥 COLPITO! Ottimo tiro!")
            griglia[riga][colonna] = "X"
            colpite.append(coordinata)
        else:
            print("💦 ACQUA! Nessuna nave lì.")
            griglia[riga][colonna] = "*"
            
        if len(colpite) == lunghezza_corazzata:
            mostra_griglia(griglia)
            print("🎉 COMPLIMENTI! Hai affondato la corazzata nemica! 🎉")
            return
            
    # Se finiamo le munizioni prima di affondarla
    mostra_griglia(griglia)
    print("💀 GAME OVER! Le munizioni sono finite. La nave nemica è scappata!")
    
    # Rivela la posizione della nave
    for riga, colonna in corazzata:
        if griglia[riga][colonna] != "X":
            griglia[riga][colonna] = "N"
    print("La nave nemica si nascondeva qui ('N'):")
    mostra_griglia(griglia)

if __name__ == "__main__":
    gioca()

Analisi del Codice Completo

Analizziamo i concetti cardine applicati in questo gioco:

1. Costruzione dinamica della matrice

Nella funzione crea_griglia, usiamo griglia.append(["~"] * dimensione_griglia). L'operatore di moltiplicazione * applicato a una lista ne duplica gli elementi. Quindi ["~"] * 5 produce ["~", "~", "~", "~", "~"]. Facendo questo cinque volte all'interno del ciclo for, otteniamo la griglia finale.

2. Posizionamento logico della Corazzata (Sfida 4)

Per generare la nave da 3 celle in orizzontale o in verticale, definiamo prima una direzione casuale. Se è "orizzontale", la nave si sviluppa da sinistra a destra: la riga viene scelta a caso tra 0 e 4, mentre la colonna di partenza può andare solo da 0 a 2 (dimensione_griglia - lunghezza_corazzata). Questo impedisce che la nave sporga fuori dalla griglia (ad esempio posizionando una parte a colonna 4 e una a colonna 5).

3. Il metodo .join()

Per visualizzare la griglia in modo gradevole anziché mostrare parentesi e virgole (es: ['~', '~']), usiamo " ".join(riga). Questo metodo prende tutti gli elementi della riga e li unisce in una singola stringa inserendo uno spazio tra di essi.

4. Uso dei Set per lo storico dei colpi

Usiamo colpi_sparati = set(). Un set in Python è una raccolta di elementi univoci. Usarlo ci permette di memorizzare le coordinate sotto forma di tuple (riga, colonna) e verificare all'istante con l'operatore in se il giocatore ha già sparato lì.


Curiosità: Cartesio e la mosca

Lo sapevi che la griglia cartesiana deve il suo nome a Cartesio (René Descartes)? Si racconta che l'idea di usare coppie di numeri per descrivere punti su una superficie gli venne guardando una mosca camminare sul soffitto della sua stanza, mentre cercava un modo per descriverne la posizione!


Provaci tu! — Mappa più grande

Prova a ingrandire la griglia portandola a 8x8 (modificando la variabile dimensione_griglia = 8) e aumenta il numero di munizioni a 15. Ricordati di aggiungere più navi per rendere la battaglia ancora più epica!


Il Piccolo Glossario

| Termine | Definizione | | --------- | ------------- | | Nested list / Lista nidificata | Una lista che contiene altre liste al suo interno, usata per creare griglie. | | Nested loops / Cicli nidificati | Struttura dove un ciclo è inserito all'interno del corpo di un altro ciclo. | | Matrix / Matrice | Una tabella bidimensionale organizzata in righe e colonne. | | Tuple / Tupla | Una struttura dati Python immutabile, racchiusa tra parentesi tonde, usata qui per memorizzare le coordinate (riga, colonna). | | set | Una collezione non ordinata di elementi univoci, ottimizzata per controlli rapidi di appartenenza. | | .join() | Metodo delle stringhe che unisce una lista di stringhe in un'unica stringa. |


Prossimo capitolo

Capitolo 4: Calcolatrice Parlante
Funzioni (def/return), try/except, ZeroDivisionError/ValueError/OverflowError, raise esplicito, variabili globali (memoria MS/MR/MC), cronologia, personalità ironica. Codice completo con 9 operazioni menu.


Questo post è un estratto dal libro "Divento Programmatore: Python e i Giochi" (Volume 2 della collana "Cervello Elettronico") di Rocco Balzamà. Target: 10-14 anni · ~180 pagine · Thonny + VS Code.

Per approfondire

Fonti e riferimenti

Condividi LinkedIn Facebook X WhatsApp
Continua a leggere

Tutti gli articoli del blog

Strumenti gratuiti