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차 구현입니다. #5

Open
wants to merge 59 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
1680d8e
docs(README): 기능 요구사항 명세 추가
hw0603 Mar 5, 2024
0605ed3
test(deckTest): 덱에서 카드 한 장을 뽑는 기능의 테스트 코드 추가
hw0603 Mar 5, 2024
c71ef54
feat(deck, card): 덱에서 카드한장을 뽑는 기능 구현
hw0603 Mar 5, 2024
98dc012
test(playerCardsTest): 플레이어의 패에 카드한장을 추가하는 테스트 작성
hw0603 Mar 5, 2024
ad74add
feat(playerCards): 플레이어의 패에 카드를 추가하는 기능 구현
hw0603 Mar 5, 2024
48f1535
feat(playerCards): 숫자 카드, J/Q/K 의 점수 계산 기능 구현
hw0603 Mar 5, 2024
57acebe
test(playerCardsTest): 플레이어의 패에 ACE가 포함된 경우 테스트 코드 추가
hw0603 Mar 5, 2024
3c535ec
feat(playerCards): 플레이어 패에 ACE가 포함된 경우 점수 계산 기능 구현
hw0603 Mar 5, 2024
5c0e1e7
refactor(playerCardsTest): ParameterizedTest 활용
hw0603 Mar 5, 2024
64e668c
test(playerTest): 덱으로 부터 카드 한장을 받아오는 테스트 추가
hw0603 Mar 5, 2024
85a9f44
feat(player): 덱으로 부터 카드 한장을 받아오는 기능 구현
hw0603 Mar 5, 2024
70c941a
refactor(playerCards): 빈 패를 생성하는 정적 팩토리 메서드 추가
hw0603 Mar 5, 2024
bdb3597
refactor(deck): LinkedList를 사용하도록 변경
hw0603 Mar 5, 2024
27a0495
test(playerTest): 플레이어의 점수를 계산하는 테스트 추가
hw0603 Mar 5, 2024
084a166
feat(player): 플레이어의 점수 계산 기능 구현
hw0603 Mar 5, 2024
5831bb0
test(playerTest): 플레이어의 버스트 여부를 판단하는 테스트 코드 추가
hw0603 Mar 5, 2024
5a31b68
feat(player): 플레이어의 버스트 여부를 판단하는 로직 구현
hw0603 Mar 5, 2024
157a259
test(dealerTest): 딜러가 카드를 받는 테스트 추가
hw0603 Mar 5, 2024
67b00f9
feat(dealer): 딜러가 카드를 받는 가능 추가
hw0603 Mar 5, 2024
f8ef8d0
feat(deck): 셔플된 댁 생성 하는 팩토리 메서드 추가
hw0603 Mar 5, 2024
7c3952e
refactor(player): 플레이어가 이름을 가지도록 수정
hw0603 Mar 6, 2024
b30c4ac
test(dealerTest): 딜러가 카드 한 장을 뽑는 기능 테스트 추가
hw0603 Mar 6, 2024
1944682
feat(dealer): 딜러가 카드 한 장을 뽑는 기능 구현
hw0603 Mar 6, 2024
7053671
feat(blackJackController): 초기 게임 세팅 구현
hw0603 Mar 6, 2024
2901c95
feat(blackJackController): 추가 카드 분배 구현
hw0603 Mar 6, 2024
5421c53
test(gameResultBoardTest): 게임의승리/패배/무승부 여부를 계산하는 테스트 추가
hw0603 Mar 7, 2024
53eedb5
feat(gameResultBoard): 게임 결과를 계산하는 기능 구현
hw0603 Mar 7, 2024
74a5116
test(gameResultBoardTest): 딜러의 전적을 계산하는 테스트 코드 추가
hw0603 Mar 7, 2024
308d0fa
feat(gameResultBoard): 딜러의 결과를 계산하는 기능 구현
hw0603 Mar 7, 2024
cd02e7b
feat(blackJackController): 게임 결과 출력 구현
hw0603 Mar 7, 2024
04573f1
test(playersTest): 중복된 이름이 있으면 예외가 발생하는 테스트 추가
hw0603 Mar 7, 2024
215fd5c
feat(players): 중복된 플레이어 이름 검사
hw0603 Mar 7, 2024
4aa6327
refactor(blackJackController): 여러 사용자들을 하나의 객체로 관리하도록 변경
hw0603 Mar 7, 2024
563c440
test(scoreTest): 음수가 점수로 사용되는 경우의 테스트 코드 추가
hw0603 Mar 7, 2024
683b13c
feat(score): 점수 범위 검증 로직 추가
hw0603 Mar 7, 2024
bca4e22
test(scoreTest): 점수에 따른 버스트 여부 테스트 추가
hw0603 Mar 7, 2024
c79d128
feat(score): 점수가 버스트 인지 여부를 판단하는 기능 구현
hw0603 Mar 7, 2024
cdbf41a
test(scoreTest): 점수 비교 로직 테스트 추가
hw0603 Mar 7, 2024
7f9a82f
feat(score): 점수 비교 로직 구현
hw0603 Mar 7, 2024
fcc9476
refactor(score): 점수를 관리하기 위한 원시값 포장
hw0603 Mar 7, 2024
0a4ed67
refactor(score, playerCards): Ace의 점수를 계산하는 로직의 위치 변경
hw0603 Mar 7, 2024
e40103e
refactor(dealer): 불필요 메서드 삭제
hw0603 Mar 7, 2024
33c10f3
test(deckTest): 중복되는 카드가 있을 경우 테스트 추가
hw0603 Mar 7, 2024
e4bb004
feat(deck): 카드의 중복 여부 검사 구현
hw0603 Mar 7, 2024
62bb0cc
refactor(deck): 테스트 용으로만 사용되던 정적 팩토리 메서드 삭제
hw0603 Mar 7, 2024
660aa6d
refactor(deck): 셔플된 덱 생성 로직 개선
hw0603 Mar 7, 2024
8cc3a8f
refactor(deck): List가 아닌 Queue를 사용하도록 변경
hw0603 Mar 7, 2024
2dc6c4c
refactor(blackJackController): 컨트롤러 로직의 메서드 추출
hw0603 Mar 8, 2024
26992e2
refactor(blackJackController, inputView): 사용자가 카드를 추가로 받을지 입력 로직 메서드 분리
hw0603 Mar 8, 2024
791504f
refactor(playerResultDto): Score 객체 대신 원시값을 가지도록 변경
hw0603 Mar 8, 2024
cd2ce8c
refactor(shapeDescription, valueDescription): 카드에 대한 설명을 뷰에서 관리 하도록 변경
hw0603 Mar 8, 2024
8056a27
test(gameResultTest): 점수에 따른 플레이어의 게임 결과를 계산하는 테스트 추가
hw0603 Mar 8, 2024
25a210f
refactor(gameResult): 플레이어의 게임 결과를 계산하는 로직 위치 변경
hw0603 Mar 8, 2024
84290d6
fix(/view/description): enum 에서 값을 찾을 수 없는 경우 예외메시지 추가
hw0603 Mar 8, 2024
c4ebb1f
test(playerTest): 플레이어의 이름에 대한 검증 테스트 추가
hw0603 Mar 8, 2024
997c9d8
refactor(playerName): 플레이어의 이름 포장
hw0603 Mar 8, 2024
c2b374c
fix(score): 매직넘버 상수화
hw0603 Mar 8, 2024
ecf3bc8
refactor(playerCards): 플레이어가 가지고 있는 카드의 개수를 반환할 수 있도록 변경
hw0603 Mar 8, 2024
edb1738
refactor(dealer): 미사용 메서드 삭제
hw0603 Mar 8, 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
4 changes: 4 additions & 0 deletions .gitmessage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@



Co-authored-by: haiseong <[email protected]>
32 changes: 32 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## 기능 요구사항 정리

### 카드
- [x] 카드는 4가지(스페이드, 클로버, 하트, 다이아몬드) 문양중 하나를 가진다.
- [x] 카드는 2~9의 숫자 또는 'A', 'J', 'Q', 'K'의 문자를 가진다.
Copy link
Member

Choose a reason for hiding this comment

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

표현이 엄밀해서 좋네요👍


### 덱
- [x] 자신이 가지고 있는 카드 중 한 장을 플레이어에게 제공할 수 있다.

### 플레이어가 받은 카드
- [x] 플레이어가 받은 한 장의 카드를 추가할 수 있다.
- [x] 받은 카드의 총 점수를 계산할 수 있다.
- 숫자 카드는 해당 숫자만큼의 점수로 계산된다.
- J, Q, K 카드는 모두 10으로 계산된다.
- [x] A 카드는 1 또는 11 중 하나를 선택하여 계산할 수 있다.
- [x] 현재 패의 버스트 여부를 판단할 수 있다.

### 플레이어
- [x] 덱으로 부터 카드 한장을 받아올 수 있다.
- [x] 자신의 점수를 계산할 수 있다.
- [x] 자신의 버스트 여부를 판단할 수 있다.
- [x] 중복된 이름이 있으면 예외가 발생한다.

### 딜러
- [x] 덱으로 부터 카드 한장을 받아올 수 있다.
- [x] 자신의 점수를 계산할 수 있다.
- [x] 자신의 버스트 여부를 판단할 수 있다.
- [x] 자신의 현재 점수가 17점 이상이 될 때까지 추가로 카드를 받는다.

### 게임 결과 계산
- [x] 플레이어들의 승리/패배/무승부 여부를 계산할 수 있다.
- [x] 딜러의 전적을 계산할 수 있다.
14 changes: 14 additions & 0 deletions src/main/java/blackjack/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package blackjack;

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

public class Application {
public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
BlackJackController blackJackController = new BlackJackController(inputView, outputView);
blackJackController.start();
Comment on lines +9 to +12
Copy link
Member

Choose a reason for hiding this comment

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

InputViewOutputView를 Controller에서 만들지 않고, Application에서 만드는 이유가 따로 있을까요?

}
}
111 changes: 111 additions & 0 deletions src/main/java/blackjack/controller/BlackJackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package blackjack.controller;

import blackjack.domain.Dealer;
import blackjack.domain.Deck;
import blackjack.domain.GameResultBoard;
import blackjack.domain.Player;
import blackjack.domain.Players;
import blackjack.domain.card.Card;
import blackjack.domain.dto.PlayerDto;
import blackjack.domain.dto.PlayerResultDto;
import blackjack.view.InputView;
import blackjack.view.OutputView;
import java.util.List;

public class BlackJackController {
public static final int INITIAL_CARDS_COUNT = 2;
Comment on lines +15 to +16
Copy link
Member

Choose a reason for hiding this comment

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

초기 카드는 개수가 2개이다라는 게임 룰을 컨트롤러가 알고 있는 부분이라고 생각해요 🧐
컨트롤러가 요청을 받고 응답에 집중하는 레이어인 만큼 추상화가 필요하다는 생각입니다 😀

private final InputView inputView;
private final OutputView outputView;

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

public void start() {
Dealer dealer = new Dealer();
Players players = Players.from(inputView.inputPlayerNames());
Deck deck = Deck.createShuffledDeck();

playGame(dealer, players, deck);
printGameResult(dealer, players);
}

private void playGame(Dealer dealer, Players players, Deck deck) {
doInitialDraw(dealer, players, deck);

players.getPlayers().forEach(
player -> doRound(player, deck)
);
doDealerRound(dealer, deck);
}

private void doInitialDraw(Dealer dealer, Players players, Deck deck) {
players.getPlayers().forEach(
player -> drawCard(player, deck, INITIAL_CARDS_COUNT)
);
drawCard(dealer.getPlayer(), deck, INITIAL_CARDS_COUNT);

outputView.printInitialMessage(players.getPlayerNames());
printInitialCards(dealer, players);
}

private void drawCard(Player player, Deck deck, int amount) {
for (int i = 0; i < amount; i++) {
player.draw(deck);
}
}

private void printInitialCards(Dealer dealer, Players players) {
List<Card> dealerCards = dealer.getCards();
outputView.printDealerInitialCard(dealerCards.get(0));

List<PlayerDto> playerDtos = players.getPlayers().stream()
.map(PlayerDto::from)
.toList();
outputView.printPlayerInitialCards(playerDtos);
}

private void doRound(Player player, Deck deck) {
while (!player.isBusted() && hasAdditionalCardRequest(player)) {
player.draw(deck);
outputView.printPlayerCard(PlayerDto.from(player));
}
}

private boolean hasAdditionalCardRequest(Player player) {
return inputView.inputDrawChoice(player.getName());
}

private void doDealerRound(Dealer dealer, Deck deck) {
dealer.drawUntilExceedMinimum(deck);
printExtraDealerDraw(dealer);
}

private void printExtraDealerDraw(Dealer dealer) {
int dealerCardsCount = dealer.getCardsCount();
int extraDrawCount = dealerCardsCount - INITIAL_CARDS_COUNT;
if (extraDrawCount > 0) {
outputView.printExtraDealerDraw(extraDrawCount);
Copy link
Member

Choose a reason for hiding this comment

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

오! 이렇게 출력하는 방법이... 좋네요

}
}

private void printGameResult(Dealer dealer, Players players) {
printCardStatus(dealer, players);
GameResultBoard gameResultBoard = new GameResultBoard(dealer, players.getPlayers());

outputView.printDealerResult(gameResultBoard.getDealerResult());
for (Player player : players.getPlayers()) {
outputView.printPlayerResult(player.getName(), gameResultBoard.getGameResult(player));
}
}

private void printCardStatus(Dealer dealer, Players players) {
PlayerResultDto dealerResult = PlayerResultDto.from(dealer.getPlayer());

List<PlayerResultDto> playerResultDtos = players.getPlayers().stream()
.map(PlayerResultDto::from)
.toList();
outputView.printCardStatus(dealerResult, playerResultDtos);
Comment on lines +104 to +109
Copy link
Member

Choose a reason for hiding this comment

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

위의 딜러결과 변수명엔 Dto가 안붙어있고 아래의 플레이어결과 변수명엔 Dto가 붙어있네요.
일관성 있는 네이밍이면 더 좋을 듯 합니다!

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

import blackjack.domain.card.Card;
import java.util.List;

public class Dealer {
private static final String DEALER_NAME = "딜러";

private final Player player;

Comment on lines +6 to +10
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
Member

Choose a reason for hiding this comment

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

저는 컴포지션 이용한 이유가 궁금합니다~~

public Dealer() {
this.player = Player.fromName(DEALER_NAME);
}

public void draw(Deck deck) {
player.draw(deck);
}

public void drawUntilExceedMinimum(Deck deck) {
while (getScore().isLessThanDealerMinimumScore()) {
draw(deck);
}
}

public List<Card> getCards() {
return player.getCards();
}

public Score getScore() {
return player.getScore();
}

public Player getPlayer() {
return player;
}
Comment on lines +33 to +35
Copy link
Member

Choose a reason for hiding this comment

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

상속보다 컴포지션을 이용하기 위해, Dealer에서 Player를 가지고 있는 것은 좋다고 생각해요. 하지만 dealer.getPlayer()라는 메서드가 어색한 것 같아요. 외부에게 필요한 정보가 있다면, player를 넘겨주는 것 보다는 외부에게 정보를 줄 수 있는 메서드를 만드는 것이 좋지 않을까요?


public int getCardsCount() {
return player.getTotalCardsCount();
}
}
53 changes: 53 additions & 0 deletions src/main/java/blackjack/domain/Deck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package blackjack.domain;

import blackjack.domain.card.Card;
import blackjack.domain.card.Shape;
import blackjack.domain.card.Value;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Deck {
private static final int SHUFFLED_DECK_SIZE = 52;

private final Queue<Card> cards;

public Deck(List<Card> cards) {
validateUniqueCard(cards);
this.cards = new LinkedList<>(cards);
}

private void validateUniqueCard(List<Card> cards) {
int distinctCount = (int) cards.stream()
.distinct()
.count();

if (distinctCount != cards.size()) {
throw new IllegalArgumentException("중복되는 카드가 있습니다.");
}
}

Comment on lines +22 to +32
Copy link
Member

Choose a reason for hiding this comment

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

  • 유니크한 개수를 센다
  • 개수를 비교 검증한다

두가지 일을 하고 있는 부분인 것 같습니다 🤔

public static Deck createShuffledDeck() {
List<Card> cards = new ArrayList<>(SHUFFLED_DECK_SIZE);

for (Shape shape : Shape.values()) {
cards.addAll(createAllCardsOf(shape));
}
Collections.shuffle(cards);

return new Deck(cards);
}

private static List<Card> createAllCardsOf(Shape shape) {
return Arrays.stream(Value.values())
.map(value -> new Card(shape, value))
.toList();
}
Comment on lines +33 to +48
Copy link
Member

Choose a reason for hiding this comment

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

이 부분... 저와 아주 비슷한데
제 리뷰어가 테스트가 되지 않고 있다고 하더라고요.
로키도 마찬가지네요.
로키는 이 부분의 테스트에 대해 어떻게 생각하나요?


public Card draw() {
return cards.poll();
}
}
22 changes: 22 additions & 0 deletions src/main/java/blackjack/domain/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package blackjack.domain;

public enum GameResult {
WIN, LOSE, DRAW;

Comment on lines +3 to +5
Copy link
Member

Choose a reason for hiding this comment

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

비기는 경우를 생각하셨군요 👍

public static GameResult calculatePlayerResult(Score playerScore, Score dealerScore) {
if (playerScore.isBusted()) {
return LOSE;
}
if (dealerScore.isBusted()) {
return WIN;
}

if (playerScore.isGreaterThan(dealerScore)) {
return WIN;
}
if (playerScore.isLessThan(dealerScore)) {
return LOSE;
}
return DRAW;
Comment on lines +7 to +20
Copy link
Member

Choose a reason for hiding this comment

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

메서드가 10줄을 넘었네요!
플레이어가 이기는 조건, 지는 조건을 정리해 메서드로 빼보면 어떨까요?

}
Comment on lines +6 to +21
Copy link
Member

Choose a reason for hiding this comment

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

아 뭔가... 더 줄일 수 있을 것 같은데... 좋은 방법 없을까? (나도 딱 떠오른게 없네...)
함수형 인터페이스를 적당히 도입하면 될 수 있지 않을까?

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

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GameResultBoard {
private final Map<String, GameResult> resultBoard = new HashMap<>();

public GameResultBoard(Dealer dealer, List<Player> players) {
Score dealerScore = dealer.getScore();
for (Player player : players) {
String playerName = player.getName();
Score playerScore = player.getScore();
GameResult gameResult = GameResult.calculatePlayerResult(playerScore, dealerScore);
resultBoard.put(playerName, gameResult);
}
}

public GameResult getGameResult(Player player) {
Copy link
Member

Choose a reason for hiding this comment

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

getGamaResultOf는 어떨까요?!

return resultBoard.get(player.getName());
}

public Map<GameResult, Integer> getDealerResult() {
return Map.of(
GameResult.WIN, getDealerWinCount(),
GameResult.DRAW, getDealerDrawCount(),
GameResult.LOSE, getDealerLoseCount()
);
}

private int getDealerWinCount() {
return (int) resultBoard.values().stream()
.filter(playerResult -> playerResult.equals(GameResult.LOSE))
.count();
}

private int getDealerLoseCount() {
return (int) resultBoard.values().stream()
.filter(playerResult -> playerResult.equals(GameResult.WIN))
.count();
}

private int getDealerDrawCount() {
return (int) resultBoard.values().stream()
.filter(playerResult -> playerResult.equals(GameResult.DRAW))
.count();
}
}
47 changes: 47 additions & 0 deletions src/main/java/blackjack/domain/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package blackjack.domain;

import blackjack.domain.card.Card;
import java.util.List;

public class Player {
private final PlayerName playerName;
private final PlayerCards playerCards;

public Player(PlayerName playerName) {
this.playerName = playerName;
this.playerCards = PlayerCards.createEmptyCards();
}

public static Player fromName(String name) {
return new Player(new PlayerName(name));
}

Comment on lines +14 to +18
Copy link
Member

Choose a reason for hiding this comment

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

from이라는 이름 만으로 충분하지 않을까요 🤔
저는 정적 팩토리 메서드 네이밍 컨벤션을 참고하는 편입니다

Copy link
Member

Choose a reason for hiding this comment

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

public void draw(Deck deck) {
Card card = deck.draw();
playerCards.append(card);
}

public boolean isBusted() {
return playerCards.isBusted();
}

public List<Card> getCards() {
return playerCards.getCards();
}

public Score getScore() {
return playerCards.calculateScore();
}

public int getScoreValue() {
return getScore().value();
}

public String getName() {
return playerName.name();
}

public int getTotalCardsCount() {
return playerCards.size();
}
}
Loading