Java Program Design - Card Shuffler
I am trying to write a card shuffler, and I know the method by which I wish to shuffle the cards. However, I am at a loss of the best object-oriented way in which to write it.
The method, a rather common one, is as follows:
- Assign each a random numeric value, between 0 and 2,147,483,647
- If there is a duplicate key value (very unlikely), throw away the deck, and start again.
- Store the cards in a set
- Order the set by each cards key value
My problem lies in the best OOP way to write this. At first I came up with an object called Card
, containing a suit value, a number value and the random key value. Then I would have a class called Deck
that extended a HashSet
, and I would store each card into the HashSet
and then sort it by key value. Where I struggled was, what is the most efficient way to 'generate' the 52 Card
objects in the first place, and how to order the set. Would I implement the interface, 'SortedSet', if so, how would I go about writing the comparators?
Quite a broad question, more based on OOP design practices, but I'd like this to be a really smooth and object based solution.
Cheers,
Tim.
EDIT:
Thanks for the help everyone. My solution was as follows:
- 2 Enums (CardValues, CardSuits), containing the 4 suits and 13 possible values
- Card class, that takes as constructor arguments a CardValue and a CardSuit.
- Deck class that extends a TreeMap
When a new deck is created and shuffled, I loop through the CardSuit 开发者_JAVA百科Enum and created Cards, then inside that loop, I go through the CardValue Enum. This creates the cards, I then generate a random key and put them in the TreeMap.
As there is always a small chance of key repetition, if the final deck size is not 52, I throw a new InvalidDeckException.
Thanks for the suggestions, I am much happier with this solution.
Use a TreeMap
, for each card generate a random number that doesn't exist in the map, and insert it in the map as the key for the card, done.
The map is now ordered by the random numbers generated.
See also http://en.wikipedia.org/wiki/Shuffling#Shuffling_algorithms
Note that this is a retarded way to shuffle, just use Collections.shuffle()
.
I wrote some poker analysis stuff. I created an Enum for all the card types, with rank
and value
fields. I then initialized all 52 card enum possibilities. So yes, I had 52 enum defs (and a separate enum for all possible 2-card starting hands -- sometimes brute force is the best option)
I then created a Deck
class that had a List
of Enum<Card>
types.
Initializing the Deck is as simple as generating an EnumSet
of the Enum, and passing that set to a List. You can then put your shuffle
method on the Deck
class and have it use the list for the Deck.
The advantage of this is you only ever have 52 cards in your app -- its like the FlyWeight pattern.
I would define the Card as a class holding the Suite and the number value. The Card shouldn't have any knowledge of the random number associated.
The Deck is a class containing a List of Cards and having the shuffle method, by use of the random numbers which maybe don't need to be stored somewhere.
Card.java:
import java.util.*;
public class Card {
public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }
public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
private final Rank rank;
private final Suit suit;
private Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}
public Rank rank() { return rank; }
public Suit suit() { return suit; }
public String toString() { return rank + " of " + suit; }
private static final List<Card> protoDeck = new ArrayList<Card>();
// Initialize prototype deck
static {
for (Suit suit : Suit.values())
for (Rank rank : Rank.values())
protoDeck.add(new Card(rank, suit));
}
public static ArrayList<Card> newDeck() {
return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
}
}
Deal.java :
import java.util.*;
public class Deal {
public static void main(String args[]) {
int numHands = Integer.parseInt(args[0]);
int cardsPerHand = Integer.parseInt(args[1]);
List<Card> deck = Card.newDeck();
Collections.shuffle(deck);
for (int i=0; i < numHands; i++)
System.out.println(deal(deck, cardsPerHand));
}
public static ArrayList<Card> deal(List<Card> deck, int n) {
int deckSize = deck.size();
List<Card> handView = deck.subList(deckSize-n, deckSize);
ArrayList<Card> hand = new ArrayList<Card>(handView);
handView.clear();
return hand;
}
}
Output:
$ java Deal 4 5
[FOUR of HEARTS, NINE of DIAMONDS, QUEEN of SPADES, ACE of SPADES, NINE of SPADES]
[DEUCE of HEARTS, EIGHT of SPADES, JACK of DIAMONDS, TEN of CLUBS, SEVEN of SPADES]
[FIVE of HEARTS, FOUR of DIAMONDS, SIX of DIAMONDS, NINE of CLUBS, JACK of CLUBS]
[SEVEN of HEARTS, SIX of CLUBS, DEUCE of DIAMONDS, THREE of SPADES, EIGHT of CLUBS]
Reference:
- Enum Usage Example.
http://download.oracle.com/javase/6/docs/api/java/util/Collections.html#shuffle(java.util.List)
See the above doc for shuffle method in java.util.Collections util class.
class Card {
private int number;
// other attributes
}
and simply use:-
Collections.shuffle(listOfCards);
The easiest thing is just assign a value attribute to the card types and sort on that (face cards J,Q,K,A->11,12,13, 14) . Ordering suits you'd have to pick some arbitrary order and organize into a "Pack" of cards which has a sorted set of each suit. Since the suits never change, the Pack always has a sorted set of each Clubs, Hearts, Diamonds, and Spades.
Generating the cards? Each pack obviously has to define 4 suits. Have a CardFactory that generates and returns a full set for each suit, given the Suit. The Factory would just blindly generate the 2-10 and face cards and return the sorted set.
You should not get into the Card
class data that is not particular of a given card. In particular, the order inside a deck and the random number is not an attribute of the card (the card is the same no matter where in the deck it is).
The card attributes should be just its value and state (shown/hidden). The deck may be implemented with a List. Shuffling would be just a method from Deck.
For shuffling there are several options:
1) you list the random numbers by themselves and order them; each change in the random number list is reproduced in the card list. The issue is that you cannot use the sort methods already available for List()
2) You add an intermediate class (lets call it ShufCard) that contains both a Card and the random number, and either:
a) The deck is a list of these intermediate objects.
b) For shuffling, you create a temporal list with the contents of the decks, shuffle, and then retrieve the cards from the temporal list mantaining its order. Something like:
List<ShufCard> list = new ArrayList<ShufCard>();
for (Card card : Deck.cards) {
list.add(new ShufCard(Card, Random.getNumber());
}
list.sort();
Deck.cards.clear();
for (ShufCard shCard : list) {
Deck.cards.add(shCard.getCard());
}
精彩评论