Skip to content
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

[블랙잭 - 2단계] 아토(이혜린) 미션 제출합니다. #17

Open
wants to merge 86 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
143ebe7
docs: 컨벤션 및 기능 요구사항 작성
hyxrxn Mar 5, 2024
21dae3a
chore: remove gitkeep
hyxrxn Mar 5, 2024
1786e9f
feat(Card): 카드 점수 계산
hyxrxn Mar 5, 2024
76ef94b
feat(Deck): 덱에서 카드 한 장 드로우
hyxrxn Mar 5, 2024
3cd8593
feat(Deck): 비어있는 덱에서 드로우할 때 예외 처리
hyxrxn Mar 5, 2024
f61ae03
feat(Player): 플레이어 점수 계산
hyxrxn Mar 5, 2024
be2c348
feat(Dealer): 딜러의 점수에 따른 카드 드로우
hyxrxn Mar 5, 2024
cc38fcc
feat(Dealer): 주어진 점수에 따른 승패 판단
hyxrxn Mar 5, 2024
959c905
feat(Hand): Ace 점수를 유리한 방향으로 계산
hyxrxn Mar 5, 2024
4b04093
refactor(Player): 드로우 가능 여부 판단 메서드 위치 변경
hyxrxn Mar 5, 2024
a10af0c
feat(Name): 이름 생성 및 검증
hyxrxn Mar 5, 2024
48dc12d
feat(Players): 플레이어들 생성 및 검증
hyxrxn Mar 5, 2024
c305c41
feat(View): 콘솔 입출력
hyxrxn Mar 5, 2024
7ceb8ba
feat(CardDisplay): 카드의 모양과 수의 표현 방식 설정
hyxrxn Mar 6, 2024
9608caf
refactor(Player): 게임 상수 추출 및 드로우 조건 수정
hyxrxn Mar 6, 2024
4ec8b0e
feat(MatchResult): 게임 결과 판단
hyxrxn Mar 6, 2024
25353e0
feat(MatchResults): 게임 결과 저장
hyxrxn Mar 6, 2024
9e6d1f3
feat(Deck): Shape와 Number의 조합으로 한 세트 생성
hyxrxn Mar 6, 2024
9c16047
feat(Command): 사용자의 명령어 변환
hyxrxn Mar 6, 2024
25088ec
refactor(OutputView): 매개변수로 `Card`를 받도록 수정
hyxrxn Mar 7, 2024
de8d257
feat(Display): 플레이어의 결과 표현 방식 설정
hyxrxn Mar 7, 2024
a1666f1
feat(BlackJackGame): 게임 흐름 제어
hyxrxn Mar 7, 2024
fc78bad
feat(BlackJackMain): 의존성 설정 및 실행
hyxrxn Mar 7, 2024
a86d395
refactor(BlackJackGame): 메서드 분리
hyxrxn Mar 7, 2024
e836cbe
refactor(Players): 불필요한 래핑 제거
hyxrxn Mar 7, 2024
3cbf5c0
style(Shape): 불필요한 세미콜론 제거
hyxrxn Mar 7, 2024
efad4ef
refactor: 상수 추출
hyxrxn Mar 7, 2024
6e0a373
refactor(OutputView): 메서드 분리
hyxrxn Mar 7, 2024
86c80ac
refactor: 패키지 이동
hyxrxn Mar 7, 2024
3e0e9ee
test(Player): 드로우 가능 여부 판단
hyxrxn Mar 7, 2024
46cc05c
fix(Dealer): 카드를 가지고 있지 않은 경우 예외 처리
hyxrxn Mar 7, 2024
4f27bbd
refactor(Deck): 덱 생성 정적 메서드 추가
hyxrxn Mar 7, 2024
c245b03
refactor(Hand): 접근 제한자 변경
hyxrxn Mar 7, 2024
70db6d4
refactor(Deck): 메서드 분리
hyxrxn Mar 7, 2024
cace848
refactor(Display): 패키지 분리
hyxrxn Mar 7, 2024
a957752
style(Number): 파일 끝 공백 추가
hyxrxn Mar 7, 2024
d4da751
refactor: 메서드 순서 변경
hyxrxn Mar 7, 2024
d888faa
refactor: 패키지 이동
hyxrxn Mar 7, 2024
5729a1d
style: 불필요한 개행 삭제
hyxrxn Mar 7, 2024
1ca43df
style(Hand): 가독성을 위한 변수 위치 및 줄바꿈 수정
hyxrxn Mar 7, 2024
ce53b0d
refactor(MatchResult): 승리 조건 별 메서드 분리
hyxrxn Mar 7, 2024
8969a9a
test(MatchResult): 승리 조건 테스트 데이터 보강
hyxrxn Mar 7, 2024
1a0d96e
style: 테스트 클래스 접근제한자 삭제
hyxrxn Mar 7, 2024
4d35231
refactor(OutputView): 중복 코드 제거 및 메서드명 번경
hyxrxn Mar 7, 2024
23b3015
style: 개행 및 오타 수정
hyxrxn Mar 7, 2024
0155159
docs: 기능 구현 사항 추가
hyxrxn Mar 11, 2024
1800705
refactor(Card): this 사용 일관성 고려해 수정
hyxrxn Mar 11, 2024
76bed3c
refactor(InputView): 상수명 수정
hyxrxn Mar 11, 2024
33d8237
refactor(Display): 메서드명 수정
hyxrxn Mar 11, 2024
1f5d1ff
test(Card): 에이스 여부 확인
hyxrxn Mar 11, 2024
eb15bf0
refactor(Hand): 점수 계산 로직 수정
hyxrxn Mar 11, 2024
cf27355
refactor: 드로우 관련 메서드명 변경
hyxrxn Mar 11, 2024
543e4a1
refactor(Command): 클래스 내부 메서드로 No 여부 확인
hyxrxn Mar 11, 2024
8c510c9
refactor(Rank): 클래스명 변경
hyxrxn Mar 11, 2024
429555c
refactor(MatchResult): 메서드명 변경
hyxrxn Mar 11, 2024
7622c5f
refactor(Players): 메서드 간소화
hyxrxn Mar 11, 2024
560a034
refactor: 예외 메시지에서 상수 사용
hyxrxn Mar 11, 2024
948d569
refactor: 상호 참조 제외
hyxrxn Mar 11, 2024
b3cc95c
refactor: 예외 발생시 메시지 출력 후 종료
hyxrxn Mar 12, 2024
b9c0665
refactor(Display): 메서드 리턴 타입 변경
hyxrxn Mar 13, 2024
5c8c80d
refactor(OutputView): 상수 추출
hyxrxn Mar 13, 2024
61950f8
feat(Score): 점수 덧셈
hyxrxn Mar 13, 2024
a1b1c15
feat(Score): 버스트 여부 판단
hyxrxn Mar 13, 2024
3e1c8f8
feat(Score): 점수 대소 비교
hyxrxn Mar 13, 2024
c99ed8e
feat(Score): 에이스 보정 점수 계산
hyxrxn Mar 13, 2024
c551c57
refactor: Score 이용
hyxrxn Mar 13, 2024
28dd3a1
test(Deck): 덱 생성
hyxrxn Mar 13, 2024
dc16898
refactor(Score): 메서드명 번경
hyxrxn Mar 14, 2024
a8150b9
[블랙잭 - 1단계] 아토(이혜린) 미션 제출합니다. (#593)
hyxrxn Mar 14, 2024
f5dc59d
Merge branch 'hyxrxn' into step2
hyxrxn Mar 15, 2024
c5476b0
feat(Money): 배팅 금액 생성 및 검증
hyxrxn Mar 15, 2024
c2ada09
feat(Hand): 블랙잭 여부 확인
hyxrxn Mar 15, 2024
d595119
docs: 기능 구현 사항 수정
hyxrxn Mar 15, 2024
feec13d
feat(MatchResult): 플레이어의 수익 비율 계산
hyxrxn Mar 15, 2024
6a713c2
refactor(Players): Set을 HashSet으로 변경
hyxrxn Mar 16, 2024
fb27032
feat(MatchResults): 플레이어별 수익 저장
hyxrxn Mar 16, 2024
2ec2baa
feat(View): 배팅 금액, 최종 수익 입출력
hyxrxn Mar 16, 2024
13c85a2
feat(PlayerBettingMoney): 플레이어별 배팅 금액 저장
hyxrxn Mar 16, 2024
f057371
refactor(BlackJackGame): 변경된 게임 흐름 반영
hyxrxn Mar 16, 2024
e02326f
refactor(Test): 접근제한자 수정
hyxrxn Mar 16, 2024
706741c
style(Test): 개행 추가
hyxrxn Mar 16, 2024
fd097e3
refactor(InputView): 단순 메시지 출력에서 예외 출력으로 변경
hyxrxn Mar 16, 2024
e9f554d
refactor(Score): final 설정
hyxrxn Mar 16, 2024
8aaf97b
docs: 기능 구현 사항 수정
hyxrxn Mar 16, 2024
dd795c3
refactor(Test): 케이스 추가
hyxrxn Mar 16, 2024
22b97bb
refactor(Dealer): 불필요한 super 삭제
hyxrxn Mar 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,68 @@

블랙잭 미션 저장소

## 우아한테크코스 코드리뷰
## 페어와 지킬 컨벤션
1. 클래스 정의 다음 줄은 공백으로 한다.
2. test code에 사용하는 메서드는 `static import`한다.

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 요구사항
### 카드
- [x] 카드는 모양과 수로 이루어져 있다.
- [x] 모양으로는 스페이드, 다이아, 하트, 클로버가 있다.
- [x] 수로는 Ace, King, Queen, Jack과 2 이상 10 이하의 정수가 있다.
- [x] 카드의 숫자 계산은 카드 숫자를 기본으로 한다.
- [x] Ace는 1 또는 11로 계산할 수 있다.
- [x] King, Queen, Jack은 각각 10으로 계산한다.

### 덱
- [x] 맨 위의 카드 한 장을 뽑는다.
- [x] 덱에 카드가 없는 경우, 예외를 발생한다.

### 플레이어
- [x] 덱에서 카드를 한 장 뽑는다.
- [x] 지금까지 뽑은 카드의 점수를 계산한다.

### 이름
- [x] 이름은 2글자 이상 5글자 이하이다.
- [x] 이름은 공백으로만 구성될 수 없다.

### 플레이어들
- [x] 플레이어들의 이름은 중복될 수 없다.
- [x] 플레이어들의 인원수는 1명 이상 10명 이하이다.

### 베팅 결과
- [x] 플레이어별 배팅 금액을 저장한다.

### 배팅 금액
- [x] 배팅 금액은 자연수이다.

### 딜러
- [x] 플레이어의 기능을 상속한다.
- [x] 17점 이상이 될 때까지 카드를 계속 뽑는다.

### 게임 결과
- [x] 게임 결과를 아래의 순서대로 올바르게 판단한다.
1. 플레이어는 배팅 금액의 1.5배만큼 돈을 번다.
- [x] 플레이어가 블랙잭이고 딜러가 블랙잭이 아닌 경우
2. 플레이어는 배팅 금액만큼 돈을 번다.
- [x] 플레이어가 버스트가 아니고 딜러가 버스트인 경우
- [x] 플레이어와 딜러가 버스트가 아니고 플레이어의 점수가 더 큰 경우
3. 플레이어는 배팅 금액을 잃는다.
- [x] 플레이어가 버스트인 경우
- [x] 플레이어와 딜러가 버스트가 아니고 딜러의 점수가 더 큰 경우
- [x] 플레이어가 블랙잭이 아니고 딜러가 블랙잭인 경우
4. 플레이어는 배팅 금액을 다시 돌려받는다.
- [x] 플레이어와 딜러가 버스트가 아니고 동점인 경우
- [x] 각 플레이어별 게임 결과를 저장한다.

---

### 게임 흐름
- [x] (hit, stand) 21을 넘지 않을 경우 원한다면 카드를 계속 뽑을 수 있다.
- [x] 게임이 시작할 때 딜러와 플레이어는 두 장의 카드를 지급 받는다.
- [x] 플레이어의 카드는 모두에게 공개된다.
- [x] 딜러의 두 번째 카드는 공개되지 않는다.
- [x] 플레이어가 카드를 새로 뽑을 때마다 카드 현황을 공개한다.
- [x] 각 플레이어는 딜러와만 승패를 겨룬다.
- [x] 딜러는 모든 플레이어와 승패를 겨룬다.
- [x] 게임을 완료한 후 딜러를 포함한 모든 플레이어의 수익을 확인한다.
Empty file removed src/main/java/.gitkeep
Empty file.
19 changes: 19 additions & 0 deletions src/main/java/blackjack/BlackJackMain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package blackjack;

import blackjack.game.BlackJackGame;
import blackjack.view.InputView;
import blackjack.view.OutputView;

public class BlackJackMain {

public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
BlackJackGame blackJackGame = new BlackJackGame(inputView, outputView);
try {
blackJackGame.play();
} catch (Exception e) {
outputView.printExceptionMessage(e);
}
}
}
48 changes: 48 additions & 0 deletions src/main/java/blackjack/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package blackjack.card;

import blackjack.player.Score;
import java.util.Objects;

public class Card {

private final Shape shape;
private final Rank rank;

public Card(Shape shape, Rank rank) {
this.shape = shape;
this.rank = rank;
}

public boolean isAce() {
return rank == Rank.ACE;
}

public Score getScore() {
return rank.getScore();
}

public Shape getShape() {
return shape;
}

public Rank getRank() {
return rank;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Card card = (Card) o;
return shape == card.shape && rank == card.rank;
}

@Override
public int hashCode() {
return Objects.hash(shape, rank);
}
}
44 changes: 44 additions & 0 deletions src/main/java/blackjack/card/Deck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package blackjack.card;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Deck {

private final Queue<Card> cards;

Deck(List<Card> cards) {
this.cards = new LinkedList<>(cards);
}

public static Deck createShuffledFullDeck() {
List<Card> cards = new LinkedList<>();
for (Shape shape : Shape.values()) {
cards.addAll(createNumberCardsOf(shape));
}
Collections.shuffle(cards);
return new Deck(cards);
}

static List<Card> createNumberCardsOf(Shape shape) {
List<Card> cards = new ArrayList<>();
for (Rank rank : Rank.values()) {
cards.add(new Card(shape, rank));
}
return cards;
}

public Card draw() {
if (cards.isEmpty()) {
throw new IllegalStateException("[ERROR] 덱이 비어있습니다.");
}
return cards.poll();
}

int size() {
return cards.size();
}
}
30 changes: 30 additions & 0 deletions src/main/java/blackjack/card/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package blackjack.card;

import blackjack.player.Score;

public enum Rank {

ACE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6),
SEVEN(7),
EIGHT(8),
NINE(9),
TEN(10),
JACK(10),
QUEEN(10),
KING(10);

private final Score score;

Rank(int score) {
this.score = new Score(score);
}

Score getScore() {
return this.score;
}
}
9 changes: 9 additions & 0 deletions src/main/java/blackjack/card/Shape.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package blackjack.card;

public enum Shape {

HEART,
SPADE,
CLOVER,
DIAMOND
}
136 changes: 136 additions & 0 deletions src/main/java/blackjack/game/BlackJackGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package blackjack.game;

import blackjack.card.Deck;
import blackjack.player.Dealer;
import blackjack.player.Player;
import blackjack.player.Players;
import blackjack.view.InputView;
import blackjack.view.OutputView;
import java.util.List;

public class BlackJackGame {

private final InputView inputView;
private final OutputView outputView;

public BlackJackGame(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void play() {
Deck deck = Deck.createShuffledFullDeck();
Dealer dealer = new Dealer();
Players players = createPlayers();
PlayerBettingMoney playerBettingMoney = decideBettingMoney(players);
initializeGame(deck, dealer, players);
proceedPlayersTurn(deck, players);
proceedDealerTurn(deck, dealer);

showCardsWithScore(dealer, players);
showMatchResult(dealer, players, playerBettingMoney);
Comment on lines +22 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 미션 진행하면서 이 컨트롤러가 많이 커져서 불편하지 않았나요? ㅎㅎㅎ

저도 너무 커져서 Controller있고 아예 다른 Game이라는 객체를 만들어 요 객체 필드에 Dealer와 Players를 가지고 있게 구현하는 방식으로 리팩터링 해보았어요

initializeGame(deck, dealer, players);
proceedPlayersTurn(deck, players);
proceedDealerTurn(deck, dealer);
showCardsWithScore(dealer, players);

이 메서드들 모두 deck, dealer, players로 동일한데 매번 파라미터로 넘겨주는 형태가 뭔가 조금 이상한 거 같더라구요

그래서 저는 deck을 dealer 필드로 넣고, dealer와 players를 Game 이라는 필드에 넣어 컨트롤러의 무게를 조금 줄이는 방법으로 해보았는데, 나쁘진 않은 거 같더라구요!

}

private Players createPlayers() {
outputView.printNamesRequest();
List<String> names = inputView.readNames();
Players players = new Players(names);
outputView.printNewLine();
return players;
}

private PlayerBettingMoney decideBettingMoney(Players players) {
PlayerBettingMoney playerBettingMoney = new PlayerBettingMoney();
for (Player player : players.getPlayers()) {
outputView.printBettingRequestMessage(player.getName());
Money money = new Money(inputView.readBattingAmount());
playerBettingMoney.addBetting(player, money);
outputView.printNewLine();
}
return playerBettingMoney;
}

private void initializeGame(Deck deck, Dealer dealer, Players players) {
players.doInitialDraw(deck);
dealer.doInitialDraw(deck);
outputView.printInitializeBlackJack(players.getNames());
showInitialCard(dealer, players);
}

private void showInitialCard(Dealer dealer, Players players) {
outputView.printDealerFirstCard(dealer.getFirstCard());

for (Player player : players.getPlayers()) {
outputView.printPlayerCards(player.getName(), player.getCards());
}
outputView.printNewLine();
}

private void proceedPlayersTurn(Deck deck, Players players) {
for (Player player : players.getPlayers()) {
proceedPlayerTurn(deck, player);
}
outputView.printNewLine();
}

private void proceedPlayerTurn(Deck deck, Player player) {
Command command = askPlayerToDrawMore(player);
if (command.isNo()) {
return;
}
player.drawCard(deck);
outputView.printPlayerCards(player.getName(), player.getCards());

if (player.hasDrawableScore()) {
proceedPlayerTurn(deck, player);
}
}

private Command askPlayerToDrawMore(Player player) {
outputView.printDrawMoreCardRequest(player.getName());
String input = inputView.readCommand();
return Command.from(input);
}

private void proceedDealerTurn(Deck deck, Dealer dealer) {
while (dealer.hasDrawableScore()) {
dealer.drawCard(deck);
outputView.printDealerDrawCard();
outputView.printNewLine();
}
}

private void showCardsWithScore(Dealer dealer, Players players) {
outputView.printDealerCardsWithScore(dealer.getCards(), dealer.getScore());
for (Player player : players.getPlayers()) {
outputView.printPlayerCardsWithScore(player.getName(), player.getCards(), player.getScore());
}
outputView.printNewLine();
}

private void showMatchResult(Dealer dealer, Players players, PlayerBettingMoney bettingResults) {
MatchResults matchResults = calculateMatchResults(dealer, players, bettingResults);
outputView.printResultStart();
showDealerResult(matchResults);
showPlayersResult(players, matchResults);
}

private MatchResults calculateMatchResults(Dealer dealer, Players players, PlayerBettingMoney bettingResults) {
MatchResults matchResults = new MatchResults(dealer.getHand());
for (Player player : players.getPlayers()) {
matchResults.addResult(player, bettingResults.getBettingAmountOf(player));
}
return matchResults;
}

private void showDealerResult(MatchResults matchResults) {
outputView.printDealerResult(matchResults.getDealerResult());
}

private void showPlayersResult(Players players, MatchResults matchResults) {
for (Player player : players.getPlayers()) {
int playerResult = matchResults.getResultOf(player);
outputView.printPlayerResult(player.getName(), playerResult);
}
}
}
26 changes: 26 additions & 0 deletions src/main/java/blackjack/game/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package blackjack.game;

import java.util.Arrays;
import java.util.Objects;

public enum Command {
YES("y"),
NO("n");
Comment on lines +6 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 카드를 더 받을지 말지 결정하는 명령어를 "y"에서 다른 문자열 요구사항으로 변경된다면
아토는 어느 패키지를 먼저 열어볼 것 같으신가요??

현재 사용자 입력을 받는 view 패키지가 따로 존재하고 BlackJackGameController 역할을 도맡아 하고있다면
추후 유지보수 측면에서 생각해봤을 때 Command의 현재 위치는 적절하지 않아보입니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 약간 고민했던 부분인데...
inputView에서 커맨드를 이용해 불리안 값을 리턴해주는 방식을 고려중입니다!


private final String value;

Command(String value) {
this.value = value;
}

public static Command from(String value) {
return Arrays.stream(values())
.filter(command -> command.value.equals(value))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("[ERROR] 존재하지 않는 명령어입니다."));
}

public boolean isNo() {
return Objects.equals(value, Command.NO.value);
}
}
Loading