15
\$\begingroup\$

I am a new programmer (I've been doing Java for about 7 weeks) and I am of the type who wants to get it right straight away, so I wonder how this code:

  • Compares to the common practice
  • is structured. Is it a good structure?
  • Readability. Is it easy to read?
  • The JavaDoc; what could I improve?

(I am not including the package or imports for space reasons)

This code has a recursive call back to main(). I have understood that that is bad and I understand why, so you needn't bother with that.

Main:

In this game I have chosen to make it as simple as I can whilst still functional. There are two players; dealer and user. User goes first, then the dealer has her turn.

The game lets the other player auto-win if the hand-total is more than 21. That same function also assures that no player will have a hand higher than 21.

The game compares the users results to each other and the highest hand wins. If there's a stalemate, dealer wins.

public class BlackJackGame {

    // Contains the players for comparing who won.
    private final Player[] players = new Player[2];

    public static void main(String[] args){

        BlackJackGame game = new BlackJackGame();

        Scanner sc = new Scanner(System.in);

        System.out.println(" ___________________________________________\n" + 
                           "|                                           |\n" + 
                           "|  Welcome to the Gemtastic BlackJack Game! |\n" +
                           "|___________________________________________|\n");


        System.out.println("What is your name?");
        String userName = sc.nextLine();

        System.out.println("Hello " + userName + "! Let's play!\n");

        // Gets an input fromt he user which will represent the user in the game.

        // Create the players and add them to array.
        Player user = new User(userName);
        game.players[0] = user;
        Player dealer = new Dealer();
        game.players[1] = dealer;

        // Time to get this game going! And user goes always first.
        user.gameStartup();
        boolean userLoseByDefault = game.playerTurn(user, dealer);

        if(userLoseByDefault){
            game.lostByDefault(sc);
        }

        // Dealer's turn
        dealer.gameStartup();
        boolean dealerLoseByDefault = game.playerTurn(dealer, user);

        if(dealerLoseByDefault){
            game.lostByDefault(sc);
        }

        Player winner = game.whoWon(game.players, dealer);

        System.out.println(user + "'s total is: " + user.getTotal() + ".\n" +
                           dealer + "'s total is : " + dealer.getTotal() + ".\n");
        System.out.printf("%s WIN!\n\n", winner);

        if (game.goAgain(sc) == true){
            main(null);
        }
    }

    /**
     * This method handles the player's turn.<p>
     * 
     * Parameters sends in the one playing and the other player. This method 
     * calls for the {@code wantToStay()} method repeatedly until it returns
     * {@code true}. It then evaluates if the player's hand is more than 21
     * and if so, it's an auto-loss for the player.<p>
     * 
     * it returns {@code loseByDefault} to tell the caller if the player have
     * already lost or not.
     * 
     * @param player
     * @param otherPlayer
     * @return 
     */
    private boolean playerTurn(Player player, Player otherPlayer){
        boolean endTurn = false;
        boolean loseByDefault = false;

        while(endTurn == false){
            endTurn = player.wantToStay();

            if(endTurn == false){
                player.drawCard();
                System.out.println(player + "'s total is: " + player.getTotal() + "\n");
            }
        }
        if(player.getTotal() > 21){
            System.out.println(player + "'s total is more than 21, " + player + " lose by default.\n" +
                               otherPlayer + " WIN!\n");
            loseByDefault = true;
        }
        return loseByDefault;
    }
    /**
     * This method iterates through the players and compare their hands. The 
     * highest hand wins. It returns the winner.
     * 
     * @param players
     * @param dealer
     * @return 
     */
    private Player whoWon(Player[] players, Player dealer){
        Player winner = players[0];

        for(int i = 1; i < players.length; i++){
            Player elem = players[i];

            if(elem.getTotal() > winner.getTotal()){
                winner = elem;
            }
            else if(elem.getTotal() == winner.getTotal()){
                winner = dealer;
            }
        }
        return winner;
    }

    /**
     * This method calls for {@code goAgain()} if either player has lost by default,
     * if not, it ends the game.
     * 
     * @param sc 
     */
    private void lostByDefault(Scanner sc){
        if(goAgain(sc) == true){
            main(null);
        }
        System.exit(0);
    }

    /**
     * The method for taking the input and evaluate if the user wants to play
     * another round.
     * 
     * @param sc
     * @return 
     */
    private boolean goAgain(Scanner sc){
        Boolean goAgain = null;

        while(goAgain == null){
            System.out.println("Would you like to play another round? \"yes\"/\"no\": ");
            String input = sc.nextLine();

            String answer = input.toLowerCase();

            switch(answer){
                case "y":
                case "yes":
                    goAgain = true;
                    break;
                case "n":
                case "no":
                    goAgain = false;
                    break;
                default:
                    System.out.println("Invalid answer. Please answer only \"yes\" or \"no\"");
                    break;
            }
            System.out.println();
        }
        return goAgain;
    }
}

Card:

public class Card {
    private final int value;

    Card(){
        value = randomCard();
    }

    public int getValue(){
        return value;
    }

    /**
     * This method randomizes a value between 2 and 11.
     * 
     * @return 
     */
    private int randomCard(){
        int min = 2;
        int max = 11;
        int range = (max - min);

        int random = new Random().nextInt(range + 1) + min;

        return random;
    }
}

Player:

public interface Player {

    // Player hand as an array.

    // Player name as a String.

    /**
     * What will happen at start of a player's turn.
     */
    void gameStartup();

    /**
     * Generate a card and add it to the player's hand.
     */
    void drawCard();

    /**
     * Ask player if (s)he wants to hit or stay, returns a boolean on the choice of staying.
     * @return 
     */
    boolean wantToStay();

    /**
     * Retrieves the total of the player's hand.
     * @return 
     */
    int getTotal();
}

Dealer:

public class Dealer implements Player {

    private final String dealer = "Dealer";

    Card[] dealerHand = new Card[0];

    /**
     * Dealer constructor draws the two initial cards for the hand and displays one of them.
     */
    Dealer(){
        dealerHand = Arrays.copyOf(dealerHand, dealerHand.length + 2);
        dealerHand[0] = new Card();
        dealerHand[1] = new Card();

        System.out.println("The dealer has a " + dealerHand[0].getValue() + " showing, and a hidden card.");
        System.out.println("Her total is hidden too.\n");
    }

    /**
     * Displays the hidden card and total
     */
    @Override
    public void gameStartup(){
        System.out.println("Okay, Dealer's turn!\n");

        int total = getTotal();
        System.out.println("Her hidden card was a " + dealerHand[1].getValue() + "\n" +
                            "Her total was " + total + ".\n");
    }

    /**
     * Will draw a card and add it to the hand.
     */
    @Override
    public void drawCard() {
        dealerHand = Arrays.copyOf(dealerHand, dealerHand.length + 1);
        dealerHand[dealerHand.length - 1] = new Card();

        int lastCard = dealerHand.length - 1;
        System.out.println("Dealer drew a " + dealerHand[lastCard].getValue());
    }

    /**
     * The dealer's decision of whether to hit. She will hit until her hand is 16.
     * @return 
     */
    @Override
    public boolean wantToStay() {
        boolean stay = false;
        int total = getTotal();

        if(total > 16){
            stay = true;
            System.out.println("Dealer stays.\n");
        }
        else{
            System.out.println("Dealer hits.");
        }
        return stay;
    }

    /**
     * Get the hand's total value
     * @return 
     */
    @Override
    public int getTotal() {
        int totalValue = 0;

        for (Card c : dealerHand) {
            int cardValue = c.getValue();
            totalValue = totalValue + cardValue;
        }

        return totalValue;
    }

    /**
     * Returns the dealer's name.
     * @return 
     */
    @Override
    public String toString(){
        return dealer;
    } 
}

User:

public class User implements Player {

    private final String user;

    private Card[] userHand = new Card[0];

    /**
     * User constructor will add the string user inputed as its nickname in 
     * this game. It will then draw and display the initial hand of two cards.
     * 
     * @param name 
     */
    User(String name){
        user = name;

        userHand = Arrays.copyOf(userHand, userHand.length + 2);
        userHand[0] = new Card();
        userHand[1] = new Card();

        System.out.println("You get a " + userHand[0].getValue() + " and a " + userHand[1].getValue() + ".");
        int total = getTotal();
        System.out.println("Your total is: " + total + "\n");
    }

    /**
     * Message upon User's turn.
     */
    @Override
    public void gameStartup(){
        System.out.println("Your turn!");
    }

    /**
     * Retrieves the total of the User's hand.
     * @return 
     */
    @Override
    public int getTotal(){
        int totalValue = 0;

        for (Card c : userHand) {
            int cardValue = c.getValue();
            totalValue = totalValue + cardValue;
        }

        return totalValue;
    }

    /**
     * Generates a card and adds it to the hand.
     */
    @Override
    public void drawCard() {
        userHand = Arrays.copyOf(userHand, userHand.length + 1);
        userHand[userHand.length - 1] = new Card();

        int lastCard = userHand.length - 1;
        System.out.println("You drew a " + userHand[lastCard].getValue());
    }

    /**
     * Asks player if (s)he wants to hit or stay, returns a boolean for if the
     * player chose stay or not.
     * 
     * @return 
     */
    @Override
    public boolean wantToStay() {
        Boolean stay = null;

        while(stay == null){
            Scanner sc = new Scanner(System.in);

            System.out.print("Would you like to \"hit\" or \"stay\"? ");
            String input = sc.nextLine();

            String hitOrStay = input.toLowerCase();

            switch(hitOrStay){
                case "hit":
                    stay = false;
                    break;
                case "stay":
                    stay = true;
                    break;
                default:
                    System.out.println("That is not a valid option.");
                    break;
            }
        }
        return stay;
    }

    /**
     * Prints the user's nickname.
     * 
     * @return 
     */
    @Override
    public String toString(){
        return user;
    }
}
\$\endgroup\$

4 Answers 4

17
\$\begingroup\$

To other reviewers: I've tackled design and the use of arrays. I didn't touch much on the actual code, although I do hint at things.

Recursive Main and Design

You can easily remove the recursive call to main by having main consist of the following:

public static void main(String[] args){
    BlackJackGame game = new BlackJackGame();
    do{
        game.playGame();
    } while (game.goAgain());
}

And by doing so, some interesting things surface.

  • Does BlackJackGame represent a single hand or a set of hands?
  • Why does BlackJackGame need to be prompted to ask for a new game... and then have someone else handle the result?

Well, if I were designing this... I'd abuse a physical object. Blackjack comes with a table. And the dealer and the player join this table. That's when they're greeted. And then they can play hands. At some point, people leave and the hands end. So perhaps a class like BlackJackTable can be responsible for a lot of things. At the very least it could take the responsibility of passing messages around and handling the game's progression.

To play Blackjack at a casino, you need to place a bet. When you're done playing, you can collect your chips and you just walk away. Sadly, you don't have a betting system, so there's no easy way of simply betting 0 and making that a way to leave the table. (Then again, card counters might favor just waiting a couple hands to get the better cards... and they do so by simply not betting.)

So yeah, you'll have to make something ask the user. I'd make that the table's responsibility. Leave the round of hands out of it.


Future Features

You have Cards. Why not a Deck? Right now, you can get a hand that contains 9 2's. That'd be beyond silly in a casino. You'd call them out on it for sure.

Blackjack has a split feature. You can split a pair into two hands. And some casinos will let you split again if you get more pairs. (That'd imply you get 4 of a kind; 77 split to 7 and 7, get two cards, you have 77 77, split again to 7 7 7 7.) ... but you don't support multiple hands right now.

Blackjack in casino's has betting, of course. That's the point of playing blackjack.

Other features I can think of:

  • Print the message "Dealer stands on X" - for blackjack, dealer rules are usually fixed.
  • Allow the game to print some help
  • Have other players play next to you, each with some silly strategy (and voila, you have something to score your betting mechanic against)
  • Aces have a value of either 1 or 11. Right now, one can draw two aces and immediately be bust.
  • When the dealer has blackjack, the dealer immediately reveals their hand. Player can't do anything about it and just loses.

Library Usage

No, not books. Java has gone through quite some versions, and certain common problems have been solved for you already. Specifically, I'd like to point you to java.util.Collection and it's subclasses. Take a look at a List, Set and Map. Right now, you have to do these weird things with copying arrays. You don't wanna bother with that, do you? With a List, you can have an ever expanding array without having to copy things around (Java will do that for you, internally). You just have to call List.add to add things to a List.


Code shortcuts

Here's some shortcuts you can use that are well-known:

/**
 * Get the hand's total value
 * @return 
 */
@Override
public int getTotal() {
    int totalValue = 0;

    for (Card c : dealerHand) {
        int cardValue = c.getValue();
        totalValue = totalValue + cardValue;
    }

    return totalValue;
}

x = x + y and x += y are the same thing. I'd rewrite the function like this:

/**
 * Get the hand's total value
 * @return 
 */
@Override
public int getTotal() {
    int totalValue = 0;

    for (Card card : dealerHand) {
        totalValue += card.getValue();
    }

    return totalValue;
}

Another shortcut:

if (game.goAgain(sc) == true){
    main(null);
}

if(x == true), where x is of type boolean (not Boolean!), can be shortened to if(x). So this becomes

if (game.goAgain(sc)){
    main(null);
}

The reason you do this is to simplify reading of your code.


Constants

if(player.getTotal() > 21){
    System.out.println(player + "'s total is more than 21, " + player + " lose by default.\n" +
                       otherPlayer + " WIN!\n");
    loseByDefault = true;
}

You should try to label your constants. This has three benefits:

  • Readability. If the statement player.getTotal() > BLACKJACK_LIMIT is true, it carries semantic value (The player is over the limit). The statement player.getTotal() > 21 is abstract and doesn't carry semantic value.
  • Maintainability. Your game now has Decks, Splits, betting, multiple players, logging, statistics, save and load, ASCII card images and basically so much features that your text-based game is now 10K lines. (At least, that's where my text-based game ended up when I started to learn programming.) How will you increase a minimum bet from 1 to 5? You can't just search your code for "1".
  • Reusabilty. Kinda like maintainability, but when you're typing new code, rather than making changes, you don't have to go and look up the minimum bet value or the blackjack limit (or worse, think you know what it was and get it wrong). You can just type BLACKJACK_LIMIT and it'll always be correct.

JavaDoc

Class documentation should tell you what it is.
Function documentation should tell you what it does.
Variable documentation should tell you what it means.
And any in-code comments should tell you why it does.

These statements generally hold true. You seem to follow these statements, ... mostly.

Be careful of comments like this:

/**
 * What will happen at start of a player's turn.
 */
void gameStartup();

This comment doesn't say what the function does. It's also contradictory to the function name - "game start up" and "start player turn"... When I start up a game, I grab a deck, maybe shuffle it and give people cards.

Your IDE generates things like @param and @return. These are for documenting your arguments or "parameters" and your return value.

So a comment like this...

/**
 * Ask player if (s)he wants to hit or stay, returns a boolean on the choice of staying.
 * @return 
 */
boolean wantToStay();

really ought to be like this.

/**
 * Ask player if (s)he wants to hit or stay
 * @return a boolean on the choice of staying
 */
boolean wantToStay();

This also directly reveals to you when you are unclear.

Right now, you've mapped a boolean to hit and stay. I have to read both the function name and the javadoc to figure out that false maps to hit. What happens if we add split, and double down? Then we can't be sure that false means hit anymore. For now, revising the comment to

/**
 * Ask player if (s)he wants to hit or stay
 * @return Whether the player wants to stay
 */
boolean wantToStay();

will do. But in the future, I think this method will change to some PlayerAction getAction() where PlayerAction is an enumeration of actions a Player could take.


I have many, many, many more words for those that make games to learn. I like such exercises. However, I think that this is enough for today. I hope you get more answers and use these in your learning project to post a follow-up question. Questions like these are fun to answer.

\$\endgroup\$
11
\$\begingroup\$

Right, I'll take things in the order that they appear, as they occur to me.

BlackJackGame class

I would make the dealer in location 0 of the players array, as this means that you can pass around your array of players, and rely on the fact that no matter how large the array is, player[0] is the dealer. As well as this, you're duplicating data by both creating a Player user and a Dealer dealer as well as storing that in the array. I'd just store it in the array.

As a slight aside, I'd make it an ArrayList, but if you're trying to just use simple data types, I understand why not, but it's something to consider if you're planning to allow more than 1 player in the future.

As well as this, moving all of your initialization logic to a single method might be a good idea. This can include your welcome text.

I know that you said that you are aware of the recursive call to main being a bad thing, but I thought I should suggest an alternative anyway. The game loop pattern, where you have a initialize() then just a update(), can apply here, in a simple form. You can make Update() be in a while(notQuit) loop, which can contain all of your logic.

PlayerTurn() has quite a few responsibilites, which is something that you don't really want out of a function. I'd pass it just one player, the one who's turn that it is, and return without printing out anything other than what it needs to (the score after the player has taken a card), as it shouldn't have any responsibility over reporting who won. Checking that should be under your whoWon method, and printing it should be controlled by another method.

In goAgain, you're checking the boolean against null. Don't do that, you've got a lovely true/false that you can check against, use it! This is how I'd write that method:

private boolean goAgain(Scanner sc){

    while(true){
        System.out.println("Would you like to play another round? \"yes\"/\"no\": ");
        String input = sc.nextLine();

        String answer = input.toLowerCase();

        switch(answer){
            case "y":
            case "yes":
                return true;
            case "n":
            case "no":
                return false;
            default:
                System.out.println("Invalid answer. Please answer only \"yes\" or \"no\"");
                break;
        }
        System.out.println();
    }
}

Or something similar.

Card class

It's functional, it works, and if you're going for simple, this is it. However, it's not very flexible, and it doesn't involve the notion of a deck, so it's possible to get 5 2s in a row. Whether you want to do that is up to you, but there's plenty of card implementations on this site that'll give you pointers.

Player interface

I'd possibly have a getName(). I know that you've overridden toString(), but that's not the first thing that I'd look for if I were using this interface.

Dealer and Player class

I'd definitely make Hand an ArrayList. They get more then 2 cards in blackjack, and the cards can have different values depending on how the hand is (Ace can be high or low). The rest of my issues with this class is summarised with make hand an arrayList. Resizing arrays is expensive and adding 1 element to a list is less so.

\$\endgroup\$
8
\$\begingroup\$

Something nobody else seems to have mentioned yet: your card-selection algorithm gives much lower odds to the player than they'd have in a real game.

It's true that there are nine possible card values in a game of blackjack, but they're not all equally likely. In a real deck of cards, there are thirteen ranks, four of which are worth ten points in blackjack.

If you pick a random card from a real deck, that means there's a 4/13 ≈ 31% chance of getting a 10-valued card. In your game, there's a 1/9 ≈ 11% chance of getting a 10-valued card. This will make the game a lot less fun.

The way I would handle this is to actually simulate all thirteen possible ranks with the numbers 2–13 in your random loop. Your Card class would then have a blackjackValue method that you can use for scoring purposes. Another option would be to build an array with all the possible scores and select a random element. You'd put the value ten in this array four times, and the nine values all go in there once. (This is slightly slower and more complex, but it gets you closer to a real shuffling algorithm.)

(If you're confident your Card will never be used for anything but Blackjack, you could just call your method points, but in my experience you should never assume your classes won't be reused for something you didn't foresee.)

Of course there's a deeper issue with your scheme, which is that in a real game the deck has a "memory" of which cards have already been played. (E.g., if you draw a 10, there are now fewer 10s left in the deck, making another 10 slightly less likely.) This is the effect that allows card counters to gain a statistical advantage in Blackjack. I think it's reasonable to ignore this effect for a simple simulated game, but if you wanted a next step you might look up shuffling algorithms: they're not too hard to implement.

Real Vegas casinos typically use something like six decks and reshuffle when they're roughly halfway through. That's enough cards that your random-card approach is going to be pretty close to real most of the time.

\$\endgroup\$
3
  • \$\begingroup\$ I am well aware of that the assignent of values for the cards is not the most lifelike, but this is a very simple game that more or less just "does what it's supposed to", without me knowing anything about BlackJack (This is based on the first half of the programming by doing site's BlackJack) \$\endgroup\$
    – Gemtastic
    Commented Oct 28, 2014 at 17:51
  • \$\begingroup\$ Essentially you're playing with an infinitely large shoe, no face cards, and two aces are a bust. Remind me not to visit your casino (except maybe for a job :-). \$\endgroup\$ Commented Oct 28, 2014 at 20:51
  • \$\begingroup\$ I'm not building a casino, I'm trying to learn how to build readable code that's safe and works as intended. You could alway improve the methods and maths, but this is just a very simple game that's just supposed to simulate BlackJack. ;) (it's hopefully a good start to build on) \$\endgroup\$
    – Gemtastic
    Commented Oct 29, 2014 at 3:31
2
\$\begingroup\$

Going with the other answers, you need to create a cards class (member of decks or similar) to put the card information in. You need to make sure you have at least 52 unique cards (Ace through to King, one of each suit) to create a deck.

The reason for this is being able to reuse the code, and for Blackjack's rules about Aces: they can be a 1 or an 11, depending on the player. Currently, they could be dealt two "11"'s, and they would instantly bust. Definitely not fun.

If they are dealt an Ace and a Nine, and they hit and get a Ten, they need to treat the first Ace as just a 1, not as an 11.

\$\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.