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 60 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
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,84 @@
## 우아한테크코스 코드리뷰

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

## 블랙잭 개요

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

## 도메인 다이어그램

```mermaid
graph TD
BlackJackController --> InputView
BlackJackController --> OutputView
BlackJackController --> Participants
BlackJackController --> Deck
Participants --> Participant
Participant --> Player
Participant --> Dealer
Participant --> Cards
Player --> Name
DeckFactory --> Deck
Cards --> Card
Card --> Suit
Card --> Number
```

## 기능 구현 목록

### 참가자

- [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] 최종 결과를 출력한다.
- [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();
}
}
118 changes: 118 additions & 0 deletions src/main/java/blackjack/controller/BlackJackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package blackjack.controller;

import blackjack.domain.card.Deck;
import blackjack.domain.card.DeckFactory;
import blackjack.domain.participant.Dealer;
import blackjack.domain.participant.Participant;
import blackjack.domain.participant.Participants;
import blackjack.domain.participant.Player;
import blackjack.domain.result.Score;
import blackjack.view.InputView;
import blackjack.view.OutputView;
import blackjack.view.dto.DealerStateResponse;
import blackjack.view.dto.ParticipantResponse;
import blackjack.view.dto.PlayerResultResponse;
import java.util.ArrayList;
import java.util.List;
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 INIT_DRAW_COUNT = 2;
private static final int DEALER_LIMIT = 16;
Copy link
Member

Choose a reason for hiding this comment

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

이 부분은 도메인이 알고 있어야 하는 룰이지 않을까요?

Copy link
Author

Choose a reason for hiding this comment

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

동의합니다 ,,, 딜러가 알아야 할 정보인 것 같습니다 '^'


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 = gatherParticipants();
final Deck deck = DeckFactory.createWithCount(Deck.TRUMP, 1);

deck.shuffle();
dealCards(participants, deck);

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

printResult(participants);
}

private Participants gatherParticipants() {
final List<Participant> participants = new ArrayList<>();

participants.add(new Dealer());
Copy link
Member

Choose a reason for hiding this comment

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

Dealer는 무조건 있어야 게임이 되는 룰이니 participants 클래스 내에서 생성하는 건 어떤가요?

Copy link
Author

Choose a reason for hiding this comment

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

너무 좋은 것 같습니다 👍

생성자에서 생성하면 클래스의 역할이 많아지게 되는 것 같아서, 딜러와 플레이어를 매개변수로 가지는 생성자를 사용하는건 어떻게 생각하시나요?

Copy link
Member

Choose a reason for hiding this comment

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

아주 좋은데요?

participants.addAll(createPlayers());

return new Participants(participants);
}

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

private void dealCards(Participants participants, Deck deck) {
participants.drawCard(deck, INIT_DRAW_COUNT);

final ParticipantResponse dealerResponse = ParticipantResponse.from(participants.getDealer());
final List<ParticipantResponse> playerResponse = getPlayerResponse(participants.getPlayers());

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

private List<ParticipantResponse> getPlayerResponse(final List<Player> players) {
return players.stream()
.map(ParticipantResponse::from)
.collect(Collectors.toList());
}

private void cardDraw(final List<Player> players, final Deck deck) {
Copy link
Member

Choose a reason for hiding this comment

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

메소드는 동사로 시작하는게 좋지 않을까요? 그리고 같은 메서드명이 3개나 있네요

Copy link
Author

@woo-chang woo-chang Mar 5, 2023

Choose a reason for hiding this comment

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

급하게 작성한 코드는 다 티가 나는 군요 😂

같은 메서드명은 오버로딩을 사용해서 도메인별로 동작을 보여주려고 했습니다!

for (final Player player : players) {
cardDraw(player, deck);
}
}

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

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

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

final Dealer dealer = participants.getDealer();

final List<PlayerResultResponse> playerResultResponses = getPlayerResultResponses(
dealer.getScore(), participants.getPlayers());
outputView.printResult(dealer.getName(), playerResultResponses);
}

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

public List<PlayerResultResponse> getPlayerResultResponses(final Score dealerScore, final List<Player> players) {
return players.stream()
.map(player -> new PlayerResultResponse(player.getName(), player.getWinningStatus(dealerScore)))
.collect(Collectors.toList());
}
}
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();
}
}
66 changes: 66 additions & 0 deletions src/main/java/blackjack/domain/card/Cards.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package blackjack.domain.card;

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

public class Cards {

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

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_SCORE;
}

public boolean isTotalScoreOver() {
return calculateTotalScore() > MAXIMUM_SCORE;
}

public boolean isMaximumScore() {
return calculateTotalScore() == MAXIMUM_SCORE;
}

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

public List<Card> getCards() {
return Collections.unmodifiableList(cards);
}
}
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.Collections;
import java.util.Stack;

public class Deck {

public static final Stack<Card> TRUMP;

static {
final Stack<Card> pack = new Stack<>();
for (final Suit suit : Suit.values()) {
for (final Number number : Number.values()) {
pack.add(new Card(number, suit));
}
}
TRUMP = pack;
Copy link
Member

Choose a reason for hiding this comment

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

1depth가 요구사항이라 stream의 flatmap이나 foreach를 사용하는 방법으로 구현해볼 수 도 있을 것 같아! 😄

Copy link
Member

Choose a reason for hiding this comment

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

저도 같은 생각입니다

Copy link
Author

Choose a reason for hiding this comment

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

좋은 의견 너무너무 감사합니다 👍👍

}

private final Stack<Card> cards;

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

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();
}

public void shuffle() {
Collections.shuffle(cards);
}
}
16 changes: 16 additions & 0 deletions src/main/java/blackjack/domain/card/DeckFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package blackjack.domain.card;

import java.util.Stack;

public class DeckFactory {

public static Deck createWithCount(final Stack<Card> pack, int count) {
final Stack<Card> cards = new Stack<>();

while (count-- > 0) {
cards.addAll(pack);
}

return new Deck(cards);
}
}
Loading