-
Notifications
You must be signed in to change notification settings - Fork 388
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단계 - 블랙잭 게임 실행] 몰리(김지민) 미션 제출합니다. #613
Changes from 96 commits
c917986
87c85ee
155f357
c3988bb
ba08356
af5cb26
e4da2a3
be71c02
06ca65b
9e75cf4
08636d4
a1792aa
d0c4ff8
f718ce7
1e65d32
8d96417
7229877
362b317
f83bbd6
795d9ed
31e992d
cb9f423
92cd701
33b1a9c
6272dac
9056d88
d461956
bb73759
fbfd65f
e9d59ef
d5a2cbd
5df2df5
a60b3b1
9148da7
41813fd
081235f
370b223
f25271f
f00858e
c3be17a
dee5f8a
dddfacd
0ca64fe
ebaeb63
b40b606
81f81d1
7edde19
8deb5b6
aff7d7f
1a911a8
d2d0e11
0a05fef
6b230af
88b115d
b13e39b
07ac6eb
a491955
e771288
78234a5
9550b82
480f725
bea1d9b
f947195
9de64e7
37bae4e
8872685
3a73079
d05b394
50cda73
518fb3c
2c47b06
cd033ea
3184b39
38e2836
f5daf94
9ff8c1d
9e84d39
981aaad
487c06b
cd42ce3
9aac0f1
5949c9c
f1389a7
c612623
16b5e83
b4b5b1c
aa6a173
34f9546
4fbdda7
ee2730b
ff0fdf5
4897520
64628eb
ca7b504
d873ea1
2d5723d
a741ccd
bf0fa4b
cc2ae77
f587f55
5de9b62
6cda451
fa3c1d3
0cbce41
5c215ad
5513d52
4255ba8
a868728
2bc11ba
2895e6a
fbb3b76
c8c4e31
ff4890e
85e709f
abc48aa
d136d50
4898bc0
62bccf8
05bc1a8
2a987dc
0365aaf
e83abda
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,69 @@ | ||
## 기능 요구 사항 | ||
|
||
- [x] 참여할 사람의 이름을 입력받는다. | ||
- [x] 참여할 인원의 수는 최소 1명 최대 10명이다.(딜러 제외) | ||
|
||
- [x] 게임을 시작하면 플레이어는 딜러에게 두 장의 카드를 지급 받는다. | ||
- [x] 딜러는 2장의 카드를 받고 한 장의 카드만 공개한다. | ||
|
||
- [x] 카드를 더 받을지 선택한다. | ||
- [x] 21이 넘지 않은 플레이어는 추가로 카드를 더 받을지 선택할 수 있다. | ||
- [x] 21이 초과된 경우 알리고 다음 플레이어의 턴으로 넘어간다. | ||
- [x] 카드 추가 여부 확인 시마다 해당 플레이어의 카드 현황을 보여준다. | ||
- [x] 카드를 더 받지 않으면 다음 플레이어에게 턴을 넘긴다. | ||
- [x] 모든 플레이어가 선택을 마칠 때까지 위의 과정을 반복한다. | ||
|
||
- [x] 딜러는 처음에 받은 2장의 합계가 16이하이면, 17이상이 넘을 때까지 카드를 추가로 받는다. | ||
|
||
- [x] 딜러와 플레이어의 카드, 결과를 공개한다. | ||
- [x] 딜러와 각 플레이어 별로 승패를 공개한다. | ||
|
||
### 플레이어 | ||
|
||
- [x] 플레이어는 카드를 받을 수 있다. | ||
|
||
### 플레이어의 승리 조건 | ||
|
||
#### 딜러가 21미만인 경우 | ||
|
||
- [x] 플레이어 결과가 딜러보다 큰 경우 | ||
- [x] 플레이어 결과가 딜성러의 결과와 동일하지만 카드 수는 적은 경우 | ||
|
||
#### 딜러가 21인 경우 | ||
|
||
- [x] 플레이어 카드만 블랙잭인 경우 | ||
- [x] 플레이어의 카드 수가 딜러의 카드 수보다 적은 경우 | ||
|
||
#### 딜러가 21 초과했을 경우 | ||
|
||
- [x] 플레이어 결과가 21이하인 경우 | ||
|
||
### 무승부 조건 | ||
|
||
- [x] 플레이어와 딜러 모두 블랙잭인 경우 | ||
- [x] 플레이어와 딜러의 결과, 카드 수가 모두 동일한 경우 | ||
- [x] 플레이어와 딜러 모두 21 초과인 경우 | ||
|
||
### 카드 | ||
|
||
- [x] 전체 카드의 수는 52장이다. | ||
- [x] 카드의 종류와 개수는 아래 사진과 같다. | ||
 | ||
|
||
- [x] Ace의 기본값은 1이다. | ||
- [x] Ace를 여러 개 가진 경우 ACE는 1점으로 계산한다. | ||
- [x] Ace를 제외한 나머지 카드의 합계가 10을 초과하면 ACE는 1점으로 계산한다. | ||
- [x] Ace를 제외한 나머지 카드의 합계가 10 이하인 경우 ACE는 11점으로 계산한다. | ||
- [x] King, Queen, Jack은 각각 10으로 계산한다. | ||
|
||
--- | ||
|
||
## 예외처리 | ||
|
||
- [x] 이름 입력 시 blank가 들어온 경우 | ||
- [x] 이름의 수가 1개 미만인 경우 | ||
- [x] 이름의 수가 10개 초과인 경우성 | ||
- [x] 이름이 중복되는 경우 | ||
|
||
- [x] 추가 선택 입력 시 blank가 들어온 경우 | ||
- [x] 추가 선택 입력 시 y 또는 n이 아닌 경우 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package blackjack; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
try { | ||
BlackJackGame game = new BlackJackGame(); | ||
game.run(); | ||
} catch (IllegalArgumentException e) { | ||
System.out.println(e.getMessage()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package blackjack; | ||
|
||
import blackjack.dto.NameCardsScore; | ||
import blackjack.model.participant.Dealer; | ||
import blackjack.model.deck.Deck; | ||
import blackjack.model.participant.Player; | ||
import blackjack.model.participant.Players; | ||
import blackjack.model.result.Referee; | ||
import blackjack.model.result.ResultCommand; | ||
import blackjack.model.result.Rule; | ||
import blackjack.util.ConsoleReader; | ||
import blackjack.view.InputView; | ||
import blackjack.view.OutputView; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class BlackJackGame { | ||
private static final ConsoleReader CONSOLE_READER = new ConsoleReader(); | ||
|
||
public void run() { | ||
final Deck deck = new Deck(); | ||
|
||
final List<String> names = InputView.readPlayerNames(CONSOLE_READER); | ||
final Players players = Players.of(names, deck.distributeInitialCard(names.size())); | ||
|
||
final Dealer dealer = new Dealer(deck.distributeInitialCard()); | ||
final Referee referee = new Referee(new Rule(dealer), players); | ||
|
||
OutputView.printDistributionSubject(players.getNames()); | ||
printInitialCards(dealer, players); | ||
|
||
playPlayersTurn(players.getPlayers(), deck); | ||
playDealerTurn(dealer, deck); | ||
|
||
printFinalResult(dealer, players, referee); | ||
} | ||
|
||
private void printInitialCards(final Dealer dealer, final Players players) { | ||
OutputView.printNameAndCards(dealer.getName(), dealer.openCard()); | ||
players.collectCardsOfEachPlayer() | ||
.forEach(OutputView::printNameAndCards); | ||
OutputView.println(); | ||
} | ||
|
||
private void playPlayersTurn(final List<Player> players, final Deck deck) { | ||
for (Player player : players) { | ||
playPlayerTurn(player, deck); | ||
} | ||
} | ||
|
||
private void playPlayerTurn(final Player player, final Deck deck) { | ||
if (!player.isBust() && player.canHit()) { | ||
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. player의 점수가 정확히 21인 경우 때문에 canHit를 검사해주었습니다. 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. 넵넵 정확히 21인 경우도 canHit에서 걸러주고 있기 때문에 |
||
final boolean isHit = InputView.readHitOrNot(player.getName(), CONSOLE_READER); | ||
distributeIfPlayerWant(isHit, player, deck); | ||
} | ||
} | ||
|
||
private void distributeIfPlayerWant(final boolean isHit, final Player player, final Deck deck) { | ||
if (isHit) { | ||
distributeNewCard(player, deck); | ||
OutputView.printNameAndCards(player.getName(), player.openCards()); | ||
playPlayerTurn(player, deck); | ||
} | ||
} | ||
|
||
private void playDealerTurn(final Dealer dealer, final Deck deck) { | ||
while (dealer.canHit()) { | ||
OutputView.printDealerHit(); | ||
distributeNewCard(dealer, deck); | ||
} | ||
} | ||
|
||
private void distributeNewCard(final Player player, final Deck deck) { | ||
player.receiveCard(deck.distribute()); | ||
} | ||
|
||
private void printFinalResult(final Dealer dealer, final Players players, final Referee referee) { | ||
printFinalCardsAndScores(dealer, players); | ||
printFinalResultCommand(referee); | ||
} | ||
|
||
private void printFinalCardsAndScores(final Dealer dealer, final Players players) { | ||
OutputView.println(); | ||
NameCardsScore dealerNameCardsScore = new NameCardsScore(dealer.getName(), dealer.openCards(), | ||
dealer.notifyScore()); | ||
List<NameCardsScore> playerNameCardsScore = players.collectFinalResults(); | ||
OutputView.printFinalCardsAndScore(dealerNameCardsScore); | ||
OutputView.printFinalCardsAndScore(playerNameCardsScore); | ||
} | ||
|
||
private void printFinalResultCommand(final Referee referee) { | ||
Map<ResultCommand, Integer> dealerResults = referee.judgeDealerResult(); | ||
OutputView.printDealerFinalResult(dealerResults); | ||
Map<String, ResultCommand> playerResults = referee.judgePlayerResult(); | ||
OutputView.printFinalResult(playerResults); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package blackjack.dto; | ||
|
||
import blackjack.model.deck.Card; | ||
import java.util.List; | ||
|
||
public record NameCardsScore(String name, List<Card> cards, int score) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package blackjack.model.deck; | ||
|
||
import java.util.Objects; | ||
|
||
public class Card { | ||
private final Shape shape; | ||
private final Score score; | ||
|
||
public Card(final Shape shape, final Score score) { | ||
this.shape = shape; | ||
this.score = score; | ||
} | ||
|
||
public boolean isAce() { | ||
return score.isAce(); | ||
} | ||
|
||
public int getScoreValue() { | ||
return score.getValue(); | ||
} | ||
|
||
public Score getScore() { | ||
return score; | ||
} | ||
|
||
public Shape getShape() { | ||
return shape; | ||
} | ||
|
||
@Override | ||
public boolean equals(final Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
Card card = (Card) o; | ||
return shape == card.shape && score == card.score; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(shape, score); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package blackjack.model.deck; | ||
|
||
import blackjack.model.participant.Hand; | ||
import java.util.ArrayDeque; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.Deque; | ||
import java.util.List; | ||
import java.util.NoSuchElementException; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.Stream; | ||
|
||
public class Deck { | ||
private final Deque<Card> deck; | ||
|
||
public Deck() { | ||
this.deck = makeDeck(); | ||
} | ||
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의 기능에 맞는 자료구조를 사용하셨네요 💯 기본 생성자를 사용하는 대신 랜덤으로 초기화해서 객체를 생성한다는 의미를 담은 정적팩토리메서드를 사용해보는 것은 어떨까요? |
||
|
||
private static Deque<Card> makeDeck() { | ||
List<Card> originDeck = Arrays.stream(Shape.values()) | ||
.flatMap(Deck::matchScore) | ||
.collect(Collectors.toList()); | ||
return new ArrayDeque<>(shuffleDeck(originDeck)); | ||
} | ||
|
||
private static Deque<Card> shuffleDeck(final List<Card> originDeck) { | ||
Collections.shuffle(originDeck); | ||
return new ArrayDeque<>(originDeck); | ||
} | ||
|
||
private static Stream<Card> matchScore(Shape shape) { | ||
return Arrays.stream(Score.values()) | ||
.map(score -> new Card(shape, score)); | ||
} | ||
|
||
public Hand distributeInitialCard() { | ||
return new Hand(List.of(distribute(), distribute())); | ||
} | ||
|
||
public List<Hand> distributeInitialCard(final int playerCount) { | ||
return IntStream.range(0, playerCount) | ||
.mapToObj(i -> distributeInitialCard()) | ||
.toList(); | ||
} | ||
|
||
public Card distribute() { | ||
try { | ||
return deck.removeFirst(); | ||
} catch (NoSuchElementException e) { | ||
throw new NoSuchElementException("카드가 부족합니다."); | ||
} | ||
} | ||
|
||
public Deque<Card> getDeck() { | ||
return deck; | ||
} | ||
Comment on lines
+60
to
+62
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. 테스트에서만 사용하는 getter네요 👀 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. 파랑! 이 부분과 관련해서 궁금한 점이 있어요! 제가 생각하기에 정말 반드시 테스트가 필요한 부분이라고 생각되면 프로덕션 코드에서 사용되지 않더라도 남겨두고, 크게 중요하지 않는 부분이라고 생각되면 테스트를 제거해 버렸었습니다. 예시로, 위의 getDeck()를 남겨둔 이유는 getDeck()이 2가지를 테스트 하고 있는데,
1번 같은 경우는 꼭 위의 카드가 배분되지 않더라도 프로그램 동작 자체에는 문제가 없기 때문에 반드시 테스트가 필요하지는 않지만, 그래서 조금의 찝찝함을 감수하더라도 getDeck()을 남겨두게 되었는데요..! 말이 조금 길어졌지만 😂 저는 3가지 방법을 생각해볼 수 있을 것 같아요...!
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. 중요한 로직에 대한 테스트이고 대체가 어렵다면 getter 정도는 남겨도 괜찮다고 생각하긴 합니다. 테스트만을 위한 코드를 최소화하되, 항상 맥락을 고려해야 하니까요. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package blackjack.model.deck; | ||
|
||
public enum Score { | ||
ACE(1), | ||
TWO(2), | ||
THREE(3), | ||
FOUR(4), | ||
FIVE(5), | ||
SIX(6), | ||
SEVEN(7), | ||
EIGHT(8), | ||
NINE(9), | ||
TEN(10), | ||
KING(10), | ||
QUEEN(10), | ||
JACK(10), | ||
; | ||
|
||
private final int value; | ||
|
||
Score(final int value) { | ||
this.value = value; | ||
} | ||
|
||
public boolean isAce() { | ||
return this == ACE; | ||
} | ||
|
||
public int getValue() { | ||
return value; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackjack.model.deck; | ||
|
||
public enum Shape { | ||
SPADE, | ||
DIA, | ||
CLOVER, | ||
HEART, | ||
; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package blackjack.model.participant; | ||
|
||
import blackjack.model.deck.Card; | ||
import java.util.List; | ||
|
||
public class Dealer extends 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. Dealer와 Player, 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. 맞아요...! 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. 단순히 이름의 문제가 아닙니다. Dealer도 Player의 한 종류라면 Player가 쓰이는 모든 곳에는 Dealer도 똑같이 들어갈 수 있어야 합니다. 지금 그렇게 동작하고 있나요? 두 개가 별개의 클래스로 사용되어야 하며 Player가 쓰이는 모든 곳에 Dealer도 함께 쓰일 수 없다면 둘은 상속 관계가 되어서는 안 됩니다. |
||
private static final int HITTABLE_THRESHOLD = 16; | ||
|
||
public Dealer(final Hand hand) { | ||
super("딜러", hand); | ||
} | ||
|
||
@Override | ||
public boolean canHit() { | ||
return hand.calculateScore() <= HITTABLE_THRESHOLD; | ||
} | ||
|
||
public List<Card> openCard() { | ||
return List.of(hand.getFirstCard()); | ||
} | ||
Comment on lines
+52
to
+54
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. 파랑의 의견이 궁금한 부분이 있어요 🥹 저는 Hand로부터 카드를 다 받아 숨길 부분을 제거하고 나머지를 공개하도록 한다. 공개할지 말지 조차 결정하는 것은 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. 어쨌거나 오픈했을 때 한 장만 보이게 한다는 요구사항만 충족한다면 어떤 방식으로 구현할지는 개발자의 몫이라고 생각합니다 😅 페어의 의견을 반드시 따를 필요는 없습니다. 본인이 좋다고 생각하는 방향으로 구현해보시죠~~ |
||
} |
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.