3
\$\begingroup\$

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();
    }
}
\$\endgroup\$

4 Answers 4

3
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ You're probably right, I should move bet and state to Hand to make things easier, that will help implementing the missing features too. I placed them in Player 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 in Blacjack"? What methods would belong to Hand instead of Player and Dealer? I think I will move bet out of Player, add a list of hands, each with a bet, that way I can implement split. \$\endgroup\$
    – dabadaba
    Commented Jul 22, 2015 at 10:11
3
\$\begingroup\$

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.

enums 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.

\$\endgroup\$
2
  • \$\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
    Commented 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.
    Commented Jul 22, 2015 at 10:20
2
\$\begingroup\$

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 in Card
  • Override Equals in Hand
  • Card should know (calculate) its own value
    • enums 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.
  • enums makes this simple
  • Player should know its own hand value
    • Just "ask" Hand what its value is
  • Dealer should know what each Players hand is
    • Just "ask" Player what its value is
  • 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 use enums
  • isBlackjack() is breathtakingly complex. Using enums 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?


\$\endgroup\$
3
  • \$\begingroup\$ I delegated returning the value of a Hand to Blackjack instead of Hand because I was thinking in Hand 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 why handValue() is a method of the Blackjack class. How is isBlackjack() so complex and how would using enum help? \$\endgroup\$
    – dabadaba
    Commented Jul 22, 2015 at 10:14
  • \$\begingroup\$ isBlackJack() is a face card plus an ace. By contrast TwentyOne is any card combination that adds to 21, 'natch. If a Card knows that it is a face card, or not, then isBlackJack() becomes virtually trivial. If a Hand 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 a Hand 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
    Commented 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 so cardValue.TWO = 2 - you do not need to fuss with "offsets" because TWO 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 make switch statements clean and clear: it avoids all the ifing/elseing convolution in your code. \$\endgroup\$
    – radarbob
    Commented Jul 22, 2015 at 13:20
0
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.