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