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

[Lev1][미션3] 블랙잭 1단계 - 다즐 #5

Open
wants to merge 80 commits into
base: woo-chang
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
93adb74
docs: 요구사항 명세 작성
kokodak Feb 28, 2023
4d669bd
feat: 이름의 길이 검증 기능 구현
kokodak Feb 28, 2023
ab04caa
feat: 금지된 이름 검증 기능 구현
kokodak Feb 28, 2023
76e478d
docs: 기능 구현 목록 수정
kokodak Mar 2, 2023
c5575b7
feat: 이름 도메인 생성
kokodak Mar 2, 2023
aeb41cc
feat: 문양 도메인 생성
kokodak Mar 2, 2023
660568b
feat: 숫자 도메인 생성
kokodak Mar 2, 2023
5529812
feat: 문양 이름 확인 기능 구현
kokodak Mar 2, 2023
f42b755
feat: 숫자 이름 확인 기능 구현
kokodak Mar 2, 2023
59212a2
feat: 점수 확인 기능 구현
kokodak Mar 2, 2023
365c189
feat: 점수 계산 기능 구현
kokodak Mar 2, 2023
14cb9da
feat: 에이스인지 확인하는 기능 구현
kokodak Mar 2, 2023
d668b8c
feat: 점수 합계 계산 기능 구현
kokodak Mar 2, 2023
07e5c72
feat: 점수 최댓값 초과 여부 확인 기능 구현
kokodak Mar 2, 2023
36dacd5
feat: 이름 확인 기능 구현
kokodak Mar 2, 2023
fe3be1e
feat: 블랙잭 여부 확인 기능 구현
kokodak Mar 2, 2023
bf4991f
fix: 메서드명 수정 및 잘못된 로직 수정
kokodak Mar 2, 2023
ed70701
feat: 카드 추가 여부 확인 기능 구현
kokodak Mar 2, 2023
4c494a0
feat: 카드 추가 기능 구현
kokodak Mar 2, 2023
43b6ec5
chore: 테스트 어노테이션 추가
kokodak Mar 2, 2023
35fd4fc
feat: 카드를 받는 기능 구현
kokodak Mar 2, 2023
d3a6af1
feat: 점수 확인 기능 구현
kokodak Mar 2, 2023
58f1fae
feat: 개수 확인 기능 구현
kokodak Mar 2, 2023
81f907a
feat: 카드 추가 여부 확인 기능 구현
kokodak Mar 2, 2023
96833fd
feat: 점수 확인 기능 구현
kokodak Mar 2, 2023
5138dc4
feat: 카드를 받는 기능 구현
kokodak Mar 2, 2023
4e58c1d
feat: 딜러 도메인 생성
kokodak Mar 2, 2023
e3ae0a0
feat: 참가자 도메인 생성
kokodak Mar 2, 2023
4cdb38c
feat: 딜러와 플레이어가 참가자 상속
kokodak Mar 2, 2023
2370e13
feat: 플레이어의 이름 중복 검증 기능 구현
kokodak Mar 2, 2023
7603a6b
feat: 참가자 인원 검증 기능 구현
kokodak Mar 2, 2023
d4b9c22
refactor: 외부 참조 끊도록 수정
kokodak Mar 2, 2023
f4e414c
feat: 카드를 뽑는 기능 구현
kokodak Mar 2, 2023
559693d
feat: 덱 상태 검증 기능 구현
kokodak Mar 2, 2023
6976a0d
feat: 덱 섞는 기능 구현
kokodak Mar 2, 2023
944cc85
feat: 덱 팩토리 도메인 생성
kokodak Mar 2, 2023
dca7962
chore: 패키지 분리 및 import 최적화
kokodak Mar 2, 2023
ec75c79
feat: 문자열 파싱 기능 구현
kokodak Mar 3, 2023
d27fd5c
feat: 문자열 앞뒤 공백 제거 기능 구현
kokodak Mar 3, 2023
9b81e50
feat: 플레이어 이름 목록 입력 기능 구현
kokodak Mar 3, 2023
6ad67e2
feat: 카드 받을 여부 입력 기능 구현
kokodak Mar 3, 2023
100a9cb
feat: 참가자들의 카드 현황 출력 기능 구현
kokodak Mar 3, 2023
9511cc9
feat: 딜러 카드 추가 여부 출력 기능 구현
kokodak Mar 3, 2023
4e929ad
feat: 최종 결과 출력 기능 구현
kokodak Mar 3, 2023
a7f6e85
feat: 최종 승패 출력 기능 구현
kokodak Mar 3, 2023
c7b4459
feat: 빈 생성자로 생성될 수 있는 기능 구현
kokodak Mar 3, 2023
7de79aa
feat: Cards 도메인에 대한 DTO 생성
kokodak Mar 3, 2023
dfa8280
feat: 딜러 상태에 대한 DTO 생성
kokodak Mar 3, 2023
f3808af
feat: 참가자에 대한 DTO 생성
kokodak Mar 3, 2023
a0c0aa3
feat: 플레이어 결과에 대한 DTO 생성
kokodak Mar 3, 2023
8fe6bf6
feat: 결과에 대한 열거형 도메인 생성
kokodak Mar 3, 2023
e0c208d
feat: 점수에 대한 도메인 생성
kokodak Mar 3, 2023
340f048
feat: 참가자들이 카드를 뽑는 기능 구현
kokodak Mar 3, 2023
53f7d74
feat: 참가자들 중 딜러와 플레이어를 찾는 기능 구현
kokodak Mar 3, 2023
70230b2
feat: 이름으로만 생성될 수 있는 기능 구현
kokodak Mar 3, 2023
0363e8e
feat: 덱에서 카드를 뽑아 참가자들에게 나눠주는 기능 구현
kokodak Mar 3, 2023
bf2e4bf
feat: 블랙잭 게임 실행 기능 구현
kokodak Mar 3, 2023
0ad7a72
fix: 게임 결과 로직 수정
kokodak Mar 3, 2023
6518a49
refactor: TRUMP는 덱이 알도록 수정
woo-chang Mar 3, 2023
24e9e03
refactor: 입력 기능 가독성 위해 메서드명 수정
woo-chang Mar 3, 2023
57cb277
refactor: 축약하지 않은 변수명으로 수정
woo-chang Mar 5, 2023
5f77913
refactor: 딜러가 카드를 추가로 뽑을 수 있는 점수는 딜러가 알도록 수정
woo-chang Mar 5, 2023
f7eb062
refactor: ACE -> Ace 일관성있는 표현으로 수정
woo-chang Mar 5, 2023
90d09d3
refactor: 테스트 목적을 명확히하도록 수정
woo-chang Mar 5, 2023
9bddfc3
refactor: 계층 구조 테스트로 의미를 명확하게 전달하도록 수정
woo-chang Mar 5, 2023
8f294bb
refactor: 자바 컨벤션에 따라 메서드명 수정
woo-chang Mar 5, 2023
ed5db59
refactor: 카드 뽑을 때 검증하도록 수정
woo-chang Mar 5, 2023
0223fca
refactor: NPE 방지 및 변수, 메서드명 수정
woo-chang Mar 5, 2023
7eeedc2
refactor: 참가자에게 딜러 여부에 대한 메시지를 던지도록 수정
woo-chang Mar 5, 2023
1a85ff7
refactor: 거짓 성공을 막기위해 검증부에는 하드코딩 하도록 수정
woo-chang Mar 5, 2023
cf364cf
refactor: TRUMP 생성 시 flatMap 사용 및 덱 생성 시 섞도록 수정
woo-chang Mar 5, 2023
b15c4c2
refactor: 플레이어 이름을 명시하도록 Name -> PlayerName 수정
woo-chang Mar 5, 2023
6506c31
refactor: 참여자 목록 생성 책임을 생성자로 수정
woo-chang Mar 5, 2023
60cd11c
refactor: Deck 생성 책임은 Deck이 가지도록 수정
woo-chang Mar 5, 2023
2458f44
refactor: 블랙잭 게임 컨트롤러 수정
woo-chang Mar 5, 2023
4daf6f8
refactor: 딜러 승패 여부 확인 로직 수정
woo-chang Mar 5, 2023
24f34f1
refactor: 카드 목록에서 사용되지 않는 메서드 제거
woo-chang Mar 6, 2023
be64ca7
refactor: 딜러가 결과 확인하는 로직 수정
woo-chang Mar 6, 2023
601b98d
test: 테스트하지 못했던 코드에 대한 테스트 코드 작성
woo-chang Mar 6, 2023
44bd964
feat: 제네릭 미션 구현
woo-chang Mar 6, 2023
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
95 changes: 95 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,98 @@
## 우아한테크코스 코드리뷰

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

## 블랙잭 개요

- 블랙잭 게임은 딜러와 플레이어가 진행한다.
- 카드의 합이 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다.
- 숫자 계산은 카드의 숫자를 기본으로 하되,
- Ace: 1 or 11
- King, Queen, Jack: 10
- 게임을 시작하면, 딜러와 플레이어는 두 장의 카드를 지급받는다.
- 플레이어는 카드 숫자의 합이 21 미만이라면 카드를 계속 뽑을 수 있다.
- 딜러는 카드 숫자의 합이 16 이하인 경우에만 1장의 카드를 추가로 받아야 한다.
- 딜러는 최대 3장의 카드만 받을 수 있다.
- 게임이 완료되면, 각 플레이어의 승패를 출력한다.

## 블랙잭 규칙

- `승`, `무`, `패`는 플레이어에 결과를 나타낸다.
- 플레이어 점수가 21점 초과일 때
- 딜러 점수 상관없이 `패`
- 플레이어 점수가 21점 이하일 때
- 딜러 점수가 21점 초과일 떄
- 플레이어 `승`
- 딜러 점수가 21점 이하일 때
- 플레이어 점수가 높으면 `승`
- 플레이어 점수가 낮으면 `패`
- 플레어어 점수와 같으면 `무`

## 도메인 다이어그램

```mermaid
graph TD
BlackJackController --> InputView
BlackJackController --> OutputView
BlackJackController --> Participants
BlackJackController --> Deck
InputView --> Parser
Participants --> Participant
Participant --> Player
Participant --> Dealer
Participant --> Cards
Player --> PlayerName
Dealer --> Result
Cards --> Card
Card --> Suit
Card --> Number
```

## 기능 구현 목록

### 참가자

- [x] 카드를 받는다.
- [x] 점수를 확인한다.
- [x] 카드 추가 여부를 결정한다.
- [x] 여러 명일 수 있다.
- [x] 딜러는 포함되어야 한다.
- [x] 중복되는 이름은 가질 수 없다.
- [x] 최대 6명이다.

### 플레이어

- [x] 이름을 가진다.
- [x] 최소 1자, 최대 10자까지 가능하다.
- [x] 중간 공백은 허용한다.
- [x] `딜러`라는 이름은 가질 수 없다.

### 카드

- [x] 문양을 가진다.
- [x] 숫자를 가진다.
- [x] 점수를 계산한다.
- [x] 에이스인지 확인한다.
- [x] 점수 최댓값 초과 여부를 확인한다.
- [x] 점수 최댓값 여부를 확인한다.
- [x] 카드를 추가한다.
- [x] 카드 개수를 확인한다.

### 덱

- [x] 카드 목록을 가진다.
- [x] 카드를 뽑는다.
- [x] 카드가 없으면 뽑을 수 없다.

### 입력

- [x] 플레이어의 이름을 입력한다.
- [x] 앞, 뒤 공백은 제거한다.
- [x] 카드를 받을 여부를 입력한다.

### 출력

- [x] 딜러와 플레이어의 카드 현황을 출력한다.
- [x] 딜러 카드 추가 여부를 출력한다.
- [x] 최종 결과를 출력한다.
- [x] 최종 승패를 출력한다.
16 changes: 16 additions & 0 deletions src/main/java/blackjack/BlackJackApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package blackjack;

import blackjack.controller.BlackJackController;
import blackjack.view.InputView;
import blackjack.view.OutputView;

public class BlackJackApplication {

public static void main(String[] args) {
final InputView inputView = new InputView();
final OutputView outputView = new OutputView();

final BlackJackController blackJackController = new BlackJackController(inputView, outputView);
blackJackController.run();
}
}
142 changes: 142 additions & 0 deletions src/main/java/blackjack/controller/BlackJackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package blackjack.controller;

import blackjack.domain.card.Card;
import blackjack.domain.card.Deck;
import blackjack.domain.participant.Dealer;
import blackjack.domain.participant.Participant;
import blackjack.domain.participant.Participants;
import blackjack.domain.participant.Player;
import blackjack.domain.participant.Result;
import blackjack.view.InputView;
import blackjack.view.OutputView;
import blackjack.view.dto.CardsResponse;
import blackjack.view.dto.DealerResultResponse;
import blackjack.view.dto.DealerStateResponse;
import blackjack.view.dto.ParticipantResponse;
import blackjack.view.dto.PlayerResultResponse;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class BlackJackController {
Copy link
Collaborator

Choose a reason for hiding this comment

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

전체적으로 Controller가 너무 많은 책임을 담당하고 있다고 생각해요~!
조금 더 개선해 볼 수 있을 것 같아요


private static final int INITIAL_DRAW_COUNT = 2;

private final InputView inputView;
private final OutputView outputView;
Comment on lines +26 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

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

View를 필드로 사용하신 이유가 있으실까요?

Copy link
Author

@woo-chang woo-chang Mar 8, 2023

Choose a reason for hiding this comment

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

static 메서드를 사용하지 않고 생성자 주입을 통해 필드로 가지는 이유는 말하는 걸까요 🤔

제가 이해한 게 맞다면 제가 필드로 사용한 이유는 다음과 같습니다.

우선 static으로 사용해도 좋다고 생각합니다. InputView, OutputView 모두 가변적인 필드를 가지고 있지 않고, 사이드 이펙트가 발생할 가능성이 존재하지 않기에 사용해도 좋은 것 같습니다!

하지만 필드로 사용한 이유는 static으로 사용하게 되면, 어디서든 사용할 수 있기에 뷰가 어디든 침범할 수 있게 되고 알게 된다고 생각합니다. 이를 컨트롤러에서만 알고 관리하기 위해서 필드로 주입받아서 사용하였습니다.


public BlackJackController(final InputView inputView, final OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void run() {
final Participants participants = new Participants(new Dealer(), gatherPlayers());
final Deck deck = Deck.createUsingTrump(1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

저만 이 1이 이해가 잘 안될까요?
다른 분들의 의견도 들어봐야 하겠지만, 처음 보는 사람 입장에서는 조금 불명확하다는 느낌이 들어요

Copy link
Author

@woo-chang woo-chang Mar 8, 2023

Choose a reason for hiding this comment

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

다시 한번 읽어보니 무슨 의미를 가지는지 명확하지 않은 것 같습니다 ..

매직 넘버를 사용하여 의미를 명확하게 드러낼 수 있도록 수정해보겠습니다!


dealCards(participants, deck);

drawCard(participants.getPlayers(), deck);
drawCard(participants.getDealer(), deck);

printResult(participants);
}

private List<Player> gatherPlayers() {
final List<String> playerNames = inputView.readPlayerNames();
return playerNames.stream()
.map(Player::new)
.collect(Collectors.toList());
}

private void dealCards(final Participants participants, final Deck deck) {
participants.drawCard(deck, INITIAL_DRAW_COUNT);
Copy link
Collaborator

Choose a reason for hiding this comment

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

INITIAL_DRAW_COUNT 이거를 컨트롤러에서 관리하는 것이 맞을까요?
컨트롤러의 책임이 무엇이라 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

뷰와 도메인 사이에서 중간 매개체 역할을 수행한다고 생각합니다. BlackJackGame을 만들지 않아 해당 역할을 Controller가 수행하도록 했었는데 해당 역할을 BlackJackGame으로 넘겨도 좋은 것 같습니다 👍


final ParticipantResponse dealerResponse = getHiddenDealerResponse(participants.getDealer());
final List<ParticipantResponse> playerResponse = getParticipantResponses(participants.getPlayers());

outputView.printDealCards(dealerResponse, playerResponse, INITIAL_DRAW_COUNT);
}

private ParticipantResponse getHiddenDealerResponse(final Dealer dealer) {
final List<Card> hiddenCards = dealer.getCards().subList(0, INITIAL_DRAW_COUNT - 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 코드는 Dealer의 자율성을 침해하는 것 같아요~!
좀 더 개선해 볼 수 있을 것 같습니다.

Copy link
Author

@woo-chang woo-chang Mar 8, 2023

Choose a reason for hiding this comment

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

그렇네요! 꼼꼼한 리뷰 감사합니다 👾

final CardsResponse cardsResponse = new CardsResponse(-1, getCardInfos(hiddenCards));
Copy link
Collaborator

Choose a reason for hiding this comment

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

이곳의 -1 역시 처음 보는 사람 입장에서 불명확한 것 같아요
-1이 의미하는 것이 무엇이죠?

Copy link
Author

Choose a reason for hiding this comment

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

이 부분도 위와 동일하게 수정해 보겠습니다!

return new ParticipantResponse(dealer.getName(), cardsResponse);
}

private List<String> getCardInfos(final List<Card> cards) {
return cards.stream()
.map(card -> card.getNumberName() + card.getSuitName())
.collect(Collectors.toList());
}

private List<ParticipantResponse> getParticipantResponses(final List<? extends Participant> participants) {
return participants.stream()
.map(this::getParticipantResponse)
.collect(Collectors.toList());
}

private ParticipantResponse getParticipantResponse(final Participant participant) {
final CardsResponse cardsResponse = new CardsResponse(
participant.getScore(), getCardInfos(participant.getCards())
);
return new ParticipantResponse(participant.getName(), cardsResponse);
}

private void drawCard(final List<Player> players, final Deck deck) {
for (final Player player : players) {
drawCard(player, deck);
}
}

private void drawCard(final Player player, final Deck deck) {
while (player.isDrawable() && inputView.readMoreDraw(player.getName())) {
player.drawCard(deck.draw());
outputView.printHandedCardsWithoutScore(getParticipantResponse(player));
}
}

private void drawCard(final Dealer dealer, final Deck deck) {
if (dealer.isDrawable()) {
dealer.drawCard(deck.draw());
outputView.printDealerDrawn(new DealerStateResponse(true, dealer.getMaximumDrawableScore()));
}
}

private void printResult(final Participants participants) {
final List<ParticipantResponse> participantResponses = getParticipantResponses(participants.getParticipants());
outputView.printHandedCardsWithScore(participantResponses);

final Dealer dealer = participants.getDealer();
final List<Player> players = participants.getPlayers();

final List<PlayerResultResponse> playerResult = getPlayerResults(dealer, players);
final DealerResultResponse dealerResult = getDealerResult(dealer, playerResult);

outputView.printFinalResult(dealerResult, playerResult);
}

private List<PlayerResultResponse> getPlayerResults(final Dealer dealer, final List<Player> players) {
return players.stream()
.map(player -> new PlayerResultResponse(player.getName(), dealer.showResult(player.getScore())))
.collect(Collectors.toList());
}

private DealerResultResponse getDealerResult(final Dealer dealer, final List<PlayerResultResponse> playerResults) {
final Map<Result, Integer> dealerResult = initResult();
for (final PlayerResultResponse playerResult : playerResults) {
final Result result = playerResult.getResult().reverse();
dealerResult.put(result, dealerResult.get(result) + 1);
}
return new DealerResultResponse(dealer.getName(), dealerResult);
}

private Map<Result, Integer> initResult() {
final Map<Result, Integer> initResult = new EnumMap<>(Result.class);
for (final Result result : Result.values()) {
initResult.put(result, 0);
}
return initResult;
}
}
28 changes: 28 additions & 0 deletions src/main/java/blackjack/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package blackjack.domain.card;

public class Card {

private final Number number;
private final Suit suit;

public Card(final Number number, final Suit suit) {
this.number = number;
this.suit = suit;
}

public boolean isAce() {
return number == Number.ACE;
}

public int getScore() {
return number.getScore();
}

public String getNumberName() {
return number.getName();
}

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

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

public class Cards {

private static final int ACE_BONUS = 10;
private static final int MAXIMUM_UPDATABLE_SCORE = 21;

private final List<Card> cards;

public Cards() {
this(new ArrayList<>());
}

public Cards(final List<Card> cards) {
this.cards = new ArrayList<>(cards);
}

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

public int calculateTotalScore() {
final int score = getTotalScore();

if (isExistAce() && isScoreUpdatable(score)) {
return score + ACE_BONUS;
}

return score;
}

private int getTotalScore() {
return cards.stream()
.mapToInt(Card::getScore)
.sum();
}

private boolean isExistAce() {
return cards.stream()
.anyMatch(Card::isAce);
}

private boolean isScoreUpdatable(final int score) {
return score + ACE_BONUS <= MAXIMUM_UPDATABLE_SCORE;
}

public int count() {
return cards.size();
}

public List<Card> getCards() {
return Collections.unmodifiableList(cards);
}
}
45 changes: 45 additions & 0 deletions src/main/java/blackjack/domain/card/Deck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package blackjack.domain.card;

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

public class Deck {

private static final Stack<Card> TRUMP;

static {
final Stack<Card> pack = new Stack<>();
final List<Card> cards = Arrays.stream(Suit.values())
.flatMap(suit -> Arrays.stream(Number.values())
.map(number -> new Card(number, suit))
)
.collect(Collectors.toList());
pack.addAll(cards);
TRUMP = pack;
}

private final Stack<Card> cards;

public Deck(final Stack<Card> cards) {
Collections.shuffle(cards);
this.cards = cards;
}

public static Deck createUsingTrump(final int count) {
final Stack<Card> pack = new Stack<>();
for (int i = 0; i < count; i++) {
pack.addAll(TRUMP);
}
return new Deck(pack);
}

public Card draw() {
if (cards.empty()) {
Copy link
Member

Choose a reason for hiding this comment

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

이런 예외가 생길 거라 생각을 못해봤는데 좋은 거 같네요! 👍

throw new IllegalStateException("덱에 더 이상의 카드가 없습니다.");
}
return cards.pop();
}
}
Loading