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단계 - 블랙잭 게임 실행] 로빈(임수빈) 미션 제출합니다. #1

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
89a8f44
docs: 요구사항 분석 및 기능 목록 작성
BurningFalls Mar 5, 2024
db91493
feat: 덱 관리 기능 구현
BurningFalls Mar 5, 2024
178ad20
feat: 카드 점수 계산 기능 구현
BurningFalls Mar 5, 2024
40019ad
refactor: CardValue 를 CardName 으로 변경
BurningFalls Mar 5, 2024
7196074
feat: 보유한 카드 점수 합계 계산 기능 구현
BurningFalls Mar 5, 2024
399fa25
feat: 플레이어 및 딜러 점수 계산 기능 구현
BurningFalls Mar 5, 2024
19cfa4c
fix: 플레이어가 카드를 뽑을 수 없는 경우에도 카드를 뽑을 수 있던 오류 수정
BurningFalls Mar 5, 2024
ab2676b
fix: 딜러가 카드를 뽑을 수 없는 경우에도 카드를 뽑을 수 있던 오류 수정
BurningFalls Mar 5, 2024
f1319fd
refactor: Player 와 Dealer 통합
BurningFalls Mar 5, 2024
8656caf
feat: 플레이어 이름 입력 기능 구현
BurningFalls Mar 5, 2024
9a66483
feat: 카드 받을지 여부 입력 기능 구현
BurningFalls Mar 5, 2024
4adbb3e
feat: 플레이어 및 딜러간 승부 계산 기능 구현
BurningFalls Mar 6, 2024
b2a5a12
refactor: 플레이어 및 딜러간 승부 계산 기능 Gamer 에서 분리
BurningFalls Mar 6, 2024
7106b9c
feat: 게임 결과 출력 기능 구현
BurningFalls Mar 6, 2024
213a3f2
docs: 기능 목록 정리
BurningFalls Mar 6, 2024
77439a2
feat: 플레이어 및 딜러 손패 출력 기능 구현
BurningFalls Mar 6, 2024
453bb19
feat: 게임 승부 결과 출력 기능 구현
BurningFalls Mar 6, 2024
aa2a65e
feat: 전체 게임 진행 기능 구현
BurningFalls Mar 6, 2024
5685701
fix: 게이머가 죽었을 경우 승부가 잘못 판단되는 오류 수정
BurningFalls Mar 6, 2024
c6e7564
feat: Ace 카드 점수 보정 기능 구현
BurningFalls Mar 6, 2024
9f15cc9
docs: TODO 목록 정리
BurningFalls Mar 7, 2024
3b47a6d
fix: Main 메서드에서 플레이어 이름을 입력받지 않는 오류 수정
BurningFalls Mar 7, 2024
a1b7720
docs: 딜러 Ace 카드 점수 보정 기능 추가
BurningFalls Mar 7, 2024
3502c5d
feat: 딜러 Ace 카드 점수 보정 기능 구현
BurningFalls Mar 7, 2024
89e773e
fix: 게임 도중 플레이어의 손패를 출력하지 않는 오류 수정
BurningFalls Mar 7, 2024
c6047ff
fix: 딜러의 카드 한장 숨기도록 수정
BurningFalls Mar 7, 2024
da5c9ce
refactor: TODO 처리
BurningFalls Mar 7, 2024
a6ba73b
refactor: domain 하위 패키지 추가
BurningFalls Mar 7, 2024
f0aae99
refactor: 클래스 및 패키지 접근지정자 조이기
BurningFalls Mar 7, 2024
6322d3d
refactor: 메서드 분리
robinjoon Mar 7, 2024
61eb780
style: 클래스와 제일 위에 위치한 필드(메서드) 사이에 공백 라인 제거
robinjoon Mar 7, 2024
4c5d1a4
refactor: 일부 매직 값을 상수 혹은 의미있는 변수로 추출
robinjoon Mar 7, 2024
1411054
fix: 플레이어 이름을 잘못 출력하는 버그 수정
robinjoon Mar 7, 2024
b7ff262
chore: 람다표현식으로 변경
robinjoon Mar 7, 2024
0adedd5
docs: 1단계 피드백 반영 예정 목록 추가
robinjoon Mar 9, 2024
adecadf
refactor: 드로우가 가능한지 확인하는 책임 컨트롤러에서 Gamer로 이동
robinjoon Mar 9, 2024
74d6688
refactor: 드로우 여부 결정하는 정책과 드로우 방식을 결정하는 정책 분리
robinjoon Mar 10, 2024
15ee815
refactor: Card Enum화
robinjoon Mar 10, 2024
5ef0343
refactor: 테스트 코드에서 사용하는 테스트 객체 생성 역할을 분리
robinjoon Mar 11, 2024
ea7eb45
test: 게이머의 점수가 잘 계산되는지 검증하는 테스트 추가
robinjoon Mar 11, 2024
f667755
refactor: view만을 위한 enum 추가
robinjoon Mar 11, 2024
6f1a623
chore: 필요 없는 TODO 주석 제거
robinjoon Mar 11, 2024
4df7ab4
refactor: Controller 내부 메서드 분리
robinjoon Mar 11, 2024
bc92c76
fix: 플레이어가 딜러의 전략을 사용하는 오류 수정
robinjoon Mar 11, 2024
671e977
refactor: 딜러와 플레이어 분리
robinjoon Mar 11, 2024
b47d4a9
refactor: 딜러의 카드 하나 숨기는 기능 Dealer로 이관
robinjoon Mar 11, 2024
92ec982
refactor: Main, Controller, View 역할 명확하게 분리
robinjoon Mar 12, 2024
839110d
refactor: Dealer 에서 이름 제거, View로 이관
robinjoon Mar 12, 2024
bea1e52
refactor: GamerOutputView를 DealerOutputView 와 PlayerOutputView로 분리
robinjoon Mar 12, 2024
f7d8e73
refactor: BlackjackController 내부 메서드 정리
robinjoon Mar 12, 2024
9e305e2
refactor: Players 일급 컬렉션 추가
robinjoon Mar 12, 2024
0f6df51
refactor: Players 생성자 변경
robinjoon Mar 12, 2024
15543db
chore: 사용하지 않는 메서드 제거
robinjoon Mar 12, 2024
0f008c7
refactor: RandomCardSelectStrategy 싱글톤으로 변경
robinjoon Mar 12, 2024
0b09e12
fix: 출력문구 누락 수정
robinjoon Mar 12, 2024
438015e
refactor: BlackJackGameMachine 내부 메서드 이름 및 접근 지정자 변경
robinjoon Mar 12, 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
69 changes: 65 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,68 @@
# java-blackjack
## 기능 요구 사항

블랙잭 미션 저장소
### 플레이어 이름 입력

## 우아한테크코스 코드리뷰
1. 플레이어의 이름은 ","을 기준으로 구분한다.
2. 플레이어의 이름은 길이가 1 이상이어야 한다.
3. 플레이어의 이름은 알파벳 대소문자와 숫자로만 구성되어야 한다.
4. 플레이어의 이름은 중복되면 안된다.

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

1. 플레이어가 소유한 카드의 점수의 합이 플레이어의 점수다.
2. 카드의 점수는 카드의 숫자로 계산한다.
3. Ace 카드는 1 혹은 11 중 카드의 소유자가 더 유리한 것으로 계산한다.
4. King, Queen, Jack은 각각 10으로 계산한다.

### 게임 진행 규칙

1. 모든 플레이어와 딜러는 카드를 2장씩 가지고 시작한다.
2. 플레이어는 서로 돌아가면서 자기 턴을 가진다.
3. 플레이어는 자신의 턴에 자신이 카드를 더 받을지 말지 선택할 수 있다. 단, 자신의 점수가 21 이상인 경우 카드를 더 받을 수 없다.
4. 플레이어는 한 번 카드를 받지 않기로 결정한 경우, 앞으로의 턴에서 카드를 더 받을 수 없다.
5. 모든 플레이어가 카드를 더 받을 수 없는 경우, 딜러의 턴으로 넘어간다.
6. 딜러의 턴에서 딜러는 딜러의 점수가 16점 이하인 경우 카드를 더 받는다. 17점 이상인 경우 카드를 더 받지 않는다.
7. 딜러의 턴이 끝나면, 최종 점수 계산 및 게임의 승패를 가린다.
8. 최종 점수가 21점에 가장 가까우면서 21점을 넘기지 않는 사람이 승리한다. 동점인 플레이어(딜러 포함)이 나온 경우, 무승부로 판단한다.
Copy link
Member

Choose a reason for hiding this comment

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

모든 플레이어가 버스트가 된 경우 딜러는 카드를 뽑지 않아도 승리하기 때문에 딜러가 승이라고 생각합니다!

image


### 게임 진행 상황 출력

1. 게임이 시작 되자마자, 딜러와 플레이어가 받은 카드를 출력한다.
2. 단, 딜러는 카드를 한장만 출력한다.
3. 이 후 플레이어의 차례마다 플레이어가 소유한 카드를 출력한다.

### 게임 결과 출력

1. 게임을 완료한 후 각 플레이어(딜러 포함)가 보유한 카드 및 점수를 출력한다.
2. 게임을 완료한 후 각 플레이어별로 승패를 출력한다.
- 딜러는 다른 모든 플레이어에 대한 승패가 출력된다.
- 딜러가 아닌 플레이어는 딜러에 대한 승패가 출력된다.

### 게임 진행 가이드 출력

- 게임의 원활한 진행을 위해 가이드 문구를 출력한다.

## 기능 목록

- [x] 플레이어 이름 입력 기능
- [x] 카드 점수 계산 기능
- [x] Ace 카드 점수 보정 기능
- [x] 플레이어 및 딜러 점수 계산 기능
- [x] 플레이어 및 딜러간 승부 계산 기능
- [x] 플레이어 및 딜러 손패 출력 기능
- [x] 게임 결과 출력 기능
- [x] 게임 승부 결과 출력 기능
- [x] 덱 관리 기능
- [x] 보유한 카드 점수 합계 계산 기능
- [x] 카드 받을지 여부 입력 기능
- [x] 전체 게임 진행 기능
- [x] 딜러 Ace 카드 점수 보정 기능

## 1단계 피드백 반영 예정 목록

- [x] 드로우가 가능한지 확인하는 책임 컨트롤러에서 다른 곳으로 이동
- [x] 드로우 여부 결정하는 정책과 드로우 방식을 결정하는 정책 분리
- [x] 의미 있는 상수화
- [x] 테스트 코드에서 사용하는 테스트 객체 생성 역할을 분리
- [x] view만을 위한 enum 추가
- [x] Card Enum화
9 changes: 9 additions & 0 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import controller.BlackjackController;
import domain.card.Deck;

public class Main {
public static void main(String[] args) {
BlackjackController blackjackController = new BlackjackController();
blackjackController.startBlackjackGame(Deck.fullDeck());
}
}
147 changes: 147 additions & 0 deletions src/main/java/controller/BlackjackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package controller;

import domain.blackjack.Dealer;
import domain.blackjack.DrawResult;
import domain.blackjack.GameResult;
import domain.blackjack.HoldingCards;
import domain.blackjack.Player;
import domain.blackjack.Players;
import domain.card.Card;
import domain.card.Deck;
import domain.card.RandomCardSelectStrategy;
import dto.DealerDTO;
import dto.DealerGameResultDTO;
import dto.PlayerDTO;
import dto.PlayerGameResultDTO;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import view.NameInputView;
import view.OutputView;
import view.YesOrNoInputView;
import view.gamer.DealerOutputView;
import view.gamer.PlayerOutputView;
import view.gameresult.GameResultOutputView;

public class BlackjackController {

public void startBlackjackGame(Deck deck) {
OutputView.printStartGame();
final Dealer dealer = generateDealer();
final Players players = generatePlayers();

initialDraw(deck, dealer, players);
printDealerAndPlayers(dealer, players);

playersTryDraw(deck, players);
dealerTryDraw(deck, dealer);

printDealerWithPoint(dealer);
printPlayersWithPoint(players);

printDealerGameResult(dealer, players);
printPlayersGameResult(dealer, players);
}


private Dealer generateDealer() {
return Dealer.of(HoldingCards.of());
}

private Players generatePlayers() {
return new Players(NameInputView.getNames());
}

private void initialDraw(Deck deck, Dealer dealer, Players players) {
final int initialDrawCount = 2;
IntStream.range(0, initialDrawCount).forEach(index -> {
players.forEach(player -> playerDraw(deck, player));
dealerDraw(deck, dealer);
});
}

private DrawResult dealerDraw(Deck deck, Dealer dealer) {
return dealer.draw(deck, RandomCardSelectStrategy.INSTANCE);
}

private DrawResult playerDraw(Deck deck, Player player) {
return player.draw(deck, RandomCardSelectStrategy.INSTANCE);
}

private void printDealerAndPlayers(Dealer dealer, Players players) {
OutputView.printInitGameDoneMessage(players.getPlayerNames());
printDealer(dealer);
players.forEach(BlackjackController::printPlayer);
}

private static void printDealer(Dealer dealer) {
List<Card> rawHoldingCards = dealer.getRawHoldingCardsWithoutFirstCard();
DealerDTO dealerDTO = new DealerDTO(rawHoldingCards, dealer.getRawSummationCardPoint());
DealerOutputView.printWithoutSummationCardPoint(dealerDTO);
}

private static void printPlayer(Player player) {
PlayerDTO playerDTO = new PlayerDTO(player.getRawName(), player.getRawHoldingCards(),
player.getRawSummationCardPoint());
PlayerOutputView.printWithoutSummationCardPoint(playerDTO);
}

private void playersTryDraw(Deck deck, Players players) {
players.forEach(player -> playerTryDraw(deck, player));
}

private void playerTryDraw(Deck deck, Player player) {
boolean hasNextDrawChance = true;
while (hasNextDrawChance) {
hasNextDrawChance = playerTryDrawOnce(deck, player);
}
}

private boolean playerTryDrawOnce(Deck deck, Player player) {
boolean needToDraw = YesOrNoInputView.getYNAsBoolean(player.getRawName());
DrawResult drawResult = null;
if (needToDraw) {
drawResult = playerDraw(deck, player);
}
printPlayer(player);
if (drawResult == null) {
return false;
}
return drawResult.hasNextChance();
}

private void dealerTryDraw(Deck deck, Dealer dealer) {
DrawResult drawResult = dealerDraw(deck, dealer);
if (drawResult.isSuccess()) {
OutputView.printDealerDrawDone();
}
}

private void printDealerWithPoint(Dealer dealer) {
DealerDTO dealerDTO = new DealerDTO(dealer.getRawHoldingCards(),
dealer.getRawSummationCardPoint());
DealerOutputView.print(dealerDTO);
}

private void printPlayersWithPoint(Players players) {
players.forEach(player -> {
PlayerDTO playerDTO = new PlayerDTO(player.getRawName(), player.getRawHoldingCards(),
player.getRawSummationCardPoint());
PlayerOutputView.print(playerDTO);
});
}

private void printDealerGameResult(Dealer dealer, Players players) {
Map<GameResult, Integer> dealerGameResultCounts = dealer.calculateGameResultWithPlayers(players);
DealerGameResultDTO dealerGameResultDTO = new DealerGameResultDTO(dealerGameResultCounts);
GameResultOutputView.print(dealerGameResultDTO);
}

private void printPlayersGameResult(Dealer dealer, Players players) {
List<PlayerGameResultDTO> playerGameResultDTOs = players.calculateGameResultsWithAsMap(dealer)
.entrySet().stream()
.map(entry -> new PlayerGameResultDTO(entry.getKey(), entry.getValue()))
.toList();
GameResultOutputView.print(playerGameResultDTOs);
}
}
63 changes: 63 additions & 0 deletions src/main/java/domain/blackjack/BlackJackGameMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package domain.blackjack;

import static domain.card.CardName.TEN;

import domain.card.Card;
import domain.card.CardDrawCondition;
import domain.card.CardSelectStrategy;
import domain.card.Deck;
import java.util.List;

class BlackJackGameMachine {
private final HoldingCards holdingCards;

BlackJackGameMachine(HoldingCards holdingCards) {
this.holdingCards = holdingCards;
}

DrawResult draw(Deck deck, CardSelectStrategy cardSelectStrategy, CardDrawCondition cardDrawCondition) {
if (isBust() || !cardDrawCondition.canDraw()) {
return DrawResult.fail("카드를 더이상 뽑을 수 없습니다.", false);
}
try {
Card draw = deck.draw(cardSelectStrategy);
holdingCards.add(draw);
return DrawResult.success(!isBust());
} catch (IllegalArgumentException | IllegalStateException e) {
return DrawResult.fail(e, !isBust());
}
}

SummationCardPoint calculateSummationCardPoint() {
SummationCardPoint summationCardPoint = holdingCards.calculateTotalPoint();
if (hasAceInHoldingCards()) {
int rawPoint = fixPoint(summationCardPoint.summationCardPoint());
return new SummationCardPoint(rawPoint);
}
return summationCardPoint;
}

private int fixPoint(int rawPoint) {
SummationCardPoint fixPoint = new SummationCardPoint(rawPoint + TEN.getCardNumber());
if (!fixPoint.isDeadPoint()) {
return fixPoint.summationCardPoint();
}
return rawPoint;
}

List<Card> getRawHoldingCards() {
return List.copyOf(holdingCards.getHoldingCards());
}

int calculateSummationCardPointAsInt() {
return calculateSummationCardPoint().summationCardPoint();
}

boolean isBust() {
return calculateSummationCardPoint().isDeadPoint();
}

boolean hasAceInHoldingCards() {
return holdingCards.hasAce();
}
}
5 changes: 5 additions & 0 deletions src/main/java/domain/blackjack/CardPoint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package domain.blackjack;

record CardPoint(int point) {

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

import static domain.card.CardName.TEN;

import domain.card.Card;
import domain.card.CardName;

class CardPointCalculator {
static CardPoint calculate(Card card) {
CardName cardName = card.cardName();
int cardNumber = cardName.getCardNumber();
if (cardNumber > TEN.getCardNumber()) {
return new CardPoint(TEN.getCardNumber());
}
return new CardPoint(cardNumber);
}
}
39 changes: 39 additions & 0 deletions src/main/java/domain/blackjack/Dealer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package domain.blackjack;

import domain.card.Card;
import domain.card.CardSelectStrategy;
import domain.card.Deck;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Dealer extends Gamer {

public static Dealer of(HoldingCards holdingCards) {
return new Dealer(new BlackJackGameMachine(holdingCards));
}

Dealer(BlackJackGameMachine blackJackGameMachine) {
super(blackJackGameMachine);
}

@Override
public DrawResult draw(Deck deck, CardSelectStrategy cardSelectStrategy) {
return blackJackGameMachine.draw(deck, cardSelectStrategy, new DealerCardDrawCondition(blackJackGameMachine));
}

public List<Card> getRawHoldingCardsWithoutFirstCard() {
List<Card> rawHoldingCards = new ArrayList<>(blackJackGameMachine.getRawHoldingCards());
rawHoldingCards.remove(0);
return List.copyOf(rawHoldingCards);
}

public Map<GameResult, Integer> calculateGameResultWithPlayers(Players players) {
List<GameResult> gameResults = players.calculateGameResultsWith(this).stream()
.map(GameResult::changeBase)
.toList();
return gameResults.stream()
.collect(Collectors.groupingBy(gameResult -> gameResult, Collectors.summingInt(value -> 1)));
}
}
20 changes: 20 additions & 0 deletions src/main/java/domain/blackjack/DealerCardDrawCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package domain.blackjack;

import domain.card.CardDrawCondition;

public final class DealerCardDrawCondition implements CardDrawCondition {
private final BlackJackGameMachine blackJackGameMachine;

public DealerCardDrawCondition(BlackJackGameMachine blackJackGameMachine) {
this.blackJackGameMachine = blackJackGameMachine;
}

@Override
public boolean canDraw() {
final int rawDealerDrawThresholdPoint = 16;
SummationCardPoint dealerDrawThresholdPoint = new SummationCardPoint(rawDealerDrawThresholdPoint);

SummationCardPoint summationCardPoint = blackJackGameMachine.calculateSummationCardPoint();
return !summationCardPoint.isBiggerThan(dealerDrawThresholdPoint);
}
}
Loading