-
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
[1단계 - 블랙잭 게임 실행] 로빈(임수빈) 미션 제출합니다. #1
base: main
Are you sure you want to change the base?
Changes from 34 commits
89a8f44
db91493
178ad20
40019ad
7196074
399fa25
19cfa4c
ab2676b
f1319fd
8656caf
9a66483
4adbb3e
b2a5a12
7106b9c
213a3f2
77439a2
453bb19
aa2a65e
5685701
c6e7564
9f15cc9
3b47a6d
a1b7720
3502c5d
89e773e
c6047ff
da5c9ce
a6ba73b
f0aae99
6322d3d
61eb780
4c5d1a4
1411054
b7ff262
0adedd5
adecadf
74d6688
15ee815
5ef0343
ea7eb45
f667755
6f1a623
4df7ab4
bc92c76
671e977
b47d4a9
92ec982
839110d
bea1e52
f7d8e73
9e305e2
0f6df51
15543db
0f008c7
0b09e12
438015e
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 |
---|---|---|
@@ -1,7 +1,59 @@ | ||
# 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점을 넘기지 않는 사람이 승리한다. 동점인 플레이어(딜러 포함)이 나온 경우, 무승부로 판단한다. | ||
|
||
### 게임 진행 상황 출력 | ||
|
||
1. 게임이 시작 되자마자, 딜러와 플레이어가 받은 카드를 출력한다. | ||
2. 단, 딜러는 카드를 한장만 출력한다. | ||
3. 이 후 플레이어의 차례마다 플레이어가 소유한 카드를 출력한다. | ||
|
||
### 게임 결과 출력 | ||
|
||
1. 게임을 완료한 후 각 플레이어(딜러 포함)가 보유한 카드 및 점수를 출력한다. | ||
2. 게임을 완료한 후 각 플레이어별로 승패를 출력한다. | ||
- 딜러는 다른 모든 플레이어에 대한 승패가 출력된다. | ||
- 딜러가 아닌 플레이어는 딜러에 대한 승패가 출력된다. | ||
|
||
### 게임 진행 가이드 출력 | ||
|
||
- 게임의 원활한 진행을 위해 가이드 문구를 출력한다. | ||
|
||
## 기능 목록 | ||
|
||
- [x] 플레이어 이름 입력 기능 | ||
- [x] 카드 점수 계산 기능 | ||
- [x] Ace 카드 점수 보정 기능 | ||
- [x] 플레이어 및 딜러 점수 계산 기능 | ||
- [x] 플레이어 및 딜러간 승부 계산 기능 | ||
- [x] 플레이어 및 딜러 손패 출력 기능 | ||
- [x] 게임 결과 출력 기능 | ||
- [x] 게임 승부 결과 출력 기능 | ||
- [x] 덱 관리 기능 | ||
- [x] 보유한 카드 점수 합계 계산 기능 | ||
- [x] 카드 받을지 여부 입력 기능 | ||
- [x] 전체 게임 진행 기능 | ||
- [x] 딜러 Ace 카드 점수 보정 기능 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import controller.BlackjackController; | ||
import domain.blackjack.Gamer; | ||
import domain.blackjack.HoldingCards; | ||
import domain.card.Deck; | ||
import java.util.List; | ||
import view.NameInputView; | ||
import view.OutputView; | ||
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. 패키지 구조를
|
||
|
||
public class Main { | ||
public static void main(String[] args) { | ||
Gamer dealer = new Gamer("딜러", HoldingCards.of()); | ||
OutputView.print("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); | ||
List<Gamer> players = NameInputView.getNames().stream() | ||
.map(name -> new Gamer(name, HoldingCards.of())) | ||
.toList(); | ||
BlackjackController blackjackController = new BlackjackController(dealer, players); | ||
blackjackController.startBlackjackGame(Deck.fullDeck()); | ||
} | ||
} | ||
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. 로빈이 생각한 Main 클래스의 역할은 무엇인가요? 💭 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package controller; | ||
|
||
import domain.blackjack.DealerRandomCardDrawStrategy; | ||
import domain.blackjack.GameResult; | ||
import domain.blackjack.GameResultCalculator; | ||
import domain.blackjack.Gamer; | ||
import domain.blackjack.PlayerRandomCardDrawStrategy; | ||
import domain.blackjack.SummationCardPoint; | ||
import domain.card.Card; | ||
import domain.card.Deck; | ||
import dto.DealerGameResultDTO; | ||
import dto.GamerDTO; | ||
import dto.PlayerGameResultDTO; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import view.GameResultOutputView; | ||
import view.GamerOutputView; | ||
import view.OutputView; | ||
import view.YesOrNoInputView; | ||
|
||
public class BlackjackController { | ||
private final Gamer dealer; | ||
private final List<Gamer> players; | ||
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. controller의 필드에 두 도메인이 있는 객체를 선언한 이유가 궁금합니다. 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. Deck은 단순히 요구사항 때문에 인자로 안넣으신건지 궁금합니다. 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 메서드 이곳 저곳에서 도메인을 사용하기 때문에, 필드로 두든, 메서드를 통해 넘겨주든 실질적인 차이가 있다고 생각하지 않습니다. 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.
네. 맞습니다. |
||
|
||
public BlackjackController(Gamer dealer, List<Gamer> players) { | ||
this.dealer = dealer; | ||
this.players = players; | ||
} | ||
|
||
public void startBlackjackGame(Deck deck) { | ||
initDealerAndPlayers(deck); | ||
printDealerAndPlayers(); | ||
|
||
playersTryDraw(deck); | ||
dealerTryDraw(deck); | ||
|
||
printDealerAndPlayersWithPoint(); | ||
printDealerAndPlayersGameResult(); | ||
} | ||
|
||
private void initDealerAndPlayers(Deck deck) { | ||
dealerDraw(deck); | ||
dealerDraw(deck); | ||
players.forEach(player -> playerDraw(deck, player)); | ||
players.forEach(player -> playerDraw(deck, player)); | ||
String namesOutput = players.stream().map(Gamer::getRawName).collect(Collectors.joining(", ")); | ||
OutputView.print("딜러와 %s에게 2장을 나누었습니다.".formatted(namesOutput)); | ||
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 dealerDraw(Deck deck) { | ||
dealer.draw(deck, new DealerRandomCardDrawStrategy(dealer)); | ||
} | ||
|
||
private void playerDraw(Deck deck, Gamer player) { | ||
player.draw(deck, new PlayerRandomCardDrawStrategy(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. 엇.. 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 printDealerAndPlayers() { | ||
printDealer(dealer); | ||
players.forEach(BlackjackController::printPlayer); | ||
} | ||
|
||
private static void printDealer(Gamer dealer) { | ||
List<Card> rawHoldingCards = new ArrayList<>(dealer.getRawHoldingCards()); | ||
rawHoldingCards.remove(0); | ||
GamerDTO gamerDTO = new GamerDTO(dealer.getRawName(), rawHoldingCards, | ||
dealer.getRawSummationCardPoint()); | ||
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. 이 부분 개행에 대한 로빈의 생각이 궁금해요 💭 |
||
GamerOutputView.printWithoutSummationCardPoint(gamerDTO); | ||
} | ||
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 static void printPlayer(Gamer player) { | ||
GamerDTO gamerDTO = new GamerDTO(player.getRawName(), player.getRawHoldingCards(), | ||
player.getRawSummationCardPoint()); | ||
GamerOutputView.printWithoutSummationCardPoint(gamerDTO); | ||
} | ||
|
||
private void playersTryDraw(Deck deck) { | ||
players.forEach(player -> playerTryDraw(deck, player)); | ||
} | ||
|
||
private void playerTryDraw(Deck deck, Gamer player) { | ||
boolean needToDraw = true; | ||
while (needToDraw && canDraw(player, new SummationCardPoint(21))) { | ||
needToDraw = playerTryDrawOnce(deck, player); | ||
} | ||
} | ||
|
||
private boolean playerTryDrawOnce(Deck deck, Gamer player) { | ||
boolean needToDraw; | ||
OutputView.print("%s은(는) 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)".formatted(player.getRawName())); | ||
needToDraw = YesOrNoInputView.getYNAsBoolean(); | ||
if (needToDraw) { | ||
playerDraw(deck, player); | ||
} | ||
printPlayer(player); | ||
return needToDraw; | ||
} | ||
|
||
private boolean canDraw(Gamer player, SummationCardPoint threshold) { | ||
return !player.getSummationCardPoint().isBiggerThan(threshold); | ||
} | ||
|
||
private void dealerTryDraw(Deck deck) { | ||
try { | ||
dealerDraw(deck); | ||
OutputView.print("딜러는 16이하라 한장의 카드를 더 받았습니다.\n"); | ||
} catch (IllegalStateException ignored) { | ||
|
||
} | ||
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 void printDealerAndPlayersWithPoint() { | ||
GamerDTO dealerDTO = new GamerDTO(dealer.getRawName(), dealer.getRawHoldingCards(), | ||
dealer.getRawSummationCardPoint()); | ||
GamerOutputView.print(dealerDTO); | ||
|
||
for (Gamer player : players) { | ||
GamerDTO playerDTO = new GamerDTO(player.getRawName(), player.getRawHoldingCards(), | ||
player.getRawSummationCardPoint()); | ||
GamerOutputView.print(playerDTO); | ||
} | ||
} | ||
|
||
private void printDealerAndPlayersGameResult() { | ||
Map<GameResult, Integer> dealerGameResultCounts = players.stream() | ||
.collect(Collectors.groupingBy(player -> GameResultCalculator.calculate(dealer, player), | ||
Collectors.summingInt(value -> 1))); | ||
DealerGameResultDTO dealerGameResultDTO = new DealerGameResultDTO(dealerGameResultCounts); | ||
|
||
List<PlayerGameResultDTO> playerGameResultDTOS = players.stream() | ||
.map(player -> new PlayerGameResultDTO(player.getRawName(), | ||
GameResultCalculator.calculate(player, dealer))) | ||
.toList(); | ||
|
||
GameResultOutputView.print(dealerGameResultDTO); | ||
playerGameResultDTOS.forEach(GameResultOutputView::print); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package domain.blackjack; | ||
|
||
import domain.card.Card; | ||
import domain.card.CardDrawStrategy; | ||
import java.util.List; | ||
import java.util.Random; | ||
|
||
abstract class AbstractRandomCardDrawStrategy implements CardDrawStrategy { | ||
@Override | ||
public final Card nextCard(List<Card> cards) { | ||
if (canDraw()) { | ||
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.
|
||
return cardSelectStrategy(cards); | ||
} | ||
throw new IllegalStateException("카드를 더이상 뽑을 수 없습니다."); | ||
} | ||
|
||
abstract boolean canDraw(); | ||
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. 전략 대신에 Gamer나 Dealer에게 물어보는 것은 어떤가요? |
||
|
||
private Card cardSelectStrategy(List<Card> cards) { | ||
Random random = new Random(); | ||
int idx = random.nextInt(cards.size()); | ||
return cards.get(idx); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package domain.blackjack; | ||
|
||
record CardPoint(int point) { | ||
|
||
} |
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.name(); | ||
int cardNumber = cardName.getCardNumber(); | ||
if (cardNumber > TEN.getCardNumber()) { | ||
return new CardPoint(TEN.getCardNumber()); | ||
} | ||
return new CardPoint(cardNumber); | ||
} | ||
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. 블랙잭에서 j,q,k 는 10으로 계산한다! 라는 룰이 있어 enum 값 세팅할 때 10으로 해두어도 되지 않았을까요? 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,16 @@ | ||
package domain.blackjack; | ||
|
||
public class DealerRandomCardDrawStrategy extends AbstractRandomCardDrawStrategy { | ||
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. 개인적인 의견이니 참고만 해주세요! 추상화 depth가 깊다고 생각해요. 꼭 필요한 추상화였을까 🤔 싶어요! |
||
private final Gamer dealer; | ||
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. 의존성에 대한 의문인데요 |
||
|
||
public DealerRandomCardDrawStrategy(Gamer dealer) { | ||
this.dealer = dealer; | ||
} | ||
|
||
@Override | ||
boolean canDraw() { | ||
SummationCardPoint summationCardPoint = dealer.getSummationCardPoint(); | ||
SummationCardPoint dealerDrawThresholdPoint = new SummationCardPoint(16); | ||
return !summationCardPoint.isBiggerThan(dealerDrawThresholdPoint); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package domain.blackjack; | ||
|
||
public enum GameResult { | ||
WIN, LOSE, TIE; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package domain.blackjack; | ||
|
||
public class GameResultCalculator { | ||
/** | ||
* baseGamer의 otherGamer 에 대한 승부 결과 반환 | ||
* | ||
* @param baseGamer 기준 게이머 | ||
* @param otherGamer 상대 게이머 | ||
* @return baseGamer의 otherGamer 에 대한 승부 결과 | ||
*/ | ||
public static GameResult calculate(Gamer baseGamer, Gamer otherGamer) { | ||
if (baseGamer.isDead() && otherGamer.isDead()) { | ||
return GameResult.TIE; | ||
} | ||
if (baseGamer.isDead()) { | ||
return GameResult.LOSE; | ||
} | ||
if (otherGamer.isDead()) { | ||
return GameResult.WIN; | ||
} | ||
return getGameResultWhenNobodyDead(baseGamer, otherGamer); | ||
} | ||
|
||
private static GameResult getGameResultWhenNobodyDead(Gamer baseGamer, Gamer otherGamer) { | ||
SummationCardPoint baseGamerSummationCardPoint = baseGamer.getSummationCardPoint(); | ||
SummationCardPoint otherGamerSummationCardPoint = otherGamer.getSummationCardPoint(); | ||
|
||
if (baseGamerSummationCardPoint.isBiggerThan(otherGamerSummationCardPoint)) { | ||
return GameResult.WIN; | ||
} | ||
if (baseGamerSummationCardPoint.equals(otherGamerSummationCardPoint)) { | ||
return GameResult.TIE; | ||
} | ||
return GameResult.LOSE; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package domain.blackjack; | ||
|
||
import static domain.card.CardName.TEN; | ||
|
||
import domain.card.Card; | ||
import domain.card.CardDrawStrategy; | ||
import domain.card.Deck; | ||
import java.util.List; | ||
|
||
public class Gamer { | ||
private final String name; | ||
private final HoldingCards holdingCards; | ||
|
||
public Gamer(String name, HoldingCards holdingCards) { | ||
this.name = name; | ||
this.holdingCards = holdingCards; | ||
} | ||
|
||
public void draw(Deck deck, CardDrawStrategy cardDrawStrategy) { | ||
Card draw = deck.draw(cardDrawStrategy); | ||
holdingCards.add(draw); | ||
} | ||
|
||
public SummationCardPoint getSummationCardPoint() { | ||
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; | ||
} | ||
|
||
public String getRawName() { | ||
return name; | ||
} | ||
|
||
public List<Card> getRawHoldingCards() { | ||
return List.copyOf(holdingCards.getHoldingCards()); | ||
} | ||
|
||
public int getRawSummationCardPoint() { | ||
return getSummationCardPoint().summationCardPoint(); | ||
} | ||
|
||
boolean isDead() { | ||
return holdingCards.calculateTotalPoint().isDeadPoint(); | ||
} | ||
|
||
boolean hasAceInHoldingCards() { | ||
return holdingCards.hasAce(); | ||
} | ||
} |
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.
모든 플레이어가 버스트가 된 경우 딜러는 카드를 뽑지 않아도 승리하기 때문에 딜러가 승이라고 생각합니다!