Skip to content
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6a4844d
docs: 기능 요구 사항 작성
3Juhwan Mar 5, 2024
34f668e
feat: 플레이어에게 카드를 분배하는 기능 구현
3Juhwan Mar 5, 2024
8163280
feat: 각 플레이어에게 2장씩 카드를 분배하는 기능 구현
3Juhwan Mar 5, 2024
4d55c54
feat: 가진 패의 합계를 구하는 기능 구현
3Juhwan Mar 6, 2024
b180f62
feat: 플레이어가 죽었는지 여부를 반환하는 기능 구현
3Juhwan Mar 6, 2024
5135ec3
feat: 카드 덱에서 무작위 카드를 뽑는 기능 구현
3Juhwan Mar 6, 2024
49b5168
feat: 딜러와 단일 플레이어의 승패를 판단하는 기능 구현
3Juhwan Mar 6, 2024
280ef53
feat: 딜러와 여러 플레이어의 승패를 판단하는 기능 구현
3Juhwan Mar 6, 2024
14abcca
refactor: 플레이어에 픽스처 적용
3Juhwan Mar 6, 2024
987e856
refactor: Dealer 클래스 분리 및 픽스쳐 수정
3Juhwan Mar 6, 2024
12bbf04
refactor: 픽스쳐 내 중복 제거
3Juhwan Mar 6, 2024
240e408
docs: 게임 승패 결정 로직 작성
3Juhwan Mar 6, 2024
933b422
feat: 승패 판단 기능 구현
3Juhwan Mar 7, 2024
b42c767
chore: 불필요한 클래스 삭제
3Juhwan Mar 7, 2024
dc3387f
docs: 뷰에 대한 기능 요구사항 추가
3Juhwan Mar 7, 2024
043a26f
feat: 딜러의 승패 횟수 계산 기능 구현
3Juhwan Mar 7, 2024
2ad912d
feat: InputView 구현
3Juhwan Mar 7, 2024
f252caa
feat: Player 이름 길이를 검증하는 기능 구현
3Juhwan Mar 7, 2024
cdb2713
feat: 딜러와 플레이어 전원에게 초기에 분배한 카드 출력하는 기능 구현
3Juhwan Mar 7, 2024
710ec42
feat: 플레이어가 보유한 모든 카드를 출력하는 기능 구현
3Juhwan Mar 7, 2024
972dee8
feat: 딜러가 추가 카드를 발급 받았는지 여부 출력하는 기능 구현
3Juhwan Mar 7, 2024
4371415
feat: 보유한 모든 카드의 합을 출력하는 기능 구현
3Juhwan Mar 7, 2024
8bb8fd2
feat: 최종 승패를 출력하는 기능 구현
3Juhwan Mar 7, 2024
35de854
feat: controller에서 사용자에게 처음 카드 2장씩 부여하는 기능 구현
3Juhwan Mar 7, 2024
2b06dd1
fix: 플레이어 카드를 출력하는 구문 수정
3Juhwan Mar 8, 2024
0f5681d
feat: Controller 전체 기능 구현
3Juhwan Mar 8, 2024
5d02370
refactor: 딜러, 플레이어 생성자에 매개변수 최소화
3Juhwan Mar 8, 2024
6e32df2
refactor: 딜러 클래스의 메서드명 수정
3Juhwan Mar 8, 2024
4940a10
refactor: 도메인 용어로 변경 (draw -> hit)
3Juhwan Mar 8, 2024
cbe46ac
refactor: 패키지 분리
3Juhwan Mar 8, 2024
0972560
refactor: view, controller 리팩터링
3Juhwan Mar 8, 2024
ac428c6
refactor: 도메인 로직 리팩터링
3Juhwan Mar 8, 2024
8ba77c3
feat: 에이스 관련 처리 기능 추가
3Juhwan Mar 8, 2024
c29212d
docs: 구현한 사항 체크
3Juhwan Mar 8, 2024
c5555e5
chore: 잘못된 변수명 수정
3Juhwan Mar 9, 2024
a0e2241
fix: 카드 계산 로직 변경으로 테스트 결과 수정
3Juhwan Mar 11, 2024
c62821a
refactor: Deck 리팩터링
3Juhwan Mar 11, 2024
78e94fd
test: Deck에 52장의 카드가 있는지 검증하는 테스트
3Juhwan Mar 11, 2024
d1907c8
refactor: Hand 클래스 수정
3Juhwan Mar 11, 2024
e23c9f5
test: Hand 클래스 테스트
3Juhwan Mar 11, 2024
944578d
refactor: 접근 제어자 수정
3Juhwan Mar 11, 2024
c30e9e4
test: DealerTest 추가
3Juhwan Mar 11, 2024
6de9287
refactor: 메서드 분리
3Juhwan Mar 11, 2024
a4df5f4
refactor: Judge의 역할을 Dealer로 옮김
3Juhwan Mar 11, 2024
1b1d5a0
refactor: 게임 로직 설정 정보 이동
3Juhwan Mar 11, 2024
c205252
refactor: 출력 메시지를 Outview로 위임
3Juhwan Mar 11, 2024
731dea0
refactor: CardGameResult를 record로 변경
3Juhwan Mar 11, 2024
adb40af
refactor: OutputView에서 메시지를 생성하는 로직을 MessageResolver로 분리
3Juhwan Mar 11, 2024
1b67385
style: 불필요한 공백 제거
3Juhwan Mar 11, 2024
6770804
refactor: 덱 생성 로직에 스트림 적용
3Juhwan Mar 11, 2024
f682149
refactor: 불필요한 위임 메서드 삭제
3Juhwan Mar 13, 2024
0279a2e
test: 게임 결과에서 플레이어가 순서대로 반환되는지 테스트
3Juhwan Mar 13, 2024
e983f17
refactor: CardGame의 역할을 Deck으로 이동
3Juhwan Mar 13, 2024
c2aafef
refactor: 승패 결과에 대한 결과 문자열을 뷰로 이동
3Juhwan Mar 13, 2024
07d47c6
refactor: BUST 조건에 대한 다른 클래스의 의존성 제거
3Juhwan Mar 13, 2024
3670388
refactor: 게임 결과를 생성하는 로직을 CardGameResult로 이동
3Juhwan Mar 13, 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
68 changes: 64 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,67 @@
# java-blackjack
# 블랙잭

블랙잭 미션 저장소

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

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

### 플레이어

- [X] 플레이어는 이름을 갖는다
- [X] 플레이어는 카드들을 갖는다
- [X] 플레이어는 카드를 저장한다
- [X] 죽었는지 안 죽었는지 여부를 반환한다 (21을 초과하는지)


### 카드

- [X] 문자와 모양을 상태로 갖는다


### Hand(카드들)

- [X] 여러개의 카드를 갖는다
- [X] 카드의 총합 계산한다
- [X] 카드 추가한다


### 카드 게임

- [X] 각 플레이어에게 카드를 2장씩 지급한다
- [X] 플레이어마다 추가 지급한다


### 덱

- [X] 모든 카드를 1장씩 갖고 있다.
- [X] 랜덤으로 카드를 뽑는다.


### 게임 승패 결정

- [X] 딜러와 모든 플레이어의 승패 여부를 결정한다.
- [X] 딜러와 플레이어 둘다 21을 초과할 경우, 플레이어가 패배한다.
- [X] 카드 합계가 딜러는 21 이하, 플레이어는 21 초과인 경우, 플레이어가 패배한다.
- [X] 카드 합계가 딜러는 21 초과, 플레이어는 21 이하인 경우, 플레이어가 승리한다.
- [X] 카드 합계가 딜러와 플레이어 모두 21 이하인 경우, 숫자가 큰 사람이 승리한다.
- [X] 카드 합계가 딜러와 플레이어 모두 21 이하이고 동일한 경우, 무승부다.


### 인풋 뷰

- [X] 참가자의 이름을 입력받는다.
- [X] 이름은 쉼표 기준으로 분리한다.
- [X] 카드 추가 여부를 입력받는다.
- [X] y 또는 n이 아닌 경우, 예외를 발생한다.
- [X] 사용자 카드 합이 21을 초과하면, 카드 추가 여부를 묻지 않는다.
- [X] 플레이어가 n을 입력할 때까지 카드 추가 여부를 묻는다.


### 아웃풋 뷰

- [X] 딜러와 플레이어 전원에게 초기에 분배한 카드 출력한다.
- [X] 딜러의 카드는 1장만 공개한다.
- [X] 플레이어가 보유한 모든 카드를 출력한다.
- [X] 딜러가 추가 카드를 발급 받았는지 여부 출력한다.
- [X] 보유한 모든 카드의 합을 출력한다.
- [X] 최종 승패를 출력한다.
61 changes: 61 additions & 0 deletions src/main/java/blackjack/BlackjackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package blackjack;

import blackjack.domain.cardgame.CardGame;
import blackjack.domain.cardgame.CardGameJudge;
import blackjack.domain.cardgame.CardGameResult;
import blackjack.domain.player.Dealer;
import blackjack.domain.player.Player;
import blackjack.view.InputView;
import blackjack.view.OutputView;

import java.util.List;
Copy link
Member

Choose a reason for hiding this comment

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

구글 자바 컨벤션에서는 non static import 사이에 엔터가 없는 게 규칙이더라구요~~~


public class BlackjackController {
public static void main(String[] args) {
final CardGame cardGame = new CardGame();
final List<String> names = InputView.askPlayerNames();
final Dealer dealer = new Dealer();
final List<Player> players = names.stream()
.map(Player::new)
.toList();

cardGame.initializeHand(dealer, players);
OutputView.printInitialHandOfEachPlayer(dealer, players);

for (final Player player : players) {
givePlayerMoreCardsIfWanted(cardGame, player);
}
giveDealerMoreCardsIfNeeded(cardGame, dealer);

printHandStatusOfEachPlayer(dealer, players);
printCardGameResult(dealer, players);
}

private static void givePlayerMoreCardsIfWanted(final CardGame cardGame, final Player player) {
final String playerName = player.getName();
while (player.isAlive() && InputView.askForMoreCard(playerName)) {
cardGame.giveCard(player);
OutputView.printPlayerCard(player);
}
}

private static void giveDealerMoreCardsIfNeeded(final CardGame cardGame, final Dealer dealer) {
while (dealer.isMoreCardNeeded()) {
cardGame.giveCard(dealer);
OutputView.printDealerHitMessage(dealer);
}
}

private static void printHandStatusOfEachPlayer(final Dealer dealer, final List<Player> players) {
OutputView.printPlayerCardWithScore(dealer);
for (final Player player : players) {
OutputView.printPlayerCardWithScore(player);
}
}

private static void printCardGameResult(final Dealer dealer, final List<Player> players) {
final CardGameJudge cardGameJudge = new CardGameJudge();
final CardGameResult cardGameResult = cardGameJudge.judge(dealer, players);
OutputView.printResult(cardGameResult);
}
}
23 changes: 23 additions & 0 deletions src/main/java/blackjack/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package blackjack.domain.card;

public class Card {
private final CardNumber cardNumber;
private final CardShape cardShape;

public Card(final CardNumber cardNumber, final CardShape cardShape) {
this.cardNumber = cardNumber;
this.cardShape = cardShape;
}

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

public int getNumber() {
return cardNumber.getValue();
}

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

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

private final int value;

CardNumber(final int value) {
this.value = value;
}

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

public enum CardShape {
SPADE("스페이드"),
HEART("하트"),
DIAMOND("다이아몬드"),
CLOVER("클로버");
Copy link
Member

Choose a reason for hiding this comment

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

출력 형식이 하트에서 HEART로 변경되면 도메인이 함께 바뀌어야 하네요.
이 부분은 View의 역할 아닐까요?
CardNumber과 WinningStatus도 마찬가지 의견입니다!


private final String name;

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

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

import blackjack.domain.card.Card;
import blackjack.domain.card.CardNumber;
import blackjack.domain.card.CardShape;

import java.util.Collections;
import java.util.Stack;

public class CardDeck {
private final Stack<Card> deck;
Copy link
Member

Choose a reason for hiding this comment

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

많은 자료구조 중에 Stack을 선택하신 이유가 궁금합니다!!


public CardDeck() {
final Stack<Card> deck = new Stack<>();

for (final CardNumber cardNumber : CardNumber.values()) {
for (final CardShape cardShape : CardShape.values()) {
deck.push(new Card(cardNumber, cardShape));
}
Copy link
Member

Choose a reason for hiding this comment

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

indent를 1까지만 허용한다는 요구 사항이 있기도 하니까 메서드를 분리해보는 건 어떨까요?
아니면 Stream을 사용하는 것도 방법일 것 같습니다. 😄

}

Collections.shuffle(deck);

this.deck = deck;
}

public Card draw() {
return deck.pop();
}
}
31 changes: 31 additions & 0 deletions src/main/java/blackjack/domain/cardgame/CardGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package blackjack.domain.cardgame;

import blackjack.domain.player.Dealer;
import blackjack.domain.player.Player;

import java.util.List;

public class CardGame {
private final CardDeck cardDeck;

public CardGame() {
this.cardDeck = new CardDeck();
}

public void giveCard(final Player player) {
player.addCard(cardDeck.draw());
}

public void initializeHand(final Dealer dealer, final List<Player> players) {
giveCard(dealer);
giveCard(dealer);
giveTwoCardsEachPlayer(players);
}
Copy link
Member

Choose a reason for hiding this comment

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

딜러에게 두장을 주는 부분은 그대로 두고 플레이어들에게 두장 주는 부분은 메서드로 따로 뺀 이유가 있나요?
그리고 initializeHand라는 메서드명으로는 두 장씩 준다는 것을 모를 수 있을 것 같은데 어떻게 생각하시나요?!


private void giveTwoCardsEachPlayer(final List<Player> players) {
Copy link
Member

@seokmyungham seokmyungham Mar 10, 2024

Choose a reason for hiding this comment

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

파라미터로 넘긴 값을 변경하지 않도록 수정해야할 것 같은데 망쵸는 어떻게 생각하시나요??

현재 값의 변경 책임이 참조로 전달된 파라미터 객체 외부에 있다는 생각이 듭니다.
이러면 final 키워드를 사용하는 의미도 퇴색된다고 생각하고
디버깅도 어려울 뿐만 아니라 유지보수 하기 힘들어질 것이라 생각해요.

Copy link
Member

Choose a reason for hiding this comment

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

가장 단순하게 제 생각에는 CardGameDealer를 갖고 있도록 변경하거나 아니면 파라미터 객체에 직접 책임을 지도록 하는 방법이 있을 것 같아요.

players는 일급 컬렉션이 따로 존재하지 않으니 메서드 지역변수로 생성해서 반환하는 방법이 존재할 것 같습니다.

for (final Player player : players) {
giveCard(player);
giveCard(player);
}
}
}
45 changes: 45 additions & 0 deletions src/main/java/blackjack/domain/cardgame/CardGameJudge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package blackjack.domain.cardgame;

import blackjack.domain.player.Player;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class CardGameJudge {
public static final int BUST_THRESHOLD = 21;

public CardGameResult judge(final Player dealer, final List<Player> players) {
Map<Player, WinningStatus> result = new LinkedHashMap<>();

for (final Player player : players) {
WinningStatus winningStatus = judge(dealer, player);
result.put(player, winningStatus);
}

return new CardGameResult(result);
}

private WinningStatus judge(final Player dealer, final Player player) {
int dealerScore = dealer.getScore();
int playerScore = player.getScore();

return doesPlayerWin(dealerScore, playerScore);
}

private WinningStatus doesPlayerWin(final int dealerScore, final int playerScore) {
if (playerScore > BUST_THRESHOLD) {
return WinningStatus.LOSE;
}
if (dealerScore > BUST_THRESHOLD) {
return WinningStatus.WIN;
}
if (dealerScore == playerScore) {
return WinningStatus.PUSH;
}
if (dealerScore < playerScore) {
return WinningStatus.WIN;
}
return WinningStatus.LOSE;
}
Copy link
Member

Choose a reason for hiding this comment

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

메서드가 너무 길어요.... 👀
제 승패 로직과 거의 비슷한 것 같은데 저도 이 부분을 줄이느라 고민을 많이 했답니다...!
어렵더라도 요구사항은 지켜야 한다고 생각해요!

+다시 보니 비슷한게 아니라 똑같군요
제 코드의 feat(MatchResult): 게임 결과 판단 커밋입니다 ㅋㅋㅋㅋㅋ

}
35 changes: 35 additions & 0 deletions src/main/java/blackjack/domain/cardgame/CardGameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package blackjack.domain.cardgame;

import blackjack.domain.player.Player;

import java.util.Collections;
import java.util.Map;

import static blackjack.domain.cardgame.WinningStatus.LOSE;
import static blackjack.domain.cardgame.WinningStatus.WIN;

public class CardGameResult {
private final Map<Player, WinningStatus> totalResult;

public CardGameResult(Map<Player, WinningStatus> totalResult) {
this.totalResult = totalResult;
}

public Map<Player, WinningStatus> getTotalResult() {
return Collections.unmodifiableMap(totalResult);
}

public int getDealerWinCount() {
return (int) totalResult.values()
.stream()
.filter(playerWinningStatus -> playerWinningStatus.equals(LOSE))
.count();
}

public int getDealerLoseCount() {
return (int) totalResult.values()
.stream()
.filter(status -> status.equals(WIN))
Copy link
Member

Choose a reason for hiding this comment

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

위에 playerWinningStatus 너무 좋은데! 여기도 수정하면 좋겠어요!

+딜러의 무승부 결과는 출력하지 않나요?

.count();
}
}
17 changes: 17 additions & 0 deletions src/main/java/blackjack/domain/cardgame/WinningStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package blackjack.domain.cardgame;

public enum WinningStatus {
WIN("승"),
PUSH("무"),
LOSE("패");

private final String value;

WinningStatus(final String value) {
this.value = value;
}

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

import blackjack.domain.card.Card;

public class Dealer extends Player {
private static final String NAME = "딜러";
Copy link
Member

Choose a reason for hiding this comment

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

망쵸~ 저도 이렇게 Dealer 안에 상수로 "딜러"라고 정의하도록 구현했었어요.
그런데 이와 관련해서 리뷰어분께 리뷰를 받게 되었습니다.

리뷰 내용은

  • 딜러에게 굳이 이름이 필요할까요?
  • 입력과 관련없는 "딜러" 라는 딜러의 이름을 표현하는 책임은, 딜러가 맞을까요?

망쵸는 이와 관련해서 어떻게 생각하시나요~? 😊

private static final int HIT_THRESHOLD = 16;

public Dealer() {
super(NAME);
}

public boolean isMoreCardNeeded() {
return this.hand.getSum() <= HIT_THRESHOLD;
}

public Card getFirstCard() {
try {
return hand.getAllCards().get(0);
} catch (IndexOutOfBoundsException e) {
throw new RuntimeException("[ERROR] 딜러가 카드를 갖고 있지 않습니다.");
}
}
}
Loading