My First Deck of Cards
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
6
down vote
favorite
I'm looking for an honest review of what I've got thus far. I'm still trying to get my head wrapped around software development and have picked Java as my starting language. That said, I figured that I would attempt to create a reusable module for which I'd use this as an external library.
I think it would be invaluable to hear from you experts on where I can improve or refactor. The code here is just snippets. Full code on GitHub.
Card.java
package com.frijolie.cards;
public interface Card extends Comparable<Card>
// returns the value of the Card. inherently coupled with the Rank
int getValue();
// returns the Rank of the Card. Values from Ace,2,3..., Jack,Queen,King
Rank getRank();
// returns the Suit of the Card. Values are Diamonds, Hearts, Spades, Clubs
Suit getSuit();
// returns the color of the Card. Values are Red or Black
Color getColor();
// display the card onscreen
void displayCard();
// compares this Card to another, returns if they are equal
boolean equals(final Object o);
// Generates a unique int value of a Card
int hashCode();
// flips a Card faceUp or faceDown
void flip();
// returns true if this Card is faceUp
boolean isFaceUp();
Rank.java
package com.frijlie.cards;
public enum Rank
ACE("Ace","A",1),
TWO("Two","2",2),
THREE("Three","3",3),
... // snipped for brevity
JACK("Jack","J",10),
QUEEN("Queen","Q",10),
KING("King","K","10);
private final String name;
private final String letter;
private final int value;
Rank(final String name, final String letter, final int value)
this.name = name;
this.letter = letter;
this.value = value;
... // accessor methods
Suit.java
package com.frijolie.cards;
public enum Suit
CLUBS("Clubs",'u2663', Color.BLACK),
DIAMONDS("Diamonds",'u2666', Color.RED),
SPADES("Spades",'u2660', Color.BLACK),
HEARTS("Hearts",'u2764', Color.RED);
private final String name;
private final char symbol;
private final Color color;
Suit(final String name, final char symbol, final Color color)
this.name = name;
this.symbol = symbol;
this.color = color;
... // accessor methods
Color.java
package com.frijolie.cards;
public enum Color
RED("Red"),
BLACK("Black");
private final String name;
Color(final String name)
this.name = name;
public final String getName()
return name;
Deck.java
package com.frijolie;
// snipped imports
public class Deck
private boolean isShuffled;
private Stack<Card> deck;
// default constructor, will create a single deck of 52 cards
public Deck()
this(1)
// overloaded constructor, will create multiple decks of 52 cards
public deck(int numberOfDecks)
deck = new Stack<>();
clearDeck();
for (int i = 0; i < numberOfDecks; i++)
populateDeck();
// a method to shuffle the collection of cards. default will shuffle 1x
public void shuffle()
shuffle(1);
// overloaded shuffle method. will shuffle the deck a number of times.
public void shuffle(int numberOfTimes)
isShuffled = true;
for (int i = 0; i < numberOfTimes; i++)
Collections.shuffle(deck);
// returns true if the deck has been shuffled
public boolean isShuffled()
return isShuffled;
// returns the number of cards remaining in the deck
public int numberOfCards()
return deck.size();
// a method to remove a card from the deck
public Card removeCard(Card card)
if(deck.contains(Objects.requireNonNull(card)))
int index = deck.indexOf(card);
return deck.remove(index);
else
return null;
// returns true if the specified card is contained in the deck
public boolean containsCard(Card card)
return deck.contains(Objects.requireNonNull(card));
// populates the deck with all possible cards in the deck. 52 cards
public void populateDeck()
for(Suit suit: Suit.values())
for(Rank rank: Rank.values())
deck.add(new PlayingCard(rank,suit));
// Overloaded method which allows any number of decks of 52 cards.
public void populateDeck(int numberOfDecks)
for(int i = 0; i < numberOfDecks; i++)
populateDeck();
// remove all cards from the deck
public void clearDeck()
deck.clear();
// returns an unmodifiable collection, a view, of the deck.
public Collection<Card> getUnmodifiableCollection()
return new ArrayList<>(deck);
// returns the first card in the collection.
public Card pop()
return deck.pop();
Hand.java
package com.frijolie.cards;
// snipped imports
public class Hand implements Comparable<Hand>
private List<Card> cards;
// default constructor
public Hand()
cards = new ArrayList<>();
// will add a specific card to the Hand
public void addCard(Card card)
cards.add(Objects.requireNonNull(card));
// will remove a specific card from the Hand if it's contained in the Hand
public Card removeCard(Card card)
if (cards.contains(Objects.requireNonNull(card)))
int index = cards.indexOf(card);
return cards.remove(index);
else
throw new IllegalArgumentException("This card did not exist in the hand. It cannot be removed.");
// method to display all the Cards in the Hand
public void displayHand()
for (Card card : cards)
if (card.isFaceUp())
card.displayCard();
else
System.out.println("[ Card Back ]");
// returns the sum of the value of all Cards in the Hand
public int calculateValue()
var handValue = 0;
for (Card card : cards)
handValue += card.getRank().getValue();
return handValue;
// remove all cards in the Hand
public void clearHand()
cards.clear();
// returns a unmodifiable collection, a view, of all cards in the Hand
public Collection<Card> getUnmodifiableCollection()
return Collections.unmodifiableCollection(cards);
// compares one Hand to another
public int compareTo(final Hand otherHand)
return this.calculateValue() - otherHand.calculateValue();
// sorts cards in the Hand by Suit. Doesn't factor Rank within the Suit when sorting
public void sortyBySuit()
cards.sort(Comparator.comparing(Card::getSuit));
// sorts cards in the Hand by Rank. Doesn't factor Suit when sorting.
public void sortByRank()
cards.sort(Comparator.comparing(Card::getRank));
// sorts cards in the Hand by Color. Doesn't factor Suit or Rank when sorting.
public void sortByColor()
cards.sort(Comparator.comparing(Card::getColor));
// sorts cards in the Hand by Suit then Rank in ascending order.
public void sort()
cards.sort((Card first, Card second) -> first.compareTo(second));
// allows a card in the hand to be flipped in the opposite orientation
public void flipCard(int index)
cards.get(index).flip();
PlayingCard.java
package com.frijolie.cards;
import java.util.Objects;
public class PlayingCard implements Card
private final Rank rank;
private final Suit suit;
private boolean isFaceUp;
// default constructor
public PlayingCard(final Rank rank, final Suit suit)
this.rank = rank;
this.suit = suit;
isFaceUp = true;
... // snipping implemented accessor methods from Card interface
// returns a string representation of the card
@Override
public String toString()
return rank.getLetter() + suit.getSymbol();
// compares one PlayingCard to another
@Override
public boolean equals(final Object o)
Objects.requireNonNull(o);
boolean isEqual;
if (!(o instanceof Card))
isEqual = false;
else if (this == 0)
isEqual = true;
else
var card = (Card) o;
isEqual = (this.getRank().equals(card.getRank()) && this.suit.equals(card.getSuit()));
return isEqual;
// generates unique int value of a card
@Override
public int hashCode()
int suitMultiplier = suit.ordinal() + 2;
int rankMultiplier = rank.ordinal() + 3;
return (suitMultiplier * rankMultiplier) * 31;
// flip the card in the opposite orientation, faceUp/faceDown
@Override
public void flip()
isFaceUp = !isFaceUp;
// returns true if the card is faceUp
@Override
public boolean isFaceUp()
return isFaceUp;
// compares one card to another and returns an integer value
@Override
public int compareTo(Card o)
var card = Objects.requireNonNull(o);
var suitCompareResult = this.suit.compareTo(card.getSuit());
if (suitCompareResult != 0)
return suitCompareResult;
else
return this.rank.compareTo(card.getRank());
What could I improve?
I've toyed with the idea to make Hand
and/or Deck
an interface. However, I opted to go with classes in favor of code reuse and contained behavior. I send unmodifiable collections (Hand
, Deck
) as I want to control the behavior of the internal collection within their respective classes. You can see what it looks like, but you can't modify the Deck or Hand outside the class. Is this a good defensive strategy?
I'm also not 100% comfortable with the requirement of a Rank and Suit. How about a Magic the Gathering Card
? That doesn't have a suit, but you could argue that it has a rank. Is it better to leave the implementation up to the other developer? Same could be said for an Uno card, Color.Red
and Color.BLACK
aren't the only options there.
I've thought about the Rank enum also. If the game you model has an ACE
as a high card (not low as supplied here), how would you fix that in your own implementation? Create a new AceHighRank
and declare them in a different order? However, that would break PlayingCard
because it's expecting a Rank
(not AceHighRank
). Right?
java beginner playing-cards
add a comment |Â
up vote
6
down vote
favorite
I'm looking for an honest review of what I've got thus far. I'm still trying to get my head wrapped around software development and have picked Java as my starting language. That said, I figured that I would attempt to create a reusable module for which I'd use this as an external library.
I think it would be invaluable to hear from you experts on where I can improve or refactor. The code here is just snippets. Full code on GitHub.
Card.java
package com.frijolie.cards;
public interface Card extends Comparable<Card>
// returns the value of the Card. inherently coupled with the Rank
int getValue();
// returns the Rank of the Card. Values from Ace,2,3..., Jack,Queen,King
Rank getRank();
// returns the Suit of the Card. Values are Diamonds, Hearts, Spades, Clubs
Suit getSuit();
// returns the color of the Card. Values are Red or Black
Color getColor();
// display the card onscreen
void displayCard();
// compares this Card to another, returns if they are equal
boolean equals(final Object o);
// Generates a unique int value of a Card
int hashCode();
// flips a Card faceUp or faceDown
void flip();
// returns true if this Card is faceUp
boolean isFaceUp();
Rank.java
package com.frijlie.cards;
public enum Rank
ACE("Ace","A",1),
TWO("Two","2",2),
THREE("Three","3",3),
... // snipped for brevity
JACK("Jack","J",10),
QUEEN("Queen","Q",10),
KING("King","K","10);
private final String name;
private final String letter;
private final int value;
Rank(final String name, final String letter, final int value)
this.name = name;
this.letter = letter;
this.value = value;
... // accessor methods
Suit.java
package com.frijolie.cards;
public enum Suit
CLUBS("Clubs",'u2663', Color.BLACK),
DIAMONDS("Diamonds",'u2666', Color.RED),
SPADES("Spades",'u2660', Color.BLACK),
HEARTS("Hearts",'u2764', Color.RED);
private final String name;
private final char symbol;
private final Color color;
Suit(final String name, final char symbol, final Color color)
this.name = name;
this.symbol = symbol;
this.color = color;
... // accessor methods
Color.java
package com.frijolie.cards;
public enum Color
RED("Red"),
BLACK("Black");
private final String name;
Color(final String name)
this.name = name;
public final String getName()
return name;
Deck.java
package com.frijolie;
// snipped imports
public class Deck
private boolean isShuffled;
private Stack<Card> deck;
// default constructor, will create a single deck of 52 cards
public Deck()
this(1)
// overloaded constructor, will create multiple decks of 52 cards
public deck(int numberOfDecks)
deck = new Stack<>();
clearDeck();
for (int i = 0; i < numberOfDecks; i++)
populateDeck();
// a method to shuffle the collection of cards. default will shuffle 1x
public void shuffle()
shuffle(1);
// overloaded shuffle method. will shuffle the deck a number of times.
public void shuffle(int numberOfTimes)
isShuffled = true;
for (int i = 0; i < numberOfTimes; i++)
Collections.shuffle(deck);
// returns true if the deck has been shuffled
public boolean isShuffled()
return isShuffled;
// returns the number of cards remaining in the deck
public int numberOfCards()
return deck.size();
// a method to remove a card from the deck
public Card removeCard(Card card)
if(deck.contains(Objects.requireNonNull(card)))
int index = deck.indexOf(card);
return deck.remove(index);
else
return null;
// returns true if the specified card is contained in the deck
public boolean containsCard(Card card)
return deck.contains(Objects.requireNonNull(card));
// populates the deck with all possible cards in the deck. 52 cards
public void populateDeck()
for(Suit suit: Suit.values())
for(Rank rank: Rank.values())
deck.add(new PlayingCard(rank,suit));
// Overloaded method which allows any number of decks of 52 cards.
public void populateDeck(int numberOfDecks)
for(int i = 0; i < numberOfDecks; i++)
populateDeck();
// remove all cards from the deck
public void clearDeck()
deck.clear();
// returns an unmodifiable collection, a view, of the deck.
public Collection<Card> getUnmodifiableCollection()
return new ArrayList<>(deck);
// returns the first card in the collection.
public Card pop()
return deck.pop();
Hand.java
package com.frijolie.cards;
// snipped imports
public class Hand implements Comparable<Hand>
private List<Card> cards;
// default constructor
public Hand()
cards = new ArrayList<>();
// will add a specific card to the Hand
public void addCard(Card card)
cards.add(Objects.requireNonNull(card));
// will remove a specific card from the Hand if it's contained in the Hand
public Card removeCard(Card card)
if (cards.contains(Objects.requireNonNull(card)))
int index = cards.indexOf(card);
return cards.remove(index);
else
throw new IllegalArgumentException("This card did not exist in the hand. It cannot be removed.");
// method to display all the Cards in the Hand
public void displayHand()
for (Card card : cards)
if (card.isFaceUp())
card.displayCard();
else
System.out.println("[ Card Back ]");
// returns the sum of the value of all Cards in the Hand
public int calculateValue()
var handValue = 0;
for (Card card : cards)
handValue += card.getRank().getValue();
return handValue;
// remove all cards in the Hand
public void clearHand()
cards.clear();
// returns a unmodifiable collection, a view, of all cards in the Hand
public Collection<Card> getUnmodifiableCollection()
return Collections.unmodifiableCollection(cards);
// compares one Hand to another
public int compareTo(final Hand otherHand)
return this.calculateValue() - otherHand.calculateValue();
// sorts cards in the Hand by Suit. Doesn't factor Rank within the Suit when sorting
public void sortyBySuit()
cards.sort(Comparator.comparing(Card::getSuit));
// sorts cards in the Hand by Rank. Doesn't factor Suit when sorting.
public void sortByRank()
cards.sort(Comparator.comparing(Card::getRank));
// sorts cards in the Hand by Color. Doesn't factor Suit or Rank when sorting.
public void sortByColor()
cards.sort(Comparator.comparing(Card::getColor));
// sorts cards in the Hand by Suit then Rank in ascending order.
public void sort()
cards.sort((Card first, Card second) -> first.compareTo(second));
// allows a card in the hand to be flipped in the opposite orientation
public void flipCard(int index)
cards.get(index).flip();
PlayingCard.java
package com.frijolie.cards;
import java.util.Objects;
public class PlayingCard implements Card
private final Rank rank;
private final Suit suit;
private boolean isFaceUp;
// default constructor
public PlayingCard(final Rank rank, final Suit suit)
this.rank = rank;
this.suit = suit;
isFaceUp = true;
... // snipping implemented accessor methods from Card interface
// returns a string representation of the card
@Override
public String toString()
return rank.getLetter() + suit.getSymbol();
// compares one PlayingCard to another
@Override
public boolean equals(final Object o)
Objects.requireNonNull(o);
boolean isEqual;
if (!(o instanceof Card))
isEqual = false;
else if (this == 0)
isEqual = true;
else
var card = (Card) o;
isEqual = (this.getRank().equals(card.getRank()) && this.suit.equals(card.getSuit()));
return isEqual;
// generates unique int value of a card
@Override
public int hashCode()
int suitMultiplier = suit.ordinal() + 2;
int rankMultiplier = rank.ordinal() + 3;
return (suitMultiplier * rankMultiplier) * 31;
// flip the card in the opposite orientation, faceUp/faceDown
@Override
public void flip()
isFaceUp = !isFaceUp;
// returns true if the card is faceUp
@Override
public boolean isFaceUp()
return isFaceUp;
// compares one card to another and returns an integer value
@Override
public int compareTo(Card o)
var card = Objects.requireNonNull(o);
var suitCompareResult = this.suit.compareTo(card.getSuit());
if (suitCompareResult != 0)
return suitCompareResult;
else
return this.rank.compareTo(card.getRank());
What could I improve?
I've toyed with the idea to make Hand
and/or Deck
an interface. However, I opted to go with classes in favor of code reuse and contained behavior. I send unmodifiable collections (Hand
, Deck
) as I want to control the behavior of the internal collection within their respective classes. You can see what it looks like, but you can't modify the Deck or Hand outside the class. Is this a good defensive strategy?
I'm also not 100% comfortable with the requirement of a Rank and Suit. How about a Magic the Gathering Card
? That doesn't have a suit, but you could argue that it has a rank. Is it better to leave the implementation up to the other developer? Same could be said for an Uno card, Color.Red
and Color.BLACK
aren't the only options there.
I've thought about the Rank enum also. If the game you model has an ACE
as a high card (not low as supplied here), how would you fix that in your own implementation? Create a new AceHighRank
and declare them in a different order? However, that would break PlayingCard
because it's expecting a Rank
(not AceHighRank
). Right?
java beginner playing-cards
3
"Go easy on me" ⦠"I look forward to your honest review. I have thick skin. I can take it." â Which do you want?
â 200_success
May 10 at 19:08
Touché. Updated.
â Frijolie
May 10 at 19:12
Nothing wrong with being self-taught.
â The Mattbat999
May 10 at 19:26
2
Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Malachiâ¦
May 10 at 19:45
add a comment |Â
up vote
6
down vote
favorite
up vote
6
down vote
favorite
I'm looking for an honest review of what I've got thus far. I'm still trying to get my head wrapped around software development and have picked Java as my starting language. That said, I figured that I would attempt to create a reusable module for which I'd use this as an external library.
I think it would be invaluable to hear from you experts on where I can improve or refactor. The code here is just snippets. Full code on GitHub.
Card.java
package com.frijolie.cards;
public interface Card extends Comparable<Card>
// returns the value of the Card. inherently coupled with the Rank
int getValue();
// returns the Rank of the Card. Values from Ace,2,3..., Jack,Queen,King
Rank getRank();
// returns the Suit of the Card. Values are Diamonds, Hearts, Spades, Clubs
Suit getSuit();
// returns the color of the Card. Values are Red or Black
Color getColor();
// display the card onscreen
void displayCard();
// compares this Card to another, returns if they are equal
boolean equals(final Object o);
// Generates a unique int value of a Card
int hashCode();
// flips a Card faceUp or faceDown
void flip();
// returns true if this Card is faceUp
boolean isFaceUp();
Rank.java
package com.frijlie.cards;
public enum Rank
ACE("Ace","A",1),
TWO("Two","2",2),
THREE("Three","3",3),
... // snipped for brevity
JACK("Jack","J",10),
QUEEN("Queen","Q",10),
KING("King","K","10);
private final String name;
private final String letter;
private final int value;
Rank(final String name, final String letter, final int value)
this.name = name;
this.letter = letter;
this.value = value;
... // accessor methods
Suit.java
package com.frijolie.cards;
public enum Suit
CLUBS("Clubs",'u2663', Color.BLACK),
DIAMONDS("Diamonds",'u2666', Color.RED),
SPADES("Spades",'u2660', Color.BLACK),
HEARTS("Hearts",'u2764', Color.RED);
private final String name;
private final char symbol;
private final Color color;
Suit(final String name, final char symbol, final Color color)
this.name = name;
this.symbol = symbol;
this.color = color;
... // accessor methods
Color.java
package com.frijolie.cards;
public enum Color
RED("Red"),
BLACK("Black");
private final String name;
Color(final String name)
this.name = name;
public final String getName()
return name;
Deck.java
package com.frijolie;
// snipped imports
public class Deck
private boolean isShuffled;
private Stack<Card> deck;
// default constructor, will create a single deck of 52 cards
public Deck()
this(1)
// overloaded constructor, will create multiple decks of 52 cards
public deck(int numberOfDecks)
deck = new Stack<>();
clearDeck();
for (int i = 0; i < numberOfDecks; i++)
populateDeck();
// a method to shuffle the collection of cards. default will shuffle 1x
public void shuffle()
shuffle(1);
// overloaded shuffle method. will shuffle the deck a number of times.
public void shuffle(int numberOfTimes)
isShuffled = true;
for (int i = 0; i < numberOfTimes; i++)
Collections.shuffle(deck);
// returns true if the deck has been shuffled
public boolean isShuffled()
return isShuffled;
// returns the number of cards remaining in the deck
public int numberOfCards()
return deck.size();
// a method to remove a card from the deck
public Card removeCard(Card card)
if(deck.contains(Objects.requireNonNull(card)))
int index = deck.indexOf(card);
return deck.remove(index);
else
return null;
// returns true if the specified card is contained in the deck
public boolean containsCard(Card card)
return deck.contains(Objects.requireNonNull(card));
// populates the deck with all possible cards in the deck. 52 cards
public void populateDeck()
for(Suit suit: Suit.values())
for(Rank rank: Rank.values())
deck.add(new PlayingCard(rank,suit));
// Overloaded method which allows any number of decks of 52 cards.
public void populateDeck(int numberOfDecks)
for(int i = 0; i < numberOfDecks; i++)
populateDeck();
// remove all cards from the deck
public void clearDeck()
deck.clear();
// returns an unmodifiable collection, a view, of the deck.
public Collection<Card> getUnmodifiableCollection()
return new ArrayList<>(deck);
// returns the first card in the collection.
public Card pop()
return deck.pop();
Hand.java
package com.frijolie.cards;
// snipped imports
public class Hand implements Comparable<Hand>
private List<Card> cards;
// default constructor
public Hand()
cards = new ArrayList<>();
// will add a specific card to the Hand
public void addCard(Card card)
cards.add(Objects.requireNonNull(card));
// will remove a specific card from the Hand if it's contained in the Hand
public Card removeCard(Card card)
if (cards.contains(Objects.requireNonNull(card)))
int index = cards.indexOf(card);
return cards.remove(index);
else
throw new IllegalArgumentException("This card did not exist in the hand. It cannot be removed.");
// method to display all the Cards in the Hand
public void displayHand()
for (Card card : cards)
if (card.isFaceUp())
card.displayCard();
else
System.out.println("[ Card Back ]");
// returns the sum of the value of all Cards in the Hand
public int calculateValue()
var handValue = 0;
for (Card card : cards)
handValue += card.getRank().getValue();
return handValue;
// remove all cards in the Hand
public void clearHand()
cards.clear();
// returns a unmodifiable collection, a view, of all cards in the Hand
public Collection<Card> getUnmodifiableCollection()
return Collections.unmodifiableCollection(cards);
// compares one Hand to another
public int compareTo(final Hand otherHand)
return this.calculateValue() - otherHand.calculateValue();
// sorts cards in the Hand by Suit. Doesn't factor Rank within the Suit when sorting
public void sortyBySuit()
cards.sort(Comparator.comparing(Card::getSuit));
// sorts cards in the Hand by Rank. Doesn't factor Suit when sorting.
public void sortByRank()
cards.sort(Comparator.comparing(Card::getRank));
// sorts cards in the Hand by Color. Doesn't factor Suit or Rank when sorting.
public void sortByColor()
cards.sort(Comparator.comparing(Card::getColor));
// sorts cards in the Hand by Suit then Rank in ascending order.
public void sort()
cards.sort((Card first, Card second) -> first.compareTo(second));
// allows a card in the hand to be flipped in the opposite orientation
public void flipCard(int index)
cards.get(index).flip();
PlayingCard.java
package com.frijolie.cards;
import java.util.Objects;
public class PlayingCard implements Card
private final Rank rank;
private final Suit suit;
private boolean isFaceUp;
// default constructor
public PlayingCard(final Rank rank, final Suit suit)
this.rank = rank;
this.suit = suit;
isFaceUp = true;
... // snipping implemented accessor methods from Card interface
// returns a string representation of the card
@Override
public String toString()
return rank.getLetter() + suit.getSymbol();
// compares one PlayingCard to another
@Override
public boolean equals(final Object o)
Objects.requireNonNull(o);
boolean isEqual;
if (!(o instanceof Card))
isEqual = false;
else if (this == 0)
isEqual = true;
else
var card = (Card) o;
isEqual = (this.getRank().equals(card.getRank()) && this.suit.equals(card.getSuit()));
return isEqual;
// generates unique int value of a card
@Override
public int hashCode()
int suitMultiplier = suit.ordinal() + 2;
int rankMultiplier = rank.ordinal() + 3;
return (suitMultiplier * rankMultiplier) * 31;
// flip the card in the opposite orientation, faceUp/faceDown
@Override
public void flip()
isFaceUp = !isFaceUp;
// returns true if the card is faceUp
@Override
public boolean isFaceUp()
return isFaceUp;
// compares one card to another and returns an integer value
@Override
public int compareTo(Card o)
var card = Objects.requireNonNull(o);
var suitCompareResult = this.suit.compareTo(card.getSuit());
if (suitCompareResult != 0)
return suitCompareResult;
else
return this.rank.compareTo(card.getRank());
What could I improve?
I've toyed with the idea to make Hand
and/or Deck
an interface. However, I opted to go with classes in favor of code reuse and contained behavior. I send unmodifiable collections (Hand
, Deck
) as I want to control the behavior of the internal collection within their respective classes. You can see what it looks like, but you can't modify the Deck or Hand outside the class. Is this a good defensive strategy?
I'm also not 100% comfortable with the requirement of a Rank and Suit. How about a Magic the Gathering Card
? That doesn't have a suit, but you could argue that it has a rank. Is it better to leave the implementation up to the other developer? Same could be said for an Uno card, Color.Red
and Color.BLACK
aren't the only options there.
I've thought about the Rank enum also. If the game you model has an ACE
as a high card (not low as supplied here), how would you fix that in your own implementation? Create a new AceHighRank
and declare them in a different order? However, that would break PlayingCard
because it's expecting a Rank
(not AceHighRank
). Right?
java beginner playing-cards
I'm looking for an honest review of what I've got thus far. I'm still trying to get my head wrapped around software development and have picked Java as my starting language. That said, I figured that I would attempt to create a reusable module for which I'd use this as an external library.
I think it would be invaluable to hear from you experts on where I can improve or refactor. The code here is just snippets. Full code on GitHub.
Card.java
package com.frijolie.cards;
public interface Card extends Comparable<Card>
// returns the value of the Card. inherently coupled with the Rank
int getValue();
// returns the Rank of the Card. Values from Ace,2,3..., Jack,Queen,King
Rank getRank();
// returns the Suit of the Card. Values are Diamonds, Hearts, Spades, Clubs
Suit getSuit();
// returns the color of the Card. Values are Red or Black
Color getColor();
// display the card onscreen
void displayCard();
// compares this Card to another, returns if they are equal
boolean equals(final Object o);
// Generates a unique int value of a Card
int hashCode();
// flips a Card faceUp or faceDown
void flip();
// returns true if this Card is faceUp
boolean isFaceUp();
Rank.java
package com.frijlie.cards;
public enum Rank
ACE("Ace","A",1),
TWO("Two","2",2),
THREE("Three","3",3),
... // snipped for brevity
JACK("Jack","J",10),
QUEEN("Queen","Q",10),
KING("King","K","10);
private final String name;
private final String letter;
private final int value;
Rank(final String name, final String letter, final int value)
this.name = name;
this.letter = letter;
this.value = value;
... // accessor methods
Suit.java
package com.frijolie.cards;
public enum Suit
CLUBS("Clubs",'u2663', Color.BLACK),
DIAMONDS("Diamonds",'u2666', Color.RED),
SPADES("Spades",'u2660', Color.BLACK),
HEARTS("Hearts",'u2764', Color.RED);
private final String name;
private final char symbol;
private final Color color;
Suit(final String name, final char symbol, final Color color)
this.name = name;
this.symbol = symbol;
this.color = color;
... // accessor methods
Color.java
package com.frijolie.cards;
public enum Color
RED("Red"),
BLACK("Black");
private final String name;
Color(final String name)
this.name = name;
public final String getName()
return name;
Deck.java
package com.frijolie;
// snipped imports
public class Deck
private boolean isShuffled;
private Stack<Card> deck;
// default constructor, will create a single deck of 52 cards
public Deck()
this(1)
// overloaded constructor, will create multiple decks of 52 cards
public deck(int numberOfDecks)
deck = new Stack<>();
clearDeck();
for (int i = 0; i < numberOfDecks; i++)
populateDeck();
// a method to shuffle the collection of cards. default will shuffle 1x
public void shuffle()
shuffle(1);
// overloaded shuffle method. will shuffle the deck a number of times.
public void shuffle(int numberOfTimes)
isShuffled = true;
for (int i = 0; i < numberOfTimes; i++)
Collections.shuffle(deck);
// returns true if the deck has been shuffled
public boolean isShuffled()
return isShuffled;
// returns the number of cards remaining in the deck
public int numberOfCards()
return deck.size();
// a method to remove a card from the deck
public Card removeCard(Card card)
if(deck.contains(Objects.requireNonNull(card)))
int index = deck.indexOf(card);
return deck.remove(index);
else
return null;
// returns true if the specified card is contained in the deck
public boolean containsCard(Card card)
return deck.contains(Objects.requireNonNull(card));
// populates the deck with all possible cards in the deck. 52 cards
public void populateDeck()
for(Suit suit: Suit.values())
for(Rank rank: Rank.values())
deck.add(new PlayingCard(rank,suit));
// Overloaded method which allows any number of decks of 52 cards.
public void populateDeck(int numberOfDecks)
for(int i = 0; i < numberOfDecks; i++)
populateDeck();
// remove all cards from the deck
public void clearDeck()
deck.clear();
// returns an unmodifiable collection, a view, of the deck.
public Collection<Card> getUnmodifiableCollection()
return new ArrayList<>(deck);
// returns the first card in the collection.
public Card pop()
return deck.pop();
Hand.java
package com.frijolie.cards;
// snipped imports
public class Hand implements Comparable<Hand>
private List<Card> cards;
// default constructor
public Hand()
cards = new ArrayList<>();
// will add a specific card to the Hand
public void addCard(Card card)
cards.add(Objects.requireNonNull(card));
// will remove a specific card from the Hand if it's contained in the Hand
public Card removeCard(Card card)
if (cards.contains(Objects.requireNonNull(card)))
int index = cards.indexOf(card);
return cards.remove(index);
else
throw new IllegalArgumentException("This card did not exist in the hand. It cannot be removed.");
// method to display all the Cards in the Hand
public void displayHand()
for (Card card : cards)
if (card.isFaceUp())
card.displayCard();
else
System.out.println("[ Card Back ]");
// returns the sum of the value of all Cards in the Hand
public int calculateValue()
var handValue = 0;
for (Card card : cards)
handValue += card.getRank().getValue();
return handValue;
// remove all cards in the Hand
public void clearHand()
cards.clear();
// returns a unmodifiable collection, a view, of all cards in the Hand
public Collection<Card> getUnmodifiableCollection()
return Collections.unmodifiableCollection(cards);
// compares one Hand to another
public int compareTo(final Hand otherHand)
return this.calculateValue() - otherHand.calculateValue();
// sorts cards in the Hand by Suit. Doesn't factor Rank within the Suit when sorting
public void sortyBySuit()
cards.sort(Comparator.comparing(Card::getSuit));
// sorts cards in the Hand by Rank. Doesn't factor Suit when sorting.
public void sortByRank()
cards.sort(Comparator.comparing(Card::getRank));
// sorts cards in the Hand by Color. Doesn't factor Suit or Rank when sorting.
public void sortByColor()
cards.sort(Comparator.comparing(Card::getColor));
// sorts cards in the Hand by Suit then Rank in ascending order.
public void sort()
cards.sort((Card first, Card second) -> first.compareTo(second));
// allows a card in the hand to be flipped in the opposite orientation
public void flipCard(int index)
cards.get(index).flip();
PlayingCard.java
package com.frijolie.cards;
import java.util.Objects;
public class PlayingCard implements Card
private final Rank rank;
private final Suit suit;
private boolean isFaceUp;
// default constructor
public PlayingCard(final Rank rank, final Suit suit)
this.rank = rank;
this.suit = suit;
isFaceUp = true;
... // snipping implemented accessor methods from Card interface
// returns a string representation of the card
@Override
public String toString()
return rank.getLetter() + suit.getSymbol();
// compares one PlayingCard to another
@Override
public boolean equals(final Object o)
Objects.requireNonNull(o);
boolean isEqual;
if (!(o instanceof Card))
isEqual = false;
else if (this == 0)
isEqual = true;
else
var card = (Card) o;
isEqual = (this.getRank().equals(card.getRank()) && this.suit.equals(card.getSuit()));
return isEqual;
// generates unique int value of a card
@Override
public int hashCode()
int suitMultiplier = suit.ordinal() + 2;
int rankMultiplier = rank.ordinal() + 3;
return (suitMultiplier * rankMultiplier) * 31;
// flip the card in the opposite orientation, faceUp/faceDown
@Override
public void flip()
isFaceUp = !isFaceUp;
// returns true if the card is faceUp
@Override
public boolean isFaceUp()
return isFaceUp;
// compares one card to another and returns an integer value
@Override
public int compareTo(Card o)
var card = Objects.requireNonNull(o);
var suitCompareResult = this.suit.compareTo(card.getSuit());
if (suitCompareResult != 0)
return suitCompareResult;
else
return this.rank.compareTo(card.getRank());
What could I improve?
I've toyed with the idea to make Hand
and/or Deck
an interface. However, I opted to go with classes in favor of code reuse and contained behavior. I send unmodifiable collections (Hand
, Deck
) as I want to control the behavior of the internal collection within their respective classes. You can see what it looks like, but you can't modify the Deck or Hand outside the class. Is this a good defensive strategy?
I'm also not 100% comfortable with the requirement of a Rank and Suit. How about a Magic the Gathering Card
? That doesn't have a suit, but you could argue that it has a rank. Is it better to leave the implementation up to the other developer? Same could be said for an Uno card, Color.Red
and Color.BLACK
aren't the only options there.
I've thought about the Rank enum also. If the game you model has an ACE
as a high card (not low as supplied here), how would you fix that in your own implementation? Create a new AceHighRank
and declare them in a different order? However, that would break PlayingCard
because it's expecting a Rank
(not AceHighRank
). Right?
java beginner playing-cards
edited May 11 at 1:31
Jamalâ¦
30.1k11114225
30.1k11114225
asked May 10 at 18:38
Frijolie
313
313
3
"Go easy on me" ⦠"I look forward to your honest review. I have thick skin. I can take it." â Which do you want?
â 200_success
May 10 at 19:08
Touché. Updated.
â Frijolie
May 10 at 19:12
Nothing wrong with being self-taught.
â The Mattbat999
May 10 at 19:26
2
Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Malachiâ¦
May 10 at 19:45
add a comment |Â
3
"Go easy on me" ⦠"I look forward to your honest review. I have thick skin. I can take it." â Which do you want?
â 200_success
May 10 at 19:08
Touché. Updated.
â Frijolie
May 10 at 19:12
Nothing wrong with being self-taught.
â The Mattbat999
May 10 at 19:26
2
Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Malachiâ¦
May 10 at 19:45
3
3
"Go easy on me" ⦠"I look forward to your honest review. I have thick skin. I can take it." â Which do you want?
â 200_success
May 10 at 19:08
"Go easy on me" ⦠"I look forward to your honest review. I have thick skin. I can take it." â Which do you want?
â 200_success
May 10 at 19:08
Touché. Updated.
â Frijolie
May 10 at 19:12
Touché. Updated.
â Frijolie
May 10 at 19:12
Nothing wrong with being self-taught.
â The Mattbat999
May 10 at 19:26
Nothing wrong with being self-taught.
â The Mattbat999
May 10 at 19:26
2
2
Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Malachiâ¦
May 10 at 19:45
Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Malachiâ¦
May 10 at 19:45
add a comment |Â
3 Answers
3
active
oldest
votes
up vote
2
down vote
I think you are really thinking well about what is going on, what potential up and downsides are. I am not really familiar with card games, but my initial guess would be that it may be a bit long stretched to find a unified interface for all types of card games. In general you have cards, hands and decks, but the similarities can pretty much end there.
I also like that you used good descriptive names, private fields, and final where possible (though you are not entirely consistent with it). There is quite a bit of code, so I'll comment on the major code wise things that spring to mind:
The method comments should really be changed to true JavaDoc, in order for them to be useful to the users of your library. EDIT: apparently you already did this in your GitHub version.
I am not sure if you would really want to force implementors to implement equals/hashcode.
Like you said, the
Rank
(more specifically, it's value) can differ from game to game. This would make more sense in theCard
implementation itself I'd think. Possibly even higher up?isShuffled
inDeck
seems a bit strange to me. Why would you need it? As soon as you shuffle the deck, it will also always betrue
.You should generally not use the
Stack
class in Java, see StackYou do not need to loop yourself in order to fill the deck multiple times, you already have a method for that!
I would throw an exception when someone passes a card which is not contained in the deck, instead of
null
.Think about how you would support decks for games which would not use the entire range of cards.
You are returning a
Collection
, which does not imply order. Your deck is very much ordered, so return aList
, or your stack-like class. Also, the method does not tell me anything about what it is giving me.I would rename
pop
to something likedrawCard
.I would not mix user interface code within your domain objects, like e.g. the
displayHand
method. What if you would want to use it with a realistic 3D user interface?The
compareTo
method inHand
is implemented somewhat risky. Only because you know value is always positive, it always works. Just something to think about.Nice use of method references with the
Comparator
! You could use a static import to writesort(comparing(Card::getSuit))
. You can also use this for the basic sort method:cards.sort(Card::compareTo)
.Return early. In the equals method, as soon as you know it is not equal, return
false
. Don't introduce unnecessary variables, unless they would help with readability.Your
hashCode
implementation is not really good, e.g. the hashcode ofsuit.ordinal = 1, rank.ordinal = 2
is equal to the one ofsuit.ordinal = 3, rank.ordinal = 0
, and there are more to be found. You can look up how to implement a correct hashcode, or use some auto generation in your IDE to get started. Returning a lot of equal values is not wrong, but can slow down e.g. hashing data structures.
Your question about only changing state through the public interface: definitely yes! It is a really good principle. It makes sure you are in control.
Finally, this is me, but I am not much of a fan of using the get
prefix. Apart from implying it always is a simple field acces (which granted, in this case is generally true), for me it feels superfluous. Apparently, it also has a name, namely the Uniform Access Principle
. There are some interesting reads about this I think.
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent ofisShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why noStack
? I've also heard don't useLinkedList
orQueues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.
â Frijolie
May 11 at 17:30
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong withLinkedList
orQueue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.com
â Koekje
May 11 at 17:52
add a comment |Â
up vote
2
down vote
Every competent non-junior developer is self-taught, because you don't learn everything you need to know in school.
This is actually a reasonably tricky problem you've picked out, so don't be discouraged by all the feedback. You need to spend more time thinking about the bounds between application-specific code (Poker rules, Uno rules, etc) and the generic library code (How does a Deck work, what does a Card look like). Right now it's all mixed together. That's not surprising - building good APIs is hard.
It also seems like you have a lot of methods that serve no particular purpose because you think they might be handy later. It's really easy to add and really hard to remove, and YAGNI. Wait for a use case before adding things, and make sure you do it right, because if you have to fix it all your clients will break.
Card
Use javadoc, not comments
equals
andhashCode
aren't doing anything. All objects have them, so everyCard
will always fulfill that part of the contract without doing any extra work.Consider having
flip()
return the new state of the card.What is the point of this interface? If you're only building for poker cards, the interface is noise. If you're building for other kinds of cards, you're forcing a ton of false requirements - Uno cards, for instance, can't fulfill this contract. Strongly consider making this a marker interface if you want to support non-poker games.
The methods are mixed-up. On the one hand, clients can ask your for your internals and you freely share them. On the other hand, the card renders itself with no inputs. Either keep your privates private and use instead methods like
hasRank(Rank rank)
or get rid of therender
method and let the client do it.
Color
- Having a class with the same name as a class in the Java API is a bad idea. You're asking for conflicts. It's not really a
Color
, it's aCardColor
or some such.
Deck
Deck
needs to be final. You can't have users extending Deck with their own functionality (like putting all the good cards in one place when "shuffling")sA deck is a well-understood concept. It's nonsensical to create a deck and specify the number of decks it has. You need a separate concept, a
Shoe
.Shoe
s can hold multiple decks of poker cards in them.You don't need to clear a new
Deck
. It's already clear.This isn't a generic
Deck
class. It's a poker deck. Do you want something useful for other games? If so, you need to take all the poker functionality into a separate class.Shuffling more than once in a row is not sensical in code. The deck will be no more shuffled after the eighth time than the first.
isShuffled()
is not useful. After you call shuffle, the deck is shuffled. Unless you add some way to unshuffle a deck?I doubt the utility of
removeCard(Card)
andhasCard(Card)
. I guess there might be games where they are helpful, but I can't think of any.It's visually ugly to use the return value of
Objects.requireNonNull()
like that. Declare it as a standalone call.populateDeck()
again assumes a poker deck. And it'spublic
, which means any yahoo with access to the deck can throw in cards at any time.clearDeck()
is the same problem. And what does it mean for a deck to becleared
? In real life, you don't ever get rid of all the cards, still have a "deck", and then add cards back in. You might take cards out of the deck or put them back in, but that's different than the abstraction you have.What's the use case for giving out a list of the cards? You should return a
List
, not aCollection
, because decks typically have an order.A deck is usually not a stack. A
Deque
might be more appropriate.pop
is leaking internal details. Nobody says "pop me a card off that deck".
Hand
Needs to be
final
for the same reason asDeck
.Is there a use case for removing a specific card from a
Hand
?Again, keep the display code out of the model. Either pass in something to do the rendering or have the UI call methods on
Hand
to render.calculateValue
- is there a use case for this? What game uses as a value the sum of all the cards?clearHand
- this needs a better name, because that's not what people talk about doing in real life. Also, it should return the cards you just tossed, to facilitate putting them back in the deck. Or maybe pass in a deck and add the cards back to it?compareTo
- there are lots of different ways to compare the strengths of two hands. You shouldn't be picking one and hard-coding it here.Don't have multiple hard-coded sort methods. Have one method that takes a comparator. Otherwise every time any client wants a new sort method, the API needs to change.
Maybe
revealed
would be better thanflipped
? I also think that therevealed
state of a card belongs here, not on theCard
.
Playing Card
Does
isFaceUp
make sense onPlayingCard
? I guess it might if your deck can have face-up cards in it. But strongly consider if that should be external state. That sounds like it belongs to the UI layer, not the model.Return early from
equals()
. The reason for a single return was made obsolete years ago.Strongly consider whether you want to implement
compareTo()
. Some games have trump suits which can change the natural ordering of the cards in the deck.
I took a stab at a generic deck class, and came up with this. It should probably have a draw(int cards)
and overloaded discard methods also, but I need to pack.
//Note that this class is *NOT* thread safe. It will definitely have problems in a multi-threaded environment.
public final class Deck<T extends Card>
private final List<T> deck = new LinkedList<>();
private final Set<T> outstandingCards = new HashSet<>();
private final List<T> discardPile = new LinkedList<>();
public Deck(final Collection<T> cards)
super();
this.deck.addAll(cards);
public void shuffle()
this.deck.addAll(this.discardPile);
this.discardPile.clear();
Collections.shuffle(this.deck);
public boolean isDeckEmpty()
return this.deck.isEmpty();
public int deckSize()
return this.deck.size();
public T draw()
if (this.isDeckEmpty())
throw new IllegalStateException();
final T card = this.deck.remove(0);
this.outstandingCards.add(card);
return card;
public void discard(final T card)
if (!this.outstandingCards.contains(card))
// Somebody tried to sneak a new card into the deck!
throw new IllegalStateException();
this.outstandingCards.remove(card);
this.discardPile.add(card);
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basicCollections#shuffle
seems to be using Java's defaultRandom
, which if I am not mistaken uses a48-bit
seed, way less than52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.
â Koekje
May 11 at 11:02
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?CardState.FACE_UP
? I get the concept of a shoe. However, why isclear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again?Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor tosort(Comparator c)
â Frijolie
May 12 at 16:00
add a comment |Â
up vote
1
down vote
In Deck.java you have:
if(deck.contains(Objects.requireNonNull(card))) {
but you have a function containsCard
that could shorten it:
if(containsCard(card))
add a comment |Â
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
I think you are really thinking well about what is going on, what potential up and downsides are. I am not really familiar with card games, but my initial guess would be that it may be a bit long stretched to find a unified interface for all types of card games. In general you have cards, hands and decks, but the similarities can pretty much end there.
I also like that you used good descriptive names, private fields, and final where possible (though you are not entirely consistent with it). There is quite a bit of code, so I'll comment on the major code wise things that spring to mind:
The method comments should really be changed to true JavaDoc, in order for them to be useful to the users of your library. EDIT: apparently you already did this in your GitHub version.
I am not sure if you would really want to force implementors to implement equals/hashcode.
Like you said, the
Rank
(more specifically, it's value) can differ from game to game. This would make more sense in theCard
implementation itself I'd think. Possibly even higher up?isShuffled
inDeck
seems a bit strange to me. Why would you need it? As soon as you shuffle the deck, it will also always betrue
.You should generally not use the
Stack
class in Java, see StackYou do not need to loop yourself in order to fill the deck multiple times, you already have a method for that!
I would throw an exception when someone passes a card which is not contained in the deck, instead of
null
.Think about how you would support decks for games which would not use the entire range of cards.
You are returning a
Collection
, which does not imply order. Your deck is very much ordered, so return aList
, or your stack-like class. Also, the method does not tell me anything about what it is giving me.I would rename
pop
to something likedrawCard
.I would not mix user interface code within your domain objects, like e.g. the
displayHand
method. What if you would want to use it with a realistic 3D user interface?The
compareTo
method inHand
is implemented somewhat risky. Only because you know value is always positive, it always works. Just something to think about.Nice use of method references with the
Comparator
! You could use a static import to writesort(comparing(Card::getSuit))
. You can also use this for the basic sort method:cards.sort(Card::compareTo)
.Return early. In the equals method, as soon as you know it is not equal, return
false
. Don't introduce unnecessary variables, unless they would help with readability.Your
hashCode
implementation is not really good, e.g. the hashcode ofsuit.ordinal = 1, rank.ordinal = 2
is equal to the one ofsuit.ordinal = 3, rank.ordinal = 0
, and there are more to be found. You can look up how to implement a correct hashcode, or use some auto generation in your IDE to get started. Returning a lot of equal values is not wrong, but can slow down e.g. hashing data structures.
Your question about only changing state through the public interface: definitely yes! It is a really good principle. It makes sure you are in control.
Finally, this is me, but I am not much of a fan of using the get
prefix. Apart from implying it always is a simple field acces (which granted, in this case is generally true), for me it feels superfluous. Apparently, it also has a name, namely the Uniform Access Principle
. There are some interesting reads about this I think.
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent ofisShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why noStack
? I've also heard don't useLinkedList
orQueues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.
â Frijolie
May 11 at 17:30
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong withLinkedList
orQueue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.com
â Koekje
May 11 at 17:52
add a comment |Â
up vote
2
down vote
I think you are really thinking well about what is going on, what potential up and downsides are. I am not really familiar with card games, but my initial guess would be that it may be a bit long stretched to find a unified interface for all types of card games. In general you have cards, hands and decks, but the similarities can pretty much end there.
I also like that you used good descriptive names, private fields, and final where possible (though you are not entirely consistent with it). There is quite a bit of code, so I'll comment on the major code wise things that spring to mind:
The method comments should really be changed to true JavaDoc, in order for them to be useful to the users of your library. EDIT: apparently you already did this in your GitHub version.
I am not sure if you would really want to force implementors to implement equals/hashcode.
Like you said, the
Rank
(more specifically, it's value) can differ from game to game. This would make more sense in theCard
implementation itself I'd think. Possibly even higher up?isShuffled
inDeck
seems a bit strange to me. Why would you need it? As soon as you shuffle the deck, it will also always betrue
.You should generally not use the
Stack
class in Java, see StackYou do not need to loop yourself in order to fill the deck multiple times, you already have a method for that!
I would throw an exception when someone passes a card which is not contained in the deck, instead of
null
.Think about how you would support decks for games which would not use the entire range of cards.
You are returning a
Collection
, which does not imply order. Your deck is very much ordered, so return aList
, or your stack-like class. Also, the method does not tell me anything about what it is giving me.I would rename
pop
to something likedrawCard
.I would not mix user interface code within your domain objects, like e.g. the
displayHand
method. What if you would want to use it with a realistic 3D user interface?The
compareTo
method inHand
is implemented somewhat risky. Only because you know value is always positive, it always works. Just something to think about.Nice use of method references with the
Comparator
! You could use a static import to writesort(comparing(Card::getSuit))
. You can also use this for the basic sort method:cards.sort(Card::compareTo)
.Return early. In the equals method, as soon as you know it is not equal, return
false
. Don't introduce unnecessary variables, unless they would help with readability.Your
hashCode
implementation is not really good, e.g. the hashcode ofsuit.ordinal = 1, rank.ordinal = 2
is equal to the one ofsuit.ordinal = 3, rank.ordinal = 0
, and there are more to be found. You can look up how to implement a correct hashcode, or use some auto generation in your IDE to get started. Returning a lot of equal values is not wrong, but can slow down e.g. hashing data structures.
Your question about only changing state through the public interface: definitely yes! It is a really good principle. It makes sure you are in control.
Finally, this is me, but I am not much of a fan of using the get
prefix. Apart from implying it always is a simple field acces (which granted, in this case is generally true), for me it feels superfluous. Apparently, it also has a name, namely the Uniform Access Principle
. There are some interesting reads about this I think.
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent ofisShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why noStack
? I've also heard don't useLinkedList
orQueues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.
â Frijolie
May 11 at 17:30
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong withLinkedList
orQueue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.com
â Koekje
May 11 at 17:52
add a comment |Â
up vote
2
down vote
up vote
2
down vote
I think you are really thinking well about what is going on, what potential up and downsides are. I am not really familiar with card games, but my initial guess would be that it may be a bit long stretched to find a unified interface for all types of card games. In general you have cards, hands and decks, but the similarities can pretty much end there.
I also like that you used good descriptive names, private fields, and final where possible (though you are not entirely consistent with it). There is quite a bit of code, so I'll comment on the major code wise things that spring to mind:
The method comments should really be changed to true JavaDoc, in order for them to be useful to the users of your library. EDIT: apparently you already did this in your GitHub version.
I am not sure if you would really want to force implementors to implement equals/hashcode.
Like you said, the
Rank
(more specifically, it's value) can differ from game to game. This would make more sense in theCard
implementation itself I'd think. Possibly even higher up?isShuffled
inDeck
seems a bit strange to me. Why would you need it? As soon as you shuffle the deck, it will also always betrue
.You should generally not use the
Stack
class in Java, see StackYou do not need to loop yourself in order to fill the deck multiple times, you already have a method for that!
I would throw an exception when someone passes a card which is not contained in the deck, instead of
null
.Think about how you would support decks for games which would not use the entire range of cards.
You are returning a
Collection
, which does not imply order. Your deck is very much ordered, so return aList
, or your stack-like class. Also, the method does not tell me anything about what it is giving me.I would rename
pop
to something likedrawCard
.I would not mix user interface code within your domain objects, like e.g. the
displayHand
method. What if you would want to use it with a realistic 3D user interface?The
compareTo
method inHand
is implemented somewhat risky. Only because you know value is always positive, it always works. Just something to think about.Nice use of method references with the
Comparator
! You could use a static import to writesort(comparing(Card::getSuit))
. You can also use this for the basic sort method:cards.sort(Card::compareTo)
.Return early. In the equals method, as soon as you know it is not equal, return
false
. Don't introduce unnecessary variables, unless they would help with readability.Your
hashCode
implementation is not really good, e.g. the hashcode ofsuit.ordinal = 1, rank.ordinal = 2
is equal to the one ofsuit.ordinal = 3, rank.ordinal = 0
, and there are more to be found. You can look up how to implement a correct hashcode, or use some auto generation in your IDE to get started. Returning a lot of equal values is not wrong, but can slow down e.g. hashing data structures.
Your question about only changing state through the public interface: definitely yes! It is a really good principle. It makes sure you are in control.
Finally, this is me, but I am not much of a fan of using the get
prefix. Apart from implying it always is a simple field acces (which granted, in this case is generally true), for me it feels superfluous. Apparently, it also has a name, namely the Uniform Access Principle
. There are some interesting reads about this I think.
I think you are really thinking well about what is going on, what potential up and downsides are. I am not really familiar with card games, but my initial guess would be that it may be a bit long stretched to find a unified interface for all types of card games. In general you have cards, hands and decks, but the similarities can pretty much end there.
I also like that you used good descriptive names, private fields, and final where possible (though you are not entirely consistent with it). There is quite a bit of code, so I'll comment on the major code wise things that spring to mind:
The method comments should really be changed to true JavaDoc, in order for them to be useful to the users of your library. EDIT: apparently you already did this in your GitHub version.
I am not sure if you would really want to force implementors to implement equals/hashcode.
Like you said, the
Rank
(more specifically, it's value) can differ from game to game. This would make more sense in theCard
implementation itself I'd think. Possibly even higher up?isShuffled
inDeck
seems a bit strange to me. Why would you need it? As soon as you shuffle the deck, it will also always betrue
.You should generally not use the
Stack
class in Java, see StackYou do not need to loop yourself in order to fill the deck multiple times, you already have a method for that!
I would throw an exception when someone passes a card which is not contained in the deck, instead of
null
.Think about how you would support decks for games which would not use the entire range of cards.
You are returning a
Collection
, which does not imply order. Your deck is very much ordered, so return aList
, or your stack-like class. Also, the method does not tell me anything about what it is giving me.I would rename
pop
to something likedrawCard
.I would not mix user interface code within your domain objects, like e.g. the
displayHand
method. What if you would want to use it with a realistic 3D user interface?The
compareTo
method inHand
is implemented somewhat risky. Only because you know value is always positive, it always works. Just something to think about.Nice use of method references with the
Comparator
! You could use a static import to writesort(comparing(Card::getSuit))
. You can also use this for the basic sort method:cards.sort(Card::compareTo)
.Return early. In the equals method, as soon as you know it is not equal, return
false
. Don't introduce unnecessary variables, unless they would help with readability.Your
hashCode
implementation is not really good, e.g. the hashcode ofsuit.ordinal = 1, rank.ordinal = 2
is equal to the one ofsuit.ordinal = 3, rank.ordinal = 0
, and there are more to be found. You can look up how to implement a correct hashcode, or use some auto generation in your IDE to get started. Returning a lot of equal values is not wrong, but can slow down e.g. hashing data structures.
Your question about only changing state through the public interface: definitely yes! It is a really good principle. It makes sure you are in control.
Finally, this is me, but I am not much of a fan of using the get
prefix. Apart from implying it always is a simple field acces (which granted, in this case is generally true), for me it feels superfluous. Apparently, it also has a name, namely the Uniform Access Principle
. There are some interesting reads about this I think.
edited May 10 at 20:14
answered May 10 at 20:01
Koekje
1,017211
1,017211
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent ofisShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why noStack
? I've also heard don't useLinkedList
orQueues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.
â Frijolie
May 11 at 17:30
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong withLinkedList
orQueue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.com
â Koekje
May 11 at 17:52
add a comment |Â
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent ofisShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why noStack
? I've also heard don't useLinkedList
orQueues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.
â Frijolie
May 11 at 17:30
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong withLinkedList
orQueue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.com
â Koekje
May 11 at 17:52
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent of
isShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why no Stack
? I've also heard don't use LinkedList
or Queues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.â Frijolie
May 11 at 17:30
Wow! Thank you for such a comprehensive review. I should have indicated I omitted several details in an interest of brevity (i.e. true Javadocs). The intent of
isShuffled()
was to indicate to the clients if the deck has ever been shuffled. I didn't assume a deck would always be shuffled. Therefore, thought a flag would be necessary. Why no Stack
? I've also heard don't use LinkedList
or Queues
either. A Deck is a LIFO collection. Thought it was a good fit. Every card should have a way to display itself, figured leave that up to the client (console or GUI). I'll look into UAP.â Frijolie
May 11 at 17:30
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong with
LinkedList
or Queue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.comâ Koekje
May 11 at 17:52
It's not really a stack in itself that is the problem, but the Java implementation leaves something to be desired, see e.g. stackoverflow.com/questions/12524826/⦠. As you state yourself, it is a LIFO data structure, however you are also indexing it when removing elements, something it is not made for (the fact that you can is an example of the questionable design). There is nothing wrong with
LinkedList
or Queue
, it just depends on the use case. You can take a look at the how each one behaves, see e.g. bigocheatsheet.comâ Koekje
May 11 at 17:52
add a comment |Â
up vote
2
down vote
Every competent non-junior developer is self-taught, because you don't learn everything you need to know in school.
This is actually a reasonably tricky problem you've picked out, so don't be discouraged by all the feedback. You need to spend more time thinking about the bounds between application-specific code (Poker rules, Uno rules, etc) and the generic library code (How does a Deck work, what does a Card look like). Right now it's all mixed together. That's not surprising - building good APIs is hard.
It also seems like you have a lot of methods that serve no particular purpose because you think they might be handy later. It's really easy to add and really hard to remove, and YAGNI. Wait for a use case before adding things, and make sure you do it right, because if you have to fix it all your clients will break.
Card
Use javadoc, not comments
equals
andhashCode
aren't doing anything. All objects have them, so everyCard
will always fulfill that part of the contract without doing any extra work.Consider having
flip()
return the new state of the card.What is the point of this interface? If you're only building for poker cards, the interface is noise. If you're building for other kinds of cards, you're forcing a ton of false requirements - Uno cards, for instance, can't fulfill this contract. Strongly consider making this a marker interface if you want to support non-poker games.
The methods are mixed-up. On the one hand, clients can ask your for your internals and you freely share them. On the other hand, the card renders itself with no inputs. Either keep your privates private and use instead methods like
hasRank(Rank rank)
or get rid of therender
method and let the client do it.
Color
- Having a class with the same name as a class in the Java API is a bad idea. You're asking for conflicts. It's not really a
Color
, it's aCardColor
or some such.
Deck
Deck
needs to be final. You can't have users extending Deck with their own functionality (like putting all the good cards in one place when "shuffling")sA deck is a well-understood concept. It's nonsensical to create a deck and specify the number of decks it has. You need a separate concept, a
Shoe
.Shoe
s can hold multiple decks of poker cards in them.You don't need to clear a new
Deck
. It's already clear.This isn't a generic
Deck
class. It's a poker deck. Do you want something useful for other games? If so, you need to take all the poker functionality into a separate class.Shuffling more than once in a row is not sensical in code. The deck will be no more shuffled after the eighth time than the first.
isShuffled()
is not useful. After you call shuffle, the deck is shuffled. Unless you add some way to unshuffle a deck?I doubt the utility of
removeCard(Card)
andhasCard(Card)
. I guess there might be games where they are helpful, but I can't think of any.It's visually ugly to use the return value of
Objects.requireNonNull()
like that. Declare it as a standalone call.populateDeck()
again assumes a poker deck. And it'spublic
, which means any yahoo with access to the deck can throw in cards at any time.clearDeck()
is the same problem. And what does it mean for a deck to becleared
? In real life, you don't ever get rid of all the cards, still have a "deck", and then add cards back in. You might take cards out of the deck or put them back in, but that's different than the abstraction you have.What's the use case for giving out a list of the cards? You should return a
List
, not aCollection
, because decks typically have an order.A deck is usually not a stack. A
Deque
might be more appropriate.pop
is leaking internal details. Nobody says "pop me a card off that deck".
Hand
Needs to be
final
for the same reason asDeck
.Is there a use case for removing a specific card from a
Hand
?Again, keep the display code out of the model. Either pass in something to do the rendering or have the UI call methods on
Hand
to render.calculateValue
- is there a use case for this? What game uses as a value the sum of all the cards?clearHand
- this needs a better name, because that's not what people talk about doing in real life. Also, it should return the cards you just tossed, to facilitate putting them back in the deck. Or maybe pass in a deck and add the cards back to it?compareTo
- there are lots of different ways to compare the strengths of two hands. You shouldn't be picking one and hard-coding it here.Don't have multiple hard-coded sort methods. Have one method that takes a comparator. Otherwise every time any client wants a new sort method, the API needs to change.
Maybe
revealed
would be better thanflipped
? I also think that therevealed
state of a card belongs here, not on theCard
.
Playing Card
Does
isFaceUp
make sense onPlayingCard
? I guess it might if your deck can have face-up cards in it. But strongly consider if that should be external state. That sounds like it belongs to the UI layer, not the model.Return early from
equals()
. The reason for a single return was made obsolete years ago.Strongly consider whether you want to implement
compareTo()
. Some games have trump suits which can change the natural ordering of the cards in the deck.
I took a stab at a generic deck class, and came up with this. It should probably have a draw(int cards)
and overloaded discard methods also, but I need to pack.
//Note that this class is *NOT* thread safe. It will definitely have problems in a multi-threaded environment.
public final class Deck<T extends Card>
private final List<T> deck = new LinkedList<>();
private final Set<T> outstandingCards = new HashSet<>();
private final List<T> discardPile = new LinkedList<>();
public Deck(final Collection<T> cards)
super();
this.deck.addAll(cards);
public void shuffle()
this.deck.addAll(this.discardPile);
this.discardPile.clear();
Collections.shuffle(this.deck);
public boolean isDeckEmpty()
return this.deck.isEmpty();
public int deckSize()
return this.deck.size();
public T draw()
if (this.isDeckEmpty())
throw new IllegalStateException();
final T card = this.deck.remove(0);
this.outstandingCards.add(card);
return card;
public void discard(final T card)
if (!this.outstandingCards.contains(card))
// Somebody tried to sneak a new card into the deck!
throw new IllegalStateException();
this.outstandingCards.remove(card);
this.discardPile.add(card);
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basicCollections#shuffle
seems to be using Java's defaultRandom
, which if I am not mistaken uses a48-bit
seed, way less than52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.
â Koekje
May 11 at 11:02
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?CardState.FACE_UP
? I get the concept of a shoe. However, why isclear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again?Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor tosort(Comparator c)
â Frijolie
May 12 at 16:00
add a comment |Â
up vote
2
down vote
Every competent non-junior developer is self-taught, because you don't learn everything you need to know in school.
This is actually a reasonably tricky problem you've picked out, so don't be discouraged by all the feedback. You need to spend more time thinking about the bounds between application-specific code (Poker rules, Uno rules, etc) and the generic library code (How does a Deck work, what does a Card look like). Right now it's all mixed together. That's not surprising - building good APIs is hard.
It also seems like you have a lot of methods that serve no particular purpose because you think they might be handy later. It's really easy to add and really hard to remove, and YAGNI. Wait for a use case before adding things, and make sure you do it right, because if you have to fix it all your clients will break.
Card
Use javadoc, not comments
equals
andhashCode
aren't doing anything. All objects have them, so everyCard
will always fulfill that part of the contract without doing any extra work.Consider having
flip()
return the new state of the card.What is the point of this interface? If you're only building for poker cards, the interface is noise. If you're building for other kinds of cards, you're forcing a ton of false requirements - Uno cards, for instance, can't fulfill this contract. Strongly consider making this a marker interface if you want to support non-poker games.
The methods are mixed-up. On the one hand, clients can ask your for your internals and you freely share them. On the other hand, the card renders itself with no inputs. Either keep your privates private and use instead methods like
hasRank(Rank rank)
or get rid of therender
method and let the client do it.
Color
- Having a class with the same name as a class in the Java API is a bad idea. You're asking for conflicts. It's not really a
Color
, it's aCardColor
or some such.
Deck
Deck
needs to be final. You can't have users extending Deck with their own functionality (like putting all the good cards in one place when "shuffling")sA deck is a well-understood concept. It's nonsensical to create a deck and specify the number of decks it has. You need a separate concept, a
Shoe
.Shoe
s can hold multiple decks of poker cards in them.You don't need to clear a new
Deck
. It's already clear.This isn't a generic
Deck
class. It's a poker deck. Do you want something useful for other games? If so, you need to take all the poker functionality into a separate class.Shuffling more than once in a row is not sensical in code. The deck will be no more shuffled after the eighth time than the first.
isShuffled()
is not useful. After you call shuffle, the deck is shuffled. Unless you add some way to unshuffle a deck?I doubt the utility of
removeCard(Card)
andhasCard(Card)
. I guess there might be games where they are helpful, but I can't think of any.It's visually ugly to use the return value of
Objects.requireNonNull()
like that. Declare it as a standalone call.populateDeck()
again assumes a poker deck. And it'spublic
, which means any yahoo with access to the deck can throw in cards at any time.clearDeck()
is the same problem. And what does it mean for a deck to becleared
? In real life, you don't ever get rid of all the cards, still have a "deck", and then add cards back in. You might take cards out of the deck or put them back in, but that's different than the abstraction you have.What's the use case for giving out a list of the cards? You should return a
List
, not aCollection
, because decks typically have an order.A deck is usually not a stack. A
Deque
might be more appropriate.pop
is leaking internal details. Nobody says "pop me a card off that deck".
Hand
Needs to be
final
for the same reason asDeck
.Is there a use case for removing a specific card from a
Hand
?Again, keep the display code out of the model. Either pass in something to do the rendering or have the UI call methods on
Hand
to render.calculateValue
- is there a use case for this? What game uses as a value the sum of all the cards?clearHand
- this needs a better name, because that's not what people talk about doing in real life. Also, it should return the cards you just tossed, to facilitate putting them back in the deck. Or maybe pass in a deck and add the cards back to it?compareTo
- there are lots of different ways to compare the strengths of two hands. You shouldn't be picking one and hard-coding it here.Don't have multiple hard-coded sort methods. Have one method that takes a comparator. Otherwise every time any client wants a new sort method, the API needs to change.
Maybe
revealed
would be better thanflipped
? I also think that therevealed
state of a card belongs here, not on theCard
.
Playing Card
Does
isFaceUp
make sense onPlayingCard
? I guess it might if your deck can have face-up cards in it. But strongly consider if that should be external state. That sounds like it belongs to the UI layer, not the model.Return early from
equals()
. The reason for a single return was made obsolete years ago.Strongly consider whether you want to implement
compareTo()
. Some games have trump suits which can change the natural ordering of the cards in the deck.
I took a stab at a generic deck class, and came up with this. It should probably have a draw(int cards)
and overloaded discard methods also, but I need to pack.
//Note that this class is *NOT* thread safe. It will definitely have problems in a multi-threaded environment.
public final class Deck<T extends Card>
private final List<T> deck = new LinkedList<>();
private final Set<T> outstandingCards = new HashSet<>();
private final List<T> discardPile = new LinkedList<>();
public Deck(final Collection<T> cards)
super();
this.deck.addAll(cards);
public void shuffle()
this.deck.addAll(this.discardPile);
this.discardPile.clear();
Collections.shuffle(this.deck);
public boolean isDeckEmpty()
return this.deck.isEmpty();
public int deckSize()
return this.deck.size();
public T draw()
if (this.isDeckEmpty())
throw new IllegalStateException();
final T card = this.deck.remove(0);
this.outstandingCards.add(card);
return card;
public void discard(final T card)
if (!this.outstandingCards.contains(card))
// Somebody tried to sneak a new card into the deck!
throw new IllegalStateException();
this.outstandingCards.remove(card);
this.discardPile.add(card);
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basicCollections#shuffle
seems to be using Java's defaultRandom
, which if I am not mistaken uses a48-bit
seed, way less than52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.
â Koekje
May 11 at 11:02
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?CardState.FACE_UP
? I get the concept of a shoe. However, why isclear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again?Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor tosort(Comparator c)
â Frijolie
May 12 at 16:00
add a comment |Â
up vote
2
down vote
up vote
2
down vote
Every competent non-junior developer is self-taught, because you don't learn everything you need to know in school.
This is actually a reasonably tricky problem you've picked out, so don't be discouraged by all the feedback. You need to spend more time thinking about the bounds between application-specific code (Poker rules, Uno rules, etc) and the generic library code (How does a Deck work, what does a Card look like). Right now it's all mixed together. That's not surprising - building good APIs is hard.
It also seems like you have a lot of methods that serve no particular purpose because you think they might be handy later. It's really easy to add and really hard to remove, and YAGNI. Wait for a use case before adding things, and make sure you do it right, because if you have to fix it all your clients will break.
Card
Use javadoc, not comments
equals
andhashCode
aren't doing anything. All objects have them, so everyCard
will always fulfill that part of the contract without doing any extra work.Consider having
flip()
return the new state of the card.What is the point of this interface? If you're only building for poker cards, the interface is noise. If you're building for other kinds of cards, you're forcing a ton of false requirements - Uno cards, for instance, can't fulfill this contract. Strongly consider making this a marker interface if you want to support non-poker games.
The methods are mixed-up. On the one hand, clients can ask your for your internals and you freely share them. On the other hand, the card renders itself with no inputs. Either keep your privates private and use instead methods like
hasRank(Rank rank)
or get rid of therender
method and let the client do it.
Color
- Having a class with the same name as a class in the Java API is a bad idea. You're asking for conflicts. It's not really a
Color
, it's aCardColor
or some such.
Deck
Deck
needs to be final. You can't have users extending Deck with their own functionality (like putting all the good cards in one place when "shuffling")sA deck is a well-understood concept. It's nonsensical to create a deck and specify the number of decks it has. You need a separate concept, a
Shoe
.Shoe
s can hold multiple decks of poker cards in them.You don't need to clear a new
Deck
. It's already clear.This isn't a generic
Deck
class. It's a poker deck. Do you want something useful for other games? If so, you need to take all the poker functionality into a separate class.Shuffling more than once in a row is not sensical in code. The deck will be no more shuffled after the eighth time than the first.
isShuffled()
is not useful. After you call shuffle, the deck is shuffled. Unless you add some way to unshuffle a deck?I doubt the utility of
removeCard(Card)
andhasCard(Card)
. I guess there might be games where they are helpful, but I can't think of any.It's visually ugly to use the return value of
Objects.requireNonNull()
like that. Declare it as a standalone call.populateDeck()
again assumes a poker deck. And it'spublic
, which means any yahoo with access to the deck can throw in cards at any time.clearDeck()
is the same problem. And what does it mean for a deck to becleared
? In real life, you don't ever get rid of all the cards, still have a "deck", and then add cards back in. You might take cards out of the deck or put them back in, but that's different than the abstraction you have.What's the use case for giving out a list of the cards? You should return a
List
, not aCollection
, because decks typically have an order.A deck is usually not a stack. A
Deque
might be more appropriate.pop
is leaking internal details. Nobody says "pop me a card off that deck".
Hand
Needs to be
final
for the same reason asDeck
.Is there a use case for removing a specific card from a
Hand
?Again, keep the display code out of the model. Either pass in something to do the rendering or have the UI call methods on
Hand
to render.calculateValue
- is there a use case for this? What game uses as a value the sum of all the cards?clearHand
- this needs a better name, because that's not what people talk about doing in real life. Also, it should return the cards you just tossed, to facilitate putting them back in the deck. Or maybe pass in a deck and add the cards back to it?compareTo
- there are lots of different ways to compare the strengths of two hands. You shouldn't be picking one and hard-coding it here.Don't have multiple hard-coded sort methods. Have one method that takes a comparator. Otherwise every time any client wants a new sort method, the API needs to change.
Maybe
revealed
would be better thanflipped
? I also think that therevealed
state of a card belongs here, not on theCard
.
Playing Card
Does
isFaceUp
make sense onPlayingCard
? I guess it might if your deck can have face-up cards in it. But strongly consider if that should be external state. That sounds like it belongs to the UI layer, not the model.Return early from
equals()
. The reason for a single return was made obsolete years ago.Strongly consider whether you want to implement
compareTo()
. Some games have trump suits which can change the natural ordering of the cards in the deck.
I took a stab at a generic deck class, and came up with this. It should probably have a draw(int cards)
and overloaded discard methods also, but I need to pack.
//Note that this class is *NOT* thread safe. It will definitely have problems in a multi-threaded environment.
public final class Deck<T extends Card>
private final List<T> deck = new LinkedList<>();
private final Set<T> outstandingCards = new HashSet<>();
private final List<T> discardPile = new LinkedList<>();
public Deck(final Collection<T> cards)
super();
this.deck.addAll(cards);
public void shuffle()
this.deck.addAll(this.discardPile);
this.discardPile.clear();
Collections.shuffle(this.deck);
public boolean isDeckEmpty()
return this.deck.isEmpty();
public int deckSize()
return this.deck.size();
public T draw()
if (this.isDeckEmpty())
throw new IllegalStateException();
final T card = this.deck.remove(0);
this.outstandingCards.add(card);
return card;
public void discard(final T card)
if (!this.outstandingCards.contains(card))
// Somebody tried to sneak a new card into the deck!
throw new IllegalStateException();
this.outstandingCards.remove(card);
this.discardPile.add(card);
Every competent non-junior developer is self-taught, because you don't learn everything you need to know in school.
This is actually a reasonably tricky problem you've picked out, so don't be discouraged by all the feedback. You need to spend more time thinking about the bounds between application-specific code (Poker rules, Uno rules, etc) and the generic library code (How does a Deck work, what does a Card look like). Right now it's all mixed together. That's not surprising - building good APIs is hard.
It also seems like you have a lot of methods that serve no particular purpose because you think they might be handy later. It's really easy to add and really hard to remove, and YAGNI. Wait for a use case before adding things, and make sure you do it right, because if you have to fix it all your clients will break.
Card
Use javadoc, not comments
equals
andhashCode
aren't doing anything. All objects have them, so everyCard
will always fulfill that part of the contract without doing any extra work.Consider having
flip()
return the new state of the card.What is the point of this interface? If you're only building for poker cards, the interface is noise. If you're building for other kinds of cards, you're forcing a ton of false requirements - Uno cards, for instance, can't fulfill this contract. Strongly consider making this a marker interface if you want to support non-poker games.
The methods are mixed-up. On the one hand, clients can ask your for your internals and you freely share them. On the other hand, the card renders itself with no inputs. Either keep your privates private and use instead methods like
hasRank(Rank rank)
or get rid of therender
method and let the client do it.
Color
- Having a class with the same name as a class in the Java API is a bad idea. You're asking for conflicts. It's not really a
Color
, it's aCardColor
or some such.
Deck
Deck
needs to be final. You can't have users extending Deck with their own functionality (like putting all the good cards in one place when "shuffling")sA deck is a well-understood concept. It's nonsensical to create a deck and specify the number of decks it has. You need a separate concept, a
Shoe
.Shoe
s can hold multiple decks of poker cards in them.You don't need to clear a new
Deck
. It's already clear.This isn't a generic
Deck
class. It's a poker deck. Do you want something useful for other games? If so, you need to take all the poker functionality into a separate class.Shuffling more than once in a row is not sensical in code. The deck will be no more shuffled after the eighth time than the first.
isShuffled()
is not useful. After you call shuffle, the deck is shuffled. Unless you add some way to unshuffle a deck?I doubt the utility of
removeCard(Card)
andhasCard(Card)
. I guess there might be games where they are helpful, but I can't think of any.It's visually ugly to use the return value of
Objects.requireNonNull()
like that. Declare it as a standalone call.populateDeck()
again assumes a poker deck. And it'spublic
, which means any yahoo with access to the deck can throw in cards at any time.clearDeck()
is the same problem. And what does it mean for a deck to becleared
? In real life, you don't ever get rid of all the cards, still have a "deck", and then add cards back in. You might take cards out of the deck or put them back in, but that's different than the abstraction you have.What's the use case for giving out a list of the cards? You should return a
List
, not aCollection
, because decks typically have an order.A deck is usually not a stack. A
Deque
might be more appropriate.pop
is leaking internal details. Nobody says "pop me a card off that deck".
Hand
Needs to be
final
for the same reason asDeck
.Is there a use case for removing a specific card from a
Hand
?Again, keep the display code out of the model. Either pass in something to do the rendering or have the UI call methods on
Hand
to render.calculateValue
- is there a use case for this? What game uses as a value the sum of all the cards?clearHand
- this needs a better name, because that's not what people talk about doing in real life. Also, it should return the cards you just tossed, to facilitate putting them back in the deck. Or maybe pass in a deck and add the cards back to it?compareTo
- there are lots of different ways to compare the strengths of two hands. You shouldn't be picking one and hard-coding it here.Don't have multiple hard-coded sort methods. Have one method that takes a comparator. Otherwise every time any client wants a new sort method, the API needs to change.
Maybe
revealed
would be better thanflipped
? I also think that therevealed
state of a card belongs here, not on theCard
.
Playing Card
Does
isFaceUp
make sense onPlayingCard
? I guess it might if your deck can have face-up cards in it. But strongly consider if that should be external state. That sounds like it belongs to the UI layer, not the model.Return early from
equals()
. The reason for a single return was made obsolete years ago.Strongly consider whether you want to implement
compareTo()
. Some games have trump suits which can change the natural ordering of the cards in the deck.
I took a stab at a generic deck class, and came up with this. It should probably have a draw(int cards)
and overloaded discard methods also, but I need to pack.
//Note that this class is *NOT* thread safe. It will definitely have problems in a multi-threaded environment.
public final class Deck<T extends Card>
private final List<T> deck = new LinkedList<>();
private final Set<T> outstandingCards = new HashSet<>();
private final List<T> discardPile = new LinkedList<>();
public Deck(final Collection<T> cards)
super();
this.deck.addAll(cards);
public void shuffle()
this.deck.addAll(this.discardPile);
this.discardPile.clear();
Collections.shuffle(this.deck);
public boolean isDeckEmpty()
return this.deck.isEmpty();
public int deckSize()
return this.deck.size();
public T draw()
if (this.isDeckEmpty())
throw new IllegalStateException();
final T card = this.deck.remove(0);
this.outstandingCards.add(card);
return card;
public void discard(final T card)
if (!this.outstandingCards.contains(card))
// Somebody tried to sneak a new card into the deck!
throw new IllegalStateException();
this.outstandingCards.remove(card);
this.discardPile.add(card);
answered May 11 at 1:15
Eric Stein
3,703512
3,703512
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basicCollections#shuffle
seems to be using Java's defaultRandom
, which if I am not mistaken uses a48-bit
seed, way less than52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.
â Koekje
May 11 at 11:02
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?CardState.FACE_UP
? I get the concept of a shoe. However, why isclear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again?Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor tosort(Comparator c)
â Frijolie
May 12 at 16:00
add a comment |Â
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basicCollections#shuffle
seems to be using Java's defaultRandom
, which if I am not mistaken uses a48-bit
seed, way less than52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.
â Koekje
May 11 at 11:02
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?CardState.FACE_UP
? I get the concept of a shoe. However, why isclear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again?Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor tosort(Comparator c)
â Frijolie
May 12 at 16:00
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basic
Collections#shuffle
seems to be using Java's default Random
, which if I am not mistaken uses a 48-bit
seed, way less than 52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.â Koekje
May 11 at 11:02
Good points! One minor thing I'd like to note is that shuffling multiple times can actually have an effect I think. The basic
Collections#shuffle
seems to be using Java's default Random
, which if I am not mistaken uses a 48-bit
seed, way less than 52!
. So shuffling multiple times would allow you to perhaps reach more states? That does not mean it is required of course, and if it is, it probably needs to be implemented differently.â Koekje
May 11 at 11:02
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?
CardState.FACE_UP
? I get the concept of a shoe. However, why is clear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again? Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor to sort(Comparator c)
â Frijolie
May 12 at 16:00
Thank you for your review. I'm still processing your comments. Mind blown. You're exactly right, I do have UI code mixed with business rules. I need to separate. You're also spot on with the YAGNI comment. Many methods were added to anticipate a need. What's meant by a card state?
CardState.FACE_UP
? I get the concept of a shoe. However, why is clear()
and (re)populateDeck()
not a good strategy? Worth the overhead to discard and add again? Hand#calculateValue
is for Blackjack--which is my intended purpose. Great idea! I'll refactor to sort(Comparator c)
â Frijolie
May 12 at 16:00
add a comment |Â
up vote
1
down vote
In Deck.java you have:
if(deck.contains(Objects.requireNonNull(card))) {
but you have a function containsCard
that could shorten it:
if(containsCard(card))
add a comment |Â
up vote
1
down vote
In Deck.java you have:
if(deck.contains(Objects.requireNonNull(card))) {
but you have a function containsCard
that could shorten it:
if(containsCard(card))
add a comment |Â
up vote
1
down vote
up vote
1
down vote
In Deck.java you have:
if(deck.contains(Objects.requireNonNull(card))) {
but you have a function containsCard
that could shorten it:
if(containsCard(card))
In Deck.java you have:
if(deck.contains(Objects.requireNonNull(card))) {
but you have a function containsCard
that could shorten it:
if(containsCard(card))
edited May 10 at 19:31
answered May 10 at 18:58
depperm
30319
30319
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194134%2fmy-first-deck-of-cards%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
3
"Go easy on me" ⦠"I look forward to your honest review. I have thick skin. I can take it." â Which do you want?
â 200_success
May 10 at 19:08
Touché. Updated.
â Frijolie
May 10 at 19:12
Nothing wrong with being self-taught.
â The Mattbat999
May 10 at 19:26
2
Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Malachiâ¦
May 10 at 19:45