Laboratorio di programmazione (11 gennaio 2017)

Smazzare una partita di bridge

Classe Carta

Implementate una classe di nome Carta che rappresenta una carta da gioco. Internamente, la classe deve mantenere un attributo di tipo intero che contiene il seme (0=cuori, 1=quadri, 2=fiori, 3=picche) e un attributo di tipo intero che contiene il valore (1=asso, 2=due, eccetera). La classe deve avere i seguenti costruttori e metodi:

Sperimentate la classe, realizzando un programma di test che permette di inserire carte immettendo seme e valore. Per leggere seme e valore utilizzate Scanner.nextToken() e Scanner.nextInt() in un ciclo che termina quando Scanner.hasNext() restituisce false.

Classe Giocatore

Implementate una classe di nome Giocatore che rappresenta un giocatore. Internamente, la classe deve mantenere un attributo di tipo stringa che contiene il nome del giocatore, e una lista di carte che rappresenta le carte correntemente in mano al giocatore. La classe deve avere i seguenti costruttori e metodi:

Classe Smazzata

Implementate una classe di nome Smazzata contenente un metodo main() che genera una permutazione aleatoria di un mazzo completo, e distribuisce le carte del mazzo a quattro giocatori inseriti dall'utente. La classe deve stampare i quattro giocatori (in modo che si possa vedere la smazzata).

Per ottenere una permutazione aleatoria procedete come segue:

  1. Costruite utilizzando due cicli annidati un vettore mazzo di 52 carte contenente un mazzo ordinato.
  2. Permutate il vettore utilizzando un generatore di numeri pseudocasuali:
    	Collections.shuffle( Arrays.asList( mazzo ), new Random() );
    

    Il metodo static Collection.shuffle() permuta in maniera aleatoria una lista, dato un generatore di numeri pseudocasuali (new Random()). Il metodo Arrays.asList() restituisce una classe lista involucro basata sull'array argomento.

  3. A questo punto, create quattro giocatori utilizzando nomi letti da riga di comando (cioè dal vettore argomento del main()), e assegnate a ciascun giocatore 13 carte come farebbe un mazziere: al primo giocatore le carte di indice 0, 4, 8, ecc.. Al secondo giocatore le carte di indice 1, 5, 9, etc. E così via.

Per finire, stampate i giocatori, in modo da poter controllare che le loro smazzate siano corrette e stampate nell'ordine corretto.

Esempio di funzionamento

java Smazzata Marco Mario Marta Maria
Marco 8♥ Q♥ A♥  6♦ J♦ K♦ A♦  2♣ 5♣ 8♣ K♣ A♣  9♠
Mario 2♥ 4♥ 5♥ 10♥ J♥ K♥  2♦ 8♦ 9♦  9♣  5♠ 6♠ A♠
Marta 3♥ 6♥  4♦ 10♦  3♣ 4♣ 6♣ 7♣ J♣  2♠ 7♠ 8♠ 10♠
Maria 7♥ 9♥  3♦ 5♦ 7♦ Q♦  10♣ Q♣  3♠ 4♠ J♠ Q♠ K♠
java Smazzata Marco Mario Marta Maria
Marco 3♥ 4♥ 7♥  5♦ 8♦ 10♦  7♣ 9♣ Q♣  2♠ 4♠ 8♠ A♠
Mario 9♥ A♥  2♦ 3♦ 6♦ 7♦  2♣ 3♣ 4♣ A♣  3♠ 6♠ K♠
Marta 2♥ Q♥  4♦ Q♦ A♦  6♣ 8♣ J♣ K♣  5♠ 7♠ 10♠ J♠
Maria 5♥ 6♥ 8♥ 10♥ J♥ K♥  9♦ J♦ K♦  5♣ 10♣  9♠ Q♠
java Smazzata Marco Mario Marta Maria
Marco 9♥ Q♥ K♥  7♦ 9♦ A♦  10♣  3♠ 4♠ 5♠ 6♠ 7♠ 10♠
Mario 6♥ J♥ A♥  3♦ 6♦ J♦ Q♦  3♣ 4♣ 8♣ J♣  Q♠ K♠
Marta 4♥ 7♥ 10♥  5♦ 8♦ K♦  5♣ 6♣ 9♣  2♠ 8♠ 9♠ J♠
Maria 2♥ 3♥ 5♥ 8♥  2♦ 4♦ 10♦  2♣ 7♣ Q♣ K♣ A♣  A♠

Life

Life (vita, in inglese) è un automa cellulare inventato dal matematico John Conway per studiare un'emulazione elementare dei processi vitali e diventato famoso dopo la sua descrizione su Scientific American nel 1971.

L'automa è composto da una matrice di n righe e m colonne di elementi, dette cellule, che possono essere in due stati: vive o morte. Attorno a ogni cellula ci sono otto cellule adiacenti (nord, sud, est, ovest, nord-ovest, nord-est, sud-ovest e sud-est): si noti che la matrice è pensata come un toro, e quindi il bordo destro è adiacente a quello sinistro, così come il bordo superiore è adiacente a quello inferiore. Per esempio, la cellula di coordinate (0, 0) ha a nord la cellula (n – 1, 0) e a ovest la cellula (0, m – 1).

Le regole di evoluzione della vita in Life sono molto semplici: se una cellula è viva, sopravvive all'istante di tempo successivo se nel suo intorno ci sono due o tre cellule vive; altrimenti muore per solitudine o sovraffollamento. Se una cellula è morta e nel suo intorno ci sono esattamente tre cellule vive diventa viva all'istante successivo.

Dovete scrivere una classe che simuli Life e visualizzi graficamente l'evoluzione di una matrice le cui dimensioni sono specificate sulla riga di comando. Per ottenere la visualizzazione grafica, potete stampare la matrice emettendo uno spazio per le cellule morte, e un altro carattere (per esempio *) per le cellule vive. Il vostro programma deve prendere in input sulla riga di comando tre parametri: il numero n, il numero m e una probabilità p tra 0 e 1 che verrà utilizzata per inizializzare la matrice: ogni cellula sarà viva indipendentemente con probabilità p.

Suggerimenti

  1. Per realizzare la simulazione, dovete utilizzare due matrici di booleani. La prima matrice rappresenta la configurazione corrente (vero/viva, falso/morta). Da essa potete calcolare la configurazione successiva, che memorizzerete nella seconda matrice. A questo punto potrete ricopiare la seconda matrice sulla prima, visualizzarla e ricominciare daccapo. Notate che per ricopiare la matrice vi serve una coppia di cicli annidati.
  2. Per ottenere l'effetto di considerare la matrice un toro, dovete incrementare o decrementare le coordinate utilizzando l'aritmetica modulare. Ad esempio, se la matrice si chiama a, le celle adiacenti ad a[x][y] sono
    a[(x+1) % n][y]
    a[(x+n-1) % n][y]
    a[x][(y+1) % m]
    a[x][(y+m-1) % m]
    a[(x+1) % n][(y+1) % m]
    a[(x+n-1) % n][(y+1) % m]
    a[(x+1) % n][(y+m-1) % m]
    a[(x+n-1) % n][(y+m-1) % m]
    

    Attenzione: non eliminate i +n e i +m apparentemente ridondanti; sono necessari perché l'implementazione del modulo sui numeri negativi è errata.

  3. Ricordatevi che per convertire i parametri sulla riga di comando in interi vi serve Integer.parseInt(), e per convertirli in double Double.parseDouble().
  4. Per stabilire se una cellula deve essere viva nello stato iniziale potete usare il test
    if (Math.random() < p) ...
    
    che restituirà vero con probabilità p.
  5. È utile, per quanto possibile, strutturare il programma: create un metodo statico visualizza() che stampa la matrice come richiesto in cima allo schermo, e provatelo separatamente.
  6. Per matrici piccole, la visualizzazione potrebbe essere troppo rapida: inserite in tal caso l'istruzione Thread.sleep(100); all'interno del ciclo principale (ritarda l'esecuzione di un decimo di secondo). È necessario che il main() abbia la dichiarazione throws Exception.
  7. Dato che per ottenere un effetto di animazione è necessario sovrascrivere la visualizzazione precedente, potete usare l'istruzione System.out.println("\x1b[H"); per posizionare il cursore nell'angolo in alto a sinistra senza cancellare lo schermo, e l'istruzione System.out.println("\x1b[H\x1b[2J"); per cancellare l'intero schermo all'inizio dell'esecuzione del programma.
  8. Una volta che il programma è ben collaudato, potete provare ad aumentare le dimensioni massime possibili a 1000×1000; scegliendo poi un corpo molto piccolo potete visualizzare animazioni di grandi dimensioni.
  9. Il programma non deve terminare mai (come la vita). Potrete interromperlo comunque con Control-C (come nella fine del mondo).

Suggerimenti aggiuntivi

Questi suggerimenti tolgono un po' del divertimento che c'è nel risolvere un problema da soli, e quindi sono a parte.

  1. Se vi considerate particolarmente furbi, potete utilizzare l'aritmetica modulare per scambiare a ogni iterazione il ruolo delle due matrici, evitando così la copia.
  2. Il modo più semplice di contare quante cellule vive sono presenti attorno a una cellula data è quello di utilizzare due cicli annidati che vanno da –1 a 1 inclusi, i cui contatori rappresentano lo spostamento per righe e per colonne:
    int c = -a[x][y];
    for(int dx=-1; dx<=1; dx++)
      for(int dy=-1; dy<=1; dy++)
        c += a[(x+dx+n) % n][(y+dy+m) % m];