-
Notifications
You must be signed in to change notification settings - Fork 6
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
[블랙잭 step1] 신은정 미션 제출합니다. #5
base: rueun
Are you sure you want to change the base?
Changes from all commits
b89e3fb
5a4aec1
0c49ea6
f031641
0e8a455
7c9dc56
16e422e
80b09ce
ebca59b
1849792
84bc97f
774e264
00e4087
ad60b20
5039485
d44f6b3
c628c78
7f71b9c
963e373
12f992d
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,12 @@ | ||
package blackjack; | ||
|
||
|
||
import blackjack.controller.BlackJackGameController; | ||
|
||
public class Application { | ||
|
||
public static void main(String[] args) { | ||
final BlackJackGameController blackJackGameController = new BlackJackGameController(); | ||
blackJackGameController.run(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package blackjack.controller; | ||
|
||
import blackjack.domain.card.CardDeck; | ||
import blackjack.domain.game.BlackJackGame; | ||
import blackjack.domain.participant.Dealer; | ||
import blackjack.domain.participant.Name; | ||
import blackjack.domain.participant.Player; | ||
import blackjack.domain.participant.Players; | ||
import blackjack.view.InputView; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
public class BlackJackGameController { | ||
|
||
public void run() { | ||
final CardDeck cardDeck = CardDeck.create(); | ||
cardDeck.shuffle(); | ||
|
||
final Dealer dealer = Dealer.create(); | ||
final Players players = initPlayers(); | ||
|
||
final BlackJackGame blackJackGame = new BlackJackGame(cardDeck, players, dealer); | ||
blackJackGame.play(); | ||
} | ||
|
||
private Players initPlayers() { | ||
final List<String> playerNames = InputView.inputPlayerNames(); | ||
final List<Player> players = playerNames.stream() | ||
.map(name -> Player.of(Name.of(name))) | ||
.collect(Collectors.toList()); | ||
|
||
return Players.of(players); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package blackjack.domain.card; | ||
|
||
import java.util.Objects; | ||
|
||
public class Card { | ||
private final Suit suit; | ||
private final Rank rank; | ||
|
||
private Card(final Suit suit, final Rank rank) { | ||
this.suit = suit; | ||
this.rank = rank; | ||
} | ||
|
||
public static Card of(final String suit, final String rank) { | ||
return new Card(Suit.of(suit), Rank.of(rank)); | ||
} | ||
|
||
public static Card of(final Suit suit, final Rank rank) { | ||
return new Card(suit, rank); | ||
} | ||
|
||
public Suit getSuit() { | ||
return suit; | ||
} | ||
|
||
public Rank getRank() { | ||
return rank; | ||
} | ||
|
||
public String showCardInfo() { | ||
return rank.getName() + suit.getName(); | ||
} | ||
Comment on lines
+30
to
+32
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.
|
||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (!(o instanceof Card card)) return false; | ||
return suit == card.suit && rank == card.rank; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(suit, rank); | ||
} | ||
Comment on lines
+34
to
+44
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. 값 객체에 대한 equals & hashcode 재정의 좋네요 👍 |
||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,73 @@ | ||||||
package blackjack.domain.card; | ||||||
|
||||||
import java.util.Arrays; | ||||||
import java.util.Collections; | ||||||
import java.util.List; | ||||||
import java.util.stream.Collectors; | ||||||
|
||||||
public class CardDeck { | ||||||
private static final int DECK_SIZE = 52; | ||||||
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.
Suggested change
물론 트럼프 카드가 문양 4개, 숫자 13개로 고정되어서 덱 사이즈가 52에서 변경될 일이 거의 없기는 해요. |
||||||
private static final String DECK_EMPTY_MESSAGE = "덱이 비었습니다."; | ||||||
private static final String DECK_SIZE_MESSAGE = String.format("덱의 크기는 %d 입니다.", DECK_SIZE); | ||||||
private static final String DUPLICATE_CARD_MESSAGE = "중복된 카드가 존재합니다."; | ||||||
Comment on lines
+10
to
+12
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_EMPTY_MESSAGE" 예외 메시지 자체를 2번 이상 사용하는 경우 상수로 관리하는 것에 대한 이득을 누릴 수 있지만, 예외 메시지는 한 번 작성하면 거의 변경되질 않기 때문에 중복되더라도 하드 코딩하는 것이 가독성을 위해 일부러 중복으로 작성하기도 합니다 :D |
||||||
|
||||||
private final List<Card> cards; | ||||||
|
||||||
private CardDeck() { | ||||||
final List<Card> initializedCards = initialize(); | ||||||
validate(initializedCards); | ||||||
this.cards = initializedCards; | ||||||
} | ||||||
|
||||||
public static CardDeck create() { | ||||||
return new CardDeck(); | ||||||
} | ||||||
|
||||||
private static List<Card> initialize() { | ||||||
return Arrays.stream(Suit.values()) | ||||||
.flatMap(suit -> Arrays.stream(Rank.values()) | ||||||
.map(rank -> Card.of(suit, rank))) | ||||||
.collect(Collectors.toList()); | ||||||
} | ||||||
|
||||||
private static void validate(final List<Card> cards) { | ||||||
if (cards.isEmpty()) { | ||||||
throw new IllegalArgumentException(DECK_EMPTY_MESSAGE); | ||||||
} | ||||||
if (cards.size() != DECK_SIZE) { | ||||||
throw new IllegalArgumentException(DECK_SIZE_MESSAGE); | ||||||
} | ||||||
if (cards.stream().distinct().count() != DECK_SIZE) { | ||||||
throw new IllegalArgumentException(DUPLICATE_CARD_MESSAGE); | ||||||
} | ||||||
} | ||||||
|
||||||
public void shuffle() { | ||||||
Collections.shuffle(cards); | ||||||
} | ||||||
|
||||||
public List<Card> getCards() { | ||||||
return List.copyOf(cards); | ||||||
} | ||||||
|
||||||
|
||||||
/** | ||||||
* 초기 카드 2장을 뽑고, 덱에서 제거 | ||||||
* @return List<Card> 초기 카드 2장 | ||||||
*/ | ||||||
public List<Card> drawInitialCards() { | ||||||
return List.of(draw(), draw()); | ||||||
} | ||||||
Comment on lines
+58
to
+60
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. CardDeck이 블랙잭 게임 초기 세팅을 위해서 카드 2장을 뽑는 것까지 알 필요가 있을지는 조금 의문이에요. |
||||||
|
||||||
/** | ||||||
* 카드를 뽑고, 덱에서 제거 | ||||||
* @return Card 뽑은 카드 | ||||||
*/ | ||||||
public Card draw() { | ||||||
if (cards.isEmpty()) { | ||||||
throw new IllegalArgumentException(DECK_EMPTY_MESSAGE); | ||||||
} | ||||||
Comment on lines
+67
to
+69
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. 'Illegal' 한 'Argument'가 없는데 IllegalArgumentException을 발생시키고 있어요. |
||||||
// 마지막 카드를 뽑아내고, 덱에서 제거 | ||||||
return cards.remove(cards.size() - 1); | ||||||
} | ||||||
Comment on lines
+70
to
+72
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. 리스트에서 카드를 제거 하는 로직 자체를 직접 구현했는데, Java 가 제공하는 기본 API를 써서 대체해볼 수 있을 것 같아요. (자료구조가 List가 아닌 큐 자료구조를 써보면 카드를 한 장 뽑는다는 로직을 쉽게 구현해볼 수 있을 것 같아요) |
||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package blackjack.domain.card; | ||
|
||
import java.util.List; | ||
|
||
public class Cards { | ||
private final List<Card> cards; | ||
|
||
private Cards(final List<Card> cards) { | ||
this.cards = cards; | ||
} | ||
|
||
public static Cards of(final List<Card> cards) { | ||
return new Cards(cards); | ||
} | ||
|
||
public void add(final Card card) { | ||
cards.add(card); | ||
} | ||
|
||
public List<String> showCardsInfo() { | ||
return cards.stream() | ||
.map(Card::showCardInfo) | ||
.toList(); | ||
} | ||
|
||
public boolean isBlackjack() { | ||
return calculateScore() == 21 && cards.size() == 2; | ||
} | ||
|
||
public int calculateScore() { | ||
|
||
int totalScore = cards.stream() | ||
.mapToInt(card -> card.getRank().getScore()) | ||
.sum(); | ||
|
||
int aceCount = (int) cards.stream() | ||
.map(Card::getRank) | ||
.filter(Rank.ACE::equals) | ||
.count(); | ||
|
||
return calculateAceScore(totalScore, aceCount); | ||
} | ||
|
||
private int calculateAceScore(final int totalScore, int aceCount) { | ||
int score = totalScore; | ||
|
||
while (aceCount > 0 && score + 10 <= 21) { | ||
score += 10; | ||
aceCount--; | ||
} | ||
|
||
return score; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,41 @@ | ||||||
package blackjack.domain.card; | ||||||
|
||||||
public enum Rank { | ||||||
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.
Suggested change
클래스명을 그 자체로 무슨 뜻인지 명확히 알 수 있게 지어보면 어떨까요? 물론 이 미션이 블랙잭 게임을 구현하는 미션이고, Rank 클래스가 프로젝트를 유지보수하면서 클래스는 계속 늘어나고, Rank가 사실은 Card의 Rank였다는 사실을 계속 인지하는 것은 점차 어려워질거에요. (어쩌면 이름은 같고 역할은 다른 Rank 클래스도 생길 수도 있고요.) 앞으로 벌어나지도 않을 일 때문에 미리 그것까지 고려해야될까라는 물음에는 저는 이정도면 충분히 고려를 해야한다고 봐요. 이러한 점들을 종합적으로 고려했을 때, 클래스를 설계하는 시점부터 클래스명을 더욱 명확하게, 의미를 좁히는 방향으로 지어보면 어떨까 싶습니다 :D |
||||||
ACE("A", 1), | ||||||
TWO("2", 2), | ||||||
THREE("3", 3), | ||||||
FOUR("4", 4), | ||||||
FIVE("5", 5), | ||||||
SIX("6", 6), | ||||||
SEVEN("7", 7), | ||||||
EIGHT("8", 8), | ||||||
NINE("9", 9), | ||||||
TEN("10", 10), | ||||||
JACK("J", 10), | ||||||
QUEEN("Q", 10), | ||||||
KING("K", 10); | ||||||
|
||||||
private final String name; | ||||||
private final int score; | ||||||
|
||||||
Rank(final String name, final int score) { | ||||||
this.name = name; | ||||||
this.score = score; | ||||||
} | ||||||
|
||||||
public static Rank of(final String name) { | ||||||
return Rank.valueOf(name); | ||||||
} | ||||||
|
||||||
public String getName() { | ||||||
return name; | ||||||
} | ||||||
|
||||||
public int getScore() { | ||||||
return score; | ||||||
} | ||||||
|
||||||
public boolean isAce() { | ||||||
return this == ACE; | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package blackjack.domain.card; | ||
|
||
import java.util.Arrays; | ||
|
||
public enum Suit { | ||
SPADE("스페이드"), | ||
HEART("하트"), | ||
DIAMOND("다이아몬드"), | ||
CLOVER("클로버"); | ||
|
||
private final String name; | ||
|
||
Suit(final String name) { | ||
this.name = name; | ||
} | ||
|
||
public static Suit of(final String name) { | ||
return Arrays.stream(Suit.values()) | ||
Comment on lines
+17
to
+18
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. 지금처럼 팩토리 메서드 네이밍을 관례로 사용할 경우, |
||
.filter(suit -> suit.name.equals(name)) | ||
.findFirst() | ||
.orElseThrow(() -> new IllegalArgumentException("유효하지 않은 카드 모양입니다.")); | ||
} | ||
|
||
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.
카드덱을 셔플하는 책임을 컨트롤러가 가져야할지는 의문이에요.
CardDeck.ofShuffled()
처럼 이미 셔플된 상태의 카드덱을 가져와도 괜찮을 것 같습니다 :)