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

[1단계 - 블랙잭 게임 실행] 우르(김현우) 미션 제출합니다. #391

Merged
merged 60 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e538576
docs : 기능 명세 작성
shin-mallang Feb 28, 2023
76aaeca
feat : cardValue 추가
shin-mallang Feb 28, 2023
97742fc
feat : 카드 서브클래스 추가
shin-mallang Feb 28, 2023
ba8e698
docs : 기능 명세 추가
shin-mallang Feb 28, 2023
ad1c3c9
feat : CardArea 생성
shin-mallang Feb 28, 2023
cd2f4e9
docs : 기능 명세 추가
shin-mallang Feb 28, 2023
1311e76
feat: 카드 추가 기능 구현
shin-mallang Feb 28, 2023
34f3580
feat : 카드 영역을 딜러 카드 영역과 참가자 카드 영역으로 나눔
shin-mallang Feb 28, 2023
d4a4c31
feat : 카드 점수 계산 기능 구현
shin-mallang Mar 1, 2023
99bc87f
feat : 카드를 더 받을 수 있는 상태인지 확인하는 기능 추가
shin-mallang Mar 1, 2023
9f5b973
feat : 카드를 더 받을 것인지 결정하는 기능 추가
shin-mallang Mar 1, 2023
1c9277d
refactor : 카드 모양을 enum으로 설정
shin-mallang Mar 1, 2023
e49a507
feat : 카드 전체 생성 기능 추가
shin-mallang Mar 1, 2023
542fb14
feat : 카드 뽑기 기능 추가
shin-mallang Mar 1, 2023
0f44385
feat : 참가자 이름 구현
shin-mallang Mar 1, 2023
fb25c59
feat : 참가자 카드 영역 Hit 여부 구현
shin-mallang Mar 1, 2023
328b773
refactor : 패키지 구조 수정
shin-mallang Mar 1, 2023
68ca019
fix : 카드값 계산 오류 수정
shin-mallang Mar 1, 2023
9311987
feat : 버스트 여부 확인
shin-mallang Mar 1, 2023
f662ffe
refactor : Player를 부모로 두고 Dealer와 Participant를 가지게끔 변경
shin-mallang Mar 2, 2023
3e41832
refactor : CardArea 를 구체 클래스로 변경
shin-mallang Mar 2, 2023
5f29f3c
feat : 블랙잭 게임 완성
shin-mallang Mar 2, 2023
a9b11a2
feat : 출력 메세지 변환
shin-mallang Mar 2, 2023
0c81d51
refactor: 게임 통계 로직 변경
shin-mallang Mar 2, 2023
390d577
refactor : 첫 번째 카드를 보여주는 행위는 CardArea 가 적합하다고 생각하여 옮김
java-saeng Mar 2, 2023
48de821
refactor : Dealer 최대 점수 상수화
java-saeng Mar 2, 2023
79fd9e7
refactor : enhanced-for 문 flatMap으로 수정
java-saeng Mar 2, 2023
97492f7
refactor : state != STAY 를 풀어서 작성
java-saeng Mar 2, 2023
5aefc5b
refactor : 패키지명 participant -> player 로 변경
java-saeng Mar 2, 2023
12fe147
refactor : dealer name 은 고정적으로 딜러다
java-saeng Mar 2, 2023
8f69d8f
feat : DealerResult 추가
java-saeng Mar 2, 2023
90940f4
refactor : OutputView 변경
java-saeng Mar 2, 2023
d33ac4a
refactor : 패키지 변경
java-saeng Mar 2, 2023
cb399f4
refactor : CardDeck의 자료구조 List -> Stack 으로 변경
java-saeng Mar 2, 2023
95b300e
style : pr 제출 전 reformatting
java-saeng Mar 3, 2023
4d88bea
fix : y 또는 n 을 입력하지 않을 경우 재입력 로직 추가
java-saeng Mar 3, 2023
94d8d7d
mission : mini mission ArrayList 구현하기
java-saeng Mar 3, 2023
fca34da
docs : readme 위치 변경
java-saeng Mar 4, 2023
61e8974
refactor : 블랙잭 규칙 중 burst -> bust 변경
java-saeng Mar 4, 2023
d236d9f
refactor : Player에 이미 점수 계산하는 메서드가 있어서 변경
java-saeng Mar 4, 2023
b09e72c
style : 개행 추가
java-saeng Mar 4, 2023
a05dfe2
refactor : 딜러에서 첫 번째 카드를 보여주는 메서드 추가
java-saeng Mar 4, 2023
30782d3
refactor : State 삭제
java-saeng Mar 4, 2023
eddb77b
feat : cardArea를 생성하는 CardTable 추가
java-saeng Mar 5, 2023
fef1279
feat : 점수를 관리하는 Score 객체 추가
java-saeng Mar 6, 2023
648f753
feat : 점수 관련 로직에서 int 가 아닌 Score 로 변경
java-saeng Mar 6, 2023
4606281
feat : 블랙잭 게임 결과를 CardTable에서 알려준다
java-saeng Mar 6, 2023
76947c8
refactor : 블랙잭 게임 결과를 CardTable 로 책임을 위임하면서 코드 변경
java-saeng Mar 6, 2023
8d60d6d
fix : 점수 출력을 위한 getter 메서드 추가
java-saeng Mar 6, 2023
254687c
refactor : 매직넘버 상수로 변경
java-saeng Mar 6, 2023
4b3dbd1
refactor : 값 객체의 변수 이름을 value 로 변경
java-saeng Mar 6, 2023
d0e0387
refactor : 카드 나눠주는 행위를 Controller에서 하지 않고 CardTable로 위임
java-saeng Mar 6, 2023
be60c82
refactor : 카드를 나눠줄 수 있으면 true를 반환
java-saeng Mar 6, 2023
f53acf3
refactor : 카드 나눠주는 행위를 CardTable 에 위임
java-saeng Mar 6, 2023
60bb6ec
style : 제출 전 reformatting
java-saeng Mar 6, 2023
7f54c2d
minimission : ArrayList, LinkedList 구현하기
java-saeng Mar 7, 2023
20fed88
feat : Score에서 Ace 계산하기
java-saeng Mar 7, 2023
4c9d00b
refactor : CardArea 에서 카드 값 계산 로직 수정
java-saeng Mar 7, 2023
3ae18c8
refactor : int 로 비교하지 않고 Score 에 있는 연산 사용
java-saeng Mar 7, 2023
0953389
style : 제출 전 code reformatting
java-saeng Mar 7, 2023
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
26 changes: 26 additions & 0 deletions docs/READMD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## 도메인

### 카드 값

- [x] `2~10`, `A`, `K`, `Q`, `J` 가 있다.
- [x] `K`, `Q`, `J` 는 10으로 계산된다.

### 카드

- [x] `다이아`, `클로버`, `하트`, `스페이드` 카드가 있다.
- [x] `카드 값`을 가진다.

### 카드 영역
- [x] 딜러 카드 영역과 참가자 카드 영역으로 나뉜다.
- [x] 카드들을 가진다.
- [x] 카드를 더 받을 수 있는 상태인지 확인한다.
- [x] 카드를 더 받을 것인지 결정한다.
- [x] 카드 현숫자를 계산한다.
- [X] `A` 는 1 또는 11로 계산될 수 있다.
- [x] 카드를 추가할 수 있다.
- [x] 처음에 카드 2장을 받아 생성된다.

### 카드 덱

- [x] 전체 카드를 가진다.
- [x] 카드를 한장씩 준다.
7 changes: 7 additions & 0 deletions src/main/java/BlackJackApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import controller.BlackJackController;

public class BlackJackApplication {
public static void main(String[] args) {
new BlackJackController().run();
}
}
124 changes: 124 additions & 0 deletions src/main/java/controller/BlackJackController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package controller;

import domain.area.CardArea;
import domain.deck.CardDeck;
import domain.player.dealer.Dealer;
import domain.player.dealer.DealerResult;
import domain.player.Name;
import domain.player.participant.Participant;
import domain.player.participant.ParticipantResult;
import domain.player.participant.State;
import view.InputView;
import view.OutputView;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.counting;

public class BlackJackController {

public void run() {

final CardDeck cardDeck = CardDeck.shuffledFullCardDeck();

final List<Participant> participants = dealParticipantsCards(cardDeck);
final Dealer dealer = dealDealerCards(cardDeck);

printStateAfterDealtCard(participants, dealer);
hittingPlayer(cardDeck, participants, dealer);
printStateAfterHittedCard(participants, dealer);

final Map<Participant, ParticipantResult> playersResult = determineWinner(participants, dealer);
final Map<DealerResult, Long> scoreBoard = countDealerResult(playersResult);

printPlayerScoreBoard(participants, playersResult, scoreBoard);
}

private static void printPlayerScoreBoard(final List<Participant> participants,
final Map<Participant, ParticipantResult> playersResult,
final Map<DealerResult, Long> scoreBoard) {
OutputView.showDealerScoreBoard(scoreBoard);
OutputView.showParticipantsScoreBoard(playersResult, participants);
}

private static void printStateAfterHittedCard(final List<Participant> participants, final Dealer dealer) {
OutputView.showPlayerStateResult(dealer);
OutputView.showParticipantsStateResult(participants);
}

private void hittingPlayer(final CardDeck cardDeck, final List<Participant> participants, final Dealer dealer) {
hitForParticipants(cardDeck, participants);
hitForDealer(cardDeck, dealer);
}

private static void printStateAfterDealtCard(final List<Participant> participants, final Dealer dealer) {
OutputView.showDealtCardTo(participants);
OutputView.showStateOf(dealer);
OutputView.showStateOf(participants);
}

private static Map<DealerResult, Long> countDealerResult(
final Map<Participant, ParticipantResult> playersResult) {
return playersResult.keySet()
.stream()
.collect(Collectors.groupingBy(participant -> playersResult.get(participant)
.convertToDealerResult(),
counting()));
}

private static Map<Participant, ParticipantResult> determineWinner(final List<Participant> participants,
final Dealer dealer) {
return participants.stream()
.collect(Collectors.toMap(
Function.identity(),
participant -> ParticipantResult.matchBetween(participant, dealer))
);
}

private void hitForDealer(final CardDeck cardDeck, final Dealer dealer) {
while (dealer.canHit()) {
OutputView.dealerOneMoreCard();
dealer.hit(cardDeck.draw());
}
}

private void hitForParticipants(final CardDeck cardDeck, final List<Participant> participants) {
participants.forEach(participant -> hitForParticipant(cardDeck, participant));
}

private void hitForParticipant(final CardDeck cardDeck, final Participant participant) {
while (participant.canHit()) {
participant.changeState(inputHitOrStay(participant));
determineHitForParticipant(cardDeck, participant);
}
}

private void determineHitForParticipant(final CardDeck cardDeck, final Participant participant) {
if (participant.wantHit()) {
participant.hit(cardDeck.draw());
}
OutputView.showStateOf(participant);
Copy link

Choose a reason for hiding this comment

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

want라는 개념을 뽑아내주셨네요 👍
다만, player의 상태를 바꿀 필요 없이

while (participant.canHit() && inputHitOrStay() == YES) {
...

이런 식으로 구현해도 되지 않을까요?

Copy link
Author

Choose a reason for hiding this comment

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

범블비 말씀을 듣고 굳이 State 가 필요없지 않을까라는 생각을 했어요.

제가 봤을 땐,

  1. canHit 가 true 라고 가정
  2. y 면 무조건 hit
  3. n 이면 stay

그래서 canHit 로 hit 할 수 있는지 없는지 판단하고, y와 n을 통해서 카드를 draw 할지 말지 결정할 수 있기 때문에 State 가 굳이 필요없다라고 판단했습니다!!

}

private State inputHitOrStay(final Participant participant) {
if (InputView.readMoreCard(participant).equals("y")) {
return State.HIT;
}
return State.STAY;
}

private Dealer dealDealerCards(final CardDeck cardDeck) {
return new Dealer(new CardArea(cardDeck.draw(), cardDeck.draw()));
}

private List<Participant> dealParticipantsCards(final CardDeck cardDeck) {
return InputView.readParticipantsName()
.stream()
.map(Name::new)
.map(name -> new Participant(name, new CardArea(cardDeck.draw(), cardDeck.draw())))
Copy link

Choose a reason for hiding this comment

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

처음 시작할 때 두 장을 뽑는다는 건 도메인 룰인 것 같아요. 모델 내부로 로직을 이동시키는 건 어떨까요?

Copy link
Author

@java-saeng java-saeng Mar 5, 2023

Choose a reason for hiding this comment

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

말씀대로 두 장을 뽑는다는 것은 도메인 규칙이라서 CardTable 이라는 객체를 통해 카드 두장을 뽑아서 Dealer와 Participant 에게 전달하는 식으로 작성했습니다.

도메인 룰에 따라서 CardTable 에 참여자 각 1장 - 딜러 1장 - 참여자 각 1장 - 딜러 1장으로 수정했습니다

.collect(Collectors.toList());
}
}
60 changes: 60 additions & 0 deletions src/main/java/domain/area/CardArea.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package domain.area;

import domain.card.Card;

import java.util.ArrayList;
import java.util.List;

public class CardArea {

private final List<Card> cards = new ArrayList<>();

public CardArea(final Card firstCard, final Card secondCard) {
cards.addAll(List.of(firstCard, secondCard));
}

public List<Card> cards() {
return new ArrayList<>(cards);
}

public void addCard(final Card card) {
cards.add(card);
}

public int calculate() {
Copy link

Choose a reason for hiding this comment

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

int CardArea#calculate란 시그니처를 보면 어떤 값을 반환하는지 확실하지 않아보여요. calculateScore 등으로 이름을 바꾸거나 Score라는 값 객체 타입을 반환하면 이를 표현해줄수도 있겠네요.

Copy link
Author

@java-saeng java-saeng Mar 6, 2023

Choose a reason for hiding this comment

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

Score 라는 값 객체를 사용해서 표현해봤습니다. int 로 나타내는 것보다 타입으로 명시적으로 보여주는게 다른 사람이 코드를 보았을 때도 무엇을 계산하는지 파악하는 것보다 점수를 반환하기 때문에 점수를 계산하는구나라고 바로 알 수 있을 것 같습니다 ~~

2차 미션 때는 돈이 나오기 때문에 int money, int score 보다는 Money, Score 로 표현하는게 더 좋다고 생각했습니다!!

int aceCount = countAceCard();
int totalValue = sumTotalCardValue();

while (aceCount > 0) {
if (totalValue <= 11) {
totalValue += 10;
}
aceCount--;
}
Copy link

Choose a reason for hiding this comment

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

  • 이런 값들은 상수로 관리하면 좋을 것 같아요
  • 블랙잭은 최대 점수가 21이기 때문에 ace 갯수를 셀 필요가 없습니다. 단순히 ace가 존재하면 조건으로 체크하면 로직을 조금 더 간단하게 표현할 수 있을 것 같아요.

Copy link
Author

@java-saeng java-saeng Mar 6, 2023

Choose a reason for hiding this comment

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

이런 값들은 상수로 관리하면 좋을 것 같아요

아,, 이 부분은 꼭 했어야했는데ㅠㅠ 다음부터는 정신차리고 하겠습니다 !!

블랙잭은 최대 점수가 21이기 때문에 ace 갯수를 셀 필요가 없습니다. 단순히 ace가 존재하면 조건으로 체크하면 로직을 조금 더 간단하게 표현할 수 있을 것 같아요.

아래 이미지는 제가 작성한 테스트 케이스 들인데요.
image

범블비가 말씀해주신 힌트를 보고 계속 생각을 해봤는데 ace가 존재하는 조건으로 체크하면
1. ace가 존재, 1점 처리
2. ace가 존재, 11점 처리
3. ace가 두개 존재, 1개는 1점, 1개는 11점 처리
4. ace가 두개 존재, 1개는 1점, 1개는 1점 처리
5. ace가 두개 초과할 땐 모두 1점 처리

되는 조건이 있어서 더 복잡하게 되지 않을까 생각이 드는데, 혹시 범블비가 생각하신 부분에 대해 힌트를 좀 더 받을 수 있을까요!!?

오늘 블랙잭 피드백 강의를 듣고 범블비의 말씀을 깨달아서 수정했습니다~~

커밋 링크

return totalValue;
}

private int sumTotalCardValue() {
return cards.stream()
.mapToInt(card -> card.cardValue().value())
.sum();
}

private int countAceCard() {
return (int) cards.stream()
.filter(card -> card.cardValue().isAce())
.count();
}

public boolean canMoreCard() {
return calculate() < 21;
}

public boolean isBurst() {
Copy link

Choose a reason for hiding this comment

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

저도 처음 구현할 때는 burst인줄 알았는데 bust 더라구요. 😄
블랙잭 룰을 한 번 자세히 읽어봐도 좋을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

오 감사합니다!! 규칙 자세히 읽고 다른 로직도 한번 수정해보겠습니다 ~~

return calculate() > 21;
}

public Card firstCard() {
return cards.get(0);
}
}
35 changes: 35 additions & 0 deletions src/main/java/domain/card/Card.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package domain.card;

import java.util.Objects;

public class Card {

private final CardShape cardShape;
private final CardValue cardValue;

public Card(final CardShape cardShape, final CardValue cardValue) {
this.cardShape = cardShape;
this.cardValue = cardValue;
}

public CardShape cardShape() {
return cardShape;
}

public CardValue cardValue() {
return this.cardValue;
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof Card)) return false;
final Card card = (Card) o;
return cardShape == card.cardShape && cardValue == card.cardValue;
}

@Override
public int hashCode() {
return Objects.hash(cardShape, cardValue);
}
}
9 changes: 9 additions & 0 deletions src/main/java/domain/card/CardShape.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package domain.card;

public enum CardShape {
DIAMOND,
CLOVER,
HEART,
SPADE,
;
}
34 changes: 34 additions & 0 deletions src/main/java/domain/card/CardValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package domain.card;

public enum CardValue {

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;

CardValue(final int value) {
this.value = value;
}

public int value() {
return value;
}

public boolean isAce() {
return this == ACE;
}
}
35 changes: 35 additions & 0 deletions src/main/java/domain/deck/CardDeck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package domain.deck;

import domain.card.Card;
import domain.card.CardShape;
import domain.card.CardValue;

import java.util.Arrays;
import java.util.Collections;
import java.util.Stack;
import java.util.stream.Collectors;

public class CardDeck {

private final Stack<Card> cards;

private CardDeck(final Stack<Card> cards) {
this.cards = cards;
}

public static CardDeck shuffledFullCardDeck() {

Stack<Card> cards = Arrays.stream(CardShape.values())
.flatMap(cardShape -> Arrays.stream(CardValue.values())
.map(cardValue -> new Card(cardShape, cardValue)))
.collect(Collectors.toCollection(Stack::new));

Collections.shuffle(cards);

return new CardDeck(cards);
}

public Card draw() {
return cards.pop();
}
}
21 changes: 21 additions & 0 deletions src/main/java/domain/player/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain.player;

public class Name {

private final String value;

public Name(final String name) {
validateEmpty(name);
this.value = name;
}

private void validateEmpty(final String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("이름은 빈칸일 수 없습니다.");
}
}

public String value() {
return value;
}
}
37 changes: 37 additions & 0 deletions src/main/java/domain/player/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package domain.player;

import domain.area.CardArea;
import domain.card.Card;

public abstract class Player {

protected final Name name;
protected final CardArea cardArea;

protected Player(final Name name, final CardArea cardArea) {
this.name = name;
this.cardArea = cardArea;
}

public Name name() {
return name;
}

public CardArea cardArea() {
return cardArea;
}

public boolean isBurst() {
return cardArea.isBurst();
}

public void hit(final Card card) {
cardArea.addCard(card);
}

public abstract boolean canHit();

public int score() {
return cardArea.calculate();
}
}
Loading