Questa pagina descrive il progetto per il corso di Informatica della Laurea in Matematica per gli appelli dell'anno accademico 2020/2021.
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.
Per ogni incontro, vengono stabiliti alcuni parametri:
NP
("numero partite"). Questo è un naturale positivo dispari. I
giocatori si sfidano fino a quando uno dei due non vince un numero
di partite superiori alla metà di NP
. (Le rimanenti
partite non vengono giocate in quanto non potrebbero alterare il
vincitore dell'incontro.)
PP
("punti partita").
Questo è un intero positivo.
Quando un giocatore
raggiunge o supera questo punteggio, si aggiudica una partita.
NB
. Questo è un intero
positivo. Sul tavolo sono presenti
NB
birilli esterni, NB
birilli interni,
e un birillo centrale rosso, per un totale di 2NB+1
birilli.
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".
Sul tavolo da gioco sono presenti i seguenti oggetti.
B
.
G
.
R
.
e0,e1,...
(NB
in totale).
i0,i1,...
(NB
in totale).
c
.
s1,s2,s3,s4
.
M
.
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
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.
Diamo prima alcune definizioni:
Un tiro viene detto "impossibile" se viola almeno uno dei seguenti vincoli:
M
, può colpire qualcosa senza precedentemente
essere colpito
Il calcolo del punteggio viene quindi definito solo per i tiri non impossibili.
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.
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.
In un tiro, si commette fallo in uno dei seguenti casi:
Nota bene: se la bilia battente abbatte un birillo è sempre fallo, ma non sempre "bevuta".
Un tiro senza falli viene detto regolare.
Si sommano i seguenti punti
[*]
[*]
[*]
NB
punti addizionali se il rosso è l'unico birillo abbattuto nel tiro [*]
[*]
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.
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
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.
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:
zunino_555555/src/Progetto.java
È 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.
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.
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:
Progetto
, i nomi o i tipi delle procedure/funzioni
segnate come da non modificare.
Non ci deve essere nessuna dichiarazione di
package
della classe.
Progetto
come descritto
sopra nel vostro progetto Eclipse.
new File(fileName)
usa il parametro, mentre
new File("fileName")
usa un nome prefissato.
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.)
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.
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.
zunino_555555/src/Progetto.java
zunino_555555/README.txt
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.
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
Roberto Zunino, 2020