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단계 - 블랙잭] 밍트(김명지) 미션 제출합니다. #805

Merged
merged 92 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
4d9405d
docs: 기능 요구 사항 작성
theminjunchoi Mar 4, 2025
41df4ce
feat: Player, Players 객체 생성
theminjunchoi Mar 4, 2025
b9543eb
feat: 문자열을 쉼표로 파싱하는 기능 구현
theminjunchoi Mar 4, 2025
5a0cb1d
feat: 이름 입력받는 기능 구현
theminjunchoi Mar 4, 2025
614ea46
feat: 카드 객체 생성
theminjunchoi Mar 4, 2025
bfc0cc9
refactor: 상수 추출
theminjunchoi Mar 4, 2025
535d1f7
feat: 카드 필드 추가
theminjunchoi Mar 5, 2025
e031a6b
feat: 방어적 복사 수행
theminjunchoi Mar 5, 2025
0eef619
refactor: 패키지 변경
theminjunchoi Mar 5, 2025
65d1c0f
feat: 카드 랜덤 생성기 구현
theminjunchoi Mar 5, 2025
8310207
feat: 카드를 분배하는 CardManager 구현
theminjunchoi Mar 5, 2025
88c3d7f
feat: 카드 여러장 분배하는 기능 구현
theminjunchoi Mar 5, 2025
959161b
test: 테스트 패키지 변경
theminjunchoi Mar 5, 2025
566edd1
feat: 딜러 카드 받는 기능 구현
theminjunchoi Mar 5, 2025
e307e6d
test: 테스트픽스쳐 구현
theminjunchoi Mar 5, 2025
2b7956b
feat: 플레이어 카드 받는 기능 구현
theminjunchoi Mar 5, 2025
88013ea
feat: 플레이어들 카드 받는 기능 구현
theminjunchoi Mar 5, 2025
daa85d1
test: 카드와 함께 플레이어 만들어주는 코드 추가
theminjunchoi Mar 5, 2025
b080bab
feat: 카드 보여주는 기능 구현
theminjunchoi Mar 5, 2025
ad8948b
test: 카드 보여주는 기능에 대한 테스트 작성
theminjunchoi Mar 5, 2025
9a22539
feat: 모든 참가자에게 카드 배분하는 기능 구현
theminjunchoi Mar 5, 2025
0c8bd40
refactor: 메서드명 변경
theminjunchoi Mar 5, 2025
f665031
refactor: 상수명 변경
theminjunchoi Mar 5, 2025
0aae138
refactor: 반환 타입 변경
theminjunchoi Mar 5, 2025
8e33ecc
feat: 분배한 카드 출력
theminjunchoi Mar 5, 2025
b82d28d
docs: 기능 요구 사항 업데이트
theminjunchoi Mar 5, 2025
4eefd15
feat: 공백 제거 기능 추가
theminjunchoi Mar 5, 2025
fbd8750
feat: 카드 분배 가능 여부 판단 기능 구현
theminjunchoi Mar 5, 2025
730f1d8
refactor: 메서드명 변경
theminjunchoi Mar 5, 2025
365edf2
feat: 카드를 분배하는 기능 구현
theminjunchoi Mar 5, 2025
660e9f5
feat: 카드를 분배 여부 입력 기능 구현
theminjunchoi Mar 5, 2025
1e0c41c
feat: Controller에서 카드 분배 기능 구현
theminjunchoi Mar 5, 2025
4605dfe
test: TestFixture 메서드 추가
theminjunchoi Mar 5, 2025
5a44346
feat: 추가로 받은 카드 출력 기능 구현
theminjunchoi Mar 5, 2025
ad55fb7
feat: 카드 추가 여부, 닉네임 조회 인터페이스로 두어 구현
theminjunchoi Mar 5, 2025
3d82e71
feat: 딜러 카드 분배 기능 구현
theminjunchoi Mar 5, 2025
88b6275
feat: 카드 출력 기능 파라미터 인터페이스로 수정
theminjunchoi Mar 5, 2025
b4a3f55
feat: 딜러에게 카드 나눠주는 기능 구현
theminjunchoi Mar 5, 2025
9506bda
test: TextFixture 메서드 추가
theminjunchoi Mar 5, 2025
cbd0604
docs: 기능 요구 사항 업데이트
theminjunchoi Mar 6, 2025
41a9bb5
refactor: 인터페이스명 변경
theminjunchoi Mar 6, 2025
635f7fb
feat: Cards 일급 컬렉션 도입
theminjunchoi Mar 6, 2025
81255e8
feat: Gamer 추상 클래스 도입 및 기존 인터페이스 제거
theminjunchoi Mar 6, 2025
5e01dfa
feat: 최종 결과 및 카드 합 보여주는 기능 구현
theminjunchoi Mar 6, 2025
205d4b3
refactor: 추상 클래스를 이용하여 하나의 메서드로 변경
theminjunchoi Mar 6, 2025
7de0f75
feat: 결과 상태 enum 구현
theminjunchoi Mar 6, 2025
c0099a6
feat: 승패 결과 계산 기능 구현
theminjunchoi Mar 6, 2025
2ecc090
refactor: 중복된 상수 제거
theminjunchoi Mar 6, 2025
e80de6b
feat: 승패 결과 enum 도입
theminjunchoi Mar 6, 2025
315e510
feat: 승패 결과 출력 기능 구현
theminjunchoi Mar 6, 2025
9b51ae0
test: testFixture 메소드 추가
theminjunchoi Mar 6, 2025
ab691dc
refactor: 카드 받는 메소드 추상 클래스의 메소드로 추출
theminjunchoi Mar 7, 2025
5c394c7
refactor: 중복 코드 제거
theminjunchoi Mar 7, 2025
26f1903
refactor: depth 1로 수정
theminjunchoi Mar 7, 2025
a9cb002
refactor: 변수를 상수로 변경
theminjunchoi Mar 7, 2025
3a6ce2e
feat: Answer 객체 생성
theminjunchoi Mar 7, 2025
d039756
feat: 잘못된 입력시 재입력 받도록 수정
theminjunchoi Mar 7, 2025
6e4602d
feat: 딜러와 플레이어가 모두 버스트가 난 경우 딜러의 승리로 계산하도록 변경
theminjunchoi Mar 7, 2025
05869dd
refactor: 10 line으로 수정
Starlight258 Mar 7, 2025
82b270d
refactor: public과 abstract 순서 수정
Starlight258 Mar 7, 2025
f8998b8
test: 딜러에게 한장의 카드 제공하는 테스트 추가
Starlight258 Mar 7, 2025
ddb46c4
refactor: 사용하지 않는 상수 제거
Starlight258 Mar 10, 2025
1870096
style: 공백 라인 제거
Starlight258 Mar 10, 2025
3ceeb07
refactor: StringParser 메서드명 변경
Starlight258 Mar 10, 2025
94d9105
feat(Dealer, Player): 비어있는 카드를 가진 정적 팩토리 생성자 생성
Starlight258 Mar 10, 2025
142ee7c
docs(README): 요구사항 문서 자세하게 작성하도록 수정
Starlight258 Mar 10, 2025
2699698
refactor(Answer): 불필요한 객체 제거
Starlight258 Mar 10, 2025
09b405e
refactor(CardGenerator): 구현체의 상수에 의존하지 않도록 수정
Starlight258 Mar 10, 2025
f1c43a5
refactor: 메서드 위치 변경 및 메서드명 수정
Starlight258 Mar 11, 2025
c04e496
style(TestFixture): 개행 추가
Starlight258 Mar 11, 2025
5f51367
refactor(Participants): 필요없는 변수 선언 제거
Starlight258 Mar 11, 2025
31c1ff9
feat(Gamer): 추상 클래스에 초기 카드를 보여주는 추상 메서드 생성
Starlight258 Mar 11, 2025
91165db
refactor(Players, Participants): 구체 클래스가 아닌 추상 클래스로 의존하도록 변경
Starlight258 Mar 11, 2025
5bcafc3
refactor(ResultView): 뷰에 존재하는 도메인 로직을 도메인으로 이동시킨다.
Starlight258 Mar 11, 2025
734abdb
refactor(Participants): index를 돌아 순회하지 않고, 가능한 Player 리스트를 순회하도록 변경
Starlight258 Mar 11, 2025
eacde29
refactor: 카드 합을 나타내는 변수명 sum -> score로 변경
Starlight258 Mar 12, 2025
0b8b33f
style: 필요없는 공백 제거
Starlight258 Mar 12, 2025
17ba658
refactor: 승패 결정 로직 Dealer로 이동
Starlight258 Mar 12, 2025
67e83f4
refactor: 카드 집합에 대해 Cards -> Hand로 이름 변경
Starlight258 Mar 12, 2025
3090f44
refactor: 비기는 경우를 draw 대신 블랙잭 용어인 push로 변경
Starlight258 Mar 12, 2025
d22b818
refactor: 추가 카드를 받을 수 있는지 여부를 블랙잭 용어인 hit 사용하여 변경
Starlight258 Mar 12, 2025
72b1f9f
refactor: BLACKJACK_NUMBER -> BURST_THRESHOLD로 변경
Starlight258 Mar 12, 2025
cdf2779
refactor: softHand -> hardHand 명확하게 이름 변경
Starlight258 Mar 12, 2025
5131ef8
docs: 사용되는 블랙잭 도메인 용어 정리
Starlight258 Mar 12, 2025
83683a4
feat: ResultStatus에서 플레이어 결과가 아닌 딜러 결과 반환하도록 수정
Starlight258 Mar 12, 2025
a5c1179
feat: 결과를 관리하는 DealerWinningResult 객체 생성
Starlight258 Mar 12, 2025
a814083
feat(CardGenerator, Deck): 카드 생성시에 한번만 셔플되도록 수정
Starlight258 Mar 12, 2025
348ecad
refactor(InputView): 상수명 컨벤션에 맞게 파스칼 케이스로 변경
Starlight258 Mar 12, 2025
c1e45a2
refactor: 카드 번호 점수를 Denomination -> CardScore로 변경
Starlight258 Mar 12, 2025
f9879f7
refactor(CardScore): 반환값이 enum 저장 순서에 의존하지 않도록 수정
Starlight258 Mar 12, 2025
763950a
feat: 단순 래핑 객체인 BlackjackGame과 Participants를 제거한다
Starlight258 Mar 12, 2025
3f30a18
chore: 주석 파일 삭제
Starlight258 Mar 13, 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
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
# java-blackjack

블랙잭 미션 저장소
## 기능목록

### 이름 입력

- [x] 이름들을 입력받는다.
- 예외
- [x] 중복된 이름인 경우

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

- [X] 참가자별로 모두 2장씩 분배한다.
- [X] 딜러는 첫 번째 카드만 공개한다.
- [X] 플레이어는 두 장 모두 공개한다.

### 참가자 카드 추가 분배

- [x] 플레이어의 카드 총합이 21미만인 경우, 한 장 더 받을 지 물어본다.
- [x] y 의 경우, 한 장 더 분배한다.
- [x] 지금까지 받은 카드를 공개한다.
- [x] n 의 경우, 턴을 종료한다.
- [x] 딜러는 카드의 합을 계산한다.
- [x] 16 이하의 경우, 한 장 더 분배한다.
- [x] 17 이상의 경우, 턴을 종료한다.

### 참가자 카드 공개

- [x] 참가자 모두의 카드를 공개한다.
- [x] 참가자가 지닌 카드의 합을 계산하여 공개한다.

### 승패 결과 출력

- [x] 딜러와 각 플레이어 사이의 승패를 계산한다.
- 딜러와 플레이어가 둘 다 버스트이면, 딜러 승
Copy link

Choose a reason for hiding this comment

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

딜러나 플레이어 한쪽만 버스트인 경우에 대해서도 같이 적어주시면 조금 더 비즈니스 규칙을 파악하기가 쉬울 것 같아요. 😃

Copy link
Contributor Author

@Starlight258 Starlight258 Mar 10, 2025

Choose a reason for hiding this comment

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

좀 더 요구사항 문서를 자세하게 적어야 소통이 잘 되겠네요! 수정하겠습니다.

docs(README): 요구사항 문서 자세하게 작성하도록 수정

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

## 블랙잭 도메인 용어
- 딜러(Dealer) : 카드를 나눠주고 게임을 진행하는 역할
- 핸드(Hand) : 플레이어나 딜러가 받은 카드 세트
- 히트(Hit) : 추가 카드를 요청하는 행동
- 버스트(Bust) : 카드 합계가 21을 초과하여 패배하는 상황
- 푸시(Push) : 플레이어와 딜러의 점수가 같아 비기는 상황
- 하드 핸드(Hard Hand) : 에이스가 없거나 에이스를 1로만 계산해야 하는 핸드
19 changes: 19 additions & 0 deletions src/main/java/blackjack/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package blackjack;

import blackjack.controller.BlackjackController;
import blackjack.view.InputView;
import blackjack.view.ResultView;

public class Application {

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

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

import blackjack.domain.DealerWinningResult;
import blackjack.domain.card.Deck;
import blackjack.domain.card.Hand;
import blackjack.domain.participant.Dealer;
import blackjack.domain.participant.Gamer;
import blackjack.domain.participant.Player;
import blackjack.domain.participant.Players;
import blackjack.domain.shuffle.ShuffleCardGenerator;
import blackjack.util.StringParser;
import blackjack.view.InputView;
import blackjack.view.ResultView;
import java.util.List;
import java.util.Map;

public class BlackjackController {

private static final int DEALER_COUNT = 1;
private static final int SPREAD_CARD_SIZE_PER_PLAYER = 2;
private static final int SPREAD_SIZE = 1;

private static final String YES = "y";
Copy link

Choose a reason for hiding this comment

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

사용하지 않는 상수인 것 같은데, 과감히 제거해주세요. 😃

Copy link
Contributor Author

@Starlight258 Starlight258 Mar 10, 2025

Choose a reason for hiding this comment

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

리팩토링 과정에서 신경쓰지 못했네요! 삭제하겠습니다 🙂

refactor: 사용하지 않는 상수 제거

private static final String NO = "n";

private final InputView inputView;
private final ResultView resultView;

public BlackjackController(InputView inputView, ResultView resultView) {
this.inputView = inputView;
this.resultView = resultView;
}

public void run() {
final Deck deck = new Deck(new ShuffleCardGenerator());
final Dealer dealer = Dealer.createEmpty();
final Players players = makePlayers();

spreadInitialCards(dealer, players, deck);
spreadExtraCards(dealer, players, deck);
showParticipantScore(dealer, players);
showWinningResult(dealer, players);
}

private void showParticipantScore(final Dealer dealer, final Players players) {
resultView.makeParticipantsWithScoreMessage(Map.entry(dealer.getNickname(), dealer.showAllCards()),
players.showTotalCards());
}

private Players makePlayers() {
String names = inputView.readNames();
List<String> parsedNames = StringParser.parseByComma(names);
return new Players(parsedNames.stream()
.map(Player::createEmpty)
.toList());
}

private void spreadInitialCards(final Dealer dealer, final Players players, final Deck deck) {
final int cardsCount = (DEALER_COUNT + players.getSize()) * SPREAD_CARD_SIZE_PER_PLAYER;
final Hand hand = deck.spreadCards(cardsCount);
dealer.receiveCards(hand.getPartialCards(0, SPREAD_CARD_SIZE_PER_PLAYER));
players.receiveCards(hand.getPartialCards(SPREAD_CARD_SIZE_PER_PLAYER, hand.getSize()),
SPREAD_CARD_SIZE_PER_PLAYER);

resultView.printSpreadCard(players.getNames(), Map.entry(dealer.getNickname(), dealer.showInitialCards()),
players.showTotalInitialCards());
}

private void spreadExtraCards(final Dealer dealer, final Players players, final Deck deck) {
Players availablePlayers = players.findHitAvailablePlayers();
for (Gamer gamer : availablePlayers.getPlayers()) {
while (gamer.canHit() && wantHit(gamer)) {
final Hand hand = deck.spreadCards(SPREAD_SIZE);
gamer.receiveCards(new Hand(List.of(hand.getFirstCard())));
resultView.printParticipantTotalCards(gamer.getNickname(), gamer.showAllCards());
}
}
while (dealer.canHit()) {
final Hand hand = deck.spreadCards(SPREAD_SIZE);
dealer.receiveCards(new Hand(List.of(hand.getFirstCard())));
resultView.printDealerExtraCard();
}
}

private boolean wantHit(final Gamer player) {
String answer = inputView.askHit(player);
if (isValidAnswer(answer)) {
return answer.equals(YES);
}
resultView.showln("잘못된 응답입니다. 다시 입력해주세요.");
return wantHit(player);
}

private boolean isValidAnswer(final String answer) {
return answer.equals(YES) || answer.equals(NO);
}

private void showWinningResult(final Dealer dealer, final Players players) {
final DealerWinningResult result = dealer.makeDealerWinningResult(players.calculateScores());
resultView.showDealerWinningResult(result);
}
}
41 changes: 41 additions & 0 deletions src/main/java/blackjack/domain/DealerWinningResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package blackjack.domain;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;

public class DealerWinningResult {

private final Map<String, ResultStatus> result;

public DealerWinningResult(final Map<String, ResultStatus> result) {
this.result = new HashMap<>(result);
}

public int countResultStatus(final ResultStatus input) {
return (int) result.values().stream()
.filter(resultStatus -> resultStatus == input)
.count();
}

public Map<String, ResultStatus> makePlayerWinningResult() {
return result.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey,
entry -> entry.getValue().makeOppositeResult()));
}

@Override
public boolean equals(final Object o) {
if (!(o instanceof final DealerWinningResult that)) {
return false;
}
return Objects.equals(result, that.result);
}

@Override
public int hashCode() {
return Objects.hashCode(result);
}
}
45 changes: 45 additions & 0 deletions src/main/java/blackjack/domain/ResultStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package blackjack.domain;

import static blackjack.domain.card.Hand.BURST_THRESHOLD;

public enum ResultStatus {

WIN,
LOSE,
PUSH;

public static ResultStatus calculateResultStatus(final int score, final int comparedScore) {
if (score <= BURST_THRESHOLD) {
return calculateResultStatusUnderBlackjackNumber(score, comparedScore);
}
return WIN;
}

private static ResultStatus calculateResultStatusUnderBlackjackNumber(final int score, final int comparedScore) {
if (comparedScore <= BURST_THRESHOLD) {
return calculateResultStatusBothUnderBlackjackNumber(score, comparedScore);
}
return LOSE;
}

private static ResultStatus calculateResultStatusBothUnderBlackjackNumber(final int score,
final int comparedScore) {
if (score > comparedScore) {
return LOSE;
}
if (score == comparedScore) {
return PUSH;
}
return WIN;
}

public ResultStatus makeOppositeResult() {
if (this == WIN) {
return LOSE;
}
if (this == LOSE) {
return WIN;
}
return PUSH;
}
}
47 changes: 47 additions & 0 deletions src/main/java/blackjack/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package blackjack.domain.card;

import java.util.Objects;

public class Card {

private final Shape shape;
private final CardScore cardScore;

public Card(final Shape shape, final CardScore cardScore) {
this.shape = shape;
this.cardScore = cardScore;
}

public boolean isAce() {
return this.cardScore == CardScore.A;
}

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

@Override
public int hashCode() {
return Objects.hash(shape, cardScore);
}

public Shape getShape() {
return shape;
}

public String getCardScoreName() {
return cardScore.getName();
}

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

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

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

public enum CardScore {

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

private static final List<CardScore> SPECIAL_CARD_SCORE = List.of(CardScore.A, CardScore.K,
CardScore.Q, CardScore.J);

private final List<Integer> scores;

CardScore(final List<Integer> scores) {
this.scores = scores;
}

public String getName() {
final boolean isSpecialCardScore = SPECIAL_CARD_SCORE.stream()
.anyMatch(cardScore -> cardScore == this);
if (isSpecialCardScore) {
return this.name();
}
return String.valueOf(scores.getFirst());
}

public int getMinNumber() {
return Collections.min(scores);
}

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

import blackjack.domain.shuffle.CardGenerator;
import java.util.stream.IntStream;

public class Deck {

private final CardGenerator cardGenerator;

public Deck(final CardGenerator cardGenerator) {
this.cardGenerator = cardGenerator;
}

public Hand spreadCards(final int count) {
return new Hand(IntStream.range(0, count)
.mapToObj(o -> cardGenerator.pickCard())
.toList());
}
}
Loading