/* * Created on 2004-7-27 */ package boggle.model; import boggle.view.*; import boggle.model.*; import boggle.controller.*; import mvc143.*; import java.util.*; import java.io.*; import javax.swing.*; /** * This interface provides the methods needed for the Boggle Model, that is, * methods to manipulate the board data, score data and dictionary model. *
* The constructor of BoggleModel must take exactly these four parameters: * a String specifying the board file to be loaded initially (may be null). * a String specifying the dictionary file to be loaded (may be null). * an int which specifies how many rows the board should be (>=4). * an int which specifies how many cols the board be (>=4). * *
* IMPORTANT NOTE #2 : * You must define these two public static fields to specify the author and a brief description. * as follows:
* public static final String author = " ... ";
* public static final String description = " ... "; *
"authors" should identify the authors in some fashion, but pseudonyms or nicknames are acceptable. "description" should give a brief description of the module, especially if there are any unexpected or unique features about it. The information in both fields may be visible to the general public, indefinitely into the future, so do not place any information here which you do not wish to be publicly available. * @author xm * */ public class BoggleModel implements IBoggleModel, IBoggleWordsModel { private char[][] board; private int rows; private int cols; private int score; //private BoggleWordsModel currentWordsModel; public static final String author = "Tom and Jing"; public static final String description = "Model that simulates the game."; public static final String depth = "deep"; private static final char[][] boggleDice = { {'A','A','C','I','O','T'}, {'D','E','N','O','S','W'}, {'A','B','I','L','T','Y'}, {'D','K','N','O','T','U'}, {'A','B','J','M','O','Q'}, {'E','E','F','H','I','Y'}, {'A','C','D','E','M','P'}, {'E','G','I','N','T','V'}, {'A','C','E','L','S','R'}, {'E','G','K','L','U','Y'}, {'A','D','E','N','V','Z'}, {'E','H','I','N','P','S'}, {'A','H','M','O','R','S'}, {'E','L','P','S','T','U'}, {'B','F','I','O','R','X'}, {'G','I','L','R','U','W'} }; public BoggleModel(String fileID, String dictionaryFile, int rows, int cols){ score = 0; this.rows = rows; this.cols = cols; emptyHistory(); loadDictionary(dictionaryFile); createBoard(fileID); } /** * create a new board by loading from a resource (file) * if fileID is null, the board is created internally (for example, it * randomly assigns the characters for each cell). * @param fileID a String specifies the resource (file) of a designed board; * or null if an internally generated board should be produced. * @return true, if the board successfully has been created; * false, otherwise */ public boolean createBoard(String fileID){ ArrayList letterList = new ArrayList(); if(fileID != null){ try{ int rows = 0; BufferedReader br = new BufferedReader(new FileReader(fileID)); String line = br.readLine(); while (line!= null){ line = line.toUpperCase(); for(int i = 0; i < line.length(); i++){ letterList.add( "" + line.charAt(i) ); } rows++; line = br.readLine(); } board = new char[rows][letterList.size()/rows]; Iterator iter = letterList.iterator(); for (int r = 0; r < rows; r++){ for (int c = 0; c < letterList.size()/rows; c++){ String tempStr = (String)iter.next(); assert tempStr.length() == 1; board[r][c] = tempStr.charAt(0); } } this.rows = rows; this.cols = letterList.size()/rows; return true; }catch(FileNotFoundException e){ System.out.println("Please choose a board file, or hit cancel to create a random one"); File selectedFile = null; JFileChooser fc = new JFileChooser(); fc.setDialogTitle("Please select a board, or cancel to create a random one"); int returnValue = fc.showDialog(null, "Use This Board"); if(returnValue == JFileChooser.APPROVE_OPTION){ selectedFile = fc.getSelectedFile(); }else{ return createBoard(null); } if (selectedFile == null){ return createBoard(null); }else{ return createBoard(selectedFile.toString()); // Now that the correct file has been found, the method is recalled. } } catch (Exception e){ return false; } } else{ //randomly assign characters based on boggle cubes Random r = new Random(); board = new char[this.rows][this.cols]; for (int i = 0; i < this.rows; i++){ for (int j = 0; j < this.cols; j++){ int nextCube = r.nextInt(15); int nextLetter = r.nextInt(5); board[i][j] = boggleDice[nextCube][nextLetter]; } } return true; } } /** * Retrive the character on the cell specified by col and row. * If the position is invalid, return -1. * @param col the column in the board * @param row the row in the board * @return the character at that position, or * -1, if the position is invalid. */ public int getLetter(int row, int col){ if(isValidPosition(row, col)){ return (int) board[row][col]; }else{ return -1; } } /** * Tells the number of columns of the board. * @return the columns of the board. */ public int getCols(){ assert cols == board[0].length: "Wrong number of columns stored!"; return cols; } /** * Tells the number of rows of the board * @return the rows of the board. */ public int getRows(){ assert rows == board.length: "Wrong number of rows stored!"; return rows; } /** * Add to the score. * @param scoreAugment >=0, the value to be added to the total score. * @return the total score after modification. */ public int addScore(int scoreArgument){ score = scoreArgument + score; return score; } /** * Tell the current score. * @return the current score. */ public int getScore(){ return score; } /** * Reset the score to zero; for example, at the start of another game. * */ public void resetScore(){ score = 0; } /** * make the words model object visible to controller * @return the words model object used by this model. */ public IBoggleWordsModel getWordsModel(){ return this; } /** * Perform cleanup when the application is about to end. Should only * be called by the Controller. * (You don't neccessarily need to do anything, but it's an opportunity.) * */ public void terminate(){ } /** * An aspect of the Model. This inferface provides the methods neccessary to * manipulate the dictionary data and word history data. The interface could * be implemented by the same concrete class that implements IBoggleModel, * or it could be a separate class. For use with the 143 Mix Matcher, a separate class * would have to be inside the same compilation unit, i.e., .java file, as * the main Model class. *
* @author xm * */ TreeSet dictionary; List history; /** * Load a dictionary from a resource (file). * if file load failed or no file specified, create an empty dictionary. * Never leave the dictionary null. Matching of words from * the dictionary is supposed to be case-insensitive. Although this * method is not responsible for matching, it may wish to convert all words * to upper case before storing them. * @param fileID a String which specifies the place to load the resource from. * @return true if successfully loaded. * false if load failed or no file specified. */ public boolean loadDictionary(String fileID){ dictionary = new TreeSet(); if (fileID != null){ try{ BufferedReader br = new BufferedReader(new FileReader(fileID)); String line = br.readLine(); while (line!= null){ StringTokenizer st = new StringTokenizer(line); while(st.hasMoreTokens()){ dictionary.add( ( ( st.nextToken() ).trim() ).toUpperCase() ); } line = br.readLine(); } return true; } catch(FileNotFoundException e){ popUpDictionaryChooser(); }catch(Exception e){ return false; } }else{ popUpDictionaryChooser(); } return false; } private boolean popUpDictionaryChooser(){ System.out.println("Please choose a dictionary..."); File selectedFile = null; JFileChooser fc = new JFileChooser(); fc.setDialogTitle("Please select a dictionary."); int returnValue = fc.showDialog(null, "Use This Dictionary"); if(returnValue == JFileChooser.APPROVE_OPTION){ selectedFile = fc.getSelectedFile(); } if (selectedFile == null){ return loadDictionary(null); }else{ return loadDictionary(selectedFile.toString()); // Now that the correct file has been found, the method is recalled. } } /** * set the dictionary to be a given one. It is expected that each * entry in the collection would be a single, trimmed string, * representing a word to be considered valid for the game. * * @param dict a non-null collection used to set the dictionary * to an external source. */ public void setDictionary(Collection dict){ TreeSet oldDict = dictionary; this.dictionary = new TreeSet(); Iterator iter = dict.iterator(); try{ while(iter.hasNext()){ String temp = (String)iter.next(); temp = (temp.trim()).toUpperCase(); ( this.dictionary ).add(temp); } } catch(Exception e){ dictionary = oldDict; } } /** * Add new words to dictionary, without changing what was already there. * * Matching of words from * the dictionary is supposed to be case-insensitive. Although this * method is not responsible for matching, it may wish to convert all words * to upper case before storing them. * @param words an array of strings to store in dictionary; each string * should be non-null and non-empty. */ public void addToDictionary(String[] words){ for(int i = 0; i < words.length; i++){ words[i] = ( words[i].trim() ).toUpperCase(); dictionary.add(words[i]); } } /** * Determine if the selected square of the board is a legal position * for the next letter of the word being developed. According to the * rules, the letters must be adjacent; this is the method that checks * that constraint. [Implementation hint: to make this method word, * you will have to know the row and column of the previous letter, too, * if there was a previous letter.] * @param row specify the row of the letter selected. * @param col specify the column of the letter selected. * @return true if it is valid; otherwise false. */ public boolean isValidPosition(int row, int col){ if (col < this.getCols() && row < this.getRows() && col >= 0 && row >= 0){ return true; }else{ return false; } } /** * Determine if the word proposed is valid or not. * * check if the word is in the dictionary (case-insensitive).
* check if the word has already been proposed(in the history) before. * ... * @param proposal a String contains the word being proposed. * @return 0 if the word is valid; 1 if the word if not in the * dictionary; 2 if the word is a duplicate. Other values also * indicate failure but are not defined. */ public int isValidWord(String proposal){ proposal = proposal.toUpperCase(); if (proposal.length()<3){ return 3; //word too short } if ( history.contains(proposal) ){ return 2; } if ( dictionary.contains(proposal) ){ return 0; } else{ return 1; } } /** * get a history (list) of all accepted words in the current game. * @return a List containing all the accepted words. * empty List if no history or not supported/implemented (don't * return null). */ public List getHistory(){ return history; } /** * Clear the history list. * */ public void emptyHistory(){ history = new ArrayList(); } /** * Add the accepted proposal to the history. *@param acceptedProposal a string that to be added to the history */ public void addToHistory(String acceptedProposal){ acceptedProposal = ( acceptedProposal.trim() ).toUpperCase(); history.add(acceptedProposal); } }