Progetto 2020/21

Questa pagina descrive il progetto per il corso di Informatica della Laurea in Matematica per gli appelli dell'anno accademico 2020/2021.

Una partita di FantaGoriziana

Descrizione generale

Due giocatori di biliardo si affrontano in un incontro di FantaGoriziana, una specialità biliardistica di fantasia.

Durante lo svolgimento del loro incontro viene tenuto un registro dettagliato degli eventi. Per ogni tiro effettuato, vengono segnati sul registro tutti gli urti tra gli oggetti presenti sul tavolo da gioco, in ordine. Per esempio, il registro potrebbe descrivere un tiro con "bilia bianca colpisce bilia gialla" seguito da "bilia gialla colpisce sponda".

Lo scopo del progetto è quello di descrivere, dato il registro di gioco in un file di input, l'andamento della partita. Dopo ogni tiro, va aggiornato il punteggio dei giocatori, e vanno segnalati eventi speciali come i falli o la vittoria di un giocatore. Il resoconto finale fa quindi scritto in un file di output.

Descriviamo qui sotto le regole del gioco.

Regole della FantaGoriziana

Incontro

Per ogni incontro, vengono stabiliti alcuni parametri:

Per tutto l'incontro, a uno dei due giocatori viene associata una bilia di colore bianco, mentre all'altro viene associata una bilia di colore giallo. I giocatori vengono quindi chiamati "giocatore bianco" e "giocatore giallo".

Tavolo da gioco

Sul tavolo da gioco sono presenti i seguenti oggetti.

Tiro

Un tiro è una sequenza di urti tra gli oggetti che stanno sul tavolo. Un urto tra un oggetto X e un oggetto Y viene scritto come X-Y ("X colpisce Y"). Per esempio, se la bilia bianca B colpisce la sponda s1 e poi la bilia gialla G facendole abbattere il birillo esterno e2, il tiro viene rappresentato con la seguente sequenza, che scriviamo su una singola riga separando con uno spazio gli urti:

B-s1 B-G G-e2

Partita

In una partita i giocatori tirano alternandosi a vicenda. Dopo il giocatore bianco tira il giallo, e viceversa.

Il giocatore che effettua il primo tiro viene alternato tra le varie partite in un incontro, iniziando dal bianco. Quindi, i tiri della prima partita seguono lo schema B,G,B,G,..., quelli della seconda G,B,G,B,..., quelli della terza B,G,B,G,... e così via.

Quando è al tiro un giocatore, la sua bilia viene detta "battente", e quella dell'altro giocatore "avversaria".

Ogni partita inizia col punteggio di zero a zero. Dopo ogni tiro viene aggiornato il punteggio fino a raggiungere o superare PP punti.

Calcolo del punteggio per ogni tiro

Diamo prima alcune definizioni:

Tiro impossibile

Un tiro viene detto "impossibile" se viola almeno uno dei seguenti vincoli:

  1. nessun oggetto, tranne la bilia battente e la mano M, può colpire qualcosa senza precedentemente essere colpito
  2. le sponde non possono colpire
  3. la mano non può essere colpita
  4. i birilli possono colpire solo altri birilli
  5. nessun oggetto può colpire se stesso
  6. il tiro viene effettuato dopo che l'incontro si è concluso

Il calcolo del punteggio viene quindi definito solo per i tiri non impossibili.

Birillo abbattuto

Un birillo abbattuto è un birillo colpito da un altro oggetto. Un birillo viene considerato abbattuto solo dal primo oggetto da cui viene colpito nel tiro. Prima di ogni tiro, tutti i birilli vengono riposizionati sul tavolo da gioco e sono considerati non ancora abbattuti.

Tiro indiretto

Un tiro è indiretto se il primo oggetto colpito dalla bilia battente, ignorando birilli già abbattuti, è una sponda. In tutti gli altri casi (incluso quelli in cui non viene colpito nulla dalla battente) il tiro è diretto.

Fallo

In un tiro, si commette fallo in uno dei seguenti casi:

  1. la mano colpisce una bilia o abbatte un birillo
  2. il primo oggetto colpito dalla bilia battente dopo eventuali sponde e birilli già abbattuti:
  3. non si applicano i punti precedenti, e la bilia battente abbatte un birillo ("bevuta")

Nota bene: se la bilia battente abbatte un birillo è sempre fallo, ma non sempre "bevuta".

Un tiro senza falli viene detto regolare.

Aggiornamento Punteggio

Si sommano i seguenti punti

Nei tiri indiretti i punti sopra contrassegnati con [*] vengono raddoppiati.

Tutti i punti così sommati sono assegnati al giocatore battente se il tiro è regolare. Altrimenti, vengono assegnati al giocatore avversario.

Implementazione in Java

Si chiede di scrivere una funzione Java avente (esattamente) nome e tipo come segue:

public static void matchLog(String inputFileName, String outputFileName)

Il parametro inputFileName indica un nome di un file da cui leggere, contenente il registro degli eventi dell'incontro.

Il parametro outputFileName indica un nome di un file in cui scrivere il resoconto finale dell'incontro.

Quando la funzione viene chiamata, vi viene garantito che il file di input inputFileName è un file di testo con esattamente la forma seguente:

  NP PP NB
  tiro1
  tiro2
  ...
  tiroK

Nella prima riga del file troviamo le costanti NP PP NB che sono i parametri dell'incontro (già descritti sopra). Dalla seconda riga in poi troviamo i tiri, uno per riga. Ogni tiro è rappresentato nel file come una sequenza di urti (come già descritto sopra). Non ci sono altre indicazioni nel file, e in particolare non ci sono "separatori" tra una partita e la successiva.

La funzione matchLog deve creare il file di output outputFileName e scrivere lì, tiro per tiro, il resoconto dell'incontro, definito come segue.

Se un tiro è impossibile, deve essere scritta nel file una riga con la parola impossibile, e il tiro va altrimenti ignorato.

Altrimenti, deve essere scritto nel file il punteggio raggiunto dopo il tiro, nel formato puntiBianco puntiGiallo. In caso di fallo, dopo il punteggio va aggiunto uno spazio e la parola fallo.

Nella riga subito dopo, se uno dei giocatori si è aggiudicato una partita, va aggiunta una nuova riga indicando il vincitore, usando partita bianco oppure partita giallo.

Nella riga subito dopo, se uno dei giocatori si è aggiudicato non solo una partita ma pure l'incontro, va aggiunta una nuova riga indicando il vincitore, usando incontro bianco oppure incontro giallo.

In caso di input malformato non corrispondente a quanto scritto subito sopra (per esempio, B-T come urto, o i23 come birillo quando NB=4) non si richiede nessun comportamento particolare. Il programma può dare un risultato qualunque, o anche generare un errore a tempo di esecuzione.

Si ribadisce che il formato di file di sopra non garantisce che non ci siano tiri "impossibili". Questi possono essere presenti e vanno gestiti come spiegato sopra.

Ecco un micro-esempio di file di input:

  3 40 4
  B-G G-c
  G-B B-s1 B-i1
  B-G
  G-B R-B
  G-s4 G-B B-e3 G-i2
  G-B B-s2
  B-s2 B-i2 i2-e1
  G-B
  B-s1 B-G G-c
  G-B

Sull'input di sopra, va generato il seguente output.

  30 0
  30 8
  30 8
  impossibile
  50 8 fallo
  partita bianco
  0 0
  0 22 fallo
  0 22
  60 22
  partita bianco
  incontro bianco
  impossibile

Qui trovate lo stesso output commentato:

  30 0             # centrale da solo: 10+5*4=30 punti
  30 8             # interno: 8 punti
  30 8             # niente punti, niente falli
  impossibile      # R non può muoversi senza essere colpita
  50 8 fallo       # indiretto (2+8)*2=20 punti, + 0 di "bevuta"
  partita bianco   # 50 >= 40 quindi vince bianco
  0 0              # niente punti, niente falli
  0 22 fallo       # indiretto (2+8)*2=20 + 2 di fallo
  0 22             # niente punti, niente falli
  60 22            # indiretto centrale da solo 2*(10+5*4)=60 punti
  partita bianco   # 60 >= 40 quindi vince bianco
  incontro bianco  # 2 partite vinte su 3, quindi vince l'incontro
  impossibile      # l'incontro è finito, niente tiri

Consigli

Ricordatevi che, quando confrontate due String in Java come per esempio i nomi degli oggetti, dovete usare s1.equals(s2) e non s1 == s2.

Potrebbe essere utile usare i comandi s.split(...), s.charAt(...), s.substring(...) per manipolare le stringhe.

Svolgimento

Si chiede di scrivere una singola classe Java Progetto, che contenga la funzione matchLog descritta sopra. In particolare, è obbligatorio usare come base per il progetto un progetto Eclipse contenente il il seguente scheletro di di classe Java.

La classe scheletro va posizionata in un progetto Eclipse come segue. Create un progetto Eclipse chiamato cognome_matricola (usate i vostri dati, per esempio tramaglino_000001 o de_paperoni_000002), e create la classe Progetto al suo interno nella cartella src. Dovete ottenere un file come segue:

È obbligatorio rispettare la posizione del file della classe come descritto sopra. Dentro quel file potete quindi inserire il codice dello scheletro fornito.

È tassativamente proibito modificare lo scheletro nelle parti segnate al suo interno come da non modificare. In particolare, non si può modificare il tipo o il nome delle procedure / funzioni già presenti all'interno. In nessun caso il file consegnato dovrà contenere una dichiarazione package.

È invece consentito (e consigliabile) definire delle procedure/funzioni addizionali all'interno della classe Progetto. È consentito aggiungere import per usare librerie di Java.

È consentito modificare il metodo main della classe nello scheletro. Si noti, tuttavia, che tale metodo potrà essere cancellato e sovrascritto, o comunque non eseguito, da chi corregge. Di conseguenza, se si decidono di usare variabili globali, queste devono essere inizializzate dentro la funzione matchLog, e non dentro il main, in quanto quest'ultimo non verrà eseguito.

Durante la correzione, la funzione matchLog verrà chiamata ripetutamente eseguendo (anche) test automatizzati.

Helper

Per potere testare più facilmente il vostro progetto, vi viene fornita la seguente libreria Java. L'uso di questa libreria è opzionale.

Dopo avere scaricato il file di sopra in una qualunque cartella, potete inserire la libreria helper nel vostro progetto Eclipse, selezionando il vostro progetto, facendo clic destro, e selezionando dal menù la voce Build Path -> Add External Archives.... Comparirà un finestra dove potete selezionare il file jar fornito sopra.

Dopo avere aggiunto la libreria helper al vostro progetto, all'interno del file Java Progetto.java potete usare le funzioni della libreria, che mostriamo sotto. Nello scheletro di progetto che vi forniamo, trovate già degli esempi su come usarle nel main(), dopo avere usato import progetto2020.Helper; all'inizio del file.

static void Helper.generate(String inputFileName)

static boolean Helper.test(String inputFileName, String outputFileName)

static void Helper.setSeed(long seed)

La procedura Helper.generate prende come parametro un nome di file. La procedura crea un file con quel nome (se già esistente, viene sovrascritto, quindi state attenti a non cancellare un file con dati importanti). Il file così creato è un file random del formato previsto per il registro di gioco, lo stesso del file di input della vostra procedura matchLog. Questo file contiene quindi i tiri di un'immaginaria partita, inclusi tiri impossibili, tiri con falli, tiri regolari e così via. Potete usare un file simile per chiamare la vostra matchLog e fare dei test.

La funzione Helper.test prende come argomenti gli stessi parametri della vostra procedura matchLog. Se chiamata subito dopo la matchLog, controlla che il file di output generato contengo il resoconto esatto relativo al registro di gioco che si trova nel file di input. Se l'output è corretto, restituisce il valore true

La procedura Helper.setSeed serve per impostare il seed del generatore di numeri casuali a un valore prefissato. Di norma, non serve chiamare questa funzione. Tuttavia, se uno è interessato a effettuare esperimenti di test riproducibili, può chiamare questa funzione subito prima della Helper.generate, passando un numero intero a piacere. Facendo ciò, la Helper.generate produrrà un file di output che dipende in modo deterministico dall'intero scelto. Nel caso in cui in un momento successivo uno voglia ripetere lo stesso test, basterà riusare lo stesso numero intero. Durante la nostra correzione dei progetti, in caso vengano falliti alcuni test, potremmo fornire i numeri interi relativi in modo da consentirvi di ripetere il test fallito.

Usare l'helper non è obbligatorio (anche se fortemente raccomandato). Se non lo volete usare, per potere compilare lo scheletro dovete rimuovere dal file sia la riga import progetto2020.Helper; che tutti i riferimenti alle funzioni Helper.

Nota bene: se usate l'helper un certo numero di volte, e se sui file così generati la vostra funzione fornisce il risultato atteso, questo non vuol dire che la funzione sia corretta in tutti i casi possibili. È possibile che, in fase di valutazione del progetto, vengano controllati altri casi, e che su questi si scopra che il vostro codice non funziona correttamente. In altre parole: con i test possiamo solo dimostrare che un programma è scorretto, ma mai che è corretto.

Requisiti e criteri di valutazione

Il progetto verrà valutato secondo i seguenti criteri, che descrivono un insieme di requisiti sul codice consegnato. Ogni requisito primario deve essere rispettato rigidamente: in caso di violazione, anche minima, di uno di questi requisiti la prova d'esame non è superata. I requisiti secondari devono essere generalmente rispettati. Violazioni minori saranno tollerate, ma violazioni gravi possono comunque causare il non superamento della prova.

Requisiti tecnici (primario). La soluzione consegnata deve seguire lo scheletro di progetto fornito sopra, modificato nel modo descritto sopra.

Devono inoltre essere rispettati questi requisiti:

  1. Non modificate il nome della classe Progetto, i nomi o i tipi delle procedure/funzioni segnate come da non modificare. Non ci deve essere nessuna dichiarazione di package della classe.
  2. Posizionate la classe Progetto come descritto sopra nel vostro progetto Eclipse.
  3. I file di input e output devono essere quelli passati come parametri, e non altri con un nome prefissato. Per esempio, new File(fileName) usa il parametro, mentre new File("fileName") usa un nome prefissato.
  4. Le procedure/funzioni richieste non devono interagire con l'utente in nessun modo (ad es., chiedere di inserire dati all'utente). I nostri test devono potere chiamare ripetutamente quelle funzioni in modo automatico, senza il nostro intervento manuale. Il main invece può interagire con l'utente, se desiderato (ma comunque come detto sopra il codice del main può essere ignorato da noi durante i test.)
  5. Non usate lettere accentate nei vostri file Java, neppure all'interno delle stringhe o dei commenti. (In passato hanno creato problemi di compilazione, visto che i sorgenti arrivavano in encoding diversi.)

Il non rispettare questi requisiti tecnici può causare il fallimento dei nostri test automatizzati, ed in tal caso la prova non sarà superata.

Correttezza (primario). Non ci devono essere errori a tempo di compilazione: il programma deve compilare. Non ci devono essere errori a tempo di esecuzione: il programma non deve generare eccezioni (per esempio, DivisionByZero oppure ArrayIndexOutOfBounds) quando eseguito su un input ben formato. Il programma deve dare l'output desiderato su qualunque input compatibile con la specifica.
Nota bene: Nel caso il programma sia scorretto, non è compito di chi corregge fare il debugging, ovvero identificare la causa dell'errore e suggerire una modifica per rimuoverla. Anche quando tale causa fosse nota a chi corregge, non verrà comunicata allo studente, in quanto sarebbe come suggerire una parte non banale della soluzione della prova. Infatti, capita frequentemente che correggere l'errore dopo averlo individuato diventi banale ("devo usare x, e non x+1"), e che la prova di conseguenza consista prevalentemente nella ricerca dell'errore.
Corollario: se provando il programma su un insieme di input campione si riscontrano già errori di correttezza, chi corregge non è tenuto ad esaminare il codice del programma.

Leggibilità (secondario). Il codice deve essere scritto in modo tale da permettere ad un altro programmatore di comprenderne la logica. Non è sufficiente che il codice "funzioni", o che sia chiaro a chi lo ha scritto. Per aiutare la lettura del codice da parte di altri, si consiglia di usare dei nomi di variabile e di funzione appropriati, strutturare il codice adeguatamente (dove ha senso, meglio dividere una funzione lunga in più funzioni ausiliarie), e di inserire dei commenti.

Non commentate come state calcolando qualcosa, commentate piuttosto cosa state calcolando. Per esempio, il seguente commento è altamente inutile:

	// incremento i
	i++;

Al contrario, il seguente aiuta a comprendere il codice:

	// passo a coordinate polari
	rho = Math.sqrt(x*x + y*y);
	theta = Math.atan2(y, x);

Efficienza (secondario). Il vostro programma deve svolgere il suo compito in tempi ragionevoli. Noi dobbiamo essere in grado di svolgere agevolmente i nostri test chiamando la funzione matchLog molte volte (ad esempio 500) su input complessi (per esempio 300 tiri, con fino a 100 urti ciascuno).

Se il vostro programma è così lento da impedirci di eseguire tutti i test nel tempo di pochi minuti, riterremo tutti i test falliti.

Consegna del progetto

Per potere partecipare allo scritto di un appello di esame, il progetto deve essere consegnato entro le date indicate nell'elenco degli appelli d'esame. La consegna si svolge su Moodle, in modo simile a quanto fatto per il tutorato.

Note Finali

Se avete dubbi sul testo del progetto, ovvero su che cosa vi sta venendo chiesto, chiedete pure delucidazioni per email o a ricevimento. Se invece avete dubbi sulla soluzione dell'esercizio, ovviamente non possiamo suggerirvi nulla.

Il progetto non viene svolto in un ambiente "controllato" come per esempio avviene per un esame scritto, ma vi viene lasciata libertà di svolgerlo dove e quando preferite (compatibilmente con le scadenze). Per esempio, potete usare i laboratori quando liberi, o farlo su un vostro computer personale.

Il progetto è individuale, non di gruppo. Tuttavia, vi è consentito discutere del progetto con altre persone per scambiarsi opinioni a riguardo. Non è consentita, ovviamente, la copia di pezzi di codice inerenti al progetto da uno studente all'altro. Allo stesso modo, farsi fare il progetto da un'altra persona è considerato equivalente a copiare. In caso di dubbi sull'autenticità del progetto, ci riserviamo la possibilità di convocarvi per un colloquio sul codice che avete consegnato, anche dopo l'esame scritto.

Vi è consentito di "copiare" invece pezzi di codice che vi abbiamo fornito noi, o di "tradurre in Java" dei pezzi di codice che potreste trovare su qualche libro o tutorial online, e che non siano soluzioni degli esercizi proposti o di una loro parte significativa. Nei casi consentiti dovete obbligatoriamente citare la fonte in un commento nel codice.

Il discutere del progetto su forum di discussione su Internet o simili non è vietato a prescindere, ma è soggetto alle stesse regole della comunicazione tra altri studenti. Inoltre, se iniziate una discussione su un forum riguardo al progetto, dovete obbligatoriamente dichiarare 1) che l'esercizio in questione è un progetto di esame, e 2) che non desiderate che qualcuno vi scriva una soluzione al posto vostro. Se anche chiarendo ciò qualcuno vi risponde includendo del codice, voi non potete includerlo nel progetto.

Varie e domande degli studenti

Se preferite, potete usare anche caratteristiche di Java non viste a lezione (costrutti diversi come gli oggetti, librerie non viste a lezione purché incluse tra quelle standard di Java), anche se il progetto si può svolgere benissimo senza. Dovete però comprendere il codice che state usando: se venite chiamati ad un colloquio (vedi sopra) vi può essere chiesto di spiegarlo.

Se ci sono parti del testo del progetto che ritenete non chiare, vi invitiamo a fare domande usando il forum che trovate su Moodle, in modo che tutti gli studenti possano leggere le nosre risposte.

Informatica - Teaching - Home


Valid CSS Valid XHTML 1.1 Roberto Zunino, 2020