EECS2030 Lab 4 Feedback 1.3 / 2 -passes all tests? -------------------- TA Comments: -in equals, you should sort and then compare copies of the the face strings of the two dice: @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Die other = (Die) obj; if (!this.getValue().equals(other.getValue())) { return false; } List<String> strings = new ArrayList<String>(this.valueMap.values()); List<String> otherStrings = new ArrayList<String>(other.valueMap.values()); Collections.sort(strings); Collections.sort(otherStrings); if (!strings.equals(otherStrings)) { return false; } return true; } -in Dictionary, you need to make a new set for this.words in the constructor -in the Boggle constructor, getDice, and shuffleAndRoll, you should be starting your loops at index 0 (not 1) -in getDice, you should be returning a deep copy (not a shallow copy) -------------------- Style checker output: YOUR SUBMISSION FAILED SOME BASIC STYLE CHECKS Here is the style checker output: Starting audit... /home/burton/work/teaching/2017F/2030/marking/lab4/jdishy/src/eecs2030/lab4/Dictionary.java:28:17: Redundant 'final' modifier. Audit done. -------------------- Unit tester output: YOUR SUMBISSION FAILED SOME UNIT TESTS Here is the test output: java -classpath .:/home/burton/work/teaching/2017F/2030/marking/lab4/_jar/* org.junit.runner.JUnitCore eecs2030.lab4.Lab4Suite JUnit version 4.12 ..........E.E...E.E.E.E.E..E.E.E.E.E.E.E.E.E Time: 0.18 There were 16 failures: 1) test09_equals(eecs2030.lab4.DieTest) java.lang.AssertionError: d1 and d2 have different faces but equals returned true. Actual: A*, B, C, D, E, F 2) test10_equals(eecs2030.lab4.DieTest) java.lang.AssertionError: d1 and d2 have different faces but equals returned true. Actual: A, A 3) test00_ctor(eecs2030.lab4.DictionaryTest) java.lang.NullPointerException 4) test01_lookUp(eecs2030.lab4.DictionaryTest) java.lang.NullPointerException 5) test02_lookUp(eecs2030.lab4.DictionaryTest) java.lang.NullPointerException 6) test03_size(eecs2030.lab4.DictionaryTest) java.lang.NullPointerException 7) test04_wordsStartingWith(eecs2030.lab4.DictionaryTest) java.lang.NullPointerException 8) test01_getDice(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 9) test02_getDice(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 10) test03_getDice(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 11) test04_getDice(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 12) test05_getDice(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 13) test06_shuffleAndRoll(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 14) test07_shuffleAndRoll(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 15) test08_isABoggleWord(eecs2030.lab4.BoggleTest) java.lang.NullPointerException 16) test09_isABoggleWord(eecs2030.lab4.BoggleTest) java.lang.NullPointerException FAILURES!!! Tests run: 28, Failures: 16 -------------------- Your submission: Die.java package eecs2030.lab4; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; import java.util.Random; /** * A class that represents an n-sided die where the sides are decorated with a * string. Every Die has at least one face. * * <p> * Implementation Details: Every n-sided Die object has-a sorted map that stores * the mapping between the face number 1, 2, ..., n to the corresponding face * string. For example, a 4-sided die whose face strings are "ONE", "TWO", * "THREE" and "FOUR" would have the following map: * * <table summary="Map of face numbers to face strings"> * <tr> * <th>Key </th> * <th>Value</th> * </tr> * <tr> * <td>1</td> * <td>"ONE"</td> * </tr> * <tr> * <td>2</td> * <td>"TWO"</td> * </tr> * <tr> * <td>3</td> * <td>"THREE"</td> * </tr> * <tr> * <td>4</td> * <td>"FOUR"</td> * </tr> * </table> * * <p> * The die also stores it current value as an integer between 1 and n. To return * the current value of the die, the die uses its current value as a key and * returns the corresponding value in the map (the current face string). * * <p> * To roll a die, the die sets its current value to a random value between 1 and * n, and returns the string of the current face. * */ public class Die { private SortedMap<Integer, String> valueMap; private int curface; /** * Initializes an n-sided die where the sides are decorated with the strings * in the specified array. Each string in the array is assigned to exactly * one face of the die. The die will have as many faces as there are strings * in the array. For example: * * <pre> * String[] faces = { "A", "A", "E", "E", "G", "N" }; * Die d = new Die(faces); * </pre> * * <p> * would construct a 6-sided die where the sides are lettered * <code>"A"</code>, <code>"A"</code>, <code>"E"</code>, <code>"E"</code>, * <code>"G"</code>, and <code>"N"</code>. * * <p> * The mapping of the letters to the die faces is unspecified; all that is * guaranteed is that each letter in the given string is mapped to one and * only one face of the die. * * <p> * The current value of the die is unspecified; calling * <code>getValue()</code> immediately after constructing a die could return * any face that belongs to the die. * * @param faces * an array of strings, one string for each face of the die * * @throws IllegalArgumentException * if faces.length == 0 * */ public Die(String[] faces) { if (faces.length == 0) { throw new IllegalArgumentException(); } this.valueMap = new TreeMap<>(); for (int i = 1; i <= faces.length; i++) { valueMap.put(i, faces[i - 1]); } this.curface = 1; } // this.faces = faces; // String[] copyfaces = new String[](faces); // this.check // this.faces = faces; /* * this.faces = faces; this(getValue()); } * * * /** Construct an independent copy of an existing die. The new die will * have the same strings on the same faces as the existing die. * * <p> The current value of this die will be the same as the other die. * * @param other the die to copy */ public Die(Die other) { this.valueMap = new TreeMap<>(other.valueMap); this.curface = other.curface; // this.faces = newfaces(other.getDie()); } /** * Returns the number of faces that this die has. * * @return the number of faces that this die has */ public int getNumberOfFaces() { return valueMap.size(); } /** * Rolls the die to a new random face, and returns the string on the face. * * @return the string on face after rolling the die */ public String roll() { Random rand = new Random(); this.curface = rand.nextInt(this.getNumberOfFaces()) + 1; return this.valueMap.get(this.curface); } /** * Returns the string corresponding to the current face value of the die. * * @return the string corresponding to the current face value of the die */ public String getValue() { return valueMap.get(curface); } /** * Returns the mapping of face numbers to strings for this die. The faces * are numbered using the <code>Integer</code> values <code>1</code> through * <code>n</code> where <code>n</code> is the number of sides of the die, * and the returned map is sorted on its keys (the face numbers). For * example, the die with faces: * * <p> * <code>1, 2, 3, 4, 5, 6</code> * * <p> * having face strings: * * <p> * <code>"C", "M", "I", "O", "U", "T"</code> * * <p> * would return the map whose <code>toString</code> method would produce the * following string: * * <p> * <code>{1=C, 2=M, 3=I, 4=O, 5=U, 6=T}</code> * * <p> * Clients are unable to modify the mapping of faces to letters using the * returned map; i.e., modifying the returned map has no effect on the die. * * @return a sorted map of the faces to letters */ public SortedMap<Integer, String> getValueMap() { SortedMap<Integer, String> sm = new TreeMap<Integer, String>(); for (int i = 1; i <= this.getNumberOfFaces(); i++) { this.curface = i; sm.put(i, this.getValue()); } return sm; } /** * Returns a hash code for this die. The returned hash code is equal to the * sum of the hash codes of the strings on the faces of the die. * * @return an integer hash code * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int sum = 0; for (int i = 1; i <= this.getNumberOfFaces(); i++) { this.curface = i; String a = getValue(); int b = a.hashCode(); sum = sum + b; } return sum; } /** * Compares this die to the specified object. The result is * <code>true</code> if and only if all of the following are * <code>true</code>: * * <ul> * <li>the argument is not <code>null</code></li> * <li>the argument is a <code>Die</code> reference</li> * <li>the strings corresponding to the current face values of this die and * the other die are <code>equals</code></li> * <li>both dice have the same number of faces</li> * <li>both dice have the same face strings</li> * </ul> * * <p> * Note that two dice can be <code>equals</code> if their mappings of faces * to strings are different; as long as both dice contain the exact same * strings it is possible for the dice to be <code>equals</code>. For * example, consider two dice with the following mappings: * * <table summary="Map of face numbers to face strings"> * <tr> * <th>Key </th> * <th>Value</th> * </tr> * <tr> * <td>1</td> * <td>"the"</td> * </tr> * <tr> * <td>2</td> * <td>"for"</td> * </tr> * <tr> * <td>3</td> * <td>"of"</td> * </tr> * <tr> * <td>4</td> * <td>"to"</td> * </tr> * </table> * * * <table summary="Map of face numbers to face strings"> * <tr> * <th>Key </th> * <th>Value</th> * </tr> * <tr> * <td>1</td> * <td>"to"</td> * </tr> * <tr> * <td>2</td> * <td>"for"</td> * </tr> * <tr> * <td>3</td> * <td>"of"</td> * </tr> * <tr> * <td>4</td> * <td>"the"</td> * </tr> * </table> * * <p> * If the first die has a current value of 1 and the second die has a * current value of 4, then the two dice would be equal because both dice * have the same face strings and the current value of both dice is the * string "the". * * @param obj * the object to compare * @return <code>true</code> if the two dice are equal (see above), and * <code>false</code> otherwise * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } Die other = (Die) obj; // if (this.getNumberOfFaces() == other.getNumberOfFaces()) { if (other.getValue().equals(this.getValue())) { return true; } return false; } /** * Returns a string representation of this die. The string are the strings * of the faces separated by a comma and space. The strings appear in order * of their numeric mapping. For example, the die with faces: * * <p> * <code>1, 2, 3, 4, 5, 6</code> * * <p> * having face strings: * * <p> * <code>"C", "M", "I", "QU", "U", "T"</code> * * <p> * has the string representation <code>"C, M, I, QU, U, T"</code>. * * * @return a string representation of this die * */ @Override public String toString() { String t = valueMap.get(1); for (int i = 2; i <= valueMap.size(); i++) { t = t + ", " + valueMap.get(i); } return t; } } Dictionary.java package eecs2030.lab4; import java.io.InputStream; import java.util.Scanner; import java.util.SortedSet; import java.util.TreeSet; /** * An implementation of an immutable dictionary. * */ public class Dictionary { private SortedSet<String> words; /** * Reads the dictionary file and stores the words from the file in the Set * this.dictionary. The words in this file are in all lower case. * * <p> * The dictionary file is named dictionary.txt and needs to be located in * the eecs2030.lab4 package directory. * * @throws RuntimeException * if dictionary.txt cannot be found * */ private final void readDictionary() { InputStream in = this.getClass().getResourceAsStream("dictionary.txt"); if (in == null) { throw new RuntimeException("dictionary.txt is missing"); } Scanner dictionaryInput = new Scanner(in); while (dictionaryInput.hasNext()) { String word = dictionaryInput.next(); this.words.add(word.trim()); } dictionaryInput.close(); } /** * Initializes a dictionary by reading the default dictionary from a file. */ public Dictionary() { readDictionary(); // TreeSet<String> aSet = new TreeSet<>(); // for (String a : this.words) { // aSet.add(a); // } } /** * Returns the number of words in the dictionary. * * @return the number of words in the dictionary */ public int size() { return this.words.size(); // return new SortedSet<String>(this.words.size()); } /** * Returns true if the specified word is in the dictionary, and false * otherwise. The case of the specified word is not important; * <code>lookUp("hello")</code> returns the same result as * <code>lookUp("HeLLo")</code>. * * @param word * a word to look up in the dictionary * @return true if the specified word is in the dictionary, and false * otherwise */ public boolean lookUp(String word) { word = word.toLowerCase(); if (this.words.contains(word)) { return true; } else { return false; } } /** * Returns a new sorted set of all of the words that are in the dictionary * beginning with the specified prefix. The case of the prefix is not * important; <code>wordsStartingWith("a")</code> returns the same result as * <code>wordsStartingWith("A")</code>. * * @param prefix * a string that each word in the returned set must start with * @return a new sorted set of words that are in the dictionary and begin * with the specified string */ public SortedSet<String> wordsStartingWith(String prefix) { SortedSet<String> subList = new TreeSet<String>(); for (String k : this.words) { if (k.startsWith(prefix)) { subList.add(k); } } return subList; } } Boggle.java package eecs2030.lab4; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A class that models how the game of Boggle is played. A Boggle object is a * composition of a Dictionary and a composition of a List of 16 Die objects. * */ public class Boggle { public static final int NUMBER_OF_DICE = 16; /** * The letters on the 16 boggle dice (strings for one die on each line). */ private static final String[][] LETTERS = { { "A", "A", "E", "E", "G", "N" }, { "E", "L", "R", "T", "T", "Y" }, { "W", "A", "O", "O", "T", "T" }, { "A", "B", "B", "J", "O", "O" }, { "E", "H", "R", "T", "V", "W" }, { "C", "I", "M", "O", "T", "U" }, { "D", "I", "S", "T", "T", "Y" }, { "E", "I", "O", "S", "S", "T" }, { "Y", "D", "E", "L", "R", "V" }, { "A", "C", "H", "O", "P", "S" }, { "U", "H", "I", "M", "N", "QU" }, { "E", "E", "I", "N", "S", "U" }, { "E", "E", "G", "H", "N", "W" }, { "A", "F", "F", "K", "P", "S" }, { "H", "L", "N", "N", "R", "Z" }, { "X", "D", "E", "I", "L", "R" } }; /** * The 16 boggle dice. */ private List<Die> dice; /** * The dictionary. */ private Dictionary dictionary; /** * Initializes a Boggle game by creating the 16 standard boggle dice and a * dictionary. * */ public Boggle() { this.dictionary = new Dictionary(); this.dice = new ArrayList<>(); for (int i = 1; i <= Boggle.NUMBER_OF_DICE; i++) { this.dice.add(new Die(Boggle.LETTERS[i])); } } /** * Returns a new list of the 16 dice in their current state. The order of * dice is guaranteed to be stable between calls to * <code>shuffleAndRoll</code>; in other words, all lists returned by this * method are equal between calls to <code>shuffleAndRoll</code> (assuming * that the returned lists and the dice in the lists are not modified). * * <p> * Clients are unable to modify the game dice using the returned list; i.e., * modifying the returned list has no effect on the dice held by the Boggle * object, and modifying the dice in the returned list has no effect on the * dice held by the Boggle object. * * @return a list of the 16 dice in their current state; modifying the list * or the dice in the list does not modify the state of the Boggle * dice */ public List<Die> getDice() { List<Die> list = new ArrayList<>(); for (int i = 1; i <= Boggle.NUMBER_OF_DICE; i++) { list.add(this.dice.get(i)); } return list; } /** * Randomly shuffles the order of the dice and rolls all of dice. This * simulates the shaking of the dice in the physical version of the game. * * <p> * Note to students: You can randomly shuffle the list of dice by using the * method <code>Collections.shuffle</code>. You should then roll each die in * the list. */ public void shuffleAndRoll() { Collections.shuffle(this.dice); for (int i = 1; i <= Boggle.NUMBER_OF_DICE; i++) { this.dice.get(i).roll(); } } /** * Returns true if the specified string is a legal Boggle word, and false * otherwise. A legal Boggle word is at least 3 letters long and can be * found in the dictionary. * * @param s * a string * @return true if the specified string is a legal Boggle word, and false * otherwise */ public boolean isABoggleWord(String s) { if (this.dictionary.lookUp(s) && s.length() >= 3) { return true; } return false; } }