Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[알트] 블랙잭 미션 제출합니다. #2

Open
wants to merge 17 commits into
base: tdd-kskim
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# java-blackjack
블랙잭 게임 미션 저장소

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 기능 목록
* [X] 게임에 참여할 사람의 이름을 입력받는다.
* [X] 참여자의 이름은 1 ~ 10자로 구성된다.
* [X] 참여자의 이름 앞, 뒤에 오는 공백은 무시한다.
* [X] 참여자의 이름은 comma(,) 단위로 구분한다.
* [X] 참여자의 수는 2 ~ 8명이다.
* [X] 각 참여자의 베팅 금액을 입력받는다.
* [X] 베팅 금액의 최소 단위는 100으로 제한한다.
* [X] 딜러와 각 참여자에게 카드를 두 장씩 분배한다.
* [X] 각 참여자에게 나누어 준 카드를 출력한다.
* [X] 딜러는 첫 번째 카드를 제외하고 한 장의 카드만 공개한다.
* [X] 각 참여자는 두 장의 카드를 공개한다.
* 딜러가 블랙잭인지 여부를 판별하고, 블랙잭이면 결과를 출력한다.
* [X] 참여자가 블랙잭이라면 무승부이다.
* [X] 참여자가 블랙잭이 아니라면 딜러의 승리이다.
* 참여자 히트
* [X] 참여자가 블랙잭, 버스트가 아니라면 추가적으로 카드를 히트할지 여부를 입력받는다.
* [X] 참여자가 스테이하면 다음 사람 턴으로 넘어간다.
* [X] 참여자가 히트하면 카드 발급 후 카드를 출력한다.
* 딜러 히트
* [X] 딜러는 가진 패의 합이 16 이하라면 반드시 히트해야 한다.
* [X] 딜러와 참여자가 받은 모든 패를 공개하고, 결과를 출력한다.
* [X] 각 참여자의 최종 수익을 출력한다.

## 게임 조건
* 두 장의 패를 뽑아 합이 21인 경우 블랙잭이다.
* 카드의 합이 21을 초과하는 경우 버스트이다.
* 각 ACE는 1 또는 11로 계산할 수 있다.
* 참여자가 버스트가 되면 딜러의 버스트 여부와 상관없이 참여자가 패배한다.

### 수익 계산
* 무승부인 경우 베팅한 금액을 돌려받아 수익이 없다.
* 참여자가 승리한 경우
* 블랙잭이 아니라면 베팅 금액만큼 수익을 얻는다.
* 블랙잭이라면 베팅 금액의 1.5배의 수익을 얻는다.
* 참여자가 패배하면 베팅한 모든 금액을 잃는다.
39 changes: 39 additions & 0 deletions src/main/java/BlackjackApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import java.util.Scanner;

import controller.GameController;
import service.DrawService;
import service.PlayerService;
import service.ProfitService;
import view.InputView;
import view.OutputView;

public class BlackjackApplication {
private final GameController gameController;

public BlackjackApplication(final GameController gameController) {
this.gameController = gameController;
}

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
InputView inputView = new InputView(scanner);
OutputView outputView = new OutputView();
PlayerService playerService = new PlayerService();
DrawService drawService = new DrawService();
ProfitService profitService = new ProfitService();

GameController gameController = new GameController(inputView, outputView, playerService, drawService,
profitService);
BlackjackApplication blackjackApplication = new BlackjackApplication(gameController);

blackjackApplication.run();
}

public void run() {
try {
gameController.run();
} catch (Exception exception) {
System.err.println(exception.getMessage());
}
}
}
87 changes: 87 additions & 0 deletions src/main/java/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package controller;

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

import domain.BettingMoney;
import domain.Name;
import domain.card.CardDeck;
import domain.card.CardDeckFactory;
import domain.participant.Dealer;
import domain.participant.Player;
import domain.result.ParticipantProfit;
import service.DrawService;
import service.PlayerService;
import service.ProfitService;
import view.InputView;
import view.OutputView;

public class GameController {
private final InputView inputView;
private final OutputView outputView;
private final PlayerService playerService;
private final DrawService drawService;
private final ProfitService profitService;

public GameController(final InputView inputView, final OutputView outputView, final PlayerService playerService,
final DrawService drawService, final ProfitService profitService) {
this.inputView = inputView;
this.outputView = outputView;
this.playerService = playerService;
this.drawService = drawService;
this.profitService = profitService;
}

public void run() {
CardDeck cardDeck = CardDeckFactory.createRandomCardDeck();
Dealer dealer = new Dealer();
List<Player> players = createPlayers();

drawCards(cardDeck, dealer, players);

calculateProfit(dealer, players);
}

private List<Player> createPlayers() {
String namesWithComma = inputView.inputNames();
List<Name> names = Name.fromComma(namesWithComma);
List<BettingMoney> bettingMonies = new ArrayList<>();
for (final Name name : names) {
int money = inputView.inputBettingMoney(name);
BettingMoney bettingMoney = new BettingMoney(money);
bettingMonies.add(bettingMoney);
}
return playerService.createPlayers(names, bettingMonies);
Comment on lines +47 to +54
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List와 List를 매치시켜서 createPlayers() 해주고 있는데요. 각 List의 요소가 1:1로 매치되는데, Map자료구조를 활용하는건 어떨까요?

}

private void drawCards(final CardDeck cardDeck, final Dealer dealer, final List<Player> players) {
drawService.drawInitialCards(cardDeck, dealer, players);
outputView.printHandsAtFirst(dealer, players);

for (final Player player : players) {
drawCardToPlayer(cardDeck, player);
}
drawCardToDealer(cardDeck, dealer);
}
Comment on lines +61 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (final Player player : players) {
drawCardToPlayer(cardDeck, player);
}
drawCardToDealer(cardDeck, dealer);
}
for (final Player player : players) {
drawCardToPlayer(cardDeck, player);
}
}

AllPlayers라는 객체가 있으면, 상속을 극대화하여 사용할 수 있을 것 같은데요
어려울까요..?


private void calculateProfit(final Dealer dealer, final List<Player> players) {
outputView.printHandsAtLast(dealer, players);
List<ParticipantProfit> participantProfits = profitService.calculateProfits(dealer, players);
outputView.printProfits(participantProfits);
}

private void drawCardToDealer(final CardDeck cardDeck, final Dealer dealer) {
while (!dealer.isFinished()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFinished 메소드를 순수하게 사용하는 곳은 없는것 같은데요.
isNotFinished 로 변경해도 좋을 것 같아요.

drawService.drawDealer(cardDeck, dealer);
outputView.printDealerHitMessage();
}
}

private void drawCardToPlayer(final CardDeck cardDeck, final Player player) {
while (!player.isFinished()) {
String decision = inputView.inputPlayerDecision(player);
drawService.drawPlayer(cardDeck, player, decision);
outputView.printHand(player);
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/domain/BettingMoney.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package domain;

import java.math.BigDecimal;

public class BettingMoney {
private static final int MIN_AMOUNT = 100;
private static final int BETTING_UNIT = 100;

private final BigDecimal amount;

public BettingMoney(final int amount) {
validate(amount);
this.amount = BigDecimal.valueOf(amount);
}

private void validate(final int amount) {
validateRange(amount);
validateUnit(amount);
}

private void validateRange(final int amount) {
if (amount < MIN_AMOUNT) {
throw new IllegalArgumentException("베팅 최소 금액을 충족하지 못했습니다.\n"
+ "amount: " + amount);
}
}

private void validateUnit(final int amount) {
if (amount % BETTING_UNIT != 0) {
throw new IllegalArgumentException("금액의 단위가 올바르지 않습니다.\n"
+ "amount: " + amount);
}
}

public BigDecimal getAmount() {
return amount;
}
}
40 changes: 40 additions & 0 deletions src/main/java/domain/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package domain;

import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

public class Name {
private static final int MAX_LENGTH = 10;
private static final int MIN_LENGTH = 1;
private static final String NAME_DELIMITER = ",";

private final String name;

public Name(String name) {
Objects.requireNonNull(name, "이름이 null입니다.");
name = name.trim();
validateLength(name);
this.name = name;
}

public static List<Name> fromComma(final String names) {
return Stream.of(names.split(NAME_DELIMITER))
.map(Name::new)
.collect(toList());
}
Comment on lines +16 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

딜러를 만들때만 생성자를 사용하는데요.
생성자의 접근지정자의 수준을 높히고, 딜러를 만드는 static 메서드를 만드는건 어떨까요?


private void validateLength(final String name) {
int length = name.length();
if (length < MIN_LENGTH || length > MAX_LENGTH) {
throw new IllegalArgumentException("이름의 길이가 올바르지 않습니다.\n"
+ "name: " + name);
}
}

public String getName() {
return name;
}
}
66 changes: 66 additions & 0 deletions src/main/java/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package domain.card;

import static java.util.stream.Collectors.toList;

import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

public class Card {
private final Face face;
private final Suit suit;

private Card(final Face face, final Suit suit) {
this.face = face;
this.suit = suit;
}

public static Card fromFaceAndSuit(final Face face, final Suit suit) {
return CardCache.cache
.stream()
.filter(card -> card.isCardOf(face, suit))
.findFirst()
.orElseThrow(() -> new CardNotFoundException("카드가 존재하지 않습니다.\n"
+ "face: " + face + "\n"
+ "suit: " + suit));
}

public static List<Card> values() {
return Collections.unmodifiableList(CardCache.cache);
}

public boolean isAce() {
return face.isAce();
}

public boolean isCardOf(final Face face, final Suit suit) {
return this.face == face && this.suit == suit;
}

public Face getFace() {
return face;
}

public Suit getSuit() {
return suit;
}

public String alias() {
return suit.alias() + face.alias();
}

private static class CardCache {
public static final List<Card> cache;

static {
cache = Stream.of(Face.values())
.flatMap(CardCache::createByFace)
.collect(toList());
}

private static Stream<Card> createByFace(final Face face) {
return Stream.of(Suit.values())
.map(suit -> new Card(face, suit));
}
}
}
5 changes: 5 additions & 0 deletions src/main/java/domain/card/CardDeck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package domain.card;

public interface CardDeck {
Card pick();
}
11 changes: 11 additions & 0 deletions src/main/java/domain/card/CardDeckFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package domain.card;

public class CardDeckFactory {
private CardDeckFactory() {
throw new AssertionError();
}

public static CardDeck createRandomCardDeck() {
return new RandomCardDeck(Card.values());
}
}
7 changes: 7 additions & 0 deletions src/main/java/domain/card/CardNotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package domain.card;

public class CardNotFoundException extends RuntimeException {
public CardNotFoundException(final String message) {
super(message);
}
}
7 changes: 7 additions & 0 deletions src/main/java/domain/card/EmptyCardDeckException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package domain.card;

public class EmptyCardDeckException extends RuntimeException {
public EmptyCardDeckException(final String message) {
super(message);
}
}
42 changes: 42 additions & 0 deletions src/main/java/domain/card/Face.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package domain.card;

public enum Face {
ACE(1, "A"),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6),
SEVEN(7),
EIGHT(8),
NINE(9),
TEN(10),
JACK(10, "J"),
QUEEN(10, "Q"),
KING(10, "K");

private final int score;
private final String alias;

Face(final int score) {
this.score = score;
this.alias = String.valueOf(score);
}

Face(final int score, final String alias) {
this.score = score;
this.alias = alias;
}

public boolean isAce() {
return this == ACE;
}

public int getScore() {
return score;
}

public String alias() {
return alias;
}
}
Loading