Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5882e1a
docs(README): 기능 리스트 수정
yejinkimis Mar 12, 2026
ffc8fa6
test(BettingAmountRequestDto): 베팅 금액 null 입력 에러 발생 테스트
yejinkimis Mar 12, 2026
d023f75
test(BettingAmountRequestDto): 베팅 금액 공백 입력 에러 발생 테스트
yejinkimis Mar 12, 2026
41ac81f
feat(BettingAmountRequestDto): 베팅 금액 null, 공백 입력 에러 발생 기능
yejinkimis Mar 12, 2026
26f6b74
test(BettingAmountRequestDto): 베팅 금액 숫자 이외 문자 입력 시 에러 발생 테스트
yejinkimis Mar 12, 2026
7a5e58e
feat(BettingAmountRequestDto): 베팅 금액 입력 문자열이 숫자가 아닌 문자일 시 에러 발생
yejinkimis Mar 12, 2026
ef1ae2c
test(BettingAmountRequestDto): 베팅 금액 입력이 양수가 아닐 시 에러 발생 검증
yejinkimis Mar 12, 2026
a84a4c9
feat(BettingAmountDto): 베팅 금액 입력 문자열이 양수가 아닌 문자면 에러 발생
yejinkimis Mar 12, 2026
63cf184
test(Betting): 이름, 베팅값으로 이름별 베팅 값 map 반환 테스트
yejinkimis Mar 13, 2026
5b00d90
feat(Bettiing): 겜블러별 베팅 금액을 저장하고 조회
yejinkimis Mar 13, 2026
1262dac
test(Betting): 겜블러 이름 중복 없음 테스트
yejinkimis Mar 13, 2026
12d28c9
feat(Betting): 겜블러 이름 중복 없음 검증
yejinkimis Mar 13, 2026
4422cab
test(Gamblers): 겜블러 이름과 베팅금액 저장 및 생성 테스트
yejinkimis Mar 13, 2026
7a76f59
feat(Gamblers): 겜블러 이름과 베팅금액 저장 및 생성 확인
yejinkimis Mar 13, 2026
e707762
test(Participants): 초기 2장 값 블랙잭 확인
yejinkimis Mar 13, 2026
d7235ed
feat(Participants): 초기 2장 값 블랙잭 확인
yejinkimis Mar 13, 2026
3ccae12
test(GameResult): 딜러와 겜블러 비교하여 결과 판별
yejinkimis Mar 13, 2026
cbd521b
feat(GameResult): 딜러와 겜블러 비교하여 결과 판별
yejinkimis Mar 13, 2026
7a3f30c
test(GameResult): 최종 결과 딜러, 참여자들 수익금액 계산 확인
yejinkimis Mar 14, 2026
481010e
feat(GamblersGameResult): 딜러, 참여자들의 수익을 정산
yejinkimis Mar 14, 2026
4f92400
refactor(BettingAmountRequestDto): 최소 베팅 금액 상수로 고정
yejinkimis Mar 14, 2026
2b5ca1f
refactor(GameCards): 카드 덱 수 상수 고정
yejinkimis Mar 14, 2026
d941c5d
refactor: 전역 상수를 해당 도메인 및 뷰 클래스 내부로 이동
yejinkimis Mar 14, 2026
88a8436
refactor: game내에서 특정 participant에게 card를 추가하는 기능 개선
yejinkimis Mar 14, 2026
d353c14
docs(README): 리드미 구현한 기능 체크
yejinkimis Mar 14, 2026
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
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,29 @@
### 입력
- [x] "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리) " 를 출력한다.
- [x] 게임에 참여하는 참가자의 이름을 쉼표 기준으로 나누는 문자열을 하나 입력받는다.
- [x] 참가자 별로 베팅할 금액을 입력
- [x] 게임을 계속 진행할 지 결정하는 (y/n) 는 y, n를 입력한다.

### 입력 예외

- [x] 이름은 쉼표 기준으로 분리되어 1개 이상이어야 한다.
- [x] 이름은 영어로 2~10자 사이여야 한다.
- [x] 이름은 영어로만 엊루이한야 져어루다한야.
- [x] 이름은 영어로만 이루어져야 한다.
- [x] 이름은 소문자 기준으로 중복되지 않는다.
- [x] 베팅할 금액은 10 이상의 양수여야 한다.
- [x] 게임을 계속 진행할 지 결정하는 y, n만 입력할 수 있다.

### 기능

1. 입력 처리

- [x] 입력받은 문자열을 ‘,’를 기준으로 파싱한다.
- [x] 파싱한 이름을 검증한다.
1-1. 참가자 이름 입력
- [x] 입력받은 문자열을 ‘,’를 기준으로 파싱한다.
- [x] 파싱한 이름을 검증한다.

1-2. 베팅 금액 입력
- [x] 참가자 별로 베팅할 금액을 입력받는다.
- [x] 베팅한 금액을 검증한다.


2. 게임 진행
Expand All @@ -37,11 +44,14 @@

3. 결과 처리

- [X] 덱은 한 덱(52장)으로 고정한다(이때 하드코딩하지 않는다)
- [x] 딜러와 각 참가자들의 카드 합을 구한다.
- [x] 참가자 승 : 딜러보다 합이 21에 더 가까운 경우 or 딜러가 버스트인 경우(참가자 버스트 X)
- [x] 참가자 무승부: 참가자와 딜러의 합이 같음(버스트 X)
- [x] 참가자 패 : 참가자 버스트 or 딜러보다 합이 적음(딜러가 버스트 X)
- [x] 딜러와 참가자 수익 계산 초기값은 '0'으로 수익, 손해는 이 값을 기준으로 계산한다.
- [x] 처음 두 장의 카드 합이 21이면 블랙잭으로, 베팅 금액의 1.5배를 딜러에게 받는다.
- [x] 딜러와 플레이어가 모두 동시에 블랙잭인 경우 플레이어는 베팅한 금액을 돌려받는다.(수익: 0)
- [x] 카드를 추가로 뽑아 21을 초과할 경우 베팅 금액을 모두 잃게 된다. (수익: -베팅 금액)
- [x] 딜러가 21을 초과하면 그 시점까지 남아 있던 플레이어들은 가지고 있는 패에 상관 없이 승리해 베팅 금액을 받는다. (수익: 베팅 금액)
- [x] 딜러가 21을 초과하더라도 플레이어가 21을 초과해 이미 베팅 금액을 잃었다면 베팅 금액을 받지 않는다. (수익: -베팅 금액)
- [x] 딜러가 21을 초과할 때 플레이어가 이미 블랙잭으로 1.5배를 받았다면 추가로 금액을 받지 않는다.

### 출력

Expand All @@ -51,4 +61,4 @@
- [x] 참가자가 추가로 받은 카드들을“ [참가자]카드: 7클로버, K스페이드 ” 의 형태로 출력한다.
- [x] 딜러가 받은 추가 카드를 “딜러는 16이하라 한장의 카드를 더 받았습니다.” 의 형태로 출력한다.
- [x] 딜러, 참가자들의 카드 목록과 카드 합 결과를 “딜러카드: 3다이아몬드, 9클로버, 8다이아몬드 - 결과: 20” 의 형태로 출력한다.
- [x] 딜러, 참가자들의 최종 승패를 “## 최종 승패 딜러: 1승 1패 [참가자]: [참가자]: ” 의 형태로 출력한다.
- [x] 딜러, 참가자들의 수익을 ## 최종 수익 딜러 : xxxx [참가자]: xxxx [참가자]: xxxx” 의 형태로 출력한다.
1 change: 1 addition & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import view.OutputView;

public class Application {

public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
Expand Down
86 changes: 47 additions & 39 deletions src/main/java/controller/BlackJackController.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package controller;

import static util.Constants.COMMA_DELIMITER;
import static util.Constants.DEALER_NAME;
import static util.Constants.DEFAULT_CARD_SET;
import static util.Constants.HIT;
import static util.Constants.STAND;

import domain.betting.Betting;
import domain.betting.BettingAmount;
import domain.card.GameCards;
import domain.game.GamblersGameResult;
import domain.game.Game;
import domain.player.Dealer;
import domain.player.Gambler;
import dto.AgreementRequestDto;
import dto.DealerResultDto;
import dto.ParticipantHandResponseDto;
import dto.ParticipantsGameInfoDto;
import dto.ParticipantsHandResponseDto;
import view.requestDto.AgreementRequestDto;
import view.requestDto.BettingAmountRequestDto;
import view.responseDto.DealerResultDto;
import view.responseDto.ParticipantHandResponseDto;
import view.responseDto.ParticipantsGameInfoDto;
import view.responseDto.ParticipantsHandResponseDto;
import java.util.List;
import util.Parser;
import java.util.Map;
import view.InputView;
import view.OutputView;

public class BlackJackController {

private final InputView inputView;
private final OutputView outputView;

Expand All @@ -30,74 +30,82 @@ public BlackJackController(InputView inputView, OutputView outputView) {

public void run() {
List<String> names = inputGamblersInfo();
Game game = initializeGame(names);
Map<String, BettingAmount> gamblerNameAndBettingInfo = betByName(names);
Game game = initializeGame(gamblerNameAndBettingInfo);

playGame(game);
checkDealerHand(game);

printParticipantsResult(game);

determineFinalGameResult(game.getResult());
determineFinalGameProfit(game.getResult());
}

private List<String> inputGamblersInfo() {
String name = inputView.askGamblerNames().name();
return Parser.parse(name, COMMA_DELIMITER);
return inputView.askGamblerNames().names();
}

private Game initializeGame(List<String> names) {
Game game = new Game(DEALER_NAME, names, DEFAULT_CARD_SET);
private Map<String, BettingAmount> betByName(List<String> names) {
Betting betting = new Betting(names);
for (String name : names) {
BettingAmountRequestDto bettingAmountRequestDto = inputView.askBettingAmount(name);
betting.betBettingAmount(name,
new BettingAmount(bettingAmountRequestDto.getBettingAmount()));
}
return betting.getBettingAmounts();
}

outputView.printInitialDeal(names);
private Game initializeGame(Map<String, BettingAmount> gamblerNameAndBettingInfo) {
Game game = new Game(Dealer.DEALER_NAME, gamblerNameAndBettingInfo,

Choose a reason for hiding this comment

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

딜러가 알고 있는 DEALER_NAME 을 외부에서 알려줄 필요가 있을까요~?

GameCards.DEFAULT_CARD_SET);

outputView.printInitialDeal(gamblerNameAndBettingInfo.keySet().stream().toList());
game.initializeGame();
outputView.printParticipantsInfo(
new ParticipantsHandResponseDto(game.getInitialParticipantsHandInfo())
);

new ParticipantsHandResponseDto(game.getInitialParticipantsHandInfo()));
return game;
}

private void playGame(Game game) {
List<Gambler> gamblers = game.getGamblersList();
for(Gambler gambler : gamblers) {
for (Gambler gambler : gamblers) {
if (gambler.isBlackJack()) {
outputView.printBlackJackMessage(gambler.getName());
continue;
}
playTurn(game, gambler);
}
}

private void playTurn(Game game, Gambler gambler) {
while(!gambler.isBust()) {
while (!gambler.isBust()) {
AgreementRequestDto agreementRequestDto = inputView.askHitOrStand(gambler.getName());
if (agreementRequestDto.agreement().equals(HIT)) {
gambler.addCard(game.pickCard());
if (agreementRequestDto.agreement().equals(InputView.HIT)) {

Choose a reason for hiding this comment

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

Suggested change
if (agreementRequestDto.agreement().equals(InputView.HIT)) {
if (agreementRequestDto.isHit()) {

값을 꺼내 쓰지 않고 메시지를 보내 확인할 수 있을 것 같아요

game.drawCardTo(gambler);
outputView.printParticipantInfo(
new ParticipantHandResponseDto(gambler.getName(), gambler.getHandInfo()));
}
if (agreementRequestDto.agreement().equals(STAND)) {
if (agreementRequestDto.agreement().equals(InputView.STAND)) {
break;
}
}
}

private void checkDealerHand(Game game) {
if (game.shouldDealerDraw()) {
outputView.printDealerCardIsUnder16();
game.addDealerCard();
outputView.printDealerCardIsBelowDrawThreshold();
game.drawCardTo(game.getDealer());
}
}

private void printParticipantsResult(Game game) {
outputView.printParticipantsGameInfo(new ParticipantsGameInfoDto(
game.getParticipantGameInfos()
));
outputView.printParticipantsGameInfo(
new ParticipantsGameInfoDto(game.getParticipantGameInfos()));
}

private void determineFinalGameResult(GamblersGameResult gamblersGameResult) {
private void determineFinalGameProfit(GamblersGameResult gamblersGameResult) {
outputView.printDealerResult(
new DealerResultDto(gamblersGameResult.countDealerWin(),
gamblersGameResult.countDealerLose(),
gamblersGameResult.countDealerDraw()));
outputView.printGamblerResult(
gamblersGameResult.getResultInfo()
);
new DealerResultDto(gamblersGameResult.getDealerProfit().getProfit()));
outputView.printGamblerResult(gamblersGameResult.getParticipantProfits());
}
}
38 changes: 38 additions & 0 deletions src/main/java/domain/betting/Betting.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package domain.betting;

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

public class Betting {

Choose a reason for hiding this comment

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

  1. Gambler와 BettingAmount의 관계
    "베팅하는 행위" 를 따로 표현하고 싶어서 Betting클래스를 따로 뒀습니다. 그런데 Gambler도 베팅한 금액을 알아야 하는 게 맞다고 생각되어서 Gambler에 베팅한 금액을 생성 시 인자로 넣어 관리하게 되었습니다.
    지금은 Betting 클래스에서 이름에 따른 베팅 금액을 입력받고 임시로 들고있다가 베팅이 다 끝나면 Gambler객체를 만드는 방식을 사용하고 있습니다. 근데 이렇게 하면 Gambler가 생기기 전까지 임시로 Map과 같은 곳에 저장해둬야 합니다.
    객체를 생성하기 위해 외부에서 모든 데이터를 수집한 뒤 주입하는 방식(완성된 객체 생성)과 객체를 먼저 생성하고 이후 상태를 채워나가는 방식 사이에서 어떤 것이 더 견고한 로직인지 궁금합니다!

좋은 고민이네요 👍 저는 가능하면 "객체를 생성하기 위해 외부에서 모든 데이터를 수집한 뒤 주입하는 방식" 을 선호하긴 하는데요, 그 이유는 객체를 생성한 후 상태변경을 최소화 하기 위함입니다! 완성된 객체를 생성했기 떄문에 Gambler 의 bettingAmount 을 불변으로 선언할 수 있었죠 ㅎㅎ
다만 저는 오히려 Betting을 굳이 도메인 객체로 빼서 둘 필요가 있나?가 고민이 되네요!, 현재 해당 클래스가 하고 있는 중복 이름 검증은 오히려 Gamblers 일급 컬렉션에서 진행 가능해보이고, Map<String, BettingAmount> 별로 겜블러를 만드는 책임은 컨트롤러나 혹은 도메인이 아닌 별도 팩터리 클래스로 두어도 괜찮아보이긴 합니다!


private final Map<String, BettingAmount> values = new LinkedHashMap<>();

public Betting(List<String> names) {
validateDuplicateNames(names);
for (String name : names) {
values.put(name, null);
}
}

public void betBettingAmount(String name, BettingAmount bettingAmount) {
values.put(name, bettingAmount);
}

public BettingAmount getBettingAmountByName(String name) {
return values.get(name);
}

public Map<String, BettingAmount> getBettingAmounts() {
return values;
}

public void validateDuplicateNames(List<String> names) {
List<String> distinctNamesCount = names.stream().
distinct().collect(Collectors.toList());
if (distinctNamesCount.size() != names.size()) {
throw new IllegalArgumentException("중복된 이름이 입력됩니다.");
}
}
}
33 changes: 33 additions & 0 deletions src/main/java/domain/betting/BettingAmount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package domain.betting;

import java.util.Objects;

public class BettingAmount {

private final int bettingAmount;

Choose a reason for hiding this comment

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

int, double, BigInteger 각각에 대해 알아보고 돈과 관련된 연산에는 어떤 자료구조가 적합한지 고민해보면 좋겠네요 ㅎㅎ


public BettingAmount(int bettingAmount) {
this.bettingAmount = bettingAmount;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BettingAmount that = (BettingAmount) o;
return bettingAmount == that.bettingAmount;
}

@Override
public int hashCode() {
return Objects.hash(bettingAmount);
}

public int getBettingAmount() {
return bettingAmount;
}
}
1 change: 1 addition & 0 deletions src/main/java/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package domain.card;

public class Card {

private final CardScore score;
private final CardKind kind;

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/domain/card/CardKind.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public enum CardKind {
CLOVER("클로버"),
HEART("하트"),
DIAMOND ("다이아몬드"),
DIAMOND("다이아몬드"),
SPADES("스페이드");

private final String kind;
Expand All @@ -18,7 +18,7 @@ public String getKind() {
return kind;
}

public static CardKind of(final String kind){
public static CardKind of(final String kind) {
return Arrays.stream(values())
.filter(val -> val.kind.equals(kind))
.findFirst()
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/domain/card/CardScore.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public String getScore() {
return this.score;
}

public static CardScore of(final String score){
public static CardScore of(final String score) {
return Arrays.stream(values())
.filter(val -> val.score.equals(score))
.findFirst()
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/domain/card/GameCards.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@

public class GameCards {

public static final int DEFAULT_CARD_SET = 1;
public static final int DEFAULT_START_CARD_COUNT = 2;

Choose a reason for hiding this comment

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

DEFAULT_START_CARD_COUNT 는 외부에서 사용되고 있는데 해당 클래스에 있는 것이 맞을까요!?
상수가 public 으로 열려있다면 해당 클래스에 있는 것이 올바를지 고민해보면 좋을 것 같습니다 ㅎㅎ


private List<Card> cards;

Choose a reason for hiding this comment

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

Suggested change
private List<Card> cards;
private final List<Card> cards;

final 로 선언 가능해보여요


public GameCards(int amount) {
public GameCards(int DEFAULT_CARD_SET) {
this.cards = Arrays.stream(CardKind.values())
.flatMap(cardKind -> Arrays.stream(CardScore.values())
.flatMap(cardScore ->
IntStream.range(0, amount)
IntStream.range(0, DEFAULT_CARD_SET)
.mapToObj(card -> new Card(cardScore.getScore(),
cardKind.getKind()))
))
Expand Down
Loading