universita` degli studi della calabria corso di laurea in ingegneria

Transcript

universita` degli studi della calabria corso di laurea in ingegneria
UNIVERSITA’ DEGLI STUDI DELLA CALABRIA
CORSO DI LAUREA IN INGEGNERIA INFORMATICA
A.A. 2006/2007
LABORATORIO DI PROGRAMMAZIONE
Docente:
Prof. Elio Masciari
Studenti:
Manti Saverio 101345
Mattia Stefano 100729
1
INDICE:
Introduzione
pag. 3
Regole Fondamentali
pag. 4
Suddivisione Classi
pag. 5
Pannello Iniziale – Classe Sudoku
pag. 6
Schema Sudoku
pag. 9
Thread Suono e Thread Suono Midi
pag. 10
Schermata Iniziale
pag. 11
Nuova Partita
pag. 13
Clessidra
pag. 14
Tempo
pag. 18
Record
pag. 19
Istruzioni e About
pag. 21
Grafici UML
pag. 22
Si ringraziano per la collaborazione e per la pazienza dimostrata l’ing. Scordio e l’ing. Guarascio i quali hanno
sopportato i nostri frequenti attacchi ai loro uffici.
Un altro ringraziamento va a tutti i colleghi che hanno sempre creduto nel nostro progetto ritenendolo una copia
conforme a quanto si trova via Web ignorando però le tante notti trascorse davanti al PC fino all’alba per cercare di
far comparire la clessidra o per sistemare le linee rosse contorno del Sudoku
2
Il gioco del Sudoku è un rompicapo giapponese che sta riscuotendo un successo
notevole nel cosiddetto “grande pubblico”: pur essendo un gioco di tipo combinatorio e
dunque con un background logico-matematico, riesce ad appassionare anche quanti non
hanno competenze specifiche in questo settore della matematica. Il motivo del successo
credo sia tutto nella grande semplicità delle regole del gioco,
affiancate dal fatto che effettivamente a fronte di regole assai semplici, la soluzione del
problema è tutt’altro che banale e propone una sfida intellettuale divertente, in grado di
dare qualche soddisfazione al giocatore vincitore.
Il problema da risolvere in una partita di Sudoku può essere riassunto nei seguenti
termini: riempire una griglia di 9×9 elementi, in modo tale che ogni riga, ogni colonna
ed ognuna delle nove sotto-griglie 3×3 contenga le cifre da 1 a 9.
La matrice inizialmente contiene solo alcuni elementi non nulli, mentre la maggior parte
delle posizioni sono vuote. L’obiettivo del gioco è proprio quello di completare la
matrice, collocando gli elementi in modo tale da rispettare i seguenti vincoli:
1. In ogni riga dovranno essere presenti le cifre da 1 a 9 senza mai essere ripetute;
2. In ogni colonna dovranno essere presenti le cifre da 1 a 9 senza mai essere ripetute;
3. In ogni riquadro, ossia sottomatrice 3x3, dovranno essere presenti le cifre da 1 a 9
senza mai essere ripetute;
Ad esempio, un problema molto interessante ed assolutamente non banale è quello di
stabilire il numero di possibili configurazioni differenti della scacchiera completata.
Vediamo dapprima alcune limitazioni superiori banali al valore incognito S.
Considerando solo le righe (o le colonne) si hanno al più 9! 9 possibilità, ovvero
S < 9! 9 = 1.0911 1050.
Ma la prima riga blocca un valore in tutte le colonne, e l’ultima colonna è bloccata dagli
altri
valori sulle righe, da cui
S < 9! 8!8 = 2.5347 1042.
Un approccio più informatico (l’uso della forza bruta dopo un certo numero di riduzioni
per simmetria) ha portato al calcolo del valore esatto:
S = 6670903752021072936960 = 6.6709 *10^21
3
REGOLE FONDAMENTALI
Regole tradizionale:
1)
2)
3)
4)
Riempire le caselle con numeri da 1 a 9;
I numeri non dovranno ripetersi su una stessa riga
I numeri non dovranno ripetersi su una stessa colonna
I numeri non dovranno ripetersi all’interno di uno stesso quadrante
Regole aggiunte:
5) Ogni volta che si chiede un suggerimento il tempo aumenterà di 15 secondi
6) Ogni volta che si chiede un controllo il tempo aumenterà di 10 secondi
7) Nella classifica rientrano i 10 giocatori col punteggio più basso ossia i giocatori
che hanno completato il gioco nel minor tempo possibile
4
SUDDIVISIONE CLASSI
Il gioco di seguito proposto consta delle seguenti classi divise in 2 package:
package logicSudo:
- Lista
- Schema Sudoku
- Sudoku
- ThreadSuono
- ThreadSuonoMidi
package graphicSudo
- About
- Casella
- Clessidra
- FrameIniziale
- Istruzioni
- PannelloIniziale
- Punteggio
- Record
- SudokuFrame
- SudokuPanel
- Tempo
5
PANNELLO INIZIALE
Viene creato un JFrame che compare appena avviato il gioco e dura per un tempo di 3
secondi.
CLASSE SUDOKU
Breve illustrazione dell’’algoritmo che si occupa di creare la matrice del Sudoku:
Di queste classi, sicuramente, la più importante è quella che riguarda la costruzione della
matrice per il Sudoku. Non avendo un’adeguata conoscenza riguardo il funzionamento
del BACKTRACKING si è scelto un diverso tipo di approccio e dopo una serie di prove
e di documentazione siamo arrivati ad un metodo che sembra essere funzionante.
Con un ciclo da 1 a 9 si cerca di inserire ogni valore nei quadranti da 1 a 9. Ad ogni
quadrante si controlla col metodo inserisci(n,quad) che sia possibile inserire il valore
all’interno del quadrante quad. Si controlla che il valore non sia già inserito. Qualora
non lo fosse è sicuro che dovrà essere inserito per cui si genera un valore di riga e di
colonna casuali all’interno del quadrante e si prova ad inserire il valore fino a quando
non avviene l’inserimento. Se viene inserito si aumenta di uno il valore del quadrante e
si procede con l’inserimento e finita la scansione dei quadranti si continua con lo
scandire i valori da 1 a n.
Qualora il valore fosse già presente nell’i-esimo quadrante o crea una ripetizione a
croce e non è possibile inserirlo all’interno di alcun quadrante vuol dire che la
configurazione utilizzata per inserire il valore precedente è errata per cui si annullano
tutti gli inserimenti fatti di n e di n-1 e si riprende ad inserire dal valore n-1 cercando di
creare una nuova configurazione corretta.
Nella classe SUDOKU viene riempita la matrice mediante il metodo crea();
Con un ciclo for si cerca di inserire i numeri da 1 a 9 e se non è possibile ritorna ad
inserire dal penultimo in modo da creare una nuova e valida configurazione(n=n-2).
private void crea(){
seleziona i numeri da inserire...da 1 a 9
for(int n=1;n<10;n++)
//Se non ha successo l'inserimento ritorna a inserire dal
penultimo numero
if(! inserisci(n))
n=n-2;
}
//
Il controllo che cerca di inserire il valore è fatto dal metodo inserisci(int n) con il quale
si cerca di inserire il valore n in uno dei quadranti controllando che l’inserimento sia
possibile (inserisci (n,quad)) e qualora l’inserimento non sia possibile si pongono a 0 le
ultime due caselle riempite in modo da provare un nuovo schema.
6
/**
*<p>Prova ad inserire il valore n in tutti i quadranti
**/
private boolean inserisci(int n){
del Sudoku</p>
for(int quad=1;quad<10;quad++)
//Se l'inserimento non ha successo annulla gli inserimenti
//del numero corrente e del numero precedente per permettere
//di reinserire i numeri con un nuovo schema valido
if(! inserisci(n,quad)){
annulla(n);
annulla(n-1);
return false;
}
return true;
}
Il metodo inserisci (n,quad) prima di tutto controlla che sia ancora possibile
inserire un valore nel quadrante (inserimentoImpossibile(val,quad)) facendo un controllo
nella sottomatrice che rappresenta il quadrante.
private boolean inserisci(int val,int quad){
if(inserimentoImpossibile(val,quad))
return false;
//cerca una posizione di inserimento valida casualmente
Random gen=new Random();
int riga=primaRigaQuadrante(quad)+gen.nextInt(3);
int col=primaColonnaQuadrante(quad)+gen.nextInt(3);
while(! inserimentoValido(val,riga,col)){
riga=primaRigaQuadrante(quad)+gen.nextInt(3);
col=primaColonnaQuadrante(quad)+gen.nextInt(3);
}
//trovata la posizione
m[riga][col]=val;
return true;
fa l'inserimento
}
private void annulla(int n){
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(m[i][j]==n)
m[i][j]=0;
}
7
Qualora non fosse possibile inserire altri valori ci si sposta in maniera casuale su una
riga e una colonna dello stesso quadrante e si guarda se l’inserimento del valore nella
riga e nella colonna può essere fatto (inserimentoValido(int val,int riga,int col)) e una
volta trovata la posizione si procede all’inserimento.
Il metodo inserimentoValido (int val,int riga,int col) controlla se il valore inserito non
crea ripetizioni né nel quadrante ne in croce(costituita dalla riga e dalla colonna
selezionate).
private boolean inserimentoValido(int val,int rig,int col){
determina il quadrante in base al valore di riga e di colonna
int quad=calcolaQuadrante(rig,col);
//
ritorna un valore boolean true se il valore non crea ripetizioni nè in
croce nè nel quadrante e il valore nella matrice è uguale a zero
return controllaCroce(val,rig,col) && controllaQuadrante(val,quad) &&
m[rig][col]==0;
}
//
Il metodo calcolaQuadrante invece, data una riga e una colonna restituisce il quadrante
a cui riga e colonna appartengono.
controllaCroce(int val,int riga,int col) controlla che il valore non sia presente nel punto
d’intersezione tra riga e colonna.
controllaQuadrante(int val,int quad) controlla che il valore non sia presente nel
quadrante quad.
I metodi doppioneCroce(int ind) e doppioneQuadrante(int quad) invece estendono il
controllo a tutta la matrice e il metodo controlla(int m[][]) è il metodo che si occupa
della correttezza della matrice anke durante la fase di gioco.
public static boolean controlla(int m[][]){
Sudoku s=new Sudoku(m);
for(int i=0;i<9;i++)
if(s.doppioneCroce(i) || s.doppioneQuadrante(i+1))
return false;
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(m[i][j]<0 || m[i][j]>9)
return false;
return true;
}
8
SCHEMASUDOKU
La classe schemaSudoku è designata alla creazione della griglia principale del gioco.
In particolare viene creata una matrice di Caselle: le Caselle sono delle JTextArea,
ognuna delle quali rappresenta un riquadro della matrice.
Si creano quindi due matrici: una è quella completa utilizzata come soluzione
(soluzione[][]) invece l’altra serve a caricare i numeri che compariranno all’inizio del
gioco (grigliaIniziale[][]) .
In questa classe ci sono inoltre altri metodi che servono ad esempio per restituire una
particolare casella della griglia (getCasella(i,j)), oppure a restituire un particolare valore
della griglia contenente la soluzione.
public SchemaSudoku(){
oggetto Sudoku che carica la matrice per il gioco
game=new Sudoku();
this.setLayout(new GridLayout(9,9));
//
grigliainiziale è la griglia che comparirà nel gioco
grigliaIniziale=game.creaGriglia();
//
soluzione è la matrice che contiene già tutti i valori e che servirà in
seguito per vedere se la partita è stata completata con successo
soluzione=game.getMatrice();
for(int i=0;i<9;i++)
for(int j=0;j<9;j++){
griglia[i][j]=new Casella(i,j);
add(griglia[i][j]);
//le caselle in cui viene aggiunto il numero fin
dall'inizio non potranno essere modificate
Casella c=griglia[i][j];
c.setText("");
c.setEditable(false);
//
}
9
THREAD SUONO e THREAD SUONO MIDI
Le classi ThreadSuono e ThreadSuonoMidi invece servono a gestire il play di file
musicali e in particolare per file di tipo wav e midi.
public ThreadSuonoMidi(String percorso){
try
{
File f2=new File(percorso);
MidiFileFormat mff2=MidiSystem.getMidiFileFormat(f2);
Sequence S=MidiSystem.getSequence(f2);
seq=MidiSystem.getSequencer();
seq.open();
seq.setSequence(S);
seq.stop();
}
catch(MidiUnavailableException ecc){}
catch(InvalidMidiDataException ecc2){}
catch(IOException ecc3){}
;
}
10
Nel package graphicSudo sono presenti le classi che sono state utilizzate per la
creazione e l’implementazione dell’interfaccia grafica.
SCHERMATA INIZIALE:
classe: FrameIniziale
La schermata iniziale del gioco e formata da un JFrame dove vi sono attaccate delle
JLabel che mostrano al giocatore le scelte possibili: scegliendo nuova partita si passa
alla creazione di un nuovo gioco, il record permette di visualizzare una lista di punteggi
realizzati nelle partite precedenti, le istruzioni sono un breve riassunto di come funziona
il gioco e del suo scopo, l’about invece da informazione riguardo il corso seguito e i
creatori, l’uscita permette la terminazione del gioco.
11
L’apertura di questa schermata sarà accompagnata dalla voce che da il benvenuto
all’interno del gioco:
// f è il file audio di benvenuto al gioco
File f = new File("audio/sample1.wav");
try {
audio = Applet.newAudioClip(f.toURL());
} catch (MalformedURLException e) {
e.printStackTrace();
}
audio.play();
Sulla schermata iniziale abbiamo creato una inner class MouseHandler e abbiamo
implementato l’interfaccia MouseMotionListener ed esteso la classe MouseAdapter
per la gestione delle superfici sensibili che al passaggio del mouse modificano le singole
JLabel sostituendo delle immagini statiche con delle Gif animate appositamente create.
public void mouseMoved(MouseEvent e){
int x=e.getX();
int y=e.getY();
Icon iconanewmov = null;
Icon iconarecordmov = null;
Icon iconistrmov = null;
Icon iconaboutmov = null;
Icon iconexitmov = null;
if(x>=362 && x<=362+300 && y>=260 && y<=260+60){//Nuova Partita
iconanewmov = newImageIcon("immagini/scrittemenu/nuovapartitamov.gif");
newgame.setIcon(iconanewmov);
newgame.repaint();}
else {
iconanewmov = new ImageIcon("immagini/scrittemenu/nuovapartita.gif");
newgame.setIcon(iconanewmov);
newgame.repaint();}
…}
Il JFrame ha un Layout nullo che ci ha permesso di aggiungere le JLabel nelle posizione
desiderate, inoltre è stato richiamato il metodo JFrame.setUndecorated(boolean var) che
ci ha permesso di eliminare la barra superiore e il bordo della finestra che vengono creati
di default da Java.
frame.setSize(1024,768);
frame.setUndecorated(true);
frame.getContentPane().add(sfondo);
sfondo.setLocation(100,100);
12
NUOVA PARTITA:
- FrameSudoku
- SudokuPanel
Nel momento in cui si seleziona nuova partita si avvia il JFrame istanziato con la
classe FrameSudoku e sul quale è “attaccato” il l’oggetto SudokuPanel estensione della
classe JPanel.
Su questo JPanel oltre ad essere aggiunto lo schema principale del Sudoku costituito
dalle 81 Caselle divise in 9 righe, 9 colonne e 9 quadranti, vengono aggiunte:
- una JLabel che mostra il tempo (classe Tempo)il quale funge anche da punteggio(
classe Punteggio) ;
- una clessidra (classe Clessidra) estensione di un JPanel che non è
altro che un vettore di immagini fatte scorrere mediante l’utilizzo
di un Thread;
13
public Clessidra(){
for (int i=0;i<im.length;i++ ) {
try{
im[i] = ImageIO.read(new File("img/cl"+(i+1)+".jpg"));
}catch(Exception e){System.out.println(e);}
}
}
public void start(){
Thread runner=new Thread(){
public void run(){
int i=0;
while (true){
disegna(i);
i++;
if(i==5)i=0;
try {
// per stabilire la durata di ogni immagine
sleep(1000-(SudokuPanel.getSecondi()+10));
} catch (Exception ex) {}
}
}
};
runner.start();//deve lasciare libero il thread che richiama paint...
}
- una “Pulsantiera” in cui sono disposti alcuni JButton per la gestione della partita:
1)
BACKGROUND: sono 4 bottoni per selezionare il colore dello sfondo delle
caselle mediante il metodo cambiaColore(Color.x)
final JButton rosa=new JButton ();
rosa.setBackground(Color.pink);
rosa.addActionListener(
new ActionListener()
{
public void actionPerformed(ActionEvent event){
cambiaColore(Color.pink);
repaint();
}});
pulsantiera.add(rosa);
2)
START: per avviare una nuova partita; Nel momento in cui si inizia una nuova
partita viene invocato il metodo gioca() il quale crea il nuovo schema.
public void gioca(){
schema.ricrea();
cless.start();
time.start();
sugg.setEnabled(true);
start.setEnabled(false);
nuovo.setEnabled(true);
risolvi.setEnabled(true);
14
controlla.setEnabled(true);
}
3)
NUOVO SCHEMA: per iniziare con uno nuovo schema;
4)
RISOLVI: mostra la soluzione del sudoku copiando i valori contenuti nella
matrice contenente la soluzione
for(int i=0;i<9;i++)
for(int j=0;j<9;j++){
schema.getGriglia(i,j).setText(""+schema.getSoluzione(i,j));
5)
CONTROLLA: dice se la disposizione attuale dei valori crea ripetizioni nelle
righe nelle colonne o nei quadranti. Ad ogni controllo il tempo aumenta di 10 secondi e
l’esito del controllo viene dato mediante una finestra di dialogo.
controlla.addActionListener(
new ActionListener()
{
public void actionPerformed(ActionEvent event){
// per ogni controllo c'è un aumento di 5 secondi
time.aumentaSecondi(10);
boolean esatto=controlla();
if(esatto)
JOptionPane.showMessageDialog(oki,"Combinazione valida");
else{
JOptionPane.showMessageDialog(oki,"Combinazione non
valida");
}
repaint();
}
});
6)
SUGGERIMENTI: suggerisce una possibile mossa.
ATTENZIONE!!! E’ bene utilizzare in maniera ponderata questa opzione perché ad
ogni suggerimento il tempo aumenta di 15 secondi.
Per controllare che non venga inserito un valore in una casella già occupata
dall’utente si è implementato il seguente controllo:
public boolean trovaMesso(int riga, int col){
int mat[][]=new int[9][9];
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(schema.getGriglia(i,j).getText().equals(""))
mat[i][j]=0;
else
mat[i][j]=Integer.parseInt(schema.getGriglia(i,j).getText());
if(mat[riga][col]==0) return true;
return false;
}
15
7)
8)
ESCI: fa uscire dal gioco.
AUDIO ON/OFF per accendere o spegnere la riproduzione musicale.
Ad ogni pulsante è associato un ActionListener per catturare i vari eventi nel
momento in cui un bottone viene premuto.
Inoltre, all’interno di questa classe vi è una inner class MENU la quale gestisce la
creazione della barra superiore del menù.
In File è possibile scegliere 3 opzioni:
- Gioca: per iniziare una partita;
- Exit: per uscire dal gioco;
- Guarda Classifica: per osservare la top 10;
In Opzioni è possibile scegliere selezionare la voce “Colore sfondo” che apre la finestra
di dialogo di Java JColorChooser con la quale è possibile scegliere ogni qualsiasi colore
per lo sfondo delle Caselle.
NB ÆEssendo le righe rosse, è consigliabile non selezionare sfondi su tonalità rosse!!!
gioco.addActionListener(
new ActionListener()
{
public void actionPerformed(ActionEvent event){
Color c=Backgroundcolor(event);
cambiaColore(c);
((SudokuPanel)getParent()).updateUI();
}
});
16
Il metodo updateUI() viene utilizzato per far il “rapaint” del SudokuPanel solamente in
modo da non appesantire le prestazioni del gioco.
In audio invece è possibile disattivare il volume dell’audio, riattivare il volume oppure
riavvolgere la riproduzione dall’inizio.
Vista l’attenzione e la calma che richiede il gioco abbiamo pensato di inserire come
sottofondo musicale una famosa composizione di Bach, “Aria Sulla IV corda”.
riavvolgi.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
musica.ferma();
musica=new ThreadSuonoMidi("audio/midi5.mid");
musica.avvia();
audio.setIcon(new ImageIcon("image/WAVPLAY.gif"));
}
});
17
TEMPO
Tempo estende Thread e gestisce l’avanzamento del tempo.
All’interno della classe tempo sono presenti i metodi:
- run() che avvia e gestisce lo scorrimento del tempo;
- nuovoTemp(String tt) ci permette di associare ad una stringa di
numeri una JLabel con delle Gif rappresentanti i numeri
appropriati ;
- getSecondi() che ritorna il valore attuale dei secondi;
- aumentaSecondi(x) che incrementa il valore dei secondi di un tempo x;
public void run(){
timer.setSize(130,60);
while(azione){
Thread t = new Thread();
String sec=" "+secondi;
t.start();
for(int i=0;i<sec.length();i++){
JLabel temp = new JLabel();
temp=nuovoTemp(String.valueOf(sec.charAt(i)));
timer.add(temp);
temp.setLocation(i*26,0);
temp.setLocation(i*26,0);
temp.setSize(26,60);}
try {t.sleep(1000);}
catch (InterruptedException ex) {}
secondi++;
timer.remove((JLabel)timer.findComponentAt(26*4,0));
timer.remove((JLabel)timer.findComponentAt(26*3,0));
timer.remove((JLabel)timer.findComponentAt(26*2,0));
timer.remove((JLabel)timer.findComponentAt(26*1,0));
}
};
Il metodo “nuovoTemp” non fa altro che associare ad ogni valore numerico
un’immagine gif del numero stesso.
18
RECORD
La classe Record permette di visualizzare su un JFrame i punteggi realizzati nelle
partite precendenti che vengono salvate in un File (record.txt) e richiamate ogni volta
che viene instanziato un oggetto di tipo record. I metodi più importanti di questa classe
sono:
- caricadamenu(): permette di
visualizzare i punteggi realizzati
senza potere apporre nessuna
modifica
- caricadagioco(int
secondi-1):
verrà richiamato alla fine di una
partita e se il tempo per
completare lo schema sarà
inferiore a uno dei punteggio
presenti, permetterà l’aggiunta
del nominativo.
Il -1 è dovuto al fatto che da
quando viene premuto l’ultimo
pulsante a quando viene creato il
tempo da salvare in record passa
proprio 1 secondo; L’aggiunta
del nome è gestita tramite l’inner
class
KeyHanlder
che
implementa
KeyListener
e
converte il tasto premuto in una
JLabel corrispondente.
19
Lista è un ArrayList di 10 posizioni (viene infatti gestita la top Ten) nel quale è possibile
aggiungere oggetti di tipo punteggio ed è possibile salvare o caricare da un file di testo il
contenuto della Lista con i metodi salva e carica metodo il quale si serve dello
StringTokenizer per leggere dal file.
public void carica()throws IOException{
ll.clear();int cnt=0;
BufferedReader br = new BufferedReader(new FileReader("record/record.txt"));
String linea = null;
StringTokenizer st = null;
for(;;){
linea = br.readLine();
if(linea==null){break;}
st = new StringTokenizer(linea," ");
String nome = st.nextToken();
int val = Integer.parseInt(st.nextToken());
Punteggio x = new Punteggio(nome,val);
ll.add(cnt,x);
cnt++;}
br.close();};//carica
20
ISTRUZIONI e ABOUT
Queste due classi avviano dei semplici JFrame riguardo le istruzioni del gioco e le ifo
sul progetto.
Queste classi che possono essere chiuse attraverso degli appositi pulsanti gestiti
all’interno della classe MouseHanlder che estende MouseAdapter e implementa
MouseMotionListener.
21
22
Gioca
23
24
25
26
27