My faith in public domain works was not shattered during this project because (praise the nondenominational, nonspecific deity who may or may not exist) Dr. Goadrich and I were able to find a set of images of a deck of cards licensed for public use. The filenames, however, contain only numbers. This makes Java very unhappy since each filename becomes a reference to a resource ID which becomes a variable name and, as such, must follow the same rules as variable names. To get around this and achieve the same ordering of cards, I wrote a quick Perl script to prepend an ‘x’ to each filename. Java was happy once more. Faced with the tedious task of composing an array of each of these resource IDs, I decided to use Perl once more to generate a comma-separated list of strings which fit the pattern “R.drawable.x%d” where %d is an integer from 1 to 52. I copied and pasted this into an array called cardIDs. This is a data member of each CardView object which is just a GUI wrapper for a Card or a Stack of Cards, as was the case here. The ordering of the cards was a little off from what I would have wanted, but I was able to work with it. The aces were first, followed then by the rest of the cards in descending order. I could have ordered the cardIDs array differently, but I chose to keep it in the same order as the images themselves.
Within CardView, the updateImages() method checks the rank of the card, and if it’s an ace, its image is selected appropriately based on its suit. For all the other cards, there is a pattern to the files: after every fourth card, a lower rank begins, and the cards’ suits follow the pattern clubs, spades, hearts, diamonds. So if we multiply the rank of the card (less 1) by 4, then add the suit (plus 1, mod 4), we get the position of the card if the resource IDs were in ascending order. So we subtract this number from 52 to arrive at the appropriate image resource ID for the given Card at the top of the Stack of each CardView.
Parallel to the development of CardView, I wrote the deckListener OnClickListener which listens on the DeckView object for clicks. The DeckView simply displays the backside of a card. If clicked and not empty, it deals out four Cards to each of the Stacks within the CardViews and notifies them to update the images. If the DeckView’s deck is empty, however, it will throw an IllegalMoveException. This we catch in the onClick() method of deckListener and make Toast to tell the user that the deck is empty.
Once the CardViews were displaying the correct images, the next step was to allow a maximum of two CardViews to be selected at any given time. Because this number is specific to this particular game, I decided this should be handled from within the Activity. So CardView was given isSelected() and toggleSelected() methods. Then in IdiotsDelightActivity, I created an OnClickListener to be applied to each of the CardViews called stackListener. In the spirit of teaching, I used a Stack (java.util.Stack, not Dr. Drake’s) called clicked to keep track of the currently selected CardViews. There are numerous conditions for which to check in the onClick() method of stackListener. (1) Is the CardView’s Stack empty? (2) Are there two CardViews selected, and (3) was the CardView that was just clicked already selected? (4) Is there a CardView selected, and was it just clicked? (5) If we couldn’t answer any of these positively, we must add the CardView that was just clicked to the clicked Stack.
(It looks much sharper on a Droid screen, I promise)
After selection rules were in place, actually removing cards from the Stacks within the CardViews followed. A big, card-sized Button which says “Remove Cards!” is what the player pushes after selecting two cards to try and remove one or both. This registers with the OnClickListener called removeListener in IdiotsDelightActivity. It checks that two cards are selected (if not, Toast pops up notifying the player), then tries to remove a low card. If that throws an IllegalMoveException, we catch it and try to remove a pair. If that throws an IllegalMoveException, we catch it and make some Toast that tells the user there isn’t a removal rule for those two cards. We can do this in one method because of the rules governing the removal of cards and because we cannot have two cards that are the same suit AND the same rank when only playing with one deck.
Dr. Goadrich and I disagreed a bit on the selection graphics. I wanted the cards to be opaque until selected, and he wanted the cards to be translucent until selection. He won out, but perhaps it would have been better to have a halo effect when a card is selected so that they will always be opaque. This is, however, a minor detail and can be easily changed in the CardView class. And it is here that the immense value of the MVC programming practice is driven home. With a few minor changes, the entirety of CardView can be reused in later projects requiring a GUI deck of cards.
I designed the icon last because I didn’t really have much inspiration for it, so I just fired up Photoshop rather than Illustrator, opened four card images, changed their perspectives a bit, distorted them to look a bit thinner, and layered them into one 72×72 transparent background, scaling it for lower resolution screens to 48×48 and 36×36. It turned out prettier than expected, so I decided to keep it.