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

[2단계 - 블랙잭 베팅] 밍트(김명지) 미션 제출합니다. #917

Merged
merged 94 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from 74 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
1d49443
refactor: 패키지 이동
Starlight258 Mar 14, 2025
ed249a7
refactor(Dealer): 우승 결과 판단 로직을 Dealer로 이동
Starlight258 Mar 14, 2025
11f3d12
feat(generator): 인터페이스에 존재하는 상수 제거
Starlight258 Mar 14, 2025
8b59a43
refactor(Deck): 카드 집합을 저장하는 Deck의 자료구조를 덱(Deque)으로 변경
Starlight258 Mar 14, 2025
7822af9
refactor: controller 이름 대신 BlackjackGame 사용
Starlight258 Mar 15, 2025
e90dbd5
fix: 자료구조 변경에 따라 적절하게 예외 발생하도록 수정
Starlight258 Mar 15, 2025
695f8c2
refactor(BlackjackGame): 메서드 호출 순서에 맞게 변경
Starlight258 Mar 15, 2025
f334a71
refactor: 응답 객체 view로 이동
Starlight258 Mar 15, 2025
f0035cf
refactor: Players 내부 필드를 Gamer가 아닌 Player로 변경
Starlight258 Mar 15, 2025
d168ccd
refactor: Participants 객체 도입
Starlight258 Mar 15, 2025
80cbd3e
refactor: 상속을 하지 않은 클래스에 final 붙임
Starlight258 Mar 15, 2025
5e7d3f9
feat: 2장으로 만들어진 블랙잭를 우선순위가 높은 승리로 고려하도록 수정
Starlight258 Mar 16, 2025
cd4e9d3
docs(README): 기능 목록에 기능 추가
Starlight258 Mar 16, 2025
4eb0495
feat: 베팅 금액 입력 기능 구현
Starlight258 Mar 16, 2025
ea6ac4d
feat: 최종 수익 계산 기능 구현
Starlight258 Mar 16, 2025
b41d91c
refactor(CardScore): CardScore 내부 자료구조를 List가 아닌 Integer로 변경
Starlight258 Mar 16, 2025
9b9621d
refactor(InputView): Scanner를 생성자에서 주입하도록 수정
Starlight258 Mar 16, 2025
771552d
feat(Players): 플레이어 생성 책임을 BlackjackGame에서 Players로 이동
Starlight258 Mar 16, 2025
b6a7c6d
feat(Deck): 셔플된 덱을 생성하는 정적 팩토리 메서드 생성
Starlight258 Mar 16, 2025
9ad6f26
refactor(BlackjackGame): game 패키지로 이동
Starlight258 Mar 16, 2025
52bc2f3
refactor(Participants): 초기 카드 개수는 Deck이 아닌 Participants가 가지도록 수정
Starlight258 Mar 16, 2025
28a7f5b
feat(Hand): 하나의 카드로 Hand를 생성하는 정적 팩토리 메서드 생성
Starlight258 Mar 16, 2025
703b530
refactor(BlackjackGame): 이중 반복문 제거
Starlight258 Mar 16, 2025
3ea4a2d
refactor(Gamer, Dealer, Player,): gamer 패키지로 묶어 가독성 향상
Starlight258 Mar 16, 2025
0bdd7e5
feat(Hand): 인덱스 검증 예외 추가
Starlight258 Mar 16, 2025
75b083a
refactor(CardScore): Set 자료구조를 이용하여 성능 향상
Starlight258 Mar 16, 2025
2ac950b
refactor(Card): 테스트 메서드명 명확하게 변경
Starlight258 Mar 16, 2025
e26a62f
refactor: 메서드명 수정 및 순서 변경
Starlight258 Mar 16, 2025
43a2777
test: 사용하지 않은 코드 제거
Starlight258 Mar 16, 2025
2891c37
refactor: 특수한 케이스를 나타내는 경우에만 정적 팩토리 메서드를 사용하고 그 외는 삭제하도록 구현
Starlight258 Mar 16, 2025
4794cc4
test(CardScore): 테스트 작성
Starlight258 Mar 16, 2025
aaa5ec1
refactor: 변수 선언시 final 붙임
Starlight258 Mar 16, 2025
b3755bf
refactor(Hand): 메서드명 변경
Starlight258 Mar 16, 2025
3b82b14
refactor(ProfitResult): 우승 결과가 아닌 수익을 저장하도록 수정
Starlight258 Mar 17, 2025
88383ef
refactor: Dealer가 초기에 카드 나눠주도록 수정
Starlight258 Mar 17, 2025
3173adc
refactor: ResultStatus가 수익률 저장하도록 수정
Starlight258 Mar 17, 2025
0e443d4
refactor: 사용하지 않은 메서드 제거
Starlight258 Mar 17, 2025
dce4437
refactor: 메서드명 수정
Starlight258 Mar 17, 2025
fef342f
feat(GameRule): 게임 규칙을 집행하는 GameRule 인터페이스 추가
Starlight258 Mar 17, 2025
8b1440c
refactor: Gamer -> Participant 클래스명 변경
Starlight258 Mar 17, 2025
c7f2053
refactor: 메서드명 수정
Starlight258 Mar 17, 2025
c1065b3
refactor: 사용하지 않는 상수 제거
Starlight258 Mar 17, 2025
af96464
refactor: 패키지 변경
Starlight258 Mar 17, 2025
e4b1985
refactor: 클래스명 수정
Starlight258 Mar 17, 2025
1cf9c4f
refactor: 플레이어 관점으로 승패 로직 조정
Starlight258 Mar 17, 2025
29cd964
feat: 승패 로직 계산을 담당하는 PlayerScore 객체 생성
Starlight258 Mar 18, 2025
e6b787d
feat: controller가 최대한 흐름 처리만 하도록 구현
Starlight258 Mar 18, 2025
625c364
refactor: 변수 추출 및 메서드명 수정
Starlight258 Mar 18, 2025
4239190
refactor(ResultStatus): 객체에게 메세지 보내도록 수정
Starlight258 Mar 18, 2025
85311bf
feat: 예외 메세지 관리 클래스 추가
Starlight258 Mar 18, 2025
44ca30e
feat(ShuffleDeckGenerator): 캐싱 적용
Starlight258 Mar 18, 2025
b547d5f
refactor: 재정의한 equals & hashCode 제거
Starlight258 Mar 19, 2025
e17290f
refactor: 클래스명 수정
Starlight258 Mar 19, 2025
3998e14
refactor: 주석 제거
Starlight258 Mar 19, 2025
7751769
refactor: static 블록 사용하여 초기화하도록 수정
Starlight258 Mar 19, 2025
af26315
refactor: 수익을 다룰 때 BigDecimal 타입 사용
Starlight258 Mar 19, 2025
034c56a
refactor: BigDecimal에서 double 타입의 값 사용시 문자열 사용하도록 변경
Starlight258 Mar 19, 2025
320b8dc
feat: 테스트를 위한 equals & hashCode 삭제
Starlight258 Mar 19, 2025
d2dec38
refactor: 사용하지 않은 코드 삭제
Starlight258 Mar 19, 2025
49287fe
refactor: BlackjackGame이 게임 진행 흐름을 가지도록 수정
Starlight258 Mar 20, 2025
b734b57
refactor: 상태 패턴으로 리팩토링
Starlight258 Mar 23, 2025
6b5538e
refactor: 패키지 분리
Starlight258 Mar 23, 2025
4968d7f
refactor: 초기 상태를 고려한 상태 분기 처리
Starlight258 Mar 23, 2025
65f4a1a
test: 테스트 추가
Starlight258 Mar 23, 2025
30d1256
refactor: 메서드명 수정
Starlight258 Mar 23, 2025
752936a
feat: 종료 상태에서 카드를 받으려고 하면 예외 발생하도록 수정
Starlight258 Mar 23, 2025
1d5ed4a
fix: 딜러는 블랙잭이 될 수 없으므로 조건문 삭제
Starlight258 Mar 23, 2025
e005f9d
test: stay 상태 테스트 추가
Starlight258 Mar 23, 2025
565fc34
refactor: 사용하지 않은 메서드 제거
Starlight258 Mar 23, 2025
c6ebc74
refactor: 사용하지 않은 ResultStatus 제거
Starlight258 Mar 23, 2025
40f8b2d
test: 테스트 추가
Starlight258 Mar 23, 2025
fff6821
refactor: 상수로 변경
Starlight258 Mar 23, 2025
28bd9e3
test: 테스트 추가
Starlight258 Mar 23, 2025
ae7730a
test: 테스트 추가
Starlight258 Mar 23, 2025
e6a314f
refactor: 불필요한 인터페이스 구현 제거
Starlight258 Mar 23, 2025
1788c58
refactor: 클래스명과 필드명를 다르게 수정
Starlight258 Mar 23, 2025
fb87c4d
refactor: 객체명을 클래스명과 같도록 수정
Starlight258 Mar 23, 2025
88c36af
feat: 딜러 관련 카드 로직은 DealerRunning으로 이동
Starlight258 Mar 23, 2025
0970a53
refactor: 파라미터명 변경
Starlight258 Mar 23, 2025
895516a
fix: 초기 카드 2장일때만 블랙잭이도록 수정
Starlight258 Mar 23, 2025
db803c2
refactor: 메서드명 직관적으로 수정
Starlight258 Mar 23, 2025
b27a59c
refactor: 메서드 위치 조정
Starlight258 Mar 23, 2025
7f578a0
feat: 카드를 나눠주는 책임을 Participants가 갖도록 수정
Starlight258 Mar 23, 2025
684059f
feat: 종료 상태일 때 stay() 호출하면 아무 작업도 수행하지 않도록 수정
Starlight258 Mar 23, 2025
332b645
refactor: 추상 클래스를 인터페이스로 수정
Starlight258 Mar 23, 2025
dce6e70
refactor: 매개변수 DealerState -> Dealer로 변경
Starlight258 Mar 24, 2025
077c78d
feat: 수익 계산 로직 State -> Player로 이동
Starlight258 Mar 24, 2025
f57d475
fix: 초기화되지 않는 문제 해결
Starlight258 Mar 24, 2025
23c121e
refactor: 메서드명 수정 및 공통으로 사용하지 않은 인터페이스 메서드 제거
Starlight258 Mar 24, 2025
421011d
refactor: 메서드명 수정
Starlight258 Mar 24, 2025
f290b64
refactor: 사용하지 않은 메서드 제거
Starlight258 Mar 24, 2025
d3fc276
refactor: 클래스명 변경 및 오버라이드하지 않은 메서드 final 붙이도록 수정
Starlight258 Mar 24, 2025
2118e6a
fix: 딜러와 플레이어 모두 블랙잭이면 수익은 0원이 되도록 수정
Starlight258 Mar 24, 2025
19ab440
refactor: 메서드명 수정
Starlight258 Mar 24, 2025
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
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
- 예외
- [x] 중복된 이름인 경우

### 베팅 금액 입력
- [x] 베팅 금액을 입력받는다.
- 예외
- [x] 숫자가 아닌 경우
- [x] 음수인 경우
- [x] 0인 경우

### 카드 2장씩 분배 후 공개

- [X] 참가자별로 모두 2장씩 분배한다.
Expand All @@ -29,16 +36,22 @@
- [x] 참가자 모두의 카드를 공개한다.
- [x] 참가자가 지닌 카드의 합을 계산하여 공개한다.

### 승패 결과 출력
### 승패 결과 계산

- [x] 딜러와 각 플레이어 사이의 승패를 계산한다.
- 딜러와 플레이어가 둘 다 버스트이면, 딜러 승
- 딜러나 플레이어 둘 중 한명이 버스트이면, 버스트가 아닌 사람이 승
- 딜러와 플레이어가 둘 다 버스트가 아닌 상황
- 카드 합이 같다면 무승부
- 카드 합이 다르다면 합이 큰 사람이 승
- [x] 딜러는 `0승 0무 0패` 형식으로 결과를 출력한다.
- [x] 플레이어는 `승` or `무` or `패` 형식으로 결과를 출력한다.

### 최종 수익 계산
- [x] 딜러와 플레이어들의 최종 수익을 계산한다.
- [x] 버스트의 경우 베팅 금액을 모두 잃음
- [x] 버스트가 아니면서 카드 총 합이 더 크면 베팅 금액을 받음
- [x] 플레이어의 처음 두 장의 합이 21일 경우 블랙잭이 되어 베팅 금액의 1.5배를 딜러에게 받음
- [x] 딜러와 플레이어가 동시에 블랙잭인 경우(비긴 경우) 플레이어는 베팅 금액을 돌려받음
- [x] 딜러와 플레이어들의 최종 수익 출력

## 블랙잭 도메인 용어
- 딜러(Dealer) : 카드를 나눠주고 게임을 진행하는 역할
Expand Down
16 changes: 9 additions & 7 deletions src/main/java/blackjack/Application.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package blackjack;

import blackjack.controller.BlackjackController;
import blackjack.controller.Controller;
import blackjack.blackjack.card.Deck;
import blackjack.view.InputView;
import blackjack.view.ResultView;
import java.util.Scanner;

public class Application {
public final class Application {

public static void main(String[] args) {
final BlackjackController blackjackController = makeController();
blackjackController.run();
Controller controller = makeController();
controller.startGame(Deck.shuffled());
}

private static BlackjackController makeController() {
final InputView inputView = new InputView();
private static Controller makeController() {
final InputView inputView = new InputView(new Scanner(System.in));
final ResultView resultView = new ResultView();
return new BlackjackController(inputView, resultView);
return new Controller(inputView, resultView);
}
}
40 changes: 40 additions & 0 deletions src/main/java/blackjack/blackjack/BlackjackGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package blackjack.blackjack;

import blackjack.blackjack.card.Deck;
import blackjack.blackjack.participant.Participants;
import blackjack.blackjack.participant.Player;
import blackjack.blackjack.result.ProfitResult;
import java.util.ArrayDeque;
import java.util.Queue;

public final class BlackjackGame {

private final Participants participants;

private Queue<Player> players;

public BlackjackGame(final Participants participants) {
this.participants = participants;
this.players = new ArrayDeque<>(participants.findHitEligiblePlayers().getPlayers());
}

public void dealInitialCards(final Deck deck) {
participants.dealInitialCards(deck);
}

public boolean isPlaying() {
return !players.isEmpty();
}

public Player findCurrentTurnPlayer() {
return players.poll();
}

public ProfitResult makeProfitResult() {
return participants.makeDealerWinningResult();
}

public Participants getParticipants() {
return participants;
}
}
47 changes: 47 additions & 0 deletions src/main/java/blackjack/blackjack/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package blackjack.blackjack.card;

import java.util.Objects;

public final class Card {

private final Suit suit;
private final Denomination denomination;

public Card(final Suit suit, final Denomination denomination) {
this.suit = suit;
this.denomination = denomination;
}

public boolean isAce() {
return this.denomination == Denomination.A;
}

@Override
public boolean equals(final Object o) {
if (!(o instanceof final Card card)) {
return false;
}
return suit == card.suit && denomination == card.denomination;
}

@Override
public int hashCode() {
return Objects.hash(suit, denomination);
}

public Suit getSuit() {
return suit;
}

public String getDenominationName() {
return denomination.getName();
}

public int getCardMinNumber() {
return denomination.getMinNumber();
}

public int getCardMaxNumber() {
return denomination.getMaxNumber();
}
}
34 changes: 34 additions & 0 deletions src/main/java/blackjack/blackjack/card/Deck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package blackjack.blackjack.card;

import blackjack.blackjack.card.generator.DeckGenerator;
import blackjack.blackjack.card.generator.ShuffleDeckGenerator;
import blackjack.util.ExceptionMessage;
import java.util.Deque;
import java.util.stream.IntStream;

public final class Deck {

private final Deque<Card> cards;

public Deck(final DeckGenerator deckGenerator) {
this.cards = deckGenerator.makeDeck();
}

public static Deck shuffled() {
return new Deck(new ShuffleDeckGenerator());
}

public Hand drawCardsByCount(final int count) {
return new Hand(IntStream.range(0, count)
.mapToObj(o -> drawCard())
.toList());
}

public Card drawCard() {
Card card = cards.pollFirst();
if (card == null) {
throw new IllegalStateException(ExceptionMessage.makeMessage("[ERROR] 카드가 더이상 없습니다."));
}
return card;
}
}
48 changes: 48 additions & 0 deletions src/main/java/blackjack/blackjack/card/Denomination.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package blackjack.blackjack.card;

import java.util.Set;

public enum Denomination {

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

private static final Set<Denomination> SPECIAL_CARD_SCORE = Set.of(Denomination.A, Denomination.K,
Denomination.Q, Denomination.J);
private static final int ACE_MAX_NUMBER = 11;

private final int score;

Denomination(final int score) {
this.score = score;
}

public String getName() {
if (SPECIAL_CARD_SCORE.contains(this)) {
return this.name();
}
return String.valueOf(score);
}

public int getMinNumber() {
return score;
}

public int getMaxNumber() {
if (this == A) {
return ACE_MAX_NUMBER;
}
return score;
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
package blackjack.domain.card;
package blackjack.blackjack.card;

import blackjack.util.ExceptionMessage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class Hand {
public final class Hand {

public static final int BURST_THRESHOLD = 21;
private static final int ACE_SUBTRACT = 10;
private static final int BLACKJACK_SIZE = 2;
private static final int DEALER_THRESHOLD = 16;

private final List<Card> hand;
Copy link

Choose a reason for hiding this comment

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

hand 객체가 상태로 hand를 가지는 것은 조금 어색하지 않을까요? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그렇네요! 클래스명과 필드명을 동일하게 하면 내부 필드를 가리키는지, 객체를 가리키는지 혼란스러울 수 있겠네요! 어색하기도 하고요. 수정했습니다!

refactor: 클래스명과 필드명를 다르게 수정


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

public int calculateResult() {
public Hand(final Card card) {
this(List.of(card));
}

public int calculateScore() {
int maxScore = calculateMaxScore(hand);
if (isNotBurst(maxScore)) {
return maxScore;
Expand All @@ -30,10 +37,31 @@ public int calculateWithHardHand() {
.sum();
}

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

public void addAll(final Hand givenHand) {
hand.addAll(givenHand.getHand());
}

public boolean isBlackjack() {
return hand.size() == BLACKJACK_SIZE && calculateScore() == BURST_THRESHOLD;
}

public boolean isBust() {
return calculateScore() > BURST_THRESHOLD;
}

public boolean isDealerStay() {
Copy link

Choose a reason for hiding this comment

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

dealer가 stay 상태인가에 대한 정의는 DealerRunning 객체에서 직접 판단하는게 좋지 않을까 싶어요.
Hand는 Dealer나 Player 둘 다 가지는 상태이다보니 Player나 Dealer의 정책은 Hand 객체 내부에 정의하지 않는 것이 좋다고 생각해요. (Dealer 정책은 Dealer 관련 객체에... Player 정책은 Player 관련 객체에...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

맞습니다! HandPlayerDealer 모두 쓰이므로 Dealer에서만 사용되는 로직은 Dealer에 두는 것이 좋습니다! 반영했습니다!

feat: 딜러 관련 카드 로직은 DealerRunning으로 이동

return calculateScore() > DEALER_THRESHOLD;
}

public Hand subHand(final int startInclusive, final int endExclusive) {
Copy link

Choose a reason for hiding this comment

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

subHand 는 어떤 행위인건지 설명해주실 수 있을까요?

동작과 네이밍의 의미를 설명해주시면 조금 더 이해가 될 것 같아요. 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

List 인터페이스의 subList처럼 카드 일부라는 의미를 표현하고 싶었습니다.
가진 카드의 startInclusive(시작 포함 인덱스)부터 endExclusive(끝 제외 인덱스)를 가져오는 메서드입니다.

메서드명이 직관적이지 않은 것 같아서, getPartialHand로 수정했습니다!

refactor: 메서드명 직관적으로 수정

validateIndex(startInclusive, endExclusive);
return new Hand(hand.subList(startInclusive, endExclusive));
}

private int calculateMaxScore(final List<Card> cards) {
return cards.stream()
.mapToInt(Card::getCardMaxNumber)
Expand All @@ -58,6 +86,16 @@ private int countAce(final List<Card> cards) {
.count();
}

private void validateIndex(final int start, final int end) {
final int size = hand.size();
if (start < 0 || end < 0 || start >= size || end > size) {
throw new IllegalArgumentException(ExceptionMessage.makeMessage("인덱스는 0 이상 hand 크기 이하여야 합니다"));
}
if (start > end) {
throw new IllegalArgumentException(ExceptionMessage.makeMessage("끝 인덱스는 시작 인덱스보다 커야합니다"));
}
}

@Override
public boolean equals(final Object o) {
if (!(o instanceof final Hand hand1)) {
Expand All @@ -71,19 +109,15 @@ public int hashCode() {
return Objects.hashCode(hand);
}

public List<Card> getHand() {
return Collections.unmodifiableList(hand);
}

public Hand getPartialCards(int start, int end) {
return new Hand(hand.subList(start, end));
public int getSize() {
return hand.size();
}

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

public int getSize() {
return hand.size();
public List<Card> getHand() {
return Collections.unmodifiableList(hand);
}
}
9 changes: 9 additions & 0 deletions src/main/java/blackjack/blackjack/card/Suit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package blackjack.blackjack.card;

public enum Suit {

SPADE,
DIAMOND,
HEART,
CLOB;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package blackjack.blackjack.card.generator;

import blackjack.blackjack.card.Card;
import java.util.Deque;

public interface DeckGenerator {

Deque<Card> makeDeck();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package blackjack.blackjack.card.generator;

import blackjack.blackjack.card.Card;
import blackjack.blackjack.card.Denomination;
import blackjack.blackjack.card.Suit;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;

public final class ShuffleDeckGenerator implements DeckGenerator {

private static final List<Card> INITIAL_CARDS;

static {
INITIAL_CARDS = Arrays.stream(Suit.values())
.flatMap(suit -> Arrays.stream(Denomination.values())
.map(cardScore -> new Card(suit, cardScore)))
.toList();
}

@Override
public Deque<Card> makeDeck() {
final List<Card> cards = new ArrayList<>(INITIAL_CARDS);
Collections.shuffle(cards);
return new ArrayDeque<>(cards);
}
}
Loading