Computer Science 15-100 (Sections S-V), Fall 2008
Homework 12
Due:  Thu 4-Dec-2008 at 9:59pm (email copy) and at Friday's class/recitation (identical physical copy)
(no late submissions accepted).


Note:  As this assignment is due the night before midterm #2, the deadline was moved forward to 9:59pm, to encourage you to get a reasonable amount of sleep before the test.  You are encouraged to finish sooner, if possible.  No late submissions will be accepted.


Read these instructions first!


The Game of Set

  1. Play the game of Set
    In this assignment, you will write the game of Set.  Before doing so, you should first be sure that you understand how to play the game of Set!  So here is the final version of our game (which differs slightly from the official game of Set in a few small ways):
       set.jar (app)   or   set.zip (zipped app)

    The basic idea is to repeatedly select 3 cards that form a "Set", which means that:
    * each of the 3 cards has the same color or each has a different color; and
    * each has the same shape or each has a different shape; and
    * each has the same count (number of shapes), or each has a different count; and
    * each has the same fill (solid, shaded, or unfilled), or each has a different fill.
    Play the game a bit to get a feel for it.
     
  2. Our Design
    We have provided you with the GUI for Set, as well as the API's for the classes needed to make the GUI work.  Your job is to implement those classes.  As bonus (see below), you can replace our GUI with your own.  At the highest level, each time you start a game, the GUI creates a new instance of the SetGame class.  The SetGame instance immediately creates one instance of the SetDeck class, which is a deck (well, an array) of Set cards, which themselves are instances of the SetCard class.

    Here is a zip file containing all the files you will need:  SetStudentFiles.zip

    This zip file contains these files:

    Hw12Main.java:  The main program.  It just calls the various test methods and then runs the Set GUI.
    Set.class:  A class file (without the Java source file) containing the Set GUI.  Call Set.playGame() to play the game.
    JComponentWithEvents.class:  The usual class file we use for our event-based graphics.
    SetCard.java:  A source file, along with a test method, for your implementation of the SetCard class.
    SetDeck.java:  A source file, along with a test method, for your implementation of the SetDeck class.
    SetGame.java:  A source file, along with a test method, for your implementation of the SetGame class.

    To finish this assignment, you should first complete the SetCard class so that its included test method passes.  Then do the same for SetDeck.  And then do the same for SetGame.  At that point, run Hw12Main and you should have a working Set game!  Have fun!!!
     
  3. Write the SetCard class

    The SetCard class is a simple class that models one card in the game of Set.  Here are the methods you need to write, along with explanations (read the comments carefully!):

    // Constructor:  Create a SetCard instance with the given count, shape,
    // color, and fill, where each of these values is in the range [1,3].
    public SetCard(int count, int shape, int color, int fill)

    // Standard accessors
    public int getCount()
    public int getShape()
    public int getColor()
    public int getFill()


    // Converts this SetCard to a unique index in the range [0,80],
    // assuming count, shape, color, and fill are all in the range [1,3]:
    // 27*(count - 1) + 9*(shape - 1) + 3*(color - 1) + (fill - 1)
    public int getCardIndex()

    // Override equals method -- two set cards are equal if they
    // have the same count, shape, color, and fill
    public boolean equals(Object object)

    // Override hashCode method -- two equals SetCard instances must
    // have the same hashCode method. If you think about it, this
    // can be easily implemented by making use of the getCardIndex method...
    public int hashCode()

    Hint: while not required, you might also find that a well-written toString method might be very useful for your debugging.  The same is true for the SetDeck and SetGame classes, too!

    Here is a test method that confirms that your methods work according to the spec:

      public static void testSetCardClass() {
        System.out.print("Testing SetCard class... ");
        SetCard setCard1 = new SetCard(1,2,3,2);
        assert(setCard1.getCount() == 1);
        assert(setCard1.getShape() == 2);
        assert(setCard1.getColor() == 3);
        assert(setCard1.getFill()  == 2);
        int expectedCardIndex = ((1-1)*27 + (2-1)*9 + (3-1)*3 + (2-1));
        assert(setCard1.getCardIndex() == expectedCardIndex);
    
        // This card equals the first card
        SetCard setCard2 = new SetCard(1,2,3,2);
        assert(setCard2.getCount() == 1);
        assert(setCard2.getShape() == 2);
        assert(setCard2.getColor() == 3);
        assert(setCard2.getFill()  == 2);
        expectedCardIndex = ((1-1)*27 + (2-1)*9 + (3-1)*3 + (2-1));
        assert(setCard2.getCardIndex() == expectedCardIndex);
    
        // And this one does not equal the previous two
        SetCard setCard3 = new SetCard(1,2,1,3);
        assert(setCard3.getCount() == 1);
        assert(setCard3.getShape() == 2);
        assert(setCard3.getColor() == 1);
        assert(setCard3.getFill()  == 3);
        expectedCardIndex = ((1-1)*27 + (2-1)*9 + (1-1)*3 + (3-1));
        assert(setCard3.getCardIndex() == expectedCardIndex);
    
        // Check equals method
        assert(setCard1.equals(setCard2) == true);
        assert(setCard1.equals(setCard3) == false);
        assert(setCard2.equals(setCard1) == true);
        assert(setCard2.equals(setCard3) == false);
        assert(setCard3.equals(setCard1) == false);
        assert(setCard3.equals(setCard2) == false);
    
        // Check hashCode method
        assert(setCard1.hashCode() == setCard2.hashCode());
        assert(setCard1.hashCode() != setCard3.hashCode());
        System.out.println("Passed all tests!");
      }
  4. Write the SetDeck class

    The SetDeck class is a bit more complicated than the SetCard class, and it models a deck of cards in the game of Set (and so it makes use of 81 SetCard instances).  Here are the methods you need to write, along with explanations (read the comments carefully!):

     // Construct a new deck of shuffled Set cards
    public SetDeck()

    // Construct a new deck of Set cards that may or may not be shuffled,
    // according to the "shuffled" parameter. In an unshuffled deck,
    // the cards must be in order from 0 to 80 according to the result of
    // getCardIndex from the SetCard class. See the test method for details.
    // Hint:  you will probably want to store the deck in an instance variable
    // that holds an array of 81 instances of SetCard.
    public SetDeck(boolean shuffled)

    // Shuffle the deck using the built-in "shuffle" method in the
    // Collections class. To do this, we have to convert the array
    // into a "List". Fortunately, the Arrays class provides the "asList"
    // method, that does exactly this for us (and we are dealing with
    // an array of SetCard instances rather than primitives, so that
    // is not a problem, either).
    public void shuffle()

    // Return true if there is another card to deal from the deck,
    // and false otherwise.
    public boolean hasNextCard()

    // Return the next SetCard to be dealt from the deck, and update
    // whatever instance variables are required so that after this call
    // there is one fewer cards left to deal from the deck.
    // You do not have to deal with the case where nextCard is called
    // when hasNextCard would return false (and so the deck is empty).
    public SetCard nextCard()


    // Return the number of cards that can still be dealt from
    // the deck. That is, the number of times nextCard can be called
    // until hasNextCard would return false.
    public int getCardsLeft()

    // Return an instance of an Iterator<SetCard> that iterates over
    // the instances of SetCard in this deck
    // Hint: you do NOT have to write your own Iterator class!
    // Instead, call Arrays.asList on your array of setCards to
    // convert it into a List, and then you can just return the result
    // from a call to the "iterator" method on that List! Wow!
    public Iterator<SetCard> iterator()

    And here is a test method that confirms that your methods work according to the spec:
      public static void testSetDeckClass() {
        System.out.print("Testing SetDeck class... ");
    
        // first test an unshuffled deck
        SetDeck setDeck = new SetDeck(false); // unshuffled!
        for (int i=0; i<81; i++) {
          assert(setDeck.getCardsLeft() == 81-i);
          assert((setDeck.hasNextCard() == true) &&
                 (setDeck.nextCard().getCardIndex() == i));
        }
        assert(setDeck.hasNextCard() == false);
        assert(setDeck.getCardsLeft() == 0);
    
        // now test that SetDeck is iterable
        setDeck = new SetDeck(false); // another unshuffled deck
        int seen = 0;
        for (SetCard setCard : setDeck)
          seen++;
        assert(seen == 81);
    
        // now test a shuffled deck
        HashSet<Integer> seenCards = new HashSet<Integer>();
        setDeck = new SetDeck(); // default is shuffled!
        for (SetCard setCard : setDeck) {
          int cardIndex = setCard.getCardIndex();
          assert((cardIndex >= 0) && (cardIndex < 81));
          assert(seenCards.contains(cardIndex) == false);
          seenCards.add(cardIndex);
        }
    
        // now test that the shuffle itself works.  We'll use a very
        // simplistic approach and just get two shuffled decks and require
        // that they not have more than 3 of the same cards occur
        // at the same indexes:
        int matches = 0;
        SetDeck setDeck1 = new SetDeck();
        SetDeck setDeck2 = new SetDeck();
        for (SetCard setCard1 : setDeck1) {
          SetCard setCard2 = setDeck2.nextCard();
          if (setCard1.equals(setCard2))
            matches++;
        }
        if (matches > 3) {
          System.out.println("There may be a problem with your shuffling, because");
          System.out.println("two different SetDecks had the same cards in ");
          System.out.println(matches + " locations, where more than 3 is very unlikely.");
          System.out.println("Try running the test again.  If this message comes up");
          System.out.println("more than once-in-a-blue-moon, then you probably have a bug.");
          assert(matches <= 3);
        }
        System.out.println("Passed all tests!");
      }
  5. Write the SetGame class

    The SetGame class is the most complicated part of the assignment, and it models a game of Set (which makes use of a SetDeck instance).  First, to implement the required methods, the following private instance variables are already defined for you:
      // THESE ARE PROVIDED FOR YOU (but, if you really want, you
      // can delete these and use other instance variables of your choosing)
    
      private SetDeck setDeck;        // The deck for this game
      private SetCard[] cardsInPlay;  // The 12 (or fewer) visible cards at this time
      private int[] selectedCardIndexes; // The indexes (from among the 12 visible cards)
                                         // of the 3 (or fewer) cards that are selected
                                         // (and so are highlighted in gray) right now
      private int selectedCardCount;  // The # of selected (gray highighted) cards right now

    Important note:  the "cards-in-play" are the 12 (or fewer) visible cards at any given time.

    Here are the methods you need to write, along with explanations (read the comments carefully!):

    // Construct a new SetGame that uses the given SetDeck.
    // The constructor must place the first 12 cards in the deck
    // into play (perhaps by storing them in a suitably-named array).
    // Right after the constructor is called (at the start of the game),
    // no cards should be selected.
    public SetGame(SetDeck setDeck)

    // Return the number of cards that are currently selected.
    public int getSelectedCardCount()

    // Return true if the card-in-play with the given index
    // is selected, and false otherwise. The index will be between
    // 0 and 11 inclusive.
    // Hint: be sure to use the selectedCardIndexes array here!
    public boolean isSelected(int index)

    // Given an index (between 0 and 11 inclusive) into the cards-in-play,
    // and a boolean whether to set that card to be selected or unselected,
    // this method tries to set the given card's selection as given.
    // If the request would result in more than 3 cards being
    // selected (which is never allowed), then the method does nothing
    // and returns false. Otherwise, it makes the change (if necessary)
    // and returns true.
    // Note that selecting an already-selected card, or unselecting an
    // already-unselected card, is not an error, and the method should
    // return true in either case.
    public boolean setSelected(int index, boolean selected)


    // This method replaces the currently-selected cards-in-play,
    // replacing as many of them as possible with cards dealt from the deck.
    // However, if there are not enough cards remaining in the deck for this,
    // then the behavior depends on whether or not the selected cards form
    // a set. If they do not form a set, then once the cards in the deck
    // are exhausted, the remaining cards should remain in play. On the other
    // hand, if they do form a set, then once the cards in the deck are exhausted,
    // the remaining cards in the set should be entirely removed from play (so
    // they are set to null rather than being replaced), so that afterwards there
    // are fewer than 12 cards in play.  If there is no selection, this method
    // does nothing.  In any case, after this method is called, there should be
    // no selected cards.
    public void replaceSelectedCards()

    // Returns true if the selected cards are a set -- that is, if they
    // are either all the same or all different in each of the 4 properties.
    // When writing this method, you may want to make use of the static
    // version of the method which follows (see below).
    public boolean isSet()

    // A static method that returns true if the given cards are a set -- that is, if they
    // are either all the same or all different in each of the 4 properties.
    public static boolean isSet(SetCard[] cards)

    // Given an index (between 0 and 11 inclusive) into the cards-in-play,
    // returns the corresponding SetCard.
    public SetCard getCardInPlay(int index)

    // Returns the number of cards left to be dealt from the deck.
    public int getCardsLeft()


    And here is a test method that confirms that your methods work according to the spec:

      public static void testSetGameClass() {
        System.out.print("Testing SetGame class... ");
    
        // first test static isSet method
        SetCard[] cards = new SetCard[3];
        cards[0] = new SetCard(1,2,1,2);
        cards[1] = new SetCard(1,2,2,3);
        cards[2] = new SetCard(1,2,3,1);
        assert(SetGame.isSet(cards) == true);
        cards[2] = new SetCard(1,2,2,1);
        assert(SetGame.isSet(cards) == false);
    
        // now test the game on an unshuffled deck (because
        // it's hard to test on a shuffled deck)!
        SetDeck setDeck = new SetDeck(false); // unshuffled!
        SetGame setGame = new SetGame(setDeck);
        for (int i=0; i<12; i++)
          assert(setGame.getCardInPlay(i).getCardIndex() == i);
        assert(setGame.getCardsLeft() == 69); // 69 == 81 - 12
        assert(setGame.isSet() == false);
        assert(setGame.getSelectedCardCount() == 0);
    
        // now select card 0
        assert(setGame.isSelected(0) == false);
        assert(setGame.setSelected(0,true) == true);
        assert(setGame.isSelected(0) == true);
        assert(setGame.getSelectedCardCount() == 1);
    
        // reselect card 0 (should have no effect)
        assert(setGame.setSelected(0,true) == true);
        assert(setGame.isSelected(0) == true);
    
        // deselect card 0
        assert(setGame.setSelected(0,false) == true);
        assert(setGame.getSelectedCardCount() == 0);
        assert(setGame.isSelected(0) == false);
    
        // select cards 0, 1, and 2 to form a set
        assert(setGame.setSelected(0,true) == true);
        assert(setGame.isSet() == false);
        assert(setGame.setSelected(1,true) == true);
        assert(setGame.isSet() == false);
        assert(setGame.setSelected(2,true) == true);
        assert(setGame.isSet() == true);
        // you can deselect when 3 cards are already selected
        assert(setGame.setSelected(2,false) == true); // deselect 2
        assert(setGame.isSet() == false);
        assert(setGame.setSelected(2,true) == true); // reselect 2
        assert(setGame.isSet() == true);
    
        // but you cannot select another card when 3 are selected
        assert(setGame.setSelected(3,true) == false); // cannot make a 4th selection
    
        // replace the selected cards (which form a set) by dealing off
        // the top of the deck of remaining cards
        setGame.replaceSelectedCards();
        assert(setGame.getSelectedCardCount() == 0);
        assert(setGame.isSet() == false);
        assert(setGame.getCardsLeft() == 66);
    
        // at this point, cards 2, 3, and 4 do NOT form a set
        assert(setGame.setSelected(2,true) == true);
        assert(setGame.setSelected(3,true) == true);
        assert(setGame.setSelected(4,true) == true);
        assert(setGame.isSet() == false);
    
        System.out.println("Passed all tests!");
      }
  6. Bonus/Optional;  Write your own Set GUI
    For bonus, you may have your Hw12Main.main method not call our Set.playGame method, but instead call your own GUI.  Your GUI should make use of the SetCard, SetDeck, and SetGame classes you just designed.  You should match our GUI's design, but then you can extend it in all kinds of interesting ways!  Keep a timesheet, as always.  And, as always, be creative and have fun!!!

Carpe diem!