[JAVA] Implemented "Blackjack".

Trigger

Graduation exams from programming beginners should develop "Blackjack"

Inspired by the article "Graduation exams from programming beginners should develop'blackjack'", I tried to create blackjack in Java. Surprisingly difficult ... The low design skills are noticeable. The difficulty of implementation was just right. Is the mounting time about 3 hours?

I would like to keep it clean, so please point it out.

Source

The source code has been uploaded to github. https://github.com/akiyu33333/blackjack

Development environment

Full source (as of 06/22)

The specifications are Qiita original article or github README Please refer to it.

~~ * Refactored after being pointed out. ~~ We also implemented refactoring & A scoring.

06/22 postscript I made further corrections in response to @ watashi_da's point. I was advised to have a CPU flag, and as a result of the refactoring, I finally decided to divide the classes into ** User ** and ** Dealer **.

Main.java

Main.java


import blackjackgame.BlackJackGame;

public class Main {
    public static void main(String[] args) {
        new BlackJackGame().start();
    }
}

BlackJackGame.java Changed class name and package name.

BlackJackGame.java


package blackjackgame;

import card.Deck;
import player.Dealer;
import player.AbstractPlayer;
import player.User;

public class BlackJackGame {

    /**
     *game start
     */
    public void start() {
        System.out.println("★ ☆ ★ ☆ ★ ☆ ★ ☆ ★ ☆ ★ ☆ Welcome to Blackjack! ★ ☆ ★ ☆ ★ ☆ ★ ☆ ★ ☆ ★ ☆\n");
        System.out.println("Start the game.\n");

        Deck deck     = new Deck();
        AbstractPlayer user   = new User("you");
        AbstractPlayer dealer = new Dealer("dealer");

        user.initCardList(deck);
        dealer.initCardList(deck);

        user.drawCard(deck);
        if(!user.isBust()) dealer.drawCard(deck);

        printGameResult(user, dealer);

        System.out.println("\n Blackjack is over! Please play again ★");
    }

    /**
     *View game results
     * @param player1 player 1
     * @param player2 player 2
     */
    private void printGameResult(AbstractPlayer player1, AbstractPlayer player2) {
        if (player1.calcScore() == player2.calcScore()) {
            System.out.println("It's a draw.");
            return;
        }
        AbstractPlayer winner = !player1.isBust() && (player2.isBust() || player1.calcScore() > player2.calcScore())
                ? player1
                : player2;
        System.out.println( winner.getName() + "Is the winner!" );
    }
}

~~Player.java~~ → AbstractPlayer.java I was conscious of generating lombok only where it was needed. At first, I made it an abstract class, but since I don't need it anymore, I made it a concrete class. Should I make an interface and implement it? ~~ getPoint () ~~ I personally felt the growth of being able to write the lambda of calcScore () (laughs)

Renamed getPoint to calcScore. I forgot the following of the readable code.

Many programmers are accustomed to the convention that methods that start with get are "lightweight accessors" that just return the value of the member. Failure to comply with these terms can be misleading.

Since calculation processing is included here, getXXX is not good.

Added calculation logic for A. The method was to calculate other than A, then calculate from the number of A and add.

06/22 postscript Since it is an abstract class, the name is also changed.

AbstractPlayer.java


package player;

import card.Card;
import card.Deck;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

public abstract class AbstractPlayer {
    protected static final int BUST_POINT = 21;

    @Getter
    private final String name;
    private List<Card> cardList = new ArrayList<>();
    @Getter
    @Setter
    private boolean isBust = false;

    public AbstractPlayer(String name) {
        this.name = name;
    }

    private void addCardList(Card card){
        cardList.add(card);
    }

    public int calcScore(){
        int score = cardList.stream().filter(card -> card.getPoint() > 1 ).mapToInt(card -> card.getPoint()).sum();
        int aceCardCount = (int) cardList.stream().filter(card -> card.getPoint() == 1 ).count();
        if (aceCardCount == 0) return score;
        int borderScore = 11 - aceCardCount;
        return score > borderScore ? score + aceCardCount : score + 10 + aceCardCount ;
    }

    public void draw(Deck deck) {
        draw(deck,false);
    }

    /**
     *Draw a card from the deck
     * @param deck deck
     * @param isHidden Hide the drawn card
     */
    public void draw(Deck deck, boolean isHidden) {
        Card card = deck.draw();
        addCardList(card);
        if (calcScore() > BUST_POINT) setBust(true);
        String msg = isHidden
                ? this.name + "I don't know the card I drew."
                : this.name + "The card drawn by" + card.toString() + "is.";
        System.out.println( msg );
    }

    /**
     *Creating an initial hand
     * @param deck deck
     */
    public abstract void initCardList(Deck deck);

    /**
     *Draw a card from the deck
     * @param deck deck
     */
    public abstract void drawCard(Deck deck);
}

User.java 06/22 postscript Newly created to distinguish between player and CPU.

User.java


package player;

import card.Deck;

import java.util.Objects;
import java.util.Scanner;

public class User extends AbstractPlayer {
    public User(String name) {
        super(name);
    }

    @Override
    public void initCardList(Deck deck) {
        draw(deck);
        draw(deck);
    }

    @Override
    public void drawCard(Deck deck) {
        System.out.println( getName() + "The current score of" + calcScore() + "It is a point.\n");
        try (Scanner sc = new Scanner(System.in)) {
            String line = null;
            while (!isBust() && !Objects.equals(line, "N")) {
                System.out.println("Do you draw a card? Enter N if you do not want to draw Y.");
                line = sc.nextLine();
                if (Objects.equals(line, "Y")) {
                    draw(deck);
                    System.out.println( getName() + "The current score of" + calcScore() + "It is a point.\n");
                } else if (!Objects.equals(line, "N")) {
                    System.out.println("Y/Anything other than N has been entered.");
                }
            }
        }
    }
}

Dealer.java 06/22 postscript Newly created to distinguish between player and CPU.

Dealer.java


package player;

import card.Deck;

public class Dealer extends AbstractPlayer {

    public Dealer(String name) {
        super(name);
    }

    @Override
    public void initCardList(Deck deck) {
        draw(deck);
        draw(deck, true);
    }
    @Override
    public void drawCard(Deck deck) {
        System.out.println( getName() + "The current score of" + calcScore() + "It is a point.\n");
        while (calcScore() < 17){
            draw(deck);
            System.out.println( getName() + "The current score of" + calcScore() + "It is a point.\n");
        }
    }
}

Card.java Made the field final. The switch and the ternary operator are awkward.

Card.java


package card;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Card {

    private final Suit suit;
    private final int rank;

    private String toDisplayValue() {
        switch (this.rank) {
            case 1:
                return "A";
            case 11:
                return "J";
            case 12:
                return "Q";
            case 13:
                return "K";
            default:
                return String.valueOf(this.rank);
        }
    }
    public int getPoint() {
        return this.rank > 10 ? 10 : this.rank;
    }
    @Override
    public String toString() {
        return this.suit.getMark() + "of" + this.toDisplayValue();
    }

}

Deck.java I tried using the initialize method. Lambda here also felt good (laughs) I want to substitute the return value of lambda to bill as it is without using ~~new ArrayList <> (), but I didn't know how to write it. ~~ I was able to write it in lambda, so I corrected it. Although the language is different, I read this article and thought that I shouldn't use foreach for anything. Using forEach in JavaScript is the last resort Actually, it's not JavaScript, so the content of the article cannot be used as it is, but I was impressed with the way of thinking. Collections.shuffle I also learned this for the first time. There is a convenient method.

Deck.java


package card;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Deck {
    private List<Card> bill;
    {
        bill = Arrays.stream(Suit.values()).flatMap(s -> IntStream.rangeClosed(1,13).mapToObj(i -> new Card(s,i))).collect(Collectors.toList());
        Collections.shuffle(bill);
    }

    public Card draw(){
        Card card = bill.get(0);
        bill.remove(0);
        return card;
    }
}

Suit.java My favorite ʻenum. I forgot to use lombok in ʻenum, so I fixed it.

Suit.java


package card;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
public enum Suit {
    SPADE("spade"),
    HEART("heart"),
    DIAMOND("Diamond"),
    CLUB("club");

    @Getter
    private final String mark;
}

Summary

was fun. IntelliJ doesn't have a shortcut to make Javadoc. Since we have made it possible to divide ~~ A between 1 and 11 and ~~ A, we would like to support multiple players.

Recommended Posts

Implemented "Blackjack".
[Production progress] BlackJack: 03
[Production progress] BlackJack: 01
[Production progress] BlackJack: 02
Implemented comment function
[Production progress] BlackJack: 04