-
Notifications
You must be signed in to change notification settings - Fork 0
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
[Lev1][미션3] 블랙잭 1단계 - 오잉 #1
base: hanueleee
Are you sure you want to change the base?
Changes from all commits
074adc6
2e463d4
461bd27
44f7607
464fed3
cf351a5
988d7a0
5ecf658
e1069e3
0a03f1e
b0f2aea
205100d
8bc2ee0
7a6efb8
86899c5
5a1d787
eaf1556
7f1ab24
8a7797e
fd7fa5e
cdc1882
bc72968
5f7a725
7109bc4
5d1c183
8392654
8b22ed2
84a5154
89819cc
e09d6d9
744892b
20884aa
f7afb0e
2f2d54c
1662ae6
f8c5f0c
37ac7a5
69e781f
1431d37
d29f513
0f74a75
7bcf603
6800d7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
## 블랙잭 게임 기능 목록 | ||
### UI | ||
- 입력 | ||
- [x] 참여할 사람의 이름 입력 | ||
- [x] 카드 추가 지급 의사 입력 | ||
|
||
- 출력 | ||
- [x] 카드 지급 완료 문구 출력 | ||
- [x] 개인 카드 목록 출력 | ||
- [x] 딜러 카드 추가 지급 여부 출력 | ||
- [x] 최종 카드 상태 출력 | ||
- [x] 최종 승패 출력 | ||
|
||
### 기능 | ||
- BlackJackGame : 게임 관리 시스템 | ||
- [x] 최종 승패를 계산한다 | ||
|
||
- Player : 게임 참여자 | ||
- [x] 초기 카드 2장을 받는다 | ||
- [x] 추가 카드를 뽑는다 | ||
- Challenger : 일반 참여자 (Player를 상속받는다) | ||
- [x] 이름을 입력받는다 | ||
- [x] 카드의 합이 21 초과인지 확인한다 | ||
- Dealer : 딜러 (Player를 상속받는다) | ||
- [x] 카드의 합이 16 초과인지 확인한다 | ||
|
||
- Players : Player 목록을 가지고 있는 일급 컬렉션 | ||
|
||
- Card : 카드 | ||
- [x] 카드 숫자를 보고 점수를 반환한다 (ex. Ace -> 1 or 11 // King, Queen, Jack -> 10) | ||
|
||
- CardDeck : 덱에 있는 카드. Card 목록을 가지고 있는 일급 컬렉션 | ||
- [x] 52장의 카드를 생성한다 | ||
- [x] 카드 순서를 무작위로 섞는다 | ||
- [x] 맨 위의 카드를 게임 참여자에게 전달한다 | ||
|
||
- HolingCards : 참여자가 소유하고 있는 카드. Card 목록을 가지고 있는 일급 컬렉션 | ||
- [x] 뽑은 카드를 저장한다 | ||
- [x] 가진 카드로 가능한 합 목록을 반환한다 | ||
- [x] 가진 카드들 중에 21보다 작은 가장 큰 값을 반환한다 | ||
|
||
- Result : 최종 결과를 계산하는 객체 | ||
- [x] 도전자들의 최종 승패를 계산한다. | ||
- [x] 딜러의 최종 승패를 계산한다. | ||
|
||
### 예외 사항 | ||
- [x] 플레이어 이름 중복 시 예외가 발생한다 | ||
- [x] 플레이어 이름이 `딜러`인 경우 예외가 발생한다 | ||
|
||
- [x] 카드를 더 받겠느냐는 질문에 `y`, `n`을 입력하지 않으면 예외가 발생한다 | ||
- [x] 더 이상 뽑을 카드가 없으면 카드 덱이 비어있다는 예외가 발생한다 | ||
|
||
--------- | ||
## 고민 | ||
- dto에도 정적 팩토리 메소드를 써야할까? | ||
- playerStatusDto(필드2개) & playerResultDto(필드3개) -> 필드2개 겹치는데 두 dto를 따로 만들어야하나? 아니면 필드를 안쓰더라도 하나로 통합?(중복방현) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중복이라고 생각된다면 생성자를 두 개 만들 수 도 있을 것 같은데? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 나같은 경우에는 그냥 따로 만들어주는 편, 각자의 변경주기가 다르다고 생각해서? |
||
- else if 사용? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack; | ||
|
||
import blackjack.controller.BlackJackController; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
BlackJackController blackJackController = new BlackJackController(); | ||
blackJackController.run(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack.common.exception; | ||
|
||
public class CustomException extends RuntimeException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CustomException 이라는 네이밍이, 조금만 더 문맥을 담고 있어도 괜찮을 것 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 누누의 의견에 동의합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이번 미션은 규모가 크지 않아서 굳이 최상단 익셉션명이 문맥을 담을 필요 없이 CustomException로 둬도 된다고 생각했습니다. |
||
|
||
private static final String ERROR_PREFIX = "[ERROR] "; | ||
|
||
public CustomException(String message) { | ||
super(ERROR_PREFIX + message); | ||
Comment on lines
+5
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분을 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 custom exception을 한번도 써본적이 없어서 연습삼아 이번 미션에서 처음으로 적용해본건데, |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package blackjack.controller; | ||
|
||
import blackjack.common.exception.CustomException; | ||
import blackjack.domain.BlackJackGame; | ||
import blackjack.domain.card.Card; | ||
import blackjack.domain.player.Dealer; | ||
import blackjack.domain.player.Player; | ||
import blackjack.domain.result.ResultMap; | ||
import blackjack.domain.result.ResultType; | ||
import blackjack.dto.ChallengerResultDto; | ||
import blackjack.dto.DealerResultDto; | ||
import blackjack.dto.PlayerStatusDto; | ||
import blackjack.view.InputView; | ||
import blackjack.view.OutputView; | ||
import java.util.ArrayList; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
public class BlackJackController { | ||
|
||
private BlackJackGame blackJackGame; | ||
|
||
public void run() { | ||
init(); | ||
start(); | ||
takeTurn(); | ||
showResult(); | ||
} | ||
|
||
private void init() { | ||
try { | ||
List<String> playerNames = InputView.inputPlayerNames(); | ||
blackJackGame = BlackJackGame.from(playerNames); | ||
} catch (CustomException e) { | ||
OutputView.printErrorMessage(e); | ||
init(); | ||
Comment on lines
+33
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 리뷰어 분들도 재귀함수는 지양하라는 내용들을 많이 봤었어서요 재귀 대신 while 로 바꿔보면 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 키워드 : call stack overflow 다른 언어는 꼬리재귀라는 키워드를 통해 이러한 문제를 해결할 수 있다고 합니다~ 시간 남으면 찾아보셔도 좋을것같네요ㅋㅋ (자바에서 의미는 없지만) |
||
} | ||
} | ||
|
||
private void start() { | ||
blackJackGame.handOutStartCards(); | ||
showStartStatus(); | ||
} | ||
|
||
private void showStartStatus() { | ||
PlayerStatusDto dealerStatus = makeDealerStatus(); | ||
List<PlayerStatusDto> challengersStatus = makeChallengersStatus(); | ||
OutputView.printStartStatus(dealerStatus, challengersStatus); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그냥 궁금해서 여쭤보는건데요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 아래가 더 낫다고 생각합니다. |
||
} | ||
|
||
private PlayerStatusDto makeDealerStatus() { | ||
Player dealer = blackJackGame.getDealer(); | ||
return makePlayerStatusDto(dealer); | ||
} | ||
|
||
private List<PlayerStatusDto> makeChallengersStatus() { | ||
List<Player> challengers = blackJackGame.getChallengers(); | ||
return challengers.stream() | ||
.map(challenger -> makePlayerStatusDto(challenger)) | ||
.collect(Collectors.toUnmodifiableList()); | ||
} | ||
|
||
private void takeTurn() { | ||
try { | ||
takeAllChallengersTurn(); | ||
takeDealerTurn(); | ||
} catch (CustomException e) { | ||
OutputView.printErrorMessage(e); | ||
} | ||
} | ||
|
||
private void takeAllChallengersTurn() { | ||
for (Player player : blackJackGame.getChallengers()) { | ||
takeEachChallengerTurn(player); | ||
} | ||
} | ||
|
||
private void takeEachChallengerTurn(Player player) { | ||
if (player.canPick()) { | ||
checkChoice(player); | ||
} | ||
} | ||
|
||
private void checkChoice(Player player) { | ||
try { | ||
inputChoice(player); | ||
} catch (CustomException e) { | ||
OutputView.printErrorMessage(e); | ||
checkChoice(player); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 while 로 바꿔보면 어떠신가요? |
||
} | ||
} | ||
|
||
private void inputChoice(Player player) { | ||
boolean choice = InputView.inputPlayerChoice(player.getName()); | ||
if (choice) { | ||
blackJackGame.hit(player); | ||
OutputView.printChallengerStatusInGame(makePlayerStatusDto(player)); | ||
takeEachChallengerTurn(player); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 다시 위쪽 함수로 실행하게 되는 구조로 되어있는데요 |
||
} | ||
} | ||
|
||
private void takeDealerTurn() { | ||
Player dealer = blackJackGame.getDealer(); | ||
boolean dealerCanPick = dealer.canPick(); | ||
if (dealerCanPick) { | ||
Comment on lines
+106
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굳이 변수로 할당한 이유가 있을까요? |
||
blackJackGame.hit(dealer); | ||
} | ||
OutputView.printDealerTurnResult(dealerCanPick, Dealer.MAXIMUM_POINT); | ||
} | ||
|
||
private void showResult() { | ||
showPoint(); | ||
showRank(); | ||
} | ||
|
||
private void showPoint() { | ||
PlayerStatusDto dealerStatus = makeDealerStatus(); | ||
List<PlayerStatusDto> challengersStatus = makeChallengersStatus(); | ||
OutputView.printEndStatus(dealerStatus, challengersStatus); | ||
} | ||
|
||
private void showRank() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rank는 순위라는 뜻으로 알고 있는데 순위를 출력하고 있지 않은 것 같아요 |
||
ResultMap resultMap = blackJackGame.makeResult(); | ||
|
||
ChallengerResultDto challengerResultDto = makeChallengerResultDto(resultMap, blackJackGame.getChallengers()); | ||
DealerResultDto dealerResultDto = makeDealerResultDto(resultMap, blackJackGame.getDealer()); | ||
OutputView.printEndRank(challengerResultDto, dealerResultDto); | ||
} | ||
|
||
private PlayerStatusDto makePlayerStatusDto(Player player) { | ||
String playerName = player.getName(); | ||
List<Card> inputCards = player.getHoldingCards().getCards(); | ||
int playerPoint = player.getTotalPoint(); | ||
return new PlayerStatusDto(playerName, makeCardInfo(inputCards), playerPoint); | ||
} | ||
|
||
private static List<String> makeCardInfo(List<Card> inputCards) { | ||
List<String> cardInfo = new ArrayList<>(); | ||
for (Card card : inputCards) { | ||
cardInfo.add(card.getSymbol().getName() + card.getShape().getName()); | ||
} | ||
return cardInfo; | ||
} | ||
|
||
private ChallengerResultDto makeChallengerResultDto(ResultMap resultMap, List<Player> challengers) { | ||
Map<String, String> nameAndResult = new LinkedHashMap<>(); | ||
for (Player challenger : challengers) { | ||
ResultType challengerResultType = resultMap.getChallengerResult(challenger); | ||
nameAndResult.put(challenger.getName(), challengerResultType.getLabel()); | ||
} | ||
return new ChallengerResultDto(nameAndResult); | ||
} | ||
|
||
private DealerResultDto makeDealerResultDto(ResultMap resultMap, Player dealer) { | ||
String dealerName = dealer.getName(); | ||
Map<ResultType, Integer> dealerResult = resultMap.getDealerResult(); | ||
int winCount = dealerResult.getOrDefault(ResultType.WIN, 0); | ||
int drawCount = dealerResult.getOrDefault(ResultType.DRAW, 0); | ||
int loseCount = dealerResult.getOrDefault(ResultType.LOSE, 0); | ||
return new DealerResultDto(dealerName, winCount, drawCount, loseCount); | ||
Comment on lines
+132
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DTO 를 만드는 로직이 컨트롤러에 너무 과하게 들어가있는 것 같아요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 원래 dto 생성자에 domain을 넘겨주고 dto내부에서 알아서 만들어주는 방식으로 구현했었는데, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 부분은 저도 여전히 고민중 ,,, |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package blackjack.domain; | ||
|
||
import blackjack.domain.card.Card; | ||
import blackjack.domain.card.CardDeck; | ||
import blackjack.domain.player.Player; | ||
import blackjack.domain.player.Players; | ||
import blackjack.domain.result.ResultMap; | ||
import java.util.List; | ||
|
||
public class BlackJackGame { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그냥 궁금해서 여쭤보는 겁니다 이 클래스가 필요한 이유는 뭐라고 생각하시나요? 이 클래스를 저도 만들고, 없애고 했었는데요 |
||
private final CardDeck cardDeck; | ||
private final Players players; | ||
|
||
private BlackJackGame(CardDeck cardDeck, Players players) { | ||
this.cardDeck = cardDeck; | ||
this.players = players; | ||
} | ||
|
||
public static BlackJackGame from(List<String> names) { | ||
CardDeck cardDeck = CardDeck.create(); | ||
Players players = Players.from(names); | ||
return new BlackJackGame(cardDeck, players); | ||
} | ||
|
||
public void handOutStartCards() { | ||
players.pickStartCards(cardDeck); | ||
} | ||
|
||
public void hit(Player player) { | ||
Card pickedCard = cardDeck.pick(); | ||
player.pickCard(pickedCard); | ||
} | ||
|
||
public ResultMap makeResult() { | ||
return ResultMap.from(players); | ||
} | ||
|
||
public Player getDealer() { | ||
return players.getDealer(); | ||
} | ||
|
||
public List<Player> getChallengers() { | ||
return players.getChallengers(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package blackjack.domain.card; | ||
|
||
public class Card { | ||
|
||
private final Shape shape; | ||
private final Symbol symbol; | ||
|
||
public Card(Shape shape, Symbol symbol) { | ||
this.shape = shape; | ||
this.symbol = symbol; | ||
} | ||
|
||
public boolean isAce() { | ||
return symbol.equals(Symbol.ACE); | ||
} | ||
|
||
public Shape getShape() { | ||
return shape; | ||
} | ||
|
||
public Symbol getSymbol() { | ||
return symbol; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리뷰어 분이 남겨주는거 확인하면서 리뷰하고 있는데 나도 이 부분은 Number를 반환하는게 맞다고 생각해! |
||
|
||
public int getPoint() { | ||
return symbol.getValue(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package blackjack.domain.card; | ||
|
||
import static java.util.stream.Collectors.toList; | ||
|
||
import blackjack.domain.card.exception.NoMoreCardException; | ||
import java.util.ArrayDeque; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.Deque; | ||
import java.util.List; | ||
|
||
public class CardDeck { | ||
|
||
private final Deque<Card> cards; | ||
|
||
private CardDeck(Deque<Card> cards) { | ||
this.cards = cards; | ||
} | ||
|
||
public static CardDeck create() { | ||
List<Card> cards = Arrays.stream(Shape.values()) | ||
.flatMap(shape -> Arrays.stream(Symbol.values()).map(symbol -> new Card(shape, symbol))) | ||
.collect(toList()); | ||
Collections.shuffle(cards); | ||
return new CardDeck(new ArrayDeque<>(cards)); | ||
} | ||
|
||
public Card pick() { | ||
validateCardExist(); | ||
return cards.remove(); | ||
} | ||
|
||
private void validateCardExist() { | ||
if (cards.isEmpty()) { | ||
throw new NoMoreCardException(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package blackjack.domain.card; | ||
|
||
public enum Shape { | ||
SPADE("스페이드"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인 로직이 뷰와 직접적으로 연관되는거 아닌가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 매우 날카로운 지적.. 안그래도 이부분 매우 걸렸는데, |
||
HEART("하트"), | ||
CLOVER("클로버"), | ||
DIAMOND("다이아몬드"); | ||
|
||
private final String name; | ||
|
||
Shape(String name) { | ||
this.name = name; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
내부에 로직이 있거나, 편하게 사용하고 싶다면 사용할 수 있을 것 같아! 👍
나는 보통 dto를 사용할 때 값을 그대로 넣어서 사용하는 편이야
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정적 팩토리 메소드를 왜 사용하시나요? 이유를 생각해보면 답이 나오지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 그냥 편하게 쓰려고 DTO.from(domain) 식으로 사용합니다