package boggle.controller; import mvc143.IBoggleController; import boggle.model.BoggleModel; import boggle.view.BoggleView; import java.util.ArrayList; import java.util.List; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.Timer; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.io.*; /** * This class provides the methods for overall control of a Boggle Game. */ public class BoggleController implements IBoggleController { public static final String author = "Kei & Vy"; public static final String description = "BoggleController controls the Boggle game."; private BoggleModel mod; private BoggleView vi; private int[][] board; private int row; //user's choice of board's row. private int col; //user's choice of board's column. private String boardName; //absolute path of user's choice of board. private String dictionaryName; //absolute path of user's choice of dictionary. private ArrayList proposal; //stores the letters in the not yet proposed word. private String proposedWord; //the lastest proposed word. private boolean submitSuccessfully; private long gametime; //total time of the game. private long timeElapsed; private boolean endGame; //tells if the game is ended. Initialized to be false. private Timer timer; private int curRow = 0; private int curCol = 0; /** * Control the process of the game. Player first choose a board file and the * a dictionary file. The board file can be null but the dictionary file cannot. */ public BoggleController() { proposal = new ArrayList(); submitSuccessfully = false; gametime = 180000; endGame = false; mod = new BoggleModel("4x4board.txt", "somedic.txt", 4, 4); vi = new BoggleView(this); } /** * This method will add the letter specified by row and col to the proposal string. * It will check if the position is valid or not before adding. * @param row the row from which the letter comes. * @param col the column from which the letter comes. * @return true, if the letter is successfully added; * false, otherwise . */ public boolean addToProposal(int row, int col) { //System.out.println("addToProposal"); boolean boo = mod.getWordsModel().isValidPosition(row, col); // System.out.println(boo); if (!endGame) { if (boo) { proposal.add(String.valueOf((char) mod.getLetter(row, col))); if (!endGame) { vi.redraw(); } return true; } } return false; } /** * Remove the last character in the proposed word. * @return true, if succeessful, * false, if not successful, or of the remove operation *is not suported by the implemention. */ public boolean removeLetterFromProposal() { if (proposal.size() != 0) { proposal.remove(proposal.size() - 1); if (!endGame) { vi.redraw(); } return true; } else { return false; } } /** * When the user submits the proposed word, * this method will check if the word is valid or not. * The score will be updated then. * @return -1 means the word was invalid. All other return values should * indicate success, but no meaning is defined for them. */ public int submitProposal() { proposedWord = ""; //System.out.println(proposal.size()); for (int i = 0; i < proposal.size(); i++) { proposedWord += ((String) proposal.get(i)); } proposedWord.toUpperCase(); newProposal(); //System.out.println(proposedWord); if (mod.getWordsModel().isValidWord(proposedWord) == 0 && proposedWord.length() >= 3) { if (proposedWord.length() == 3) { mod.addScore(1); } else if (proposedWord.length() == 4) { mod.addScore(1); } else if (proposedWord.length() == 5) { mod.addScore(2); } else if (proposedWord.length() == 6) { mod.addScore(3); } else if (proposedWord.length() == 7) { mod.addScore(5); } else if (proposedWord.length() >= 8) { mod.addScore(11); } mod.getWordsModel().addToHistory(proposedWord); submitSuccessfully = true; if (!endGame) { vi.redraw(); } return 1; } else { submitSuccessfully = false; return -1; } } /** *The current word proposal will be cleared. */ public void newProposal() { //System.out.println("newProposal"); proposal.clear(); } /** * Determine if the current word proposal is empty or not, i.e., whether * any letters have been added to it. * It may or may not be useful in your implementation, but keep it for someone's need. *@return true if the proposal is empty * false otherwise */ public boolean isNewProposal() { if (proposal.isEmpty()) { return true; } else { return false; } } /** * Get the number of columns in the board. * @return the cols of the boggle board */ public int getBoardCols() { //System.out.println(mod.getCols()); return mod.getCols(); } /** * get the number of rows in the board. * @return the rows of the boggle board */ public int getBoardRows() { //System.out.println(mod.getRows()); return mod.getRows(); } /** * return the character at the position specified by row and col. (Implement * this by asking the model for the information). * @param row an integer specifying the row in the board * @param col an integer specifying the col in the board * @return the character, or * -1, if the position is not valid */ public int getLetterInBoard(int row, int col) { //System.out.println("getLetterInBoard"); if (row < getBoardRows() && col < getBoardCols()) { curRow = row; curCol = col; return mod.getLetter(row, col); } else { return -1; } } /** * Set the time duration of one game. If no time is ever set, then the * default of three minutes (as specified by official Boggle rules) * should be used. * @param gametime the long type integer to specify the duration in milliseconds. */ public void setGameTime(long gametime) { this.gametime = gametime; } /** * Get the total time duration of a game in milliseconds. * @return a long type integer to specify the time duration of one game. */ public long getGameTime() { return gametime; } /** * Get the remaining left in the current game. * @return the time remained for one game in milliseconds, if a game is in * progress; return 0 if no game is in progress. */ public long getMillisRemaining() { //System.out.println(timeElapsed); //System.out.println(gametime); if (!((gametime - timeElapsed) == 0)) { return (gametime - timeElapsed); } else { endGame(); return 0; } } /** * Start the timer, if the timer is stopped * (create a new timer and start it, if there wasn't already a timer) */ public void startTimer() { if (timer == null) { timeElapsed = 0; timer = new Timer(1000, new ActionListener() { public void actionPerformed(ActionEvent evt) { // System.out.println(gametime + " " + timeElapsed); // System.out.println(endGame); timeElapsed += 1000; if (!endGame) { vi.redraw(); } } }); } if (!timer.isRunning()) { timer.start(); } } public void chooseRowNum() { try { row = Integer.parseInt((String) JOptionPane.showInputDialog(null, "Please type the number of rows (>=4) of the board.\n" + "You may type integer only.", "Choose row number", JOptionPane.PLAIN_MESSAGE)); if (row < 4) { JOptionPane.showMessageDialog(null, "Number of rows must be " + ">= 4"); chooseRowNum(); } } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "Type integer only!"); chooseRowNum(); } } public void chooseColNum() { try { col = Integer.parseInt((String) JOptionPane.showInputDialog(null, "Please type the number of columns (>=4) of the board.\n" + "You may type integer only.", "Choose column number", JOptionPane.PLAIN_MESSAGE)); if (col < 4) { JOptionPane.showMessageDialog(null, "Number of columns must be " + ">= 4"); chooseColNum(); } } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "Type integer only!"); chooseColNum(); } } public String chooseDictionaryFile() { try { JFileChooser dictionaryChooser = new JFileChooser(); dictionaryChooser.setDialogTitle("Choose a dictionary file"); int result = dictionaryChooser.showOpenDialog(null); if (result == JFileChooser.APPROVE_OPTION) { //a file was selected File dictionary = dictionaryChooser.getSelectedFile(); dictionaryName = dictionary.getAbsolutePath(); } else if (result == JFileChooser.CANCEL_OPTION) { //No file was selected JOptionPane.showMessageDialog(null, "You must choose a dictionary file."); chooseDictionaryFile(); } else { chooseDictionaryFile(); } } catch (Exception e) { chooseDictionaryFile(); } return dictionaryName; } public int chooseGameTimeOption() { Object[] gameTimeOptions = {"Set Game Time", "Use Default Time (3 min)"}; int result = JOptionPane.showOptionDialog(null, "Please choose your game time option.", "Game Time Options", JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, gameTimeOptions, gameTimeOptions[1]); return result; } public long chooseTime() { long time = 0; try { time = Long.parseLong((String) JOptionPane.showInputDialog(null, "Please type your desired game time in minute.\n" + "You may type integer only.", "Set Game Time", JOptionPane.PLAIN_MESSAGE)); return (time * 60000); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "Type integer only!"); chooseTime(); } return time; } /** * Start a new game, with a new internally generated board. * Starting a game may involve inititalizing or reinitializing * various other parameters and features of the board. */ public void startGame() { //System.out.println("start game"); endGame = false; boardName = null; chooseRowNum(); chooseColNum(); chooseDictionaryFile(); int result = chooseGameTimeOption(); if (result == JOptionPane.YES_OPTION) { setGameTime(chooseTime()); } else if (result == JOptionPane.NO_OPTION) { setGameTime(180000); } //System.out.println(row + " " + col); mod = new BoggleModel("4x4board.txt", dictionaryName , row, col); mod.getWordsModel().emptyHistory(); mod.resetScore(); startTimer(); // vi.redraw(); } /** * Start a new game, intializing the board from the file given. * Starting a new game may also involve inititalizing or reinitializing * various other parameters and features of the game. * If the fileID is null or the file read fails, leave the board unchanged, * supposing we have already have one. *@param fileID the location of the board to use. */ public void startGame(String fileID) { //System.out.println("start game with file"); if (fileID != null) { endGame = false; chooseDictionaryFile(); int result = chooseGameTimeOption(); if (result == JOptionPane.YES_OPTION) { setGameTime(chooseTime()); } else if (result == JOptionPane.NO_OPTION) { setGameTime(180000); } mod = new BoggleModel(fileID, dictionaryName , 0, 0); mod.getWordsModel().emptyHistory(); mod.resetScore(); startTimer(); vi.redraw(); } } /** * End the current running game. * */ public void endGame() { timer.stop(); timer = null; proposal.clear(); endGame = true; } /** * This method will tell if the game is ended or not. * Typically when the time is up, the game should be ended by controller * automatically; * everything will be stopped and waiting for start again * @return return true, if the game is still running * false, if a game is not in progress. */ public boolean isRunning() { //System.out.println("isRunning"); //addToProposal(curRow, curCol); //System.out.println(timer.isRunning()); return timer.isRunning(); } /** * Get the score for the current game. * @return the current score */ public int getScore() { return mod.getScore(); } /** * Return a message which this components wants to be visible to the user. * It is strongly encouraged that this message contain the current status * of the proposal (for example, the proposal itself; a message indicating * rejection or acceptance if the proposal was recently submitted, etc.) * For example, the View might choose to periodically display the message. * @return a string containing the status message */ public String getStatus() { //System.out.println("getStatus"); //System.out.println(proposal.size()); if (proposal.size() != 0) { String word = ""; for (int i = 0; i < proposal.size(); i++) { word += proposal.get(i); } return word; } else if (proposedWord != null) { if (submitSuccessfully) { return proposedWord + " is accepted."; } else { return proposedWord + " is rejected."; } } else { return ""; } } /** * Get the words history from the Model. * The idea of "history" is to let the model keep track of all the words * the user has already got correct in the current game. * @return a list of history information; if there is no history or a history * is simly not implemented, should return empty List; should not return null. * */ public List getWordsHistory() { //System.out.println("getWordsHistory"); return mod.getWordsModel().getHistory(); } /** * Load a dictionary from a resource (file). * @param fileID a string to specify the place to load the resource from. * @return true if succeed * false if not succeed or no file specified */ public boolean loadDictionary(String fileID) { return mod.getWordsModel().loadDictionary(fileID); } /** * 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 the dictionary */ public void addToDictionary(String[] words) { //System.out.println("addToDictionary"); mod.getWordsModel().addToDictionary(words); } /** Terminate the application. Among other things, * call the terminate methods of the Model and the View so that they can * perform any required clean-up. * */ public void terminateApplication() { mod.terminate(); vi.terminateView(); } /** * Main method creates a BoggleController object. * * @param args not used. */ public static void main(String[] args) { BoggleController controller = new BoggleController(); } }