Continuing my previous post, I have completed my game of Blackjack, the simple version. I am aware it's very long. I haven't included some of the suggested changes suggested in the previously to the Deck
implementation and the other classes involved.
It is simple because I omitted some rules (I am planning on implementing them in the future):
- Double
- Split (I think this would be the hardest one)
- Retire
- Dealer checking second card when first is Ace or figure
About the Player
and Dealer
classes:
I am almost tempted to make a superclass since they share too many class attributes and methods. But I think the idea of "dealer" and "player" are very different concepts even though in this situation they share many features. Furthermore, I have no clue how I would name the superclass.
I would love suggestions on how to make this game better and also approaches about the missing features.
Code on Pastebin.
import java.util.Stack;
import java.util.Random;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Iterator;
public class Blackjack {
public static String capitalizeFirstLetter(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
public static class Rank {
private int value;
private static final String[] string_values = {
"Ace",
"Two",
"Three",
"Four",
"Five",
"Six",
"Seven",
"Eight",
"Nine",
"Ten",
"Jack",
"Queen",
"King"
};
public Rank(int value) throws IllegalArgumentException {
if (value < 1 || value > 13) {
throw new IllegalArgumentException("Invalid card value (must be between 1 and 13).");
}
this.value = value;
}
public Rank(String value) throws IllegalArgumentException {
int i = 0;
String r = capitalizeFirstLetter(value);
while (i < string_values.length && !r.equals(string_values[i])) {
i++;
}
if (i == string_values.length) {
throw new IllegalArgumentException("Invalid card rank.");
} else {
this.value = i + 1;
}
}
public int getValue() {
return value;
}
public boolean isAce() {
return value == 1;
}
public String toString() {
return string_values[value - 1];
}
public String simpleString() {
String str = new String();
if (value == 1) {
str += "A";
} else if (value > 10) {
if (value == 11) {
str += "J";
} else if (value == 12) {
str += "Q";
} else {
str += "K";
}
} else {
str += value;
}
return str;
}
}
public static class Suit {
private String name;
private static final String[] string_names = {
"Hearts",
"Diamonds",
"Clubs",
"Spades"
};
public Suit(String name) throws IllegalArgumentException {
int i = 0;
String n = capitalizeFirstLetter(name);
while (i < string_names.length && !n.equals(string_names[i])) {
i++;
}
if (i == string_names.length) {
throw new IllegalArgumentException("Invalid suit name.");
} else {
this.name = n;
}
}
public String toString() {
return name;
}
public String symbolString() {
switch(name) {
case "Hearts": return "\u2665";
case "Diamonds": return "\u2666";
case "Clubs": return "\u2663";
case "Spades": return "\u2660";
default: return null;
}
}
}
public static class Card {
private Rank rank;
private Suit suit;
public Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}
public Card(int rank, String suit) {
this.rank = new Rank(rank);
this.suit = new Suit(suit);
}
public Card(String rank, String suit) {
this.rank = new Rank(rank);
this.suit = new Suit(suit);
}
public Rank getRank() {
return rank;
}
public String toString() {
String str = rank.simpleString() + suit.symbolString() + " " + rank + " of " + suit;
return str;
}
}
public static class Deck {
private Stack<Card> deck;
private static final int number = 52;
public Deck() {
deck = new Stack<Card>();
Suit hearts, diamonds, clubs, spades;
hearts = new Suit("hearts");
diamonds = new Suit("diamonds");
clubs = new Suit("clubs");
spades = new Suit("spades");
Suit[] suits = { hearts, diamonds, clubs, spades };
for (int i = 0; i < suits.length; i++) {
for (int j = 1; j <= 13; j++) {
deck.push(new Card(new Rank(j), suits[i]));
}
}
}
public void shuffle() {
long seed = System.nanoTime();
Collections.shuffle(deck, new Random(seed));
}
public void add(Card card) {
deck.push(card);
}
public void add(List<Card> cards) {
for (int i = 0; i < cards.size(); i++) {
deck.push(cards.get(i));
}
}
public Card draw() {
return deck.pop();
}
public boolean isEmpty() {
return deck.isEmpty();
}
public String toString() {
String str = new String();
for (int i = 0; i < deck.size(); i++) {
str += deck.get(i) + "\n";
}
return str;
}
}
public static class Hand {
private List<Card> cards;
public Hand() {
cards = new ArrayList<Card>();
}
public void add(Card card) {
cards.add(card);
}
public List<Card> cards() {
return cards;
}
public int size() {
return cards.size();
}
public void clear() {
cards.clear();
}
public String toString() {
String str = new String();
for (int i = 0; i < cards.size(); i++) {
str += "\t" + cards.get(i) + "\n";
}
return str;
}
}
public static class Player {
private String name;
private int money;
private int bet;
private Hand hand;
private State state;
public static enum State { PLAYING, TWENTYONE, BLACKJACK, STAND, BUSTED, RETIRED, RUINED };
public Player(String name, int money) {
this.name = name;
this.money = money;
bet = 0;
hand = new Hand();
state = State.PLAYING;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public int getBet() {
return bet;
}
public void setBet(int bet) {
this.bet = bet;
}
public Hand hand() {
return hand;
}
public Play choosePlay() {
char choice;
do {
p(name + ", choose your play; (H)IT, (S)TAND, (D)OUBLE, S(P)LIT OR S(U)RRENDER:");
pnln("> ");
choice = Character.toLowerCase(in.next().charAt(0));
} while (!(choice == 'h' || choice == 's' || choice == 'd' || choice == 'p' || choice == 'u' ));
switch(choice) {
case 'h': return Play.HIT;
case 's': return Play.STAND;
case 'd': return Play.DOUBLE;
case 'p': return Play.SPLIT;
case 'u': return Play.SURRENDER;
default: return null;
}
}
// public void double() {
// }
// public void split() {
// }
// public void surrender() {
// money =/ 2;
// }
public boolean bet(int quantity) {
int remainder = money - quantity;
if (remainder >= 0) {
money = remainder;
return true;
} else {
return false;
}
}
public void startsTurn() {
state = State.PLAYING;
}
public void stand() {
state = State.STAND;
}
public void busted() {
state = State.BUSTED;
}
public void blackjack() {
state = State.BLACKJACK;
}
public void twentyoneScore() {
state = State.TWENTYONE;
}
public State getState() {
return state;
}
public boolean hasBlackjack() {
return state == State.BLACKJACK;
}
public boolean hasTwentyone() {
return state == State.TWENTYONE;
}
public boolean isPlaying() {
return state == State.PLAYING;
}
public boolean isBusted() {
return state == State.BUSTED;
}
public String toString() {
return name;
}
}
public static class Dealer {
private String name;
private Hand hand;
private State state;
public static enum State { PLAYING, TWENTYONE, BLACKJACK, BUSTED };
public Dealer() {
name = "Dealer";
hand = new Hand();
state = State.PLAYING;
}
public Hand hand() {
return hand;
}
public void startsTurn() {
state = State.PLAYING;
}
public void busted() {
state = State.BUSTED;
}
public void blackjack() {
state = State.BLACKJACK;
}
public void twentyoneScore() {
state = State.TWENTYONE;
}
public boolean isBusted() {
return state == State.BUSTED;
}
public boolean hasBlackjack() {
return state == State.BLACKJACK;
}
public boolean hasTwentyone() {
return state == State.TWENTYONE;
}
public String toString() {
return name;
}
public String initialHandString() {
return "\t" + hand.cards().get(0) + "\n" + "\t?\n";
}
}
private Deck deck;
private Dealer dealer;
private List<Player> players;
private List<Card> usedCards;
private static Scanner in = new Scanner(System.in);
private static enum Play { HIT, STAND, DOUBLE, SPLIT, SURRENDER };
public Blackjack(Player[] players, Deck deck) throws IllegalArgumentException {
if (players.length < 1 || players.length > 6) {
throw new IllegalArgumentException("Number of players must be 1-6.");
}
this.deck = deck;
dealer = new Dealer();
this.players = new ArrayList<Player>(Arrays.asList(players));
usedCards = new ArrayList<Card>();
}
private boolean playersFinished() {
for (int i = 0; i < players.size(); i++) {
if (players.get(i).isPlaying()) {
return false;
}
}
return true;
}
private void resetPlayersStates() {
for (int i = 0; i < players.size(); i++) {
players.get(i).startsTurn();
}
dealer.startsTurn();
}
private void feedDeckIfEmpty() {
if (deck.isEmpty()) {
p("\nShuffling new deck...\n");
resetDeck();
}
}
private void resetDeck() {
deck.add(usedCards);
deck.shuffle();
usedCards.clear();
}
private void placeBets() {
p("\nPlayers, place your bets!\n");
for (int i = 0; i < players.size(); i++) {
Player player = players.get(i);
int bet;
boolean betIsPossible = false;
do {
pnln(player + ", place your bet: ");
bet = in.nextInt();
if (bet <= 0) {
p("Incorrect amount! Should be a possitive amount.");
} else {
boolean hasMoney = player.bet(bet);
if (hasMoney) {
betIsPossible = true;
} else {
p("Incorrect amount! You don't have that much money (max. " + player.getMoney() + ").");
}
}
} while(!betIsPossible);
player.setBet(bet);
}
}
private void dealCards() {
p("\nDealer dealing cards...\n");
for (int i = 0; i < 2*players.size(); i++) {
feedDeckIfEmpty();
Card card = deck.draw();
Player player = players.get(i % players.size());
player.hand().add(card);
p(player + " gets " + card);
if ((i + 1) % players.size() == 0) {
feedDeckIfEmpty();
card = deck.draw();
if (i + 1 == players.size()) {
p(dealer + " gets " + card);
} else {
p(dealer + " gets a second card; face down");
}
dealer.hand().add(card);
}
}
}
private boolean isBlackjack(Hand hand) {
if (hand.size() != 2) {
return false;
}
Card card1, card2;
card1 = hand.cards().get(0);
card2 = hand.cards().get(1);
boolean card1_is_ace, card2_is_ace;
card1_is_ace = card1.getRank().isAce();
card2_is_ace = card2.getRank().isAce();
if (card1_is_ace && card2_is_ace) {
return false;
} else if (card1_is_ace) {
return cardValue(card2) == 10;
} else if (card2_is_ace) {
return cardValue(card1) == 10;
} else {
return false;
}
}
private int cardValue(Card card) {
int rank = card.getRank().getValue();
if (rank == 1) {
int v;
do {
pnln("Choose rank for " + card + ", (1) or (11): ");
v = in.nextInt();
if (v == 1 || v == 11) {
return v;
} else {
p("Invalid rank, you must choose 1 or 11.");
}
} while (!(v == 1 || v == 11));
} else if (rank == 11 || rank == 12 || rank == 13) {
return 10;
} else {
return rank;
}
return 0;
}
private int handValue(Hand hand) {
List<Integer> hand_values = handValues(hand);
if (hand_values.size() == 1) {
return hand_values.get(0);
} else {
int first = hand_values.get(0), second = hand_values.get(1);
if (first > 21 && second > 21) {
if (first <= second) {
return first;
} else {
return second;
}
} else if (first > 21) {
return second;
} else if (second > 21) {
return first;
} else if (first >= second) {
return first;
} else {
return second;
}
}
}
private List<Integer> handValues(Hand hand) {
List<Card> cards = hand.cards();
boolean has_ace = false;
List<Integer> hand_values = new ArrayList<Integer>();
for (int i = 0; i < cards.size(); i++) {
if (cards.get(i).getRank().isAce()) {
has_ace = true;
}
}
if (!has_ace) {
int value = 0;
for (int i = 0; i < cards.size(); i++) {
value += cardValue(cards.get(i));
}
hand_values.add(value);
} else {
int value1 = 0, value2 = 0;
for (int i = 0; i < cards.size(); i++) {
Card card = cards.get(i);
if (card.getRank().isAce()) {
value1 += 1;
value2 += 11;
} else {
int val = cardValue(card);
value1 += val;
value2 += val;
}
}
if (value1 <= 21 && value2 <= 21) {
hand_values.add(value1);
hand_values.add(value2);
} else {
if (value1 <= value2) {
hand_values.add(value1);
} else {
hand_values.add(value2);
}
}
}
return hand_values;
}
private String handValueString(Hand hand) {
List<Integer> hand_values = handValues(hand);
if (hand_values.size() == 1) {
return Integer.toString(hand_values.get(0));
} else {
return hand_values.get(0) + " or " + hand_values.get(1);
}
}
private void printTurnsSummary() {
if (playersFinished()) {
p("Dealer's hand:\n" + dealer.hand());
} else {
p("Dealer's hand:\n" + dealer.initialHandString());
}
for (int i = 0; i < players.size(); i++) {
Player player = players.get(i);
Hand hand = player.hand();
p("\n< " + player + " >\nCurrent hand:\n" + hand + "\nBet: " + player.getBet() + "\n");
switch(player.getState()) {
case PLAYING:
p("This player is still on the game.");
break;
case STAND:
p("This player chose to stand. Waiting for the end of the game.");
break;
case TWENTYONE:
p("This played got a score of 21.");
break;
case BLACKJACK:
p("This player got blackjack!");
break;
case BUSTED:
p("This player was busted!");
break;
case RETIRED:
p("This player has retired, will walk out of the game at the end with half bet back.");
break;
}
p("---------------");
}
}
private void printPlayersMoney() {
p("Players' money:\n");
for (int i = 0; i < players.size(); i++) {
Player player = players.get(i);
p(player + ": " + player.getMoney());
}
}
private void printPlayersHands() {
p("\n\n---------------\nPlayers' hands:\n");
for (int i = 0; i < players.size(); i++) {
Player player = players.get(i);
p(player + ":\n" + player.hand());
}
p("---------------\n");
}
private void payOutWins() {
p("\n------------- Players Payouts ---------------\n");
for (int i = 0; i < players.size(); i++) {
Player player = players.get(i);
int bet = player.getBet();
if (dealer.hasBlackjack()) {
if (player.hasBlackjack()) {
player.setMoney(player.getMoney() + bet);
p("Both dealer and " + player + " have blackjacks, which results in a push. Player gets back " + bet + ".");
} else {
p("The dealer has blackjack, " + player + " loses bet (" + bet + ").");
}
} else if (player.hasBlackjack()) {
int win = bet + bet*3/2;
player.setMoney(player.getMoney() + win);
p(player + " has blackjack, gets paid at 3:2, winning " + win + ".");
} else if (player.isBusted()) {
p(player + " got busted, loses bet (" + bet + ").");
} else if (dealer.isBusted()) {
p("Dealer got busted, player gets paid at 1:1, winning " + 2*bet + ".");
player.setMoney(player.getMoney() + 2*bet);
} else if (handValue(player.hand()) > handValue(dealer.hand())) {
p(player + " has a higher scoring hand (" + handValue(player.hand()) + ") than dealer's " + handValue(dealer.hand()) + " , player gets paid at 1:1, winning " + 2*bet + ".");
player.setMoney(player.getMoney() + 2*bet);
} else if (handValue(player.hand()) < handValue(dealer.hand())) {
p(player + " has a lower scoring hand (" + handValue(player.hand()) + ") than dealer's " + handValue(dealer.hand()) + " , player loses bet, " + bet + ".");
} else {
p(player + " has a the same scoring hand (" + handValue(player.hand()) + ") than dealer's " + handValue(dealer.hand()) + " , gets bet back, " + bet + ".");
player.setMoney(player.getMoney() + bet);
}
p(player + "'s current money: " + player.getMoney() + "\n");
}
for (int i = 0; i < players.size(); i++) {
players.get(i).setBet(0);
}
}
private void clearHands() {
for (int i = 0; i < players.size(); i++) {
players.get(i).hand().clear();
}
dealer.hand().clear();
}
private void expelRuinedPlayers() {
for (Iterator<Player> iterator = players.iterator(); iterator.hasNext(); ) {
Player player = iterator.next();
if (player.getMoney() == 0) {
p(player + " has no money left and got kicked out of the game!");
iterator.remove();
}
}
}
private void pressAnyKeyToContinue() {
p("Press any key to continue...");
try {
System.in.read();
} catch(Exception e) {}
}
public void play() {
p("\n\n#############################\n\n WELCOME TO BLACKJACK 21\n\n#############################\n");
deck.shuffle();
while (!players.isEmpty()) {
p("\n\n--------------- NEW ROUND ---------------\n");
printPlayersMoney();
resetPlayersStates();
placeBets();
dealCards();
printPlayersHands();
pressAnyKeyToContinue();
while (!playersFinished()) {
p("\n\n--------------- NEW ROUND OF TURNS ---------------");
for (int i = 0; i < players.size(); i++) {
Player player = players.get(i);
if (player.isPlaying()) {
feedDeckIfEmpty();
p("\n< " + player + "'s turn > | Money " + player.getMoney() + " | Bet " + player.getBet());
p("\nDealer's hand:\n" + dealer.initialHandString() + "\n" + player + "'s hand:\n" + player.hand());
if (isBlackjack(player.hand())) {
player.blackjack();
p("\nYou have blackjack!!");
} else {
Hand player_hand = player.hand();
p("Hand value: " + handValueString(player_hand) + "\n");
Play play = player.choosePlay();
p("");
switch(play) {
case HIT:
feedDeckIfEmpty();
Card card = deck.draw();
p(player + " draws " + card + ".");
player_hand.add(card);
p("Resulting hand value: " + handValueString(player_hand));
break;
case STAND:
p(player + " stands.");
player.stand();
break;
case DOUBLE:
p(player + " doubles bet.");
break;
case SPLIT:
p(player + " splits hand.");
break;
case SURRENDER:
p(player + " gives up half the bet and retires from the game.");
break;
}
if (player.isPlaying()) {
int hand_value = handValue(player_hand);
if (hand_value > 21) {
player.busted();
p("\nYou have been busted!");
} else if (hand_value == 21) {
player.twentyoneScore();
p("\nYou got a score of 21");
}
}
}
p("\n------------------------------");
pressAnyKeyToContinue();
}
}
p("--------------- END OF ROUND OF TURNS --------------- \n");
p("\n----- SUMMARY -----\n");
printTurnsSummary();
pressAnyKeyToContinue();
}
p("\n--------------------------------------\n");
p("\n\n<<<<<<<<<<<<<<< END OF ROUND >>>>>>>>>>>>>> \n");
Hand dealer_hand = dealer.hand();
p("\nDealer's hand:\n" + dealer_hand);
if (isBlackjack(dealer_hand)) {
dealer.blackjack();
p("\nThe dealer got blackjack!");
} else {
int hand_value = handValue(dealer_hand);
while (hand_value < 17) {
feedDeckIfEmpty();
Card card = deck.draw();
p(dealer + " draws " + card + ".");
dealer_hand.add(card);
hand_value = handValue(dealer_hand);
}
p("\nDealer's final hand:\n" + dealer_hand + "\nDealer's hand value: " + hand_value + "\n");
if (hand_value > 21) {
dealer.busted();
p("\nThe dealer got busted!");
} else if (hand_value == 21) {
dealer.twentyoneScore();
p("\nThe dealer got a score of 21.");
}
}
payOutWins();
clearHands();
expelRuinedPlayers();
resetDeck();
pressAnyKeyToContinue();
}
}
public static <T> void p(T output) {
System.out.println(output);
}
public static <T> void pnln(T output) {
System.out.print(output);
}
public static void main(String[] args) {
Player player1, player2, player3;
player1 = new Player("Player 1", 500);
Player[] players = { player1 };
Deck deck = new Deck();
Blackjack blackjack = new Blackjack(players, deck);
blackjack.play();
}
}
4 Answers 4
You don't mention why you didn't follow the advice from your previous post about using enum
values rather than custom classes. Note that you can customize an enum
.
Overall, I think you make too much use of interior classes. There are times to do that, but this doesn't seem to be one of them. Note that Deck
, Suit
, Value
, and Card
are all more general concepts that exist outside of blackjack. Hand
also exists in other games, but the rules are different. Perhaps Hand
should stay in Blackjack
. It would be more common to put each class in separate files in a common package named something like tld.domain.blackjack
.
Player player1, player2, player3; player1 = new Player("Player 1", 500); Player[] players = { player1 };
You never use player2
or player3
. You don't actually need any of them. You could just say
Player[] players = { new Player("Player 1", 500) };
As a general rule, if you are numbering your variables, you are probably doing something wrong.
private static final int number = 52;
I'm guessing that you intend this as the number of cards in a deck. A more intuitive name is something like CARDS_PER_DECK
or DECK_SIZE
. But of course, you never use this constant, so you can get rid of it.
Suit hearts, diamonds, clubs, spades; hearts = new Suit("hearts"); diamonds = new Suit("diamonds"); clubs = new Suit("clubs"); spades = new Suit("spades"); Suit[] suits = { hearts, diamonds, clubs, spades }; for (int i = 0; i < suits.length; i++) { for (int j = 1; j <= 13; j++) { deck.push(new Card(new Rank(j), suits[i])); } }
You have a magic number (13
) here. You should get rid of it. The obvious solution would be a constant, but we can do better with enums for suits and ranks.
for (Suit suit : Suit.values()) {
for (Rank rank : Rank.values()) {
deck.push(new Card(rank, suit));
}
}
This is not only more idiomatic but shorter. If you really wanted, you could do this with arrays instead. But enums fit this situation exactly.
public static class Player { private String name; private int money; private int bet; private Hand hand; private State state;
The state
and bet
variables should be a characteristic of a Hand
, not a Player
. Either there should be a collection of hands per player, or Hand
should not be part of Player
at all. Doing either of those is going to make it much easier to implement split.
This is also why your Player
and Dealer
classes share so many methods. You put a bunch of methods on them that belong on Hand
instead. I'm not sure that you even need a Dealer
class. It might make more sense to track a Table
which would have a collection of players or hands (or both).
public void busted() { state = State.BUSTED; }
This would make more sense if it was called bust
rather than busted
. But adding a card to a hand should change the hand's state, not a call from outside. I.e. you shouldn't need this because Hand
should change the state internally.
char choice; do { p(name + ", choose your play; (H)IT, (S)TAND, (D)OUBLE, S(P)LIT OR S(U)RRENDER:"); pnln("> "); choice = Character.toLowerCase(in.next().charAt(0)); } while (!(choice == 'h' || choice == 's' || choice == 'd' || choice == 'p' || choice == 'u' )); switch(choice) { case 'h': return Play.HIT; case 's': return Play.STAND; case 'd': return Play.DOUBLE; case 'p': return Play.SPLIT; case 'u': return Play.SURRENDER; default: return null; }
If you move the switch
into the loop, you can simplify things and make them more extensible.
while (true) {
System.out.println(name + ", choose your play; (H)IT, (S)TAND, (D)OUBLE, S(P)LIT OR S(U)RRENDER:");
System.out.print("> ");
char choice = Character.toLowerCase(in.next().charAt(0));
switch (choice) {
case 'h': return Play.HIT;
case 's': return Play.STAND;
case 'd': return Play.DOUBLE;
case 'p': return Play.SPLIT;
case 'u': return Play.SURRENDER;
}
}
This way adding or removing an option only requires a change in one place.
I find it confusing that p
calls System.out.println
while pnln
calls System.out.print
.
-
\$\begingroup\$ You're probably right, I should move
bet
andstate
toHand
to make things easier, that will help implementing the missing features too. I placed them inPlayer
because it makes more sense to think "a player placed or has a bet and has a current state (playing, busted, etc.)". What do you mean when you say "Hand
should stay inBlacjack
"? What methods would belong toHand
instead ofPlayer
andDealer
? I think I will move bet out ofPlayer
, add a list of hands, each with a bet, that way I can implement split. \$\endgroup\$dabadaba– dabadaba2015年07月22日 10:11:35 +00:00Commented Jul 22, 2015 at 10:11
It's me again. :)
public static <T> void p(T output) {
System.out.println(output);
}
public static <T> void pnln(T output) {
System.out.print(output);
}
I still frown at this, because you're making up the tediousness of typing appropriately-named method names with too-simplified ones, when this can easily be mitigated using snippets in Sublime, your editor of choice.
Furthermore, if I may say so, it's now even worse as what looks like printing a line pnln()
is not (System.out.print()
), and what looks like a non-newline-terminated operation p()
is (System.out.println()
). This is... layered confusion, I'm afraid. You also definitely do not need the generic typing here, so leaving your method argument as Object
will work equally well.
enum
s are implicitly static final
, so you can drop those modifiers for your enum
declarations.
If for some reason you still prefer your array-based approach to create new instances of Rank
and Suit
classes, may I suggest using a List
instead so that comparisons with valid inputs can be done in a more compact and arguably efficient way? Let's use Suit
as an example:
public static class Suit {
private String name;
private static final String[] string_names = {
"Hearts",
"Diamonds",
"Clubs",
"Spades"
};
public Suit(String name) throws IllegalArgumentException {
int i = 0;
String n = capitalizeFirstLetter(name);
while (i < string_names.length && !n.equals(string_names[i])) {
i++;
}
if (i == string_names.length) {
throw new IllegalArgumentException("Invalid suit name.");
} else {
this.name = n;
}
}
}
Here, you have to explicitly loop through your array in order to validate the String
input. When you use Arrays.asList()
however...
public static class Suit {
private String name;
private static final List<String> stringNames = Arrays.asList(
"Hearts",
"Diamonds",
"Clubs",
"Spades"
);
public Suit(String name) {
String result = capitalizeFirstLetter(name);
if (!stringNames.contains(result)) {
throw new IllegalArgumentException("Invalid suit name.");
}
this.name = result;
}
}
I forgot to point this out earlier, but Java's standard naming convention is camelCase
, not snake_case
.
You probably can do away with the throws
declaration for the unchecked IllegalArgumentException
, and some answers over at StackOverflow back this up.
Finally, by using a List
and its List.contains(Object)
method, I can easily determine whether the input is accepted or not. The else
part is also redundant as we will already be throwing the Exception
for invalid names.
-
\$\begingroup\$ What do you mean with the exceptions? And yeah, for Java I tend to use snake case for local variables and camel case for attributes, I should I have used the latter in the case you mention. \$\endgroup\$dabadaba– dabadaba2015年07月22日 10:05:46 +00:00Commented Jul 22, 2015 at 10:05
-
\$\begingroup\$ @dabadaba there are checked and unchecked exceptions in Java, with
IllegalArgumentException
being in the latter group. These can be 'freely' thrown within having to mention them in the method declaration. You only need to specify the checked exceptions, e.g.public void method() throws InterruptedException
. \$\endgroup\$h.j.k.– h.j.k.2015年07月22日 10:20:18 +00:00Commented Jul 22, 2015 at 10:20
I would love suggestions on how to make this game better ...
- Follow the advice from @h.j.k in your previous post
enum
use especially- Override
Equals
inCard
- Override
Equals
inHand
Card
should know (calculate) its own valueenum
s makes this simple
Hand
should know (calculate) its own value- just "ask" each card what its value is and add them up!
- knows if the value is blackjack, busted, 21, etc. These are all just different scores.
enum
s makes this simplePlayer
should know its own hand value- Just "ask"
Hand
what its value is
- Just "ask"
Dealer
should know what eachPlayer
s hand is- Just "ask"
Player
what its value is
- Just "ask"
- The user prompt string suggests that capital letters should be entered, but the code only checks for lower case.
cardValue()
is not necessary if you useenum
sisBlackjack()
is breathtakingly complex. Usingenum
s will fantastically simplify this code.
... and also approaches about the missing features.
do the basics right - the stuff above; then code based on these will be easier.
But I think the idea of "dealer" and "player" are very different concepts
Agreed. But on the other hand the dealer is also playing. But he gets to play using different rules doesn't he?
-
\$\begingroup\$ I delegated returning the value of a Hand to
Blackjack
instead ofHand
because I was thinking inHand
as a more generic class that has nothing to do with the game. It's the rules of the game that define what value a hand has, and that's whyhandValue()
is a method of theBlackjack
class. How isisBlackjack()
so complex and how would usingenum
help? \$\endgroup\$dabadaba– dabadaba2015年07月22日 10:14:14 +00:00Commented Jul 22, 2015 at 10:14 -
\$\begingroup\$
isBlackJack()
is a face card plus an ace. By contrastTwentyOne
is any card combination that adds to 21, 'natch. If aCard
knows that it is a face card, or not, thenisBlackJack()
becomes virtually trivial. If aHand
is the collection of cards held by a player then adding up the total value is a natural thing to do. I don't see how this has nothing to do with the game. If not why have it? If aHand
is where the players cards/had is, then here is where we must calculate the hand's value. In OOP a class is responsible for itself. \$\endgroup\$radarbob– radarbob2015年07月22日 13:11:08 +00:00Commented Jul 22, 2015 at 13:11 -
\$\begingroup\$
enum
are type-safe - it is not just an integer. it has specific members - it cannot be just any "integer".enum
members can be given explicit values socardValue.TWO = 2
- you do not need to fuss with "offsets" becauseTWO
is first in the list.enum
members can be grouped, so you can easily make a "face card" group.enum
are easy to use and really makeswitch
statements clean and clear: it avoids all theif
ing/else
ing convolution in your code. \$\endgroup\$radarbob– radarbob2015年07月22日 13:20:16 +00:00Commented Jul 22, 2015 at 13:20
Instead of string concatenation (including an unnecessary variable):
String str = rank.simpleString() + suit.symbolString() + " " + rank + " of " + suit;
return str;
you could simpler write:
return String.format("%s%s %s of %s",
rank.simpleString(), suit.symbolString(), rank, suit);
This also makes the structure of the resulting string more obvious at a glance.