Edited at

リファクタリング: Javaでブラックジャックを作る

追記:

@k73i55no5さんより、より良いリファクタリング案をコメントいただきました。ありがとうございます。詳しくはコメントをご覧下さい。

クラス・メンバ構成(構造)のリファクタリングだけしています。

処理コード(内装、実装)のリファクタリングには着手していません。

(ここまで追記)

@yuta-yoshinaga さんが投稿された「Javaでブラックジャックを作る」のソースコードを拝見しました。

セッター・ゲッターだらけでカプセル化が破壊されていて、仕事をしていないクラスもあり、ちょっと辛辣なコメントを書いてしまいました。

コメントだけでは何なので、私なりにリファクタリングしてみました。

まだ見直す個所はありますが、何か参考になることがあれば幸いです。


BlackJack.java

public class BlackJack {

private final Player player;
private final Dealer dealer;

public BlackJack() {
this(new CUIPlayer(), new Dealer());
}

public BlackJack(Player player, Dealer dealer) {
this.player = player;
this.dealer = dealer;
}

public void play() {
player.reset();
dealer.reset();
for (int i = 0; i < 2; i++) {
dealer.dealCard(player);
dealer.dealCard(dealer);
}
dealer.show();
if (player.play(dealer)) {
dealer.play(dealer);
showJudge();
}
}

public void showJudge() {
dealer.show();
player.show();
Player winner = judge();
System.out.println("----------");
if (winner == player) {
System.out.println("You are the winner.");
} else if (winner == dealer) {
System.out.println("It is your loss.");
} else {
System.out.println("It is a draw.");
}
}

public Player judge() {
if (player.isBust()) {
return dealer;
} else if (dealer.isBust()) {
return player;
} else if (player.isBlackJack() && !dealer.isBlackJack()) {
return player;
} else if (dealer.isBlackJack() && !player.isBlackJack()) {
return dealer;
}
int diff = player.calcScore() - dealer.calcScore();
if (diff > 0) {
return player;
} else if (diff < 0) {
return dealer;
} else {
return null;
}
}

public static void main(String[] args) {
BlackJack blackjack = new BlackJack();
while (true) {
blackjack.play();
blackjack.player.play(null);
}
}
}



Player.java

import java.util.List;

import java.util.ArrayList;
import java.util.stream.Collectors;

abstract public class Player {
public final String name;
protected final List<Card> cards = new ArrayList<Card>();
protected boolean stand = false;

public Player() {
this("Player");
}

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

public void reset() {
cards.clear();
stand = false;
}

public void holdCard(Card card) {
cards.add(card);
}

abstract boolean play(CardDealer dealer);

public int calcScore() {
int score = 0;
boolean haveAce = false;
for (Card card: cards) {
score += card.rank < 10 ? card.rank : 10;
if (card.rank == 1) {
haveAce = true;
}
}
if (score <= 11 && haveAce) {
score += 10;
}
return score;
}

public boolean isBlackJack() {
return cards.size() == 2 && calcScore() == 21;
}

public boolean isBust() {
return calcScore() > 21;
}

public void show() {
System.out.println("----------");
showCards();
System.out.println(name + "'s score: " + calcScore());
}

public void showCards() {
System.out.println(name + "'s card: " + cards.stream().map(Object::toString).collect(Collectors.joining(", ")));
}
}



CUIPlayer.java

import java.util.Scanner;

public class CUIPlayer extends Player {
private Scanner sc = new Scanner(System.in);

public boolean play(CardDealer dealer) {
while (!isBust() || dealer == null) {
if (!stand) {
show();
}
System.out.println("----------");
System.out.println("Please enter a command.");
System.out.println(" q: quit");
System.out.println(" r: restart");
if (!stand && dealer != null) {
System.out.println(" h: hit");
System.out.println(" s: stand");
}
System.out.print("? ");
String inputStr = sc.nextLine();
switch (inputStr) {
case "q":
case "quit":
System.out.println("bye.");
sc.close();
System.exit(0);
break;
case "r":
case "reset":
return false;
case "h":
case "hit":
if (!stand && dealer != null) {
dealer.dealCard(this);
}
break;
case "s":
case "stand":
stand = true;
return true;
default:
System.out.println("Unsupported command.");
break;
}
}
stand = true;
return true;
}
}



Dealer.java

public class Dealer extends Player implements CardDealer {

private final Deck deck;

public Dealer() {
this(new Deck());
}

public Dealer(Deck deck) {
super("Dealer");
this.deck = deck;
}

public void dealCard(Player player) {
player.holdCard(deck.drowCard());
}

public boolean play(CardDealer dealer) {
while (calcScore() < 17) {
dealer.dealCard(this);
}
stand = true;
return true;
}

@Override
public void show() {
if (stand || cards.size() != 2) {
super.show();
} else {
System.out.println("----------");
cards.get(1).faceDown();
showCards();
cards.get(1).faceUp();
}
}
}



CardDealer.java

public interface CardDealer {

public void dealCard(Player player);
}


Deck.java

import java.util.ArrayList;

import java.util.Collections;

public class Deck {
private final ArrayList<Card> cards = new ArrayList<Card>();

public Deck() {
reset();
}

void reset() {
cards.clear();
for (Suit suit: Suit.values()) {
for (int rank = 1; rank <= 13; rank++) {
cards.add(new Card(suit, rank));
}
}
shuffle();
}

public void shuffle() {
Collections.shuffle(cards);
}

public Card drowCard() {
if (cards.size() == 0) {
reset();
}
return cards.remove(0);
}
}



Card.java

enum Suit {

SPADE, CLOBBER, HEART, DIAMOND;
}

public class Card {
public static String[] RANK = {
"", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"
};

public final Suit suit;
public final int rank;
private boolean visible;

public Card(Suit suit, int rank) {
this.suit = suit;
this.rank = rank;
faceUp();
}

public void faceUp() {
this.visible = true;
}

public void faceDown() {
this.visible = false;
}

@Override
public String toString() {
if (visible) {
return suit.name() + ' ' + RANK[rank];
} else {
return "???";
}
}
}