diff --git a/README.md b/README.md index 70b16d15c3..91b33ae5e8 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,16 @@ - **(예외 처리)** 공백 문자열인 경우 - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 +- 베팅 금액을 입력받는다 + - 공백 문자열인 경우에 오류가 발생한다 + ### 출력 - 게임 안내 문구를 출력한다. - 이름 입력 안내 - HIT 혹은 STAND 여부 질문 - 딜러의 카드 추가 여부 + - 베팅 금액 입력 안내 - 초기 카드 내역을 출력한다. - 최종 카드 내역을 출력한다. - 최종 승패 결과를 출력한다. @@ -26,74 +30,62 @@ ## 도메인 ### MultiPlayers - - 플레이어들 생성 및 관리 - 이름 중복 검증 -- 플레이 가능한 사용자 찾기 -- 특정 플레이어에게 hit, stand 지시 -- 플레이어들의 게임 결과 조사 -- 플레이어들의 초기 카드 정보 조사 + - **(예외 처리)** 플레이어 수가 5명을 초과할 경우 예외 발생 (`IllegalArgumentException`) + - **(예외 처리)** 중복된 이름이 존재할 경우 예외 발생 (`IllegalArgumentException`) +- 플레이어 베팅 관리 + - **(예외 처리)** 등록되지 않은 플레이어에게 액션을 지시할 경우 예외 발생 +- 게임 진행 대기 중인(Hit/Stand 가능한) 플레이어 탐색 +- 특정 플레이어에게 Hit/Stand 지시 +- 모든 플레이어의 초기 (카드) 상태 및 게임 승패 결과 조회 ### Participant - - 이름 검증 - - 공백 불가 - - 한국어, 알파벳만 허용 -- Hand를 이용한 보유 카드 정보 제공 - - 초기 카드 - - 구성 - - 합 -- Hand를 이용한 Bust 정보 제공 -- Hand를 이용한 BlackJack 여부 제공 + - **(예외 처리)** 이름이 공백인 경우 예외 발생 + - **(예외 처리)** 영어나 한글 외의 문자가 포함된 경우 예외 발생 +- 참가자의 초기 부여 카드 및 전체 보유 카드 반환 +- 참가자가 베팅을 마쳤는지 여부 반환 +- 참가자가 보유한 카드 숫자의 총합 반환 ### Dealer - -- 조건에 따른 카드 추가 - - 점수 합이 16 이하인 경우 +- 점수 합이 16 이하인 경우 조건에 따른 카드 추가(Hit) +- 점수가 16을 초과할 경우 자동으로 카드 추가 중지(Stay) 상태로 전환 +- 딜러의 초기 카드(1장) 반환 기능 ### Player - -- 게임 진행 가능 여부 판별 - - Hand를 이용하여 isBust 판별 -- 게임 진행 의사 관리 -- 초기 보유 카드 보여주기 -- Hand를 이용한 카드 추가 -- 조건에 따른 카드 추가 - - 버스트 상태가 아닐 경우 +- 배팅 금액 관리 및 베팅 상태 점검 +- 플레이어 단위의 Hit/Stand 게임 진행 액션 수행 +- 게임 진행 가능 여부(Bust 또는 Blackjack 상태가 아님) 판별 +- 플레이어 턴의 종료 여부 확인 및 딜러와의 승패 결과(GameResult) 계산 +- 초기 부여받은 2장의 보유 카드 반환 ### Card +- 카드의 형상(Spade, Heart, Diamond, Club) 및 숫자(Ace ~ King) 정보 관리 및 제공 -- 카드 정보 관리 및 제공 +### BetAmount +- 배팅 금액 추적 및 예외 상황 관리 + - **(예외 처리)** 정수로 변환 불가능한 값 입력 시 예외 발생 (`IllegalArgumentException`) + - **(예외 처리)** 베팅 금액이 0원 이하일 경우 예외 발생 (`IllegalArgumentException`) ### Deck - -- 전체 카드 관리 -- 카드 1장 뽑기 -- 카드 2장 뽑기 +- 전략(Random 등) 기반 덱 구성 및 전체 카드 관리 +- 초기 부여용 카드 2장 뽑기 및 카드 1장 뽑기 + - **(예외 처리)** 덱에 더 이상 카드가 남아있지 않은 상태에서 카드를 뽑으려 할 경우 예외 발생 (`IllegalArgumentException`) ### Hand - -- 사용자 보유 카드 관리 -- Bust 판정 -- blackJack 판정 -- 게임 조건에 따른 카드 추가 -- 총점 계산 +- 보유 카드의 상태(Bust, Blackjack, 보유 카드 총점) 판단 +- 게임 조건에 맞춰 카드를 추가해 새로운 핸드 구성 반환 ### BlackJackGame - -- 게임 준비 - - 딜러, 플레이어들, 전체 덱 생성 -- 게임의 turn 관리. -- 특정 사용자를 찾아 hit / stand 실행 -- 모든 게임 참가자들의 초기 정보 조사 -- 모든 게임 참가자들의 결과 조사 +- 게임 준비 (딜러, 멀티 플레이어들, 전체 덱 생성) +- 현재 누구의 턴(베팅 턴 또는 플레이 턴)인지 확인 및 상태 추적 +- 현재 턴인 사용자에 맞게 Hit, Stand, 혹은 Dealer Play 단계 실행 + - **(예외 처리)** 베팅을 진행할 플레이어가 없는데 베팅을 시도할 경우 예외 발생 (`IllegalStateException`) + - **(예외 처리)** 카드를 더 이상 받을 수 있는 플레이어가 없는데 Hit/Stand를 시도할 경우 예외 발생 (`IllegalStateException`) +- 모든 게임 참가자들의 초기 상태 및 최종 결과 정보 조사 반환 ### BlackJackController - -- 게임 전반에서 필요한 I/O 관리 - - 게임 진행 상황 출력 관리 - - 초기 상태 - - 특정 단계에서의 사용자 정보 - - 사용자 이름 입력 관리 - - hit 혹은 stand 여부 입력 관리 - - 게임 결과 출력 관리 \ No newline at end of file +- 입출력(View)과 도메인 모델 간의 데이터 연동 및 전체 게임 흐름 제어 +- 초기 설정(참가자 등록), 베팅 턴 진행, 플레이 턴 진행 단계별 실행 +- 예외 상황 발생 시 에러 메시지를 출력하고, 정상 입력이 들어올 때까지 반복 입력(I/O Retry) 흐름 관리 \ No newline at end of file diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 5271d81586..3c0061097d 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -8,8 +8,11 @@ public enum ErrorMessage { ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), NAME_UNIQUENESS_ERR("이름은 중복되면 안됩니다"), PLAYER_NOT_FOUND("해당 플레이어를 찾을 수 없습니다"), - NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어가 없습니다."), + NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어를 찾지 못했습니다"), + NO_MORE_BETTABLE_PLAYER("베팅을 진행할 수 있는 플레이어를 찾지 못했습니다"), NOT_ALLOW_METHOD_CALL("현재 상태에서 허용되지 않는 메소드 호출입니다"), + ZERO_MINUS_MONEY("0원 이하 금액을 베팅할 수는 없습니다"), + ONLY_NUMBER("숫자만 입력 가능합니다"), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index 33a591538e..ae7340a885 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -25,10 +25,8 @@ public BlackJackController(InputView inputView, public void doGameProcess() { BlackJackGame game = retry(this::readyGame); - outputView.printInitialStates( - game.getDealerGameSettingState(), - game.getPlayersGameSettingStates() - ); + betPlayers(game); + outputView.printInitialStates(game.getGameSettingState()); playPlayersTurn(game); playDealerTurn(game); @@ -37,6 +35,15 @@ public void doGameProcess() { outputView.printGameResult(gameResults); } + private void betPlayers(BlackJackGame game) { + while (game.whoseBettingTurn().isPresent()) { + Player currentPlayer = game.whoseBettingTurn().get(); + outputView.printBetAmountPrompt(currentPlayer.getName()); + String betAmountValue = retry(inputView::readBetAmountValue); + game.doBetProcess(betAmountValue); + } + } + private BlackJackGame readyGame() { outputView.printNamePrompt(); List playerNames = inputView.readNames(); @@ -50,22 +57,16 @@ private void playDealerTurn(BlackJackGame game) { } private void playPlayersTurn(BlackJackGame game) { - while (game.whoseTurn().isPresent()) { - Player currentPlayer = game.whoseTurn().get(); - - if (currentPlayer.isFinished()) { - handlePlayerStandProcess(game); - continue; - } - + while (game.whosePlayTurn().isPresent()) { + Player currentPlayer = game.whosePlayTurn().get(); outputView.printHitOrStandPrompt(currentPlayer.getName()); - String hitOrStandInfo = retry(inputView::readHitOrStand); - doHitOrStand(hitOrStandInfo, game); + boolean wantToHit = retry(inputView::wantToHit); + doHitOrStand(wantToHit, game); } } - private void doHitOrStand(String hitOrStand, BlackJackGame game) { - if (hitOrStand.equals("y")) { + private void doHitOrStand(boolean wantToHit, BlackJackGame game) { + if (wantToHit) { handlePlayerHitProcess(game); } else { handlePlayerStandProcess(game); @@ -93,5 +94,4 @@ private T retry(Supplier supplier) { } } } -} - +} \ No newline at end of file diff --git a/src/main/java/domain/BetAmount.java b/src/main/java/domain/BetAmount.java new file mode 100644 index 0000000000..6869a6ee18 --- /dev/null +++ b/src/main/java/domain/BetAmount.java @@ -0,0 +1,36 @@ +package domain; + +import common.ErrorMessage; + +public record BetAmount( + int betAmount +) { + public static BetAmount of(String betAmountValue) { + int betAmount = parseBetAmount(betAmountValue); + validatePositiveNumber(betAmount); + return new BetAmount(betAmount); + } + + private static void validatePositiveNumber(int betAmount) { + if (betAmount <= 0) { + throw new IllegalArgumentException(ErrorMessage.ZERO_MINUS_MONEY.getMessage()); + } + } + + private static int parseBetAmount(String betAmountValue) { + try { + return Integer.parseInt(betAmountValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.ONLY_NUMBER.getMessage()); + } + } + + public static BetAmount empty() { + return new BetAmount(0); + } + + public boolean isBetPlaced() { + return betAmount != 0; + } +} + diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index cf0749a12d..727a071f76 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -4,6 +4,7 @@ import domain.state.GameState; import dto.DealerResultDto; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import dto.PlayerResultDto; import java.util.List; @@ -36,14 +37,24 @@ private static Dealer createNewDealer(Deck totalDeck) { dealersInitialCards.get(1) ); return Dealer.from( - GameState.createDealerInitialGameState(initialDealerHand) + GameState.createInitialGameState(initialDealerHand) ); } - public Optional whoseTurn() { + public Optional whoseBettingTurn() { + return multiPlayers.findNotBetPlayer(); + } + + public Optional whosePlayTurn() { return multiPlayers.findNotStayPlayer(); } + public void doBetProcess(String betAmountValue) { + Player target = multiPlayers.findNotBetPlayer() + .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_BETTABLE_PLAYER.getMessage())); + multiPlayers.executeBet(target, betAmountValue); + } + public ParticipantDto doHitProcess() { Player newPlayer = multiPlayers.findNotStayPlayer() .map(player -> multiPlayers.executeHit(player, totalDeck::drawCard)) @@ -64,12 +75,11 @@ public boolean doDealerHitOrStandProcess() { return !newDealer.gameState.isFinished(); } - public List getPlayersGameSettingStates() { - return multiPlayers.getInitialStates(); - } - - public ParticipantDto getDealerGameSettingState() { - return ParticipantDto.consistWithInitialInfo(dealer); + public GameStateDto getGameSettingState() { + return GameStateDto.from( + ParticipantDto.consistWithInitialInfo(dealer), + multiPlayers.getInitialStates() + ); } public GameResultDto getGameResults() { diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 311bd4cdd0..7eaa4230c9 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -6,6 +6,7 @@ public class Dealer extends Participant { private static final String DEALER_NAME = "딜러"; + private static final int DEALER_CARD_SUM_MIN = 16; private Dealer(GameState gameState) { super(DEALER_NAME, gameState); @@ -22,7 +23,11 @@ public List showInitialCard() { } public Dealer addCard(Supplier cardSupplier) { - GameState newGameState = gameState.hit(cardSupplier); - return Dealer.from(newGameState); + if (gameState.getCardsSum() <= DEALER_CARD_SUM_MIN) { + GameState newGameState = gameState.hit(cardSupplier); + return Dealer.from(newGameState); + } + GameState stayGameState = gameState.stay(); + return Dealer.from(stayGameState); } } diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java index e349546e6a..741695722c 100644 --- a/src/main/java/domain/GameResult.java +++ b/src/main/java/domain/GameResult.java @@ -3,9 +3,25 @@ import domain.state.GameState; public enum GameResult { - 승, 무, 패; + 블랙잭(1.5), + 승(1), + 무(0), + 패(-1); + + private final double allocation; + + GameResult(double allocation) { + this.allocation = allocation; + } + + public double getAllocation() { + return allocation; + } + + public static GameResult decidePlayerResult(Player player, Dealer dealer) { + GameState playerGameState = player.gameState; + GameState dealerGameState = dealer.gameState; - public static GameResult decidePlayerResult(GameState playerGameState, GameState dealerGameState) { if (playerGameState.isBust()) { return GameResult.패; } @@ -16,7 +32,7 @@ public static GameResult decidePlayerResult(GameState playerGameState, GameState return GameResult.무; } if (playerGameState.isBlackJack()) { - return GameResult.승; + return GameResult.블랙잭; } if (dealerGameState.isBlackJack()) { return GameResult.패; @@ -45,4 +61,4 @@ public GameResult reverse() { return GameResult.무; } -} +} \ No newline at end of file diff --git a/src/main/java/domain/MultiPlayers.java b/src/main/java/domain/MultiPlayers.java index fb78bcf9ab..fafb5f954a 100644 --- a/src/main/java/domain/MultiPlayers.java +++ b/src/main/java/domain/MultiPlayers.java @@ -4,8 +4,9 @@ import domain.state.GameState; import dto.ParticipantDto; import dto.PlayerResultDto; -import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -13,25 +14,22 @@ public class MultiPlayers { private static final int MAX_PLAYER_NUMBER = 5; - private final List players; + private final Map players; - private MultiPlayers(List players) { + private MultiPlayers(Map players) { this.players = players; } public static MultiPlayers of(List playerNames, Deck totalDeck) { - return new MultiPlayers( - new ArrayList<>(createPlayers(playerNames, totalDeck)) - ); - } - - private static List createPlayers(List playerNames, Deck totalDeck) { validateUserCountLimit(playerNames); validateNameUniqueness(playerNames); - return playerNames.stream() - .map(name -> createNewPlayer(totalDeck, name)) - .toList(); + Map newPlayers = new LinkedHashMap<>(); + for (String name : playerNames) { + newPlayers.put(name, createNewPlayer(totalDeck, name)); + } + + return new MultiPlayers(newPlayers); } private static Player createNewPlayer(Deck totalDeck, String name) { @@ -39,7 +37,8 @@ private static Player createNewPlayer(Deck totalDeck, String name) { Hand newPlayerHand = Hand.of(twoCards.get(0), twoCards.get(1)); return Player.from( name, - GameState.createPlayerInitialGameState(newPlayerHand)); + GameState.createInitialGameState(newPlayerHand) + ); } private static void validateNameUniqueness(List playerNames) { @@ -55,40 +54,50 @@ private static void validateUserCountLimit(List playerNames) { } } + public Optional findNotBetPlayer() { + return players.values().stream() + .filter(player -> !player.isBet()) + .findFirst(); + } + + public void executeBet(Player player, String betMoneyValue) { + applyAction(player.getName(), p -> p.bet(betMoneyValue)); + } + public Optional findNotStayPlayer() { - return players.stream() - .filter(player -> !player.isFinished()) + return players.values().stream() + .filter(Player::isPlayable) .findFirst(); } public Player executeHit(Player player, Supplier cardSupplier) { - return applyAction(player, p -> p.hit(cardSupplier)); + return applyAction(player.getName(), p -> p.hit(cardSupplier)); } public Player executeStand(Player player) { - return applyAction(player, Player::stand); + return applyAction(player.getName(), Player::stand); } public List getInitialStates() { - return players.stream() + return players.values().stream() .map(ParticipantDto::consistWithInitialInfo) .toList(); } public List checkPlayersGameResult(Dealer dealer) { - return players.stream().map( - player -> PlayerResultDto.from(player, dealer.gameState) - ).toList(); + return players.values().stream() + .map(player -> PlayerResultDto.from(player, dealer)) + .toList(); } - private Player applyAction(Player player, UnaryOperator action) { - int index = players.indexOf(player); - if (index == -1) { + private Player applyAction(String name, UnaryOperator action) { + Player player = players.get(name); + if (player == null) { throw new IllegalArgumentException(ErrorMessage.PLAYER_NOT_FOUND.getMessage()); } - Player newPlayer = action.apply(player); - players.set(index, newPlayer); - return newPlayer; + Player updatedPlayer = action.apply(player); + players.put(name, updatedPlayer); + return updatedPlayer; } -} +} \ No newline at end of file diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index df54ad2b6e..1286f24bc9 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -25,12 +25,4 @@ public List showOwnCards() { public int getOwnCardsSum() { return this.gameState.getCardsSum(); } -// -// public boolean isBust() { -// return gameState.is(); -// } -// -// public boolean isBlackJack() { -// return hand.isBlackJack(); -// } } \ No newline at end of file diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index a42adc58f9..800e0eec57 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -6,19 +6,39 @@ import java.util.function.Supplier; public class Player extends Participant { - private Player(String name, GameState gameState) { + private final BetAmount betAmount; + + private Player(String name, GameState gameState, BetAmount betAmount) { super(name, gameState); + this.betAmount = betAmount; } public static Player from(String name, GameState gameState) { - return new Player(name, gameState); + return new Player(name, gameState, BetAmount.empty()); + } + + public Player bet(String betAmountValue) { + return new Player( + this.participantName.name(), + this.gameState, + BetAmount.of(betAmountValue) + ); + } + + public boolean isBet() { + return betAmount.isBetPlaced(); + } + + public int getBetAmount() { + return betAmount.betAmount(); } public Player hit(Supplier cardSupplier) { GameState newGameState = gameState.hit(cardSupplier); return new Player( this.participantName.name(), - newGameState + newGameState, + this.betAmount ); } @@ -26,16 +46,21 @@ public Player stand() { GameState newStandGameState = gameState.stay(); return new Player( this.participantName.name(), - newStandGameState + newStandGameState, + this.betAmount ); } + public boolean isPlayable() { + return gameState.isPlayable(); + } + public boolean isFinished() { return gameState.isFinished(); } - public GameResult calculateGameResult(GameState dealerGameState) { - return gameState.compare(gameState, dealerGameState); + public GameResult calculateGameResult(Dealer dealer) { + return GameResult.decidePlayerResult(this, dealer); } @Override diff --git a/src/main/java/domain/state/BlackJackGameState.java b/src/main/java/domain/state/BlackJackGameState.java index ad1daa32d6..c102e304d0 100644 --- a/src/main/java/domain/state/BlackJackGameState.java +++ b/src/main/java/domain/state/BlackJackGameState.java @@ -2,7 +2,7 @@ import domain.Hand; -public class BlackJackGameState extends EndGameState { +public class BlackJackGameState extends FinishedGameState { public BlackJackGameState(Hand hand) { super(hand); } diff --git a/src/main/java/domain/state/BustGameState.java b/src/main/java/domain/state/BustGameState.java index 3c3ee63ddd..c30a79eac0 100644 --- a/src/main/java/domain/state/BustGameState.java +++ b/src/main/java/domain/state/BustGameState.java @@ -2,7 +2,7 @@ import domain.Hand; -public class BustGameState extends EndGameState { +public class BustGameState extends FinishedGameState { public BustGameState(Hand hand) { super(hand); } diff --git a/src/main/java/domain/state/CanHitGameState.java b/src/main/java/domain/state/CanHitGameState.java deleted file mode 100644 index 7ee31d331e..0000000000 --- a/src/main/java/domain/state/CanHitGameState.java +++ /dev/null @@ -1,23 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.Hand; -import java.util.function.Supplier; - -public abstract class CanHitGameState extends CommonGameState { - - public CanHitGameState(Hand hand) { - super(hand); - } - - @Override - public abstract GameState hit(Supplier cardSupplier); - - @Override - public abstract GameState stay(); - - @Override - public boolean isFinished() { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/domain/state/CommonGameState.java b/src/main/java/domain/state/CommonGameState.java deleted file mode 100644 index 3e5c4643b2..0000000000 --- a/src/main/java/domain/state/CommonGameState.java +++ /dev/null @@ -1,49 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.GameResult; -import domain.Hand; -import java.util.List; -import java.util.function.Supplier; - -public abstract class CommonGameState implements GameState { - protected final Hand hand; - - public CommonGameState(Hand hand) { - this.hand = hand; - } - - @Override - public abstract GameState hit(Supplier cardSupplier); - - @Override - public abstract GameState stay(); - - @Override - public boolean isBlackJack() { - return hand.isBlackJack(); - } - - @Override - public boolean isBust() { - return hand.isBust(); - } - - @Override - public abstract boolean isFinished(); - - @Override - public List showOwnCards() { - return hand.showCards(); - } - - @Override - public int getCardsSum() { - return hand.calculateCardScoreSum(); - } - - @Override - public GameResult compare(GameState playerState, GameState dealerState) { - return GameResult.decidePlayerResult(playerState, dealerState); - } -} diff --git a/src/main/java/domain/state/DealerCanHitGameState.java b/src/main/java/domain/state/DealerCanHitGameState.java deleted file mode 100644 index 396ef39188..0000000000 --- a/src/main/java/domain/state/DealerCanHitGameState.java +++ /dev/null @@ -1,35 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.Hand; -import java.util.function.Supplier; - -public class DealerCanHitGameState extends CanHitGameState { - private static final int MINIMUM_TOTAL_SCORE = 16; - - public DealerCanHitGameState(Hand hand) { - super(hand); - } - - @Override - public GameState hit(Supplier cardSupplier) { - Hand newHand = this.hand.addCard(cardSupplier.get()); - if (newHand.isBust()) { - return new BustGameState(newHand); - } - - if (newHand.calculateCardScoreSum() > MINIMUM_TOTAL_SCORE) { - return new StayGameState(newHand); - } - - return new DealerCanHitGameState(newHand); - } - - @Override - public GameState stay() { - if (hand.calculateCardScoreSum() > MINIMUM_TOTAL_SCORE) { - return new StayGameState(hand); - } - return this; - } -} diff --git a/src/main/java/domain/state/EndGameState.java b/src/main/java/domain/state/FinishedGameState.java similarity index 74% rename from src/main/java/domain/state/EndGameState.java rename to src/main/java/domain/state/FinishedGameState.java index 8034c42ad9..498ef7a945 100644 --- a/src/main/java/domain/state/EndGameState.java +++ b/src/main/java/domain/state/FinishedGameState.java @@ -5,8 +5,8 @@ import domain.Hand; import java.util.function.Supplier; -public abstract class EndGameState extends CommonGameState { - public EndGameState(Hand hand) { +public abstract class FinishedGameState extends StartedGameState { + protected FinishedGameState(Hand hand) { super(hand); } @@ -24,4 +24,9 @@ public GameState stay() { public boolean isFinished() { return true; } + + @Override + public boolean isPlayable() { + return false; + } } diff --git a/src/main/java/domain/state/GameState.java b/src/main/java/domain/state/GameState.java index 56c3c3d0d0..d24dc9124e 100644 --- a/src/main/java/domain/state/GameState.java +++ b/src/main/java/domain/state/GameState.java @@ -1,39 +1,31 @@ package domain.state; import domain.Card; -import domain.GameResult; import domain.Hand; import java.util.List; import java.util.function.Supplier; public interface GameState { - static GameState createPlayerInitialGameState(Hand hand) { + static GameState createInitialGameState(Hand hand) { if (hand.isBlackJack()) { return new BlackJackGameState(hand); } - return new PlayerCanHitGameState(hand); - } - - static GameState createDealerInitialGameState(Hand hand) { - if (hand.isBlackJack()) { - return new BlackJackGameState(hand); - } - return new DealerCanHitGameState(hand); + return new RunningGameState(hand); } GameState hit(Supplier cardSupplier); GameState stay(); + boolean isPlayable(); + + boolean isFinished(); + boolean isBlackJack(); boolean isBust(); - boolean isFinished(); - List showOwnCards(); int getCardsSum(); - - GameResult compare(GameState playerState, GameState dealerState); } \ No newline at end of file diff --git a/src/main/java/domain/state/PlayerCanHitGameState.java b/src/main/java/domain/state/PlayerCanHitGameState.java deleted file mode 100644 index 9d731953e6..0000000000 --- a/src/main/java/domain/state/PlayerCanHitGameState.java +++ /dev/null @@ -1,25 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.Hand; -import java.util.function.Supplier; - -public class PlayerCanHitGameState extends CanHitGameState { - public PlayerCanHitGameState(Hand hand) { - super(hand); - } - - @Override - public GameState hit(Supplier cardSupplier) { - Hand newHand = hand.addCard(cardSupplier.get()); - if (newHand.isBust()) { - return new BustGameState(newHand); - } - return new PlayerCanHitGameState(newHand); - } - - @Override - public GameState stay() { - return new StayGameState(hand); - } -} diff --git a/src/main/java/domain/state/RunningGameState.java b/src/main/java/domain/state/RunningGameState.java new file mode 100644 index 0000000000..5ed72a4980 --- /dev/null +++ b/src/main/java/domain/state/RunningGameState.java @@ -0,0 +1,45 @@ +package domain.state; + +import domain.Card; +import domain.Hand; +import java.util.function.Supplier; + +public class RunningGameState extends StartedGameState { + + public RunningGameState(Hand hand) { + super(hand); + } + + @Override + public GameState hit(Supplier cardSupplier) { + Hand newHand = hand.addCard(cardSupplier.get()); + if (newHand.isBust()) { + return new BustGameState(newHand); + } + + if (newHand.isBlackJack()) { + return new BlackJackGameState(newHand); + } + + if (newHand.isFull()) { + return new StayGameState(newHand); + } + + return new RunningGameState(newHand); + } + + @Override + public GameState stay() { + return new StayGameState(hand); + } + + @Override + public boolean isPlayable() { + return true; + } + + @Override + public boolean isFinished() { + return false; + } +} diff --git a/src/main/java/domain/state/StartedGameState.java b/src/main/java/domain/state/StartedGameState.java new file mode 100644 index 0000000000..d8ce744407 --- /dev/null +++ b/src/main/java/domain/state/StartedGameState.java @@ -0,0 +1,33 @@ +package domain.state; + +import domain.Card; +import domain.Hand; +import java.util.List; + +public abstract class StartedGameState implements GameState { + protected final Hand hand; + + protected StartedGameState(Hand hand) { + this.hand = hand; + } + + @Override + public boolean isBlackJack() { + return hand.isBlackJack(); + } + + @Override + public boolean isBust() { + return hand.isBust(); + } + + @Override + public List showOwnCards() { + return hand.showCards(); + } + + @Override + public int getCardsSum() { + return hand.calculateCardScoreSum(); + } +} diff --git a/src/main/java/domain/state/StayGameState.java b/src/main/java/domain/state/StayGameState.java index 4b1daee655..f705ffe085 100644 --- a/src/main/java/domain/state/StayGameState.java +++ b/src/main/java/domain/state/StayGameState.java @@ -2,7 +2,7 @@ import domain.Hand; -public class StayGameState extends EndGameState { +public class StayGameState extends FinishedGameState { public StayGameState(Hand hand) { super(hand); } diff --git a/src/main/java/dto/DealerResultDto.java b/src/main/java/dto/DealerResultDto.java index 0a1748b4de..85f66418e1 100644 --- a/src/main/java/dto/DealerResultDto.java +++ b/src/main/java/dto/DealerResultDto.java @@ -1,32 +1,20 @@ package dto; import domain.Dealer; -import domain.GameResult; -import java.util.EnumMap; import java.util.List; -import java.util.Map; public record DealerResultDto( ParticipantDto dealerDto, int score, - Map dealerResult + double dealerEarnMoney ) { public static DealerResultDto from(Dealer dealer, List playerResults) { - Map totalResult = initializeTotalResult(); + double earnMoney = 0; - playerResults.stream() - .map(PlayerResultDto::result) - .map(GameResult::reverse) - .forEach(result -> totalResult.merge(result, 1, Integer::sum)); - - return new DealerResultDto(ParticipantDto.from(dealer), dealer.getOwnCardsSum(), totalResult); - } - - private static Map initializeTotalResult() { - Map map = new EnumMap<>(GameResult.class); - for (GameResult result : GameResult.values()) { - map.put(result, 0); + for (PlayerResultDto playerResult : playerResults) { + earnMoney += playerResult.playerEarnMoney() * -1; } - return map; + + return new DealerResultDto(ParticipantDto.from(dealer), dealer.getOwnCardsSum(), earnMoney); } } \ No newline at end of file diff --git a/src/main/java/dto/GameStateDto.java b/src/main/java/dto/GameStateDto.java new file mode 100644 index 0000000000..eced367a61 --- /dev/null +++ b/src/main/java/dto/GameStateDto.java @@ -0,0 +1,13 @@ +package dto; + +import java.util.List; + +public record GameStateDto( + ParticipantDto dealerDto, + List multiPlayersDtos +) { + + public static GameStateDto from(ParticipantDto dealerDto, List multiPlayersDtos) { + return new GameStateDto(dealerDto, multiPlayersDtos); + } +} diff --git a/src/main/java/dto/PlayerResultDto.java b/src/main/java/dto/PlayerResultDto.java index 54e790762e..632c13dc0e 100644 --- a/src/main/java/dto/PlayerResultDto.java +++ b/src/main/java/dto/PlayerResultDto.java @@ -1,19 +1,21 @@ package dto; +import domain.Dealer; import domain.GameResult; import domain.Player; -import domain.state.GameState; public record PlayerResultDto( ParticipantDto playerDto, int score, - GameResult result + double playerEarnMoney ) { - public static PlayerResultDto from(Player player, GameState dealerGameState) { + public static PlayerResultDto from(Player player, Dealer dealer) { + GameResult gameResult = player.calculateGameResult(dealer); + double earnMoney = gameResult.getAllocation() * player.getBetAmount(); return new PlayerResultDto( ParticipantDto.from(player), player.getOwnCardsSum(), - player.calculateGameResult(dealerGameState) + earnMoney ); } -} +} \ No newline at end of file diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index d482849d6d..8cfa4c9599 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -5,5 +5,7 @@ public interface InputView { List readNames(); - String readHitOrStand(); + String readBetAmountValue(); + + Boolean wantToHit(); } diff --git a/src/main/java/view/InputViewImpl.java b/src/main/java/view/InputViewImpl.java index f66b3d6ac0..bf0549947a 100644 --- a/src/main/java/view/InputViewImpl.java +++ b/src/main/java/view/InputViewImpl.java @@ -16,11 +16,17 @@ public List readNames() { return Arrays.stream(line.split(DELIMITER)).toList(); } - public String readHitOrStand() { + public String readBetAmountValue() { + String line = sc.nextLine(); + validateIsBlank(line); + return line; + } + + public Boolean wantToHit() { String input = sc.nextLine().trim(); validateIsBlank(input); validateHitOrStandValue(input); - return input; + return input.equals("y"); } private void validateIsBlank(String line) { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 49a5d4b41b..44b27b4b42 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,15 +1,17 @@ package view; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; -import java.util.List; public interface OutputView { void printErrorMessage(Exception e); void printNamePrompt(); - void printInitialStates(ParticipantDto dealerDto, List players); + void printBetAmountPrompt(String name); + + void printInitialStates(GameStateDto gameStateDto); void printHitOrStandPrompt(String name); diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java index 19c2ea8104..fcd749c6dc 100644 --- a/src/main/java/view/OutputViewImpl.java +++ b/src/main/java/view/OutputViewImpl.java @@ -1,25 +1,27 @@ package view; -import domain.GameResult; import dto.CardDto; import dto.DealerResultDto; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import dto.PlayerResultDto; +import java.text.NumberFormat; import java.util.List; -import java.util.Map; +import java.util.Locale; import java.util.stream.Collectors; public class OutputViewImpl implements OutputView { private static final String DELIMITER = ", "; private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; + private static final String BET_AMOUNT_PROMPT = "\n%s의 배팅 금액은?\n"; private static final String INITIAL_CARD_SHARE = "\n딜러와 %s에게 2장을 나누었습니다.\n"; private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; private static final String DEALER_ADD_CARD_NOTICE = "\n딜러는 16이하라 한장의 카드를 더 받았습니다.\n"; private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; private static final String WIN_LOSS_RESULT_HEADER = "\n## 최종 승패"; private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; - private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; + private static final String RESULT_FORMAT = "%s: %s\n"; public void printErrorMessage(Exception e) { System.out.println(e.getMessage()); @@ -29,15 +31,20 @@ public void printNamePrompt() { System.out.println(NAME_PROMPT); } - public void printInitialStates(ParticipantDto dealerDto, List players) { - String playerNames = players.stream() + public void printBetAmountPrompt(String name) { + System.out.printf(BET_AMOUNT_PROMPT, name); + } + + public void printInitialStates(GameStateDto gameStateDto) { + List playersDtos = gameStateDto.multiPlayersDtos(); + ParticipantDto dealerDto = gameStateDto.dealerDto(); + String playerNames = playersDtos.stream() .map(ParticipantDto::name) .collect(Collectors.joining(DELIMITER)); System.out.printf(INITIAL_CARD_SHARE, playerNames); - printUserState(dealerDto); - players.forEach(this::printUserState); + playersDtos.forEach(this::printUserState); System.out.println(); } @@ -78,9 +85,7 @@ private void printFinalWinLoss(GameResultDto gameResultDto) { System.out.print(formatDealerResult(dealerResultDto)); List playerResultDtos = gameResultDto.playerResultDto(); - playerResultDtos.forEach(dto -> - System.out.printf(WIN_LOSS_RESULT_FORMAT, dto.playerDto().name(), dto.result().name()) - ); + playerResultDtos.forEach(dto -> System.out.printf(formatPlayerResult(dto))); } private String formatParticipantCards(ParticipantDto participantDto) { @@ -96,18 +101,20 @@ private String formatCardsInfo(List cards) { private String formatDealerResult(DealerResultDto dealerResultDto) { String dealerName = dealerResultDto.dealerDto().name(); - String resultString = formatDealerResultInfo(dealerResultDto.dealerResult()); - return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, resultString); - } - - private String formatDealerResultInfo(Map dealerGameResult) { - StringBuilder sb = new StringBuilder(); - for (GameResult result : GameResult.values()) { - int count = dealerGameResult.getOrDefault(result, 0); - if (count > 0) { - sb.append(count).append(result.name()).append(" "); - } - } - return sb.toString().trim(); + double dealerEarnMoney = dealerResultDto.dealerEarnMoney(); + String formattedDealerEarnMoney = formatToKoreanCurrencyInstance(dealerEarnMoney); + return String.format(RESULT_FORMAT, dealerName, formattedDealerEarnMoney); + } + + private String formatPlayerResult(PlayerResultDto playerResultDto) { + String dealerName = playerResultDto.playerDto().name(); + double dealerEarnMoney = playerResultDto.playerEarnMoney(); + String formattedPlayerEarnMoney = formatToKoreanCurrencyInstance(dealerEarnMoney); + return String.format(RESULT_FORMAT, dealerName, formattedPlayerEarnMoney); + } + + private String formatToKoreanCurrencyInstance(double money) { + NumberFormat krwFormat = NumberFormat.getCurrencyInstance(Locale.KOREA); + return krwFormat.format(money); } } diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index 52fc1485e8..161c3d620f 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -7,6 +7,7 @@ import domain.CardCreationStrategy; import domain.CardShape; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import java.util.ArrayDeque; import java.util.Deque; @@ -40,7 +41,8 @@ void askPlayerNames_success() { static class TestInputViewImpl implements InputView { - private static final Deque hitOrStandOrder = new ArrayDeque<>(List.of("y", "n", "n", "n")); + private static final Deque hitOrStandOrder = new ArrayDeque<>( + List.of(Boolean.TRUE, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE)); @Override public List readNames() { @@ -48,7 +50,12 @@ public List readNames() { } @Override - public String readHitOrStand() { + public String readBetAmountValue() { + return "100000"; + } + + @Override + public Boolean wantToHit() { return hitOrStandOrder.poll(); } } @@ -64,7 +71,11 @@ public void printNamePrompt() { } @Override - public void printInitialStates(ParticipantDto dealerDto, List players) { + public void printBetAmountPrompt(String name) { + } + + @Override + public void printInitialStates(GameStateDto gameStateDto) { } @Override diff --git a/src/test/java/domain/BetAmountTest.java b/src/test/java/domain/BetAmountTest.java new file mode 100644 index 0000000000..6e93aa4400 --- /dev/null +++ b/src/test/java/domain/BetAmountTest.java @@ -0,0 +1,61 @@ +package domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import common.ErrorMessage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BetAmountTest { + + @Test + @DisplayName("숫자가 아닌 값으로 생성 시도 시 오류 발생") + void of_fail_not_number() { + //given + String notNumber = "NaN"; + + //when + assertThatThrownBy( + () -> BetAmount.of(notNumber) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.ONLY_NUMBER.getMessage()); + } + + @Test + @DisplayName("양수가 아니면 오류 발생") + void of_fail_not_positive_num() { + //given + String notPosNum = "-1"; + //when + assertThatThrownBy( + () -> BetAmount.of(notPosNum) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.ZERO_MINUS_MONEY.getMessage()); + + } + + @Test + @DisplayName("양수면 정상 생성") + void of_good() { + String posNum = "10000"; + + assertDoesNotThrow( + () -> BetAmount.of(posNum) + ); + } + + @Test + @DisplayName("0이면 베팅을 안한 것으로 친다") + void zero_mean_not_bet_yet() { + //given + String zero = "0"; + + //when + BetAmount testBetAmount = BetAmount.of(zero); + + //then + assertTrue(testBetAmount.isBetPlaced()); + } +} \ No newline at end of file diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java index 604980487d..1021e32465 100644 --- a/src/test/java/domain/BlackJackGameTest.java +++ b/src/test/java/domain/BlackJackGameTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import java.util.ArrayDeque; import java.util.ArrayList; @@ -23,25 +24,43 @@ class BlackJackGameTest { @Test @DisplayName("생성 잘 한다") void ready_good() { - //given - Deck sampleDeck = Deck.createDeck(this::createSampleCards); - //when, then assertDoesNotThrow( () -> BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards) ); } + @Test + @DisplayName("베팅을 안한 사용자가 있으면 제공한다") + void whoseBettingTurn_exist() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + Optional result = testGame.whoseBettingTurn(); + + assertTrue(result.isPresent()); + } + @Test @DisplayName("다음 턴의 사용자가 있으면 제공한다") - void whoseTure_success() { + void whosePlayTurn_exist() { BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); - Optional result = testGame.whoseTurn(); + Optional result = testGame.whosePlayTurn(); assertTrue(result.isPresent()); } + @Test + @DisplayName("bet을 시켜도 오류가 안난다") + void doBetProcess_success() { + String betAmountValue = "1000"; + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + assertDoesNotThrow( + () -> testGame.doBetProcess(betAmountValue) + ); + } + @Test @DisplayName("hit 프로세스를 적합한 턴의 플레이어에게 요청하고 결과로 ParticipantDto를 반환받는다") void doHitProcess_success() { @@ -125,21 +144,16 @@ void doDealerHitOrStandProcess_return_false() { } @Test - @DisplayName("플레이어들의 게임 초기 상태를 인원 수 대로 잘 가져온다") - void getPlayersGameSettingStates_good() { - BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); - - int expectSize = TEST_PLAYER_NAMES.size(); - int resultSize = testGame.getPlayersGameSettingStates().size(); - assertEquals(expectSize, resultSize); - } - - @Test - @DisplayName("딜러의 게임 초기 상태를 ParticipantDto 형태로 잘 받는다") + @DisplayName("게임 초기 상태를 GameStateDto 형태로 잘 받고, 플레이어 정보도 인원 수 만큼 존재한다") void getDealerGameSettingState_good() { BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + int expectMultiPlayersCount = TEST_PLAYER_NAMES.size(); + + GameStateDto gameSettingState = testGame.getGameSettingState(); - assertEquals(ParticipantDto.class, testGame.getDealerGameSettingState().getClass()); + int resultMultiPlayerCount = gameSettingState.multiPlayersDtos().size(); + assertEquals(GameStateDto.class, gameSettingState.getClass()); + assertEquals(expectMultiPlayersCount, resultMultiPlayerCount); } @Test diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index a07d396958..911c97b04f 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -14,7 +14,7 @@ public class DealerTest { @Test @DisplayName("Dealer를 생성할 때 오류 발생 안함") void dealer_create_success() { - GameState dealerGameState = GameState.createDealerInitialGameState( + GameState dealerGameState = GameState.createInitialGameState( Hand.of( new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.FIVE) @@ -34,7 +34,7 @@ void addCard_success() { }; Deck totalDeck = Deck.createDeck(onlyJCardCreation); - GameState dealerGameState = GameState.createDealerInitialGameState( + GameState dealerGameState = GameState.createInitialGameState( Hand.of( new Card(CardShape.하트, CardContents.SIX), new Card(CardShape.스페이드, CardContents.TEN) diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index a81a0ceaf1..d74e40aed5 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -7,7 +7,6 @@ import common.ErrorMessage; import java.util.ArrayDeque; -import java.util.Deque; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -16,14 +15,11 @@ class DeckTest { Deck deck; - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public Deque create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + CardCreationStrategy fixedCardCreationStrategy = () -> { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - return new ArrayDeque<>(List.of(spadeJ, clover5)); - } + return new ArrayDeque<>(List.of(spadeJ, clover5)); }; @BeforeEach diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java index 4a740c0d13..e52b31ea0c 100644 --- a/src/test/java/domain/GameResultTest.java +++ b/src/test/java/domain/GameResultTest.java @@ -12,6 +12,9 @@ import org.junit.jupiter.api.Test; class GameResultTest { + + private static final String GUMP = "gump"; + Hand normalHand = Hand.of( new Card(CardShape.다이아몬드, CardContents.A), new Card(CardShape.다이아몬드, CardContents.TWO) @@ -31,28 +34,28 @@ class decidePlayerResultTest { class blackJackCaseTest { @Test - @DisplayName("플레이어가 블랙잭이면 플레이어가 이긴 결과를 도출한다") + @DisplayName("플레이어가 블랙잭이면 플레이어가 블랙잭으로 이긴 결과를 도출한다") void decidePlayerResult_player_blackjack() { //given - GameState dealerInitialGameState = GameState.createDealerInitialGameState(normalHand); - GameState playerInitialGameState = GameState.createPlayerInitialGameState(blackJackHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(normalHand)); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(blackJackHand)); //when - GameResult result = GameResult.decidePlayerResult(playerInitialGameState, dealerInitialGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then - assertEquals(GameResult.승, result); + assertEquals(GameResult.블랙잭, result); } @Test @DisplayName("딜러가 블랙잭이면 플레이어가 진 결과를 도출한다") void decidePlayerResult_dealer_blackjack() { //given - GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); - GameState playerGameState = GameState.createPlayerInitialGameState(normalHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(normalHand)); //when - GameResult result = GameResult.decidePlayerResult(playerGameState, dealerGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.패, result); @@ -67,11 +70,11 @@ void decidePlayerResult_both_blackjack() { new Card(CardShape.하트, CardContents.TEN) ); - GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); - GameState playerGameState = GameState.createPlayerInitialGameState(anotherBlackJackHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(anotherBlackJackHand)); //when - GameResult result = GameResult.decidePlayerResult(playerGameState, dealerGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.무, result); @@ -93,16 +96,13 @@ class bustCaseTest { @DisplayName("플레이어가 bust 이면 패 결과를 반환") void player_bust_case() { //given - GameState dealerGameState = GameState.createDealerInitialGameState(normalHand); - GameState playerGameState = GameState.createPlayerInitialGameState(handThatValue16); - Player testPlayer = Player.from( - "gump", - playerGameState - ); + + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(handThatValue16)); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(normalHand)); testPlayer = testPlayer.hit(testTotalDeck::drawCard); //when - GameResult result = GameResult.decidePlayerResult(testPlayer.gameState, dealerGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.패, result); @@ -112,15 +112,13 @@ void player_bust_case() { @DisplayName("딜러가 bust 이면 승 결과를 반환") void dealer_bust_case() { //given - GameState dealerGameState = GameState.createDealerInitialGameState(handThatValue16); - GameState playerGameState = GameState.createPlayerInitialGameState(normalHand); - Dealer testDealer = Dealer.from( - dealerGameState - ); - Dealer newDealer = testDealer.addCard(testTotalDeck::drawCard); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(normalHand)); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(handThatValue16)); + + testDealer = testDealer.addCard(testTotalDeck::drawCard); //when - GameResult result = GameResult.decidePlayerResult(playerGameState, newDealer.gameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.승, result); diff --git a/src/test/java/domain/MultiPlayersTest.java b/src/test/java/domain/MultiPlayersTest.java index 70e3c28978..b10346bc6f 100644 --- a/src/test/java/domain/MultiPlayersTest.java +++ b/src/test/java/domain/MultiPlayersTest.java @@ -70,6 +70,43 @@ void of_fail_too_many_players() { } } + @Nested + class findNotBetPlayer { + @Test + @DisplayName("isBet가 False인 사용자가 있으면 Player를 반환한다") + void findNotStayPlayer_exist() { + //given + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + + //when + Optional result = testMultiPlayers.findNotStayPlayer(); + + //then + assertTrue(result.isPresent()); + } + + @Test + @DisplayName("모든 플레이어가 베팅을 끝내면 없으면 빈 Optional를 반환한다") + void findNotStayPlayer_not_exist() { + //given + String commonBetAmountValue = "1000"; + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + while (true) { + Optional findResult = testMultiPlayers.findNotBetPlayer(); + if (findResult.isEmpty()) { + break; + } + testMultiPlayers.executeBet(findResult.get(), commonBetAmountValue); + } + + //when + Optional result = testMultiPlayers.findNotBetPlayer(); + + //then + assertTrue(result.isEmpty()); + } + } + @Nested class findNotStayPlayerTest { @Test @@ -86,7 +123,7 @@ void findNotStayPlayer_exist() { } @Test - @DisplayName("stay가 아닌 사용자가 없으면 빈 Optional를 반환한다") + @DisplayName("모든 Player가 종료된 상태가 되면 빈 Optional를 반환한다") void findNotStayPlayer_not_exist() { //given MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); @@ -135,6 +172,20 @@ void executeStand_good() { //then assertEquals(Player.class, result.getClass()); } + + @Test + @DisplayName("양수의 금액으로 bet를 플레이이에게 요청 시 오류가 발생하지 않는다.") + void executeBet_good() { + //given + String betMoney = "10000"; + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + Player testPlayer = testMultiPlayers.findNotStayPlayer().get(); + + //when, then + assertDoesNotThrow( + () -> testMultiPlayers.executeBet(testPlayer, betMoney) + ); + } } @Test @@ -151,7 +202,7 @@ void getInitialState_success() { ParticipantDto expect = ParticipantDto.from( Player.from( testerName, - GameState.createPlayerInitialGameState(Hand.of(heartTen, heartJ)) + GameState.createInitialGameState(Hand.of(heartTen, heartJ)) ) ); @@ -176,15 +227,15 @@ void checkPlayersGameResult_success() { Queue onlyTwoTenCards = new LinkedList<>(List.of(heartTen, heartJ)); Dealer testDealer = Dealer.from( - GameState.createDealerInitialGameState(Hand.of(heartTwo, heartThree)) + GameState.createInitialGameState(Hand.of(heartTwo, heartThree)) ); Player expectedPlayer = Player.from( testerName, - GameState.createPlayerInitialGameState(Hand.of(heartTen, heartJ)) + GameState.createInitialGameState(Hand.of(heartTen, heartJ)) ); CardCreationStrategy onlyTwoTenCardsCreationStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); MultiPlayers multiPlayers = MultiPlayers.of(onlyOneNames, Deck.createDeck(onlyTwoTenCardsCreationStrategy)); - PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer.gameState); + PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer); //when List result = multiPlayers.checkPlayersGameResult(testDealer); diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 049f658fc4..5c34cfccc9 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import common.ErrorMessage; import domain.state.GameState; @@ -16,6 +17,8 @@ import org.junit.jupiter.api.Test; class PlayerTest { + private static final String GUMP = "gump"; + @Test @DisplayName("Player를 생성할 때 오류 발생 안함") void player_create_success() { @@ -24,10 +27,8 @@ void player_create_success() { new Card(CardShape.클로버, CardContents.K) ); - String name = "pobi"; - assertDoesNotThrow( - () -> Player.from(name, GameState.createPlayerInitialGameState(playerHand)) + () -> Player.from(GUMP, GameState.createInitialGameState(playerHand)) ); } @@ -45,10 +46,9 @@ class hitTest { )); Supplier onlyTwoTenCardSupplier = onlyTwoTenCards::poll; - String testName = "gump"; Player testPlayerWhoHoldTotal15Cards = Player.from( - testName, - GameState.createPlayerInitialGameState(playerHand) + GUMP, + GameState.createInitialGameState(playerHand) ); @Test @@ -88,10 +88,9 @@ void hit_butBust_and_finish() { new Card(CardShape.스페이드, CardContents.TEN), new Card(CardShape.클로버, CardContents.TEN) ); - String testName = "gump"; Player testPlayer = Player.from( - testName, - GameState.createPlayerInitialGameState(playerHand) + GUMP, + GameState.createInitialGameState(playerHand) ); testPlayer.hit(onlyTwoTenCardSupplier); @@ -111,10 +110,9 @@ void stand_and_finish() { new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.FIVE) ); - String testName = "gump"; Player testPlayer = Player.from( - testName, - GameState.createPlayerInitialGameState(playerHand) + GUMP, + GameState.createInitialGameState(playerHand) ); //when @@ -128,7 +126,6 @@ void stand_and_finish() { @DisplayName("compare는 게임 결과 객체를 잘 반환한다") void lose_when_dealer_blackjack() { //given - String testPlayerName = "rati"; Hand blackJackHand = Hand.of( new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.A) @@ -137,14 +134,14 @@ void lose_when_dealer_blackjack() { new Card(CardShape.스페이드, CardContents.TWO), new Card(CardShape.클로버, CardContents.THREE) ); - GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); Player testPlayer = Player.from( - testPlayerName, - GameState.createPlayerInitialGameState(notBlackJackAndNotBustHand) + GUMP, + GameState.createInitialGameState(notBlackJackAndNotBustHand) ); //when - GameResult result = testPlayer.calculateGameResult(dealerGameState); + GameResult result = testPlayer.calculateGameResult(testDealer); //then assertEquals(GameResult.class, result.getClass()); @@ -153,7 +150,6 @@ void lose_when_dealer_blackjack() { @Test @DisplayName("이름이 같으면 같은 Player로 본다") void equal_when_name_equal() { - String testName = "gump"; Hand playerHand1 = Hand.of( new Card(CardShape.스페이드, CardContents.A), new Card(CardShape.클로버, CardContents.TWO) @@ -163,14 +159,36 @@ void equal_when_name_equal() { new Card(CardShape.클로버, CardContents.FOUR) ); Player firstGump = Player.from( - testName, - GameState.createPlayerInitialGameState(playerHand1) + GUMP, + GameState.createInitialGameState(playerHand1) ); Player secondGump = Player.from( - testName, - GameState.createPlayerInitialGameState(playerHand2) + GUMP, + GameState.createInitialGameState(playerHand2) ); //when, then Assertions.assertThat(firstGump.equals(secondGump)).isTrue(); } + + @Test + @DisplayName("베팅을 하면 베팅 한 것으로 인정된다") + void bet_after_isBet_True() { + //given + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.클로버, CardContents.TWO) + ); + Player gump = Player.from( + GUMP, + GameState.createInitialGameState(playerHand) + ); + + String gumpBetAmountValue = "1000"; + + //when + Player bettedGump = gump.bet(gumpBetAmountValue); + + //then + assertTrue(bettedGump.isBet()); + } } \ No newline at end of file