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

[1단계 - 블랙잭 게임 실행] 상돌(이상진) 미션 제출합니다. #632

Merged
merged 43 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cc6c638
docs(README): 기능 요구 사항 작성
pricelees Mar 5, 2024
9c89c6f
feat: 카드 문양과 숫자에 대한 Enum 클래스 생성
pricelees Mar 5, 2024
b7011cb
feat(Deck): 모든 카드를 가지는 Deck 클래스 및 테스트 구현
pricelees Mar 5, 2024
1ab946f
style(DeckTest): 중복 코드 제거
pricelees Mar 6, 2024
54db712
feat(BlackJackGamer): 딜러와 플레이어의 공통 추상 클래스 생성
pricelees Mar 6, 2024
1432f2d
feat(Dealer): BlackJackGamer를 상속받는 딜러 클래스 생성과 메서드 재정의
pricelees Mar 6, 2024
56af966
feat(Player): BlackJackGamer를 상속받는 플레이어 클래스 생성과 메서드 재정의
pricelees Mar 6, 2024
af51dfa
feat(Name): 문자열 이름을 포장하는 클래스 생성
pricelees Mar 6, 2024
59f51bc
feat(Players): Player의 일급 컬렉션 생성
pricelees Mar 6, 2024
e75ea3b
refactor: 예외 메시지 추가 및 테스트 수정
pricelees Mar 6, 2024
939882c
test(NameTest): 정상 입력에 대한 테스트 추가
pricelees Mar 6, 2024
f050926
test(PlayersTest): 정상 입력에 대한 테스트 추가
pricelees Mar 6, 2024
9b0e50d
feat(Player): 딜러의 점수를 입력받아 플레이어의 승패 여부를 결정하는 기능 추가
pricelees Mar 7, 2024
ae70681
feat(Hand): 참여자의 카드 리스트에 대한 일급 컬렉션 생성
pricelees Mar 7, 2024
f6afbdc
refactor(BlackjackGamer): Hand를 사용하도록 수정
pricelees Mar 7, 2024
8d1b095
refactor(Dealer, Player): 생성자 및 메서드 수정
pricelees Mar 7, 2024
69e7124
feat(GameResult): 승,패를 표현하는 Enum 클래스 생성
pricelees Mar 7, 2024
1a9df2a
feat: DTO 생성 및 각 도메인에 DTO 생성 로직 추가
pricelees Mar 7, 2024
d74858c
refactor(Hand): 카드 숫자의 합계를 구하는 메서드 세분화 및 상수 추가
pricelees Mar 7, 2024
9fd5c3d
style(Dealer): 상수 추가 및 dto 생성 메서드명 수정
pricelees Mar 7, 2024
e0351cf
style(Player): 매직 넘버 상수화
pricelees Mar 7, 2024
da6b980
feat(Players): 전체 플레이어의 패를 초기화 하는 기능 및 dto 생성 기능 추가
pricelees Mar 7, 2024
6f1ef7b
feat(GameResult): 패배인지 확인하는 기능 및 getter 추가
pricelees Mar 7, 2024
23a4321
feat(InputView): 입력을 받아들이는 InputView 클래스 생성
pricelees Mar 7, 2024
38f9367
feat(OutputView): 출력을 담당하는 OutputView 클래스 생성
pricelees Mar 7, 2024
c3443d3
feat(BlackjackController): 블랙잭 게임 흐름을 제어하는 컨트롤러 클래스 생성
pricelees Mar 7, 2024
74bbc4c
style: 출력 요구사항에 맞춰 빈 줄 출력 포맷 수정
pricelees Mar 7, 2024
8250c40
feat(Application): 컨트롤러를 생성하고 호출하여 게임을 시작하는 클래스 생성
pricelees Mar 7, 2024
1b7ead1
docs(README): 기능 요구 사항 정리
pricelees Mar 8, 2024
a978ec5
refactor: 각 DTO 클래스에 생성 메서드 추가 및 도메인에서의 DTO 생성 로직 제거
pricelees Mar 10, 2024
bbc9f0d
style(Deck): 모든 카드를 준비하는 것을 별도의 메서드로 분리
pricelees Mar 10, 2024
950d1ed
style: DTO 클래스의 팩토리 메서드 이름 수정
pricelees Mar 10, 2024
0676747
test(Dealer): 딜러의 승,패 횟수 계산 메서드 분리에 의한 테스트 추가
pricelees Mar 10, 2024
d2912ec
style(PlayerResultsDto): 필드 타입 변환에 따른 변수명 수정
pricelees Mar 10, 2024
67bc477
refactor(OutputView): 줄 간격 조절을 Controller에서 하도록 수정 및 개행문자 상수화
pricelees Mar 10, 2024
86d7666
refactor(OutputView): 중복 메서드 제거
pricelees Mar 10, 2024
4a9c177
refactor(Deck): 컬렉션을 LinkedList에서 ArrayList로 수정 및 더이상 뽑을 카드가 없을 때의 예외…
pricelees Mar 10, 2024
892aec0
refactor(Deck): 52장의 카드를 다 뽑으면, 다음에 카드를 뽑을 때 전체 카드를 다시 가져오도록 수정
pricelees Mar 10, 2024
1eb5b16
comment(Hand): 주석 수정
pricelees Mar 10, 2024
03b625d
test(Card): 동등성 판단에 대한 테스트 추가
pricelees Mar 10, 2024
5bd812e
feat(BlackjackConstants): 게임과 관련된 상수들을 별도의 Enum으로 분리
pricelees Mar 10, 2024
b5c98bc
refactor(BlackjackGamer): 처음에 받는 카드의 개수를 입력받을 수 있도록 수정
pricelees Mar 10, 2024
1f98e7f
refactor(Player): 블랙잭인 경우에 대한 승,패 판정 기능 추가
pricelees Mar 10, 2024
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
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@

블랙잭 미션 저장소

## 우아한테크코스 코드리뷰
## 기능 요구 사항

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
### 게임

- [x] 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 쪽이 승리한다.
- [x] 게임을 시작하면 플레이어는 두 장의 카드를 지급 받는다.
- [x] 이후 플레이어와 딜러의 카드를 출력한다.
- [x] 단, 딜러의 카드는 하나만 출력한다.
- [x] 플레이어는 카드의 숫자 합이 21을 초과하지 않는다면 카드를 원하는 만큼 다시 뽑을 수 있다.
- [x] 새로 받을 때 마다 해당 플레이어의 카드를 출력한다.
- [x] 플레이어가 카드를 다 받으면 딜러의 카드를 확인한다.
- [x] 딜러의 카드 합이 17 이상이 될 때 까지 카드를 받는다.
- [x] 딜러와 플레이어의 카드, 결과와 최종 승패를 출력한다.

### 카드

- [x] 클로버, 스페이드, 하트, 다이아몬드 모양을 가진다.
- [x] 카드는 2부터 10까지의 숫자와 Ace, King, Queen, Jack으로 이루어져 있다.
- [x] King, Queen, Jack은 10으로 계산한다.
- [x] ACE는 1 또는 11로 계산할 수 있다.

### 딜러, 플레이어 공통

- [x] 최소 1글자, 최대 5글자의 이름을 가진다.
- [x] ACE 카드를 가지는 경우, 일단 11로 계산한 뒤, 합이 21을 초과하면 1로 계산한다.
- [x] 현재 가진 카드 숫자의 합을 기준으로 카드를 더 받을 수 있는지 결정한다.
- [x] 딜러는 숫자의 합이 16 이하이면 카드를 더 받을 수 있다.
- [x] 플레이어는 숫자의 합이 21 이하이면 카드를 더 받을 수 있다.

### 플레이어

- [x] 플레이어의 이름은 중복될 수 없다.
- [x] 플레이어는 최소 2명부터 최대 8명까지 가능하다.
- [x] 딜러의 점수를 입력받아 승,패를 결정한다.
- [x] 플레이어의 점수가 21을 초과하면 딜러의 점수와 무관하게 패배한다.
- [x] 플레이어의 점수가 21 이하이고, 딜러와 동점인 경우 패배한다.
- [x] 플레이어의 점수가 21 이하이고, 딜러의 점수가 21을 초과하면 승리한다.
- [x] 플레이어와 딜러의 점수가 모두 21 이하이고, 딜러의 점수보다 크면 승리한다.
11 changes: 11 additions & 0 deletions src/main/java/blackjack/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package blackjack;

import blackjack.controller.BlackjackController;

public class Application {

public static void main(String[] args) {
BlackjackController blackjackController = new BlackjackController();
blackjackController.run();
}
}
117 changes: 117 additions & 0 deletions src/main/java/blackjack/controller/BlackjackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package blackjack.controller;

import java.util.List;

import blackjack.domain.card.Deck;
import blackjack.domain.gamer.Dealer;
import blackjack.domain.gamer.Player;
import blackjack.domain.gamer.Players;
import blackjack.dto.DealerInitialHandDto;
import blackjack.dto.DealerResultDto;
import blackjack.dto.GamerHandDto;
import blackjack.dto.PlayerResultsDto;
import blackjack.view.InputView;
import blackjack.view.OutputView;

public class BlackjackController {

private static final String HIT_COMMAND = "y";
private static final String STAND_COMMAND = "n";

private final InputView inputView;
private final OutputView outputView;

public BlackjackController() {
this.inputView = new InputView();
this.outputView = new OutputView();
}

public void run() {
Players players = getPlayers();
Dealer dealer = new Dealer();
Deck deck = new Deck();
deck.shuffle();
outputView.printEmptyLine();

setUpInitialHands(players, deck, dealer);
distributeCardToPlayers(players, deck);
distributeCardToDealer(dealer, deck);
printAllGamerScores(dealer, players);
printResult(dealer, players);
}

private Players getPlayers() {
List<String> playerNames = inputView.receivePlayerNames();

return new Players(playerNames);
}

private void setUpInitialHands(Players players, Deck deck, Dealer dealer) {
players.initAllPlayersCard(deck);
dealer.initCard(deck);
printInitialHands(players, dealer);
}

private void printInitialHands(Players players, Dealer dealer) {
DealerInitialHandDto dealerInitialHandDto = DealerInitialHandDto.fromDealer(dealer);
List<GamerHandDto> playerInitialHandDto = players.getPlayers().stream()
.map(GamerHandDto::fromGamer)
.toList();

outputView.printInitialHands(dealerInitialHandDto, playerInitialHandDto);
outputView.printEmptyLine();
}

private void distributeCardToPlayers(Players players, Deck deck) {
for (Player player : players.getPlayers()) {
distributeCardToPlayer(deck, player);
}
}

private void distributeCardToPlayer(Deck deck, Player player) {
while (canDistribute(player)) {
player.addCard(deck.draw());
outputView.printGamerNameAndHand(GamerHandDto.fromGamer(player));
}
outputView.printEmptyLine();
}

private boolean canDistribute(Player player) {
return player.canReceiveCard() && HIT_COMMAND.equals(getCommand(player));
}

private String getCommand(Player player) {
String command = inputView.receiveCommand(player.getName().value());
if (HIT_COMMAND.equals(command) || STAND_COMMAND.equals(command)) {
return command;
}
throw new IllegalArgumentException(HIT_COMMAND + " 또는 " + STAND_COMMAND + "만 입력 가능합니다.");
}

private void distributeCardToDealer(Dealer dealer, Deck deck) {
while (dealer.canReceiveCard()) {
dealer.addCard(deck.draw());
outputView.printDealerMessage(dealer.getName().value());
}
outputView.printEmptyLine();
}

private void printAllGamerScores(Dealer dealer, Players players) {
outputView.printScore(GamerHandDto.fromGamer(dealer), dealer.getScore());
printPlayersScores(players);
outputView.printEmptyLine();
}

private void printPlayersScores(Players players) {
players.getPlayers().forEach(player -> outputView.printScore(
GamerHandDto.fromGamer(player), player.getScore()
));
}

private void printResult(Dealer dealer, Players players) {
PlayerResultsDto playerResultsDto = PlayerResultsDto.ofPlayersAndDealerScore(players, dealer.getScore());
DealerResultDto dealerResultDto = DealerResultDto.ofDealerAndPlayers(dealer, players);

outputView.printFinalResult(dealerResultDto, playerResultsDto);
}
}
12 changes: 12 additions & 0 deletions src/main/java/blackjack/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package blackjack.domain.card;

public record Card(CardShape cardShape, CardNumber cardNumber) {

public boolean isAce() {
return cardNumber == CardNumber.ACE;
}

public int getNumber() {
return cardNumber.getValue();
}
}
34 changes: 34 additions & 0 deletions src/main/java/blackjack/domain/card/CardNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package blackjack.domain.card;

public enum CardNumber {

ACE("A", 11),
Comment on lines +3 to +5

Choose a reason for hiding this comment

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

카드의 숫자는 CardNumber, 카드의 모양은 CardShape 라는 Enum을 이용하여 정의하였습니다.
이때, CardNumber는 ACE(”A”, 11) 과 같이 출력에 사용될 이름과 숫자 값으로 구성되어 있고, CardShape는 HEART("하트") 와 같이 출력에 사용될 이름으로 구성되어 있습니다.
여기서 질문드리고 싶은 것은, 사실 “A”와 “하트”는 출력 형식이라고 생각됩니다. 따라서 이렇게 정의하게 되면 Domain 과 View 가 완전히 독립은 아니게 될 것이라고 생각합니다.
그래서 “A”와 “하트”와 같은 값은 View에서 처리하는 별도의 Enum을 만드는 것이 이를 해결해줄 것이라고 생각하지만, 이 클래스를 생성해야 하는 단점이 크다고 생각했고, 동시에 Domain에 있는 Enum에 정의하는 것 정도는 허용될 수 있다고 생각하여 지금의 형태로 구현하게 되었습니다.
이것에 대한 제이님의 의견을 말씀해주시면 감사하겠습니다 ㅎㅎ

화면에 보여지는것이 무조건 view의 내용이다 라고 생각을 하신거같아요. 도메인에서 변하지 않는값들이 무엇인지 생각해보면 좋을거같아요. 일반적으로 KING을 K로 사용하고 점수는 10점으로 계산이 됩니다. 화면에서 KING을 킹 혹은 왕 같은 단어로 표기를 해야한다면 그건 view의 역할이겠지만 본연의 도메인이 가지고있는 속성은 지금처럼 enum에 들고있는게 괜찮다고 생각이 들어요~
지금 구현해주신 정도까지는 괜찮습니다. 다른 의견 있으시면 말씀해주세요!

Copy link
Author

Choose a reason for hiding this comment

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

화면에 보여지는것이 무조건 view의 내용이다 라고 생각을 하신거같아요. 도메인에서 변하지 않는값들이 무엇인지 생각해보면 좋을거같아요. 일반적으로 KING을 K로 사용하고 점수는 10점으로 계산이 됩니다. 화면에서 KING을 킹 혹은 왕 같은 단어로 표기를 해야한다면 그건 view의 역할이겠지만 본연의 도메인이 가지고있는 속성은 지금처럼 enum에 들고있는게 괜찮다고 생각이 들어요~ 지금 구현해주신 정도까지는 괜찮습니다. 다른 의견 있으시면 말씀해주세요!

저는 이번에 구현된 것 처럼 View에서 J,Q,K와 같이 매핑하는 것이 좋겠지만, 이정도는 허용할만한 범위라고 생각했습니다. 질문을 드렸던 이유는 "A,J,Q,K"는 도메인 패키지 안에 정의하지 않는 것이 좋다는 다른 크루들의 의견도 합리적이라고 생각했었기 때문이에요.

그런데 제이의 의견을 듣고 생각해보니, "일반적으로 사용되는 것"에 대해선 제가 생각을 못했던 것 같아요. 앞으로 기준을 세울 때 정말 많은 도움이 될 것 같습니다. 감사합니다 ㅎㅎ

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

private final String name;
private final int value;

CardNumber(String name, int value) {
this.name = name;
this.value = value;
}

public String getName() {
return name;
}

public int getValue() {
return value;
}
}
19 changes: 19 additions & 0 deletions src/main/java/blackjack/domain/card/CardShape.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package blackjack.domain.card;

public enum CardShape {

HEART("하트"),
CLOVER("클로버"),
SPADE("스페이드"),
DIAMOND("다이아몬드");

private final String name;

CardShape(String name) {
this.name = name;
}

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class Deck {

private final LinkedList<Card> cards;

public Deck() {
this.cards = initAllCards();
}

private static LinkedList<Card> initAllCards() {
return Arrays.stream(CardShape.values())
.flatMap(cardShape -> Arrays.stream(CardNumber.values())
.map(number -> new Card(cardShape, number)))
.collect(Collectors.toCollection(LinkedList::new));
}

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

public Card draw() {
return cards.poll();
}

Choose a reason for hiding this comment

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

계속 poll을 하면 어떤일이 벌어질까요?

Copy link
Author

Choose a reason for hiding this comment

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

계속 poll을 하면 어떤일이 벌어질까요?

카드의 숫자 합이 21을 초과하면 더 이상 받을 수 없고, 최대 플레이어의 수를 8명으로 제한하였기에 이 상황에선 52장의 카드를 모두 뽑을 수는 없다고 생각했습니다..ㅎㅎ

하지만 이건 충분히 열릴 수 있는 부분이라고 생각했고, 모든 카드를 다 뽑으면 새로 52장의 카드를 세팅한뒤 뽑도록 수정하였습니다!

Choose a reason for hiding this comment

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

개인적인 의견이지만 비어있을때는 예외를 발생시키고 추후에 새로운 Deck객체를 생성하는게 더 나을거같아요. 덱이 비어있을때 같은 객체에서 계속해서 카드가 무한히 나오도록 세팅되는게 어색하지 않을까요?


public List<Card> getCards() {
return new ArrayList<>(cards);
}
}
44 changes: 44 additions & 0 deletions src/main/java/blackjack/domain/gamer/BlackjackGamer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package blackjack.domain.gamer;

import java.util.ArrayList;

import blackjack.domain.card.Card;
import blackjack.domain.card.Deck;

public abstract class BlackjackGamer {

private final Name name;
private final Hand hand;

public BlackjackGamer(Name name) {
this.name = name;
this.hand = new Hand(new ArrayList<>());
}

public abstract boolean canReceiveCard();

public void initCard(Deck deck) {
addCard(deck.draw());
addCard(deck.draw());
}

public void addCard(Card card) {
hand.add(card);
}

public Card getFirstCard() {
return hand.getFirstCard();
}

public int getScore() {
return hand.calculateScore();
}

public Name getName() {
return name;
}

public Hand getHand() {
return hand;
}
}
30 changes: 30 additions & 0 deletions src/main/java/blackjack/domain/gamer/Dealer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package blackjack.domain.gamer;

import java.util.List;

public class Dealer extends BlackjackGamer {

private static final String DEFAULT_DEALER_NAME = "딜러";
private static final int DEALER_DRAW_THRESHOLD = 16;

public Dealer() {
super(new Name(DEFAULT_DEALER_NAME));
}

@Override
public boolean canReceiveCard() {
return getScore() <= DEALER_DRAW_THRESHOLD;
}

public int calculateWinCount(List<GameResult> playerResults) {
return (int)playerResults.stream()
.filter(GameResult::isLose)
.count();
}

public int calculateLoseCount(List<GameResult> playerResults) {
return (int)playerResults.stream()
.filter(GameResult::isWin)
.count();
}
}
25 changes: 25 additions & 0 deletions src/main/java/blackjack/domain/gamer/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package blackjack.domain.gamer;

public enum GameResult {

WIN("승"),
LOSE("패");

private final String name;

GameResult(String name) {
this.name = name;
}

public boolean isWin() {
return this == WIN;
}

public boolean isLose() {
return this == LOSE;
}

public String getName() {
return name;
}
}
Loading