package boggle.model; import mvc143.IBoggleModel; import mvc143.IBoggleWordsModel; import java.util.Collection; import java.util.List; import java.util.TreeSet; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; import java.io.*; import java.net.URL; import javax.swing.JOptionPane; /** * BoggleModel manipulates the board data, score data and dictionary model * and word history. */ public class BoggleModel implements IBoggleModel, IBoggleWordsModel { public static final String author = "Kei & Vy"; public static final String description = "BoggleModel manipulates the board data, score data and dictionary model"; private String cube[][]; //stores letters on the board. private int totalScore; private Collection wordSet; //stores words in dictionary. private boolean isFirstLetter = true; //tells whether the letter in a proposed word is the first letter. //initialized to be true. private ArrayList letterPosList; //stores the positions of letters in the proposal. private List history; /** * Creates the game board and loads dictionary. * * @param board the absolute path of the board text file. * @param dictionary the absolute path of the dictionary text file. * @param rows number of rows on the board. * @param cols number of columns on the board. */ public BoggleModel(String board, String dictionary, int rows, int cols) { // if (board == null) { cube = new String[rows][cols]; // } createBoard(board); wordSet = new TreeSet(); letterPosList = new ArrayList(); history = new ArrayList(); loadDictionary(dictionary); } /** * 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) { if (fileID != null) { try { //Find the row and column of the matrix and initiate cube. BufferedReader bf1 = new BufferedReader(new FileReader(fileID)); String line1 = bf1.readLine(); int row = 0; int col = 0; if (line1 != null) { for (int i = 0; i < line1.length(); i++) { col++; } row++; } while (bf1.readLine() != null) { row++; } bf1.close(); cube = new String[row][col]; //Read the file again and create the board. BufferedReader bf2 = new BufferedReader(new FileReader(fileID)); String line2 = bf2.readLine(); int rowCounter = 0; while (line2 != null) { for (int j = 0; j < col; j++) { cube[rowCounter][j] = String.valueOf(line2.charAt(j)); } rowCounter++; line2 = bf2.readLine(); } bf2.close(); return true; } catch (FileNotFoundException e) { Random ran = new Random(); for (int i = 0; i < cube.length; i++) { for (int j = 0; j < cube[0].length; j++) { int randomNum = ran.nextInt(26) + 65; cube[i][j] = String.valueOf((char) randomNum); } } return true; } catch (IOException e) { //JOptionPane.showMessageDialog(null, e); JOptionPane.showMessageDialog(null, "Error loading the board file."); return false; } catch (Exception e) { JOptionPane.showMessageDialog(null, "Unexpected exceptions!"); return false; } } else { //generate a random board. Random ran = new Random(); for (int i = 0; i < cube.length; i++) { //System.out.println(cube.length + " " + cube[0].length); for (int j = 0; j < cube[0].length; j++) { int randomNum = ran.nextInt(26) + 65; cube[i][j] = String.valueOf((char) randomNum); } } return true; } } /** * Retrive the int representing the character on the cell specified by col and row. * Return 91 if the "Qu" is detected. * 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 (row >= cube.length && col >= cube[row].length) { return -1; } else { //System.out.println(row + " " + col); return (int) (cube[row][col]).charAt(0); } } /** * Tells the number of columns of the board. * @return the columns of the board. */ public int getCols() { return cube[0].length; } /** * Tells the number of rows of the board * @return the rows of the board. */ public int getRows() { return cube.length; } /** * 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 scoreAugment) { return totalScore += scoreAugment; } /** * Tell the current score. * @return the current score. */ public int getScore() { return totalScore; } /** * Reset the score to zero; for example, at the start of another game. * */ public void resetScore() { totalScore = 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() { resetScore(); emptyHistory(); } /** * 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) { if (fileID != null) { try { //URL url = getClass().getClassLoader().getResource(fileID); //InputStream is = url.openStream(); BufferedReader bf = new BufferedReader(new FileReader(fileID)); String line = bf.readLine(); while (line != null) { line = line.toUpperCase(); String[] words = (line.trim()).split("\\W+"); for (int i = 0; i < words.length; i++) { wordSet.add(words[i]); } line = bf.readLine(); } bf.close(); return true; } catch (FileNotFoundException e) { return false; } catch (IOException e) { JOptionPane.showMessageDialog(null, "Error loading the dictionary."); return false; } } else { //JOptionPane.showMessageDialog(null, "Please choose a dictionary file."); return false; } } /** * 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) { wordSet = dict; } /** * 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++) { wordSet.add(words[i].toUpperCase()); } } /** * 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 work, * 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 (isFirstLetter) { isFirstLetter = false; letterPosList.add(String.valueOf(row) + " " + String.valueOf(col)); return true; } Iterator posIter = letterPosList.iterator(); String[] pos = new String[2]; while (posIter.hasNext()) { pos = ((String) posIter.next()).split(" "); if (Integer.parseInt(pos[0]) == row && Integer.parseInt(pos[1]) == col) { return false; } } int prevRow = Integer.parseInt(pos[0]); int prevCol = Integer.parseInt(pos[1]); if (row == prevRow - 1 || row == prevRow || row == prevRow + 1) { if (col == prevCol - 1 || col == prevCol || col == prevCol + 1) { letterPosList.add(String.valueOf(row) + " " + String.valueOf(col)); return true; } } 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) { isFirstLetter = true; //proposal is new. letterPosList.clear(); Iterator dictIter = wordSet.iterator(); Iterator histIter = history.iterator(); while (dictIter.hasNext()) { if (((String) dictIter.next()).equalsIgnoreCase(proposal)) { while (histIter.hasNext()) { if (((String) histIter.next()).equalsIgnoreCase(proposal)) { return 2; } } return 0; } } 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) { history.add(acceptedProposal); } }