diff --git a/README.md b/README.md index 03ba7ed3..ecb8cce9 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ -# java-blackjack \ No newline at end of file +# java-blackjack + +## 연료 주입 + +### 기능 요구사항 + +- [X] 각 보유 차량 (5대) 렌트할 때 대략적인 이동거리를 입력 + - [X] 자동차 객체 생성 시 생성자에 이동거리 주입 + - [X] 연료량 계산 +- [X] 연료 주입에 필요한 연료량 보고서 생성 + +### 프로그래밍 요구사항 + +- [X] 상속 / 추상 메서드 활용 +- [X] 조건문 X + +### 블랙잭 + +- [x] 카드계산 + - [x] 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산 +- [x] 카드지급 기준 + - [x] 게임을 시작하면 플레이어는 두 장의 카드를 지급 받음 + - [x] 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있음 + -[x] 딜러는 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없음 +- [x] 승자 결정 기준 + - [x] 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이김 + - [x] 게임을 완료한 후 각 플레이어별로 승패를 출력 + +### 프로그래밍 요구사항 + +- [x] 모든 엔티티를 작게 유지 +- [x] 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않음 +- [x] 딜러와 플레이어에서 발생하는 중복 코드를 제거해야 함 diff --git a/src/main/java/blackjack/Application.java b/src/main/java/blackjack/Application.java new file mode 100644 index 00000000..058a761c --- /dev/null +++ b/src/main/java/blackjack/Application.java @@ -0,0 +1,14 @@ +package blackjack; + +import blackjack.controller.GameController; + +public class Application { + + + public static void main(String[] args) { + + final GameController gameController = new GameController(); + gameController.run(); + } + +} diff --git a/src/main/java/blackjack/controller/GameController.java b/src/main/java/blackjack/controller/GameController.java new file mode 100644 index 00000000..62d05217 --- /dev/null +++ b/src/main/java/blackjack/controller/GameController.java @@ -0,0 +1,82 @@ +package blackjack.controller; + +import static blackjack.domain.Dealer.DEALER_DRAW_LIMIT; + +import blackjack.domain.Dealer; +import blackjack.domain.Deck; +import blackjack.domain.Players; +import blackjack.domain.ScoreBoard; +import blackjack.view.InputView; +import blackjack.view.ResultView; + +public class GameController { + + private final Players players; + private final Dealer dealer; + + public GameController() { + this.players = Players.create(InputView.getNames()); + this.dealer = new Dealer(); + } + + public void run() { + final Deck deck = Deck.create(); + + setUpPerson(deck); + play(deck); + showGameResult(); + } + + private void setUpPerson(final Deck deck) { + ResultView.margin(); + + players.initializeDeck(deck); + dealer.initializeDeck(deck); + + ResultView.shareCards(dealer.nameInfo().getPersonName(), players.nameInfos()); + ResultView.openCardInfo(dealer.openCards(), players.openCards()); + } + + private void play(final Deck deck) { + ResultView.margin(); + + playersJudgment(deck); + dealerJudgment(deck); + + ResultView.scoreboard(dealer.scoreInfo()); + ResultView.scoreboard(players.scoreInfos()); + } + + private void showGameResult() { + ScoreBoard scoreBoard = players.match(dealer); + ResultView.matchResult(scoreBoard.dealerMatches(dealer), + scoreBoard.playerMatches()); + } + + private void playersJudgment(final Deck deck) { + while (players.hasActivePlayer()) { + activePlayerJudgement(deck); + } + } + + private void activePlayerJudgement(final Deck deck) { + while (players.checkActivePlayerCanDrawCard() && InputView.drawChoice( + players.getActivePlayerNameInfo())) { + players.drawCardToActivePlayer(deck); + ResultView.playerCardsInfo(players.getActivePlayerCardInfo()); + } + players.nextActivePlayer(); + } + + private void dealerJudgment(final Deck deck) { + ResultView.margin(); + + while (dealer.canDrawCard()) { + dealer.drawCard(deck); + ResultView.dealerDrawDecision(dealer.nameInfo(), DEALER_DRAW_LIMIT); + } + + ResultView.margin(); + } + +} diff --git a/src/main/java/blackjack/domain/Card.java b/src/main/java/blackjack/domain/Card.java new file mode 100644 index 00000000..f99cf2aa --- /dev/null +++ b/src/main/java/blackjack/domain/Card.java @@ -0,0 +1,45 @@ +package blackjack.domain; + +import java.util.Objects; + +public class Card { + + private static final String CARD_NAME_FORMAT = "%s%s"; + + private final CardNumber cardNumber; + private final Suit suit; + + public Card(final CardNumber cardNumber, final Suit suit) { + this.cardNumber = cardNumber; + this.suit = suit; + } + + public boolean isAce() { + return cardNumber.isAce(); + } + + public String getCardName() { + return String.format(CARD_NAME_FORMAT, cardNumber.getInitial(), suit.getName()); + } + + public int getCardScore() { + return cardNumber.getScore(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Card card = (Card) o; + return suit == card.suit && cardNumber == card.cardNumber; + } + + @Override + public int hashCode() { + return Objects.hash(suit, cardNumber); + } +} diff --git a/src/main/java/blackjack/domain/CardNumber.java b/src/main/java/blackjack/domain/CardNumber.java new file mode 100644 index 00000000..1fc0390c --- /dev/null +++ b/src/main/java/blackjack/domain/CardNumber.java @@ -0,0 +1,39 @@ +package blackjack.domain; + +public enum CardNumber { + + 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 initial; + private final int score; + + CardNumber(final String initial, final int score) { + this.initial = initial; + this.score = score; + } + + public boolean isAce() { + return this == ACE; + } + + public int getScore() { + return this.score; + } + + public String getInitial() { + return this.initial; + } + +} diff --git a/src/main/java/blackjack/domain/Cards.java b/src/main/java/blackjack/domain/Cards.java new file mode 100644 index 00000000..ba87262b --- /dev/null +++ b/src/main/java/blackjack/domain/Cards.java @@ -0,0 +1,64 @@ +package blackjack.domain; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class Cards { + + private static final int ADDITION_ACE_SCORE = 10; + private static final int LIMITED_ACE_SCORE = 11; + private static final int REFERENCE_POINT = 21; + private static final String CARD_SIZE_ZERO_ERROR = "카드가 존재하지 않습니다."; + private static final int FRONT = 0; + + private final List cards; + + public Cards(final List cards) { + this.cards = cards; + } + + public int size() { + return cards.size(); + } + + public int totalScore() { + final int score = cards.stream() + .mapToInt(Card::getCardScore) + .sum(); + + if (hasAce() && score <= LIMITED_ACE_SCORE) { + return score + ADDITION_ACE_SCORE; + } + return score; + } + + public void add(final Card drawCard) { + cards.add(drawCard); + } + + private boolean hasAce() { + return cards.stream().anyMatch(Card::isAce); + } + + public List openCardOne() { + if (cards.isEmpty()) { + throw new RuntimeException(CARD_SIZE_ZERO_ERROR); + } + return Arrays.asList(cards.get(FRONT).getCardName()); + } + + public List openCardAll() { + return cards.stream() + .map(Card::getCardName) + .collect(Collectors.toList()); + } + + public boolean isBust() { + return totalScore() > REFERENCE_POINT; + } + + public boolean blackjack() { + return totalScore() == REFERENCE_POINT; + } +} diff --git a/src/main/java/blackjack/domain/Dealer.java b/src/main/java/blackjack/domain/Dealer.java new file mode 100644 index 00000000..34155021 --- /dev/null +++ b/src/main/java/blackjack/domain/Dealer.java @@ -0,0 +1,30 @@ +package blackjack.domain; + +import blackjack.dto.CardInfo; +import blackjack.dto.MatchInfo; +import java.util.ArrayList; + +public class Dealer extends Person { + + public static final int DEALER_DRAW_LIMIT = 16; + private static final String DEALER_NAME = "딜러"; + + public Dealer(final String userName,final Cards cards) { + super(userName, cards); + } + + public Dealer() { + this(DEALER_NAME, new Cards(new ArrayList<>())); + } + + @Override + public boolean canDrawCard() { + return cards.totalScore() <= DEALER_DRAW_LIMIT; + } + + @Override + public CardInfo openCards() { + return new CardInfo(userName, cards.openCardOne()); + } + +} diff --git a/src/main/java/blackjack/domain/Deck.java b/src/main/java/blackjack/domain/Deck.java new file mode 100644 index 00000000..017609d8 --- /dev/null +++ b/src/main/java/blackjack/domain/Deck.java @@ -0,0 +1,46 @@ +package blackjack.domain; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Deck { + + private static final int REMOVE_INDEX = 0; + private final List cards; + + public Deck(final List cards) { + this.cards = cards; + } + + public static Deck create() { + final List cards = createEntireCards(); + Collections.shuffle(cards); + return new Deck(cards); + } + + public Card drawCard() { + if (cards.isEmpty()) { + throw new RuntimeException("더 이상 카드를 뽑을 수 없습니다."); + } + return cards.remove(REMOVE_INDEX); + } + + public int deckSize() { + return cards.size(); + } + + private static List createEntireCards() { + return Arrays.stream(CardNumber.values()) + .flatMap(Deck::createEntireSuitCards) + .collect(Collectors.toCollection(LinkedList::new)); + } + + private static Stream createEntireSuitCards(final CardNumber cardNumber) { + return Arrays.stream(Suit.values()) + .map(suit -> new Card(cardNumber, suit)); + } +} diff --git a/src/main/java/blackjack/domain/Person.java b/src/main/java/blackjack/domain/Person.java new file mode 100644 index 00000000..9625f306 --- /dev/null +++ b/src/main/java/blackjack/domain/Person.java @@ -0,0 +1,44 @@ +package blackjack.domain; + +import blackjack.dto.CardInfo; +import blackjack.dto.NameInfo; +import blackjack.dto.ScoreInfo; + +public abstract class Person { + + public static final int INIT_CARD_COUNT = 2; + + final String userName; + final Cards cards; + + public Person(final String userName,final Cards cards) { + this.userName = userName; + this.cards = cards; + } + + public void initializeDeck(final Deck deck) { + for (int i = 0; i < INIT_CARD_COUNT; i++) { + cards.add(deck.drawCard()); + } + } + + public void drawCard(final Deck deck) { + cards.add(deck.drawCard()); + } + + public int cardSize() { + return cards.size(); + } + + public NameInfo nameInfo() { + return new NameInfo(userName); + } + + public ScoreInfo scoreInfo() { + return new ScoreInfo(userName, cards.openCardAll(), cards.totalScore()); + } + + public abstract boolean canDrawCard(); + + public abstract CardInfo openCards(); +} diff --git a/src/main/java/blackjack/domain/Player.java b/src/main/java/blackjack/domain/Player.java new file mode 100644 index 00000000..5eff8869 --- /dev/null +++ b/src/main/java/blackjack/domain/Player.java @@ -0,0 +1,33 @@ +package blackjack.domain; + +import blackjack.dto.CardInfo; +import java.util.ArrayList; + +public class Player extends Person { + + public Player(final String username) { + this(username, new Cards(new ArrayList<>())); + } + + public Player(final String userName, final Cards cards) { + super(userName, cards); + } + + public Score compareScore(final Dealer dealer) { + return Score.judge(cards, dealer.cards); + } + + public int totalScore() { + return cards.totalScore(); + } + + @Override + public boolean canDrawCard() { + return !cards.blackjack() && !cards.isBust(); + } + + @Override + public CardInfo openCards() { + return new CardInfo(userName, cards.openCardAll()); + } +} diff --git a/src/main/java/blackjack/domain/Players.java b/src/main/java/blackjack/domain/Players.java new file mode 100644 index 00000000..bd3f7421 --- /dev/null +++ b/src/main/java/blackjack/domain/Players.java @@ -0,0 +1,84 @@ +package blackjack.domain; + +import blackjack.dto.CardInfo; +import blackjack.dto.NameInfo; +import blackjack.dto.ScoreInfo; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class Players { + + private final List players; + + private int activePlayerIndex; + + public Players(final List players) { + this.players = players; + this.activePlayerIndex = 0; + } + + public static Players create(final List names) { + return new Players( + names.stream() + .map(name -> new Player(name, new Cards(new ArrayList<>()))) + .collect(Collectors.toList()) + ); + } + + public void initializeDeck(final Deck deck) { + players.stream().forEach(player -> player.initializeDeck(deck)); + } + + public boolean checkActivePlayerCanDrawCard() { + return getActivePlayer().canDrawCard(); + } + + public void drawCardToActivePlayer(final Deck deck) { + getActivePlayer().drawCard(deck); + } + + public void nextActivePlayer() { + activePlayerIndex++; + } + + public boolean hasActivePlayer() { + return activePlayerIndex < players.size(); + } + + public ScoreBoard match(final Dealer dealer) { + final Map map = new LinkedHashMap<>(); + for (Player player : players) { + map.put(player, player.compareScore(dealer)); + } + + return new ScoreBoard(map); + } + + public CardInfo getActivePlayerCardInfo() { + final Player player = getActivePlayer(); + return new CardInfo(player.userName, player.cards.openCardAll()); + } + + public NameInfo getActivePlayerNameInfo() { + return new NameInfo(getActivePlayer().userName); + } + + public List nameInfos() { + return players.stream().map(Person::nameInfo).collect(Collectors.toList()); + } + + public List openCards() { + return players.stream().map(Player::openCards).collect(Collectors.toList()); + } + + public List scoreInfos() { + return players.stream().map(Player::scoreInfo).collect(Collectors.toList()); + } + + private Player getActivePlayer() { + return players.get(activePlayerIndex); + } +} diff --git a/src/main/java/blackjack/domain/Score.java b/src/main/java/blackjack/domain/Score.java new file mode 100644 index 00000000..ed370d6e --- /dev/null +++ b/src/main/java/blackjack/domain/Score.java @@ -0,0 +1,64 @@ +package blackjack.domain; + +import java.util.Arrays; + +public enum Score { + WIN(1, "승"), + DRAW(0, "무"), + LOSE(-1, "패"); + + private static final String UNDETECTABLE_EXCEPTION_OCCURRED = "예기치 않은 오류가 발생했습니다."; + + private final int key; + private final String name; + + Score(final int key, final String name) { + this.key = key; + this.name = name; + } + + public static Score of(final int key) { + return Arrays.stream(values()) + .filter(score -> score.key == key) + .findFirst() + .orElseThrow(() -> new RuntimeException("존재하지 않는 키입니다.")); + } + + public static Score judge(final Cards playerCards, final Cards dealerCards) { + if (playerCards.isBust()) { + return LOSE; + } + + if (playerCards.blackjack() && dealerCards.blackjack()) { + return DRAW; + } + if (playerCards.blackjack()) { + return WIN; + } + if (dealerCards.blackjack()) { + return LOSE; + } + + if (playerCards.totalScore() == dealerCards.totalScore()) { + return DRAW; + } + + if (playerCards.totalScore() > dealerCards.totalScore()) { + return WIN; + } + + if (playerCards.totalScore() < dealerCards.totalScore()) { + return LOSE; + } + + throw new RuntimeException(UNDETECTABLE_EXCEPTION_OCCURRED); + } + + public Score oppositeScore() { + return Score.of(-key); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/blackjack/domain/ScoreBoard.java b/src/main/java/blackjack/domain/ScoreBoard.java new file mode 100644 index 00000000..f16ddbed --- /dev/null +++ b/src/main/java/blackjack/domain/ScoreBoard.java @@ -0,0 +1,40 @@ +package blackjack.domain; + +import blackjack.dto.MatchInfo; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ScoreBoard { + + private final static String DELIMITER = " "; + private final static String SCORE_RESULT_FORMAT = "%s%s"; + + private final Map scoreMap; + + public ScoreBoard(final Map scoreMap) { + this.scoreMap = scoreMap; + } + + public MatchInfo dealerMatches(final Dealer dealer) { + final Map countingMap = scoreMap.values().stream() + .map(Score::oppositeScore) + .collect(Collectors.groupingBy(o -> o, Collectors.counting())); + + return new MatchInfo(dealer.userName, dealerResultFormat(countingMap)); + } + + public List playerMatches() { + return scoreMap.entrySet().stream() + .map(map -> new MatchInfo(map.getKey().userName, map.getValue().getName())) + .collect(Collectors.toList()); + } + + private String dealerResultFormat(final Map countingMap) { + return countingMap.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(map -> String.format(SCORE_RESULT_FORMAT, map.getValue(), map.getKey().getName())) + .collect(Collectors.joining(DELIMITER)); + } + +} diff --git a/src/main/java/blackjack/domain/Suit.java b/src/main/java/blackjack/domain/Suit.java new file mode 100644 index 00000000..8ca090b2 --- /dev/null +++ b/src/main/java/blackjack/domain/Suit.java @@ -0,0 +1,19 @@ +package blackjack.domain; + +public enum Suit { + SPADE("스페이드"), + HEART("하트"), + DIAMOND("다이아몬드"), + CLUB("클로버"); + + private String name; + + Suit(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/src/main/java/blackjack/dto/CardInfo.java b/src/main/java/blackjack/dto/CardInfo.java new file mode 100644 index 00000000..a283b52d --- /dev/null +++ b/src/main/java/blackjack/dto/CardInfo.java @@ -0,0 +1,41 @@ +package blackjack.dto; + +import java.util.List; +import java.util.Objects; + +public class CardInfo { + + private final String personName; + private final List cardNames; + + public CardInfo(final String personName, final List cardNames) { + this.personName = personName; + this.cardNames = cardNames; + } + + public String getPersonName() { + return personName; + } + + public List getCardNames() { + return cardNames; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CardInfo cardInfo = (CardInfo) o; + return Objects.equals(personName, cardInfo.personName) && Objects.equals( + cardNames, cardInfo.cardNames); + } + + @Override + public int hashCode() { + return Objects.hash(personName, cardNames); + } +} diff --git a/src/main/java/blackjack/dto/MatchInfo.java b/src/main/java/blackjack/dto/MatchInfo.java new file mode 100644 index 00000000..a2a80be1 --- /dev/null +++ b/src/main/java/blackjack/dto/MatchInfo.java @@ -0,0 +1,40 @@ +package blackjack.dto; + +import java.util.Objects; + +public class MatchInfo { + + private final String name; + private final String matches; + + public MatchInfo(final String name, final String matches) { + this.name = name; + this.matches = matches; + } + + public String getName() { + return name; + } + + public String getMatches() { + return matches; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MatchInfo matchInfo = (MatchInfo) o; + return Objects.equals(name, matchInfo.name) && Objects.equals(matches, + matchInfo.matches); + } + + @Override + public int hashCode() { + return Objects.hash(name, matches); + } +} diff --git a/src/main/java/blackjack/dto/NameInfo.java b/src/main/java/blackjack/dto/NameInfo.java new file mode 100644 index 00000000..7cd4f912 --- /dev/null +++ b/src/main/java/blackjack/dto/NameInfo.java @@ -0,0 +1,33 @@ +package blackjack.dto; + +import java.util.Objects; + +public class NameInfo { + + private final String personName; + + public NameInfo(final String personName) { + this.personName = personName; + } + + public String getPersonName() { + return personName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NameInfo nameInfo = (NameInfo) o; + return Objects.equals(personName, nameInfo.personName); + } + + @Override + public int hashCode() { + return Objects.hash(personName); + } +} diff --git a/src/main/java/blackjack/dto/ScoreInfo.java b/src/main/java/blackjack/dto/ScoreInfo.java new file mode 100644 index 00000000..38afd9ad --- /dev/null +++ b/src/main/java/blackjack/dto/ScoreInfo.java @@ -0,0 +1,47 @@ +package blackjack.dto; + +import java.util.List; +import java.util.Objects; + +public class ScoreInfo { + + private final String name; + private final List cardNames; + private final int score; + + public ScoreInfo(final String name, final List cardNames, final int score) { + this.name = name; + this.cardNames = cardNames; + this.score = score; + } + + public String getName() { + return name; + } + + public List getCardNames() { + return cardNames; + } + + public int getScore() { + return score; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ScoreInfo scoreInfo = (ScoreInfo) o; + return score == scoreInfo.score && Objects.equals(name, scoreInfo.name) + && Objects.equals(cardNames, scoreInfo.cardNames); + } + + @Override + public int hashCode() { + return Objects.hash(name, cardNames, score); + } +} diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java new file mode 100644 index 00000000..67568492 --- /dev/null +++ b/src/main/java/blackjack/view/InputView.java @@ -0,0 +1,66 @@ +package blackjack.view; + +import blackjack.dto.NameInfo; +import java.sql.SQLOutput; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +public class InputView { + + private static final Scanner SCANNER; + private static final String SPLITTER = ","; + private static final String MESSAGE_GET_NAMES = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; + private static final String MESSAGE_UNEXPECTABLE_INPUT = "의도치 않은 입력이 들어왔습니다."; + private static final String MESSAGE_CHOICE_DECISION = "%s는 한장의 카드를 더 받겠습니까?(예는 %s, 아니오는 %s)\n"; + private static final String ANSWER_YES = "y"; + private static final String ANSWER_NO = "n"; + + static { + SCANNER = new Scanner(System.in); + } + + enum Answer { + YES(ANSWER_YES, true), + NO(ANSWER_NO, false); + + private final String choice; + + private final boolean decision; + + Answer(final String choice, final boolean decision) { + this.choice = choice; + this.decision = decision; + } + + public static Answer of(final String choice) { + return Arrays.stream(values()) + .filter(answer -> answer.choice.equals(choice)) + .findFirst() + .orElseThrow(() -> new RuntimeException(MESSAGE_UNEXPECTABLE_INPUT)); + } + + public boolean getDecision() { + return decision; + } + + } + + private InputView() {} + + public static boolean drawChoice(final NameInfo activePlayerNameInfo) { + System.out.format(MESSAGE_CHOICE_DECISION, activePlayerNameInfo.getPersonName(), + Answer.YES.choice, Answer.NO.choice); + return Answer.of(SCANNER.nextLine()).getDecision(); + } + + public static List getNames() { + System.out.println(MESSAGE_GET_NAMES); + + return Arrays.stream(SCANNER.nextLine().split(SPLITTER)) + .map(String::trim) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/blackjack/view/ResultView.java b/src/main/java/blackjack/view/ResultView.java new file mode 100644 index 00000000..7a77c5e2 --- /dev/null +++ b/src/main/java/blackjack/view/ResultView.java @@ -0,0 +1,83 @@ +package blackjack.view; + +import static blackjack.domain.Person.INIT_CARD_COUNT; + +import blackjack.dto.CardInfo; +import blackjack.dto.MatchInfo; +import blackjack.dto.NameInfo; +import blackjack.dto.ScoreInfo; +import java.util.List; +import java.util.stream.Collectors; + +public class ResultView { + + private static final String INIT_SHARE_MESSAGE = "%s와 %s에게 %d장을 받았습니다."; + private static final String DELIMITER = ", "; + private static final String DEALER_FORMAT = "%s: %s"; + private static final String PLAYER_FORMAT = "%s카드: %s"; + private static final String SCOREBOARD_FORMAT = "%s 카드: %s - 결과: %s\n"; + private static final String MATCH_RESULT_MESSAGE = "## 최종 승패"; + private static final String DEALER_DRAW_FORMAT = "%s는 %s이하라 한장의 카드를 더 받았습니다.\n"; + private static final String MATCH_RESULT_FORMAT = "%s: %s\n"; + + public static void shareCards(final String dealerName, final List playerNames) { + String playersName = playerNames.stream() + .map(NameInfo::getPersonName) + .collect(Collectors.joining(DELIMITER)); + System.out.println( + String.format(INIT_SHARE_MESSAGE, dealerName, playersName, INIT_CARD_COUNT)); + } + + public static void openCardInfo(final CardInfo dealerCardInfo, final List playerCardsInfo) { + System.out.println( + String.format(DEALER_FORMAT, dealerCardInfo.getPersonName(), join(dealerCardInfo))); + + for (CardInfo playerCardInfo : playerCardsInfo) { + System.out.println( + String.format(PLAYER_FORMAT, playerCardInfo.getPersonName(), join(playerCardInfo))); + } + } + + private static String join(final CardInfo cardInfo) { + return cardInfo.getCardNames().stream() + .collect(Collectors.joining(DELIMITER)); + } + + public static void playerCardsInfo(final CardInfo activePlayerCardsInfo) { + System.out.println(String.format(PLAYER_FORMAT, activePlayerCardsInfo.getPersonName(), + join(activePlayerCardsInfo))); + } + + public static void dealerDrawDecision(final NameInfo dealerNameInfo, final int limit) { + System.out.format(DEALER_DRAW_FORMAT, dealerNameInfo.getPersonName(), limit); + } + + public static void margin() { + System.out.println(); + } + + public static void scoreboard(final List scoreInfo) { + scoreInfo.forEach(ResultView::scoreboard); + margin(); + } + + public static void scoreboard(final ScoreInfo scoreInfo) { + System.out.format(SCOREBOARD_FORMAT, scoreInfo.getName(), + scoreInfo.getCardNames().stream().collect( + Collectors.joining(DELIMITER)), scoreInfo.getScore()); + } + + public static void matchResult(final MatchInfo dealerMatchInfo, final List playersMatchInfo) { + System.out.println(MATCH_RESULT_MESSAGE); + printMatchResult(dealerMatchInfo); + printMatchResult(playersMatchInfo); + } + + private static void printMatchResult(final List playersMatchInfo) { + playersMatchInfo.forEach(ResultView::printMatchResult); + } + + private static void printMatchResult(final MatchInfo matchInfo) { + System.out.format(MATCH_RESULT_FORMAT, matchInfo.getName(), matchInfo.getMatches()); + } +} diff --git a/src/main/java/empty.txt b/src/main/java/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/rentcompany/domain/RentCompany.java b/src/main/java/rentcompany/domain/RentCompany.java new file mode 100644 index 00000000..de4bcb96 --- /dev/null +++ b/src/main/java/rentcompany/domain/RentCompany.java @@ -0,0 +1,37 @@ +package rentcompany.domain; + +import java.util.ArrayList; +import java.util.List; +import rentcompany.domain.car.PassengerCar; + +public class RentCompany { + + private static final String COLON = " : "; + private static final String LITER = "리터"; + private static final String NEWLINE = "\n"; + + private final List cars; + + public RentCompany() { + this.cars = new ArrayList<>(); + } + + public void addCar(final PassengerCar car) { + cars.add(car); + } + + public String generateReport() { + StringBuilder sb = new StringBuilder(); + + for (PassengerCar car: cars) { + sb.append(car.getName()) + .append(COLON) + .append(String.format("%d", (int) car.getChargeQuantity())) + .append(LITER) + .append(NEWLINE); + } + + return sb.toString(); + } + +} diff --git a/src/main/java/rentcompany/domain/car/Avante.java b/src/main/java/rentcompany/domain/car/Avante.java new file mode 100644 index 00000000..ac36f003 --- /dev/null +++ b/src/main/java/rentcompany/domain/car/Avante.java @@ -0,0 +1,27 @@ +package rentcompany.domain.car; + +public class Avante extends PassengerCar { + + private static final int MILEAGE = 15; + + private final int distance; + + public Avante(final int distance) { + this.distance = distance; + } + + @Override + public double getDistancePerLiter() { + return MILEAGE; + } + + @Override + public double getTripDistance() { + return distance; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/rentcompany/domain/car/Car.java b/src/main/java/rentcompany/domain/car/Car.java new file mode 100644 index 00000000..318c1c53 --- /dev/null +++ b/src/main/java/rentcompany/domain/car/Car.java @@ -0,0 +1,9 @@ +package rentcompany.domain.car; + +public interface Car { + + double getDistancePerLiter(); + double getTripDistance(); + String getName(); + +} diff --git a/src/main/java/rentcompany/domain/car/K5.java b/src/main/java/rentcompany/domain/car/K5.java new file mode 100644 index 00000000..ae1a942f --- /dev/null +++ b/src/main/java/rentcompany/domain/car/K5.java @@ -0,0 +1,27 @@ +package rentcompany.domain.car; + +public class K5 extends PassengerCar { + + private static final int MILEAGE = 13; + + private final int distance; + + public K5(final int distance) { + this.distance = distance; + } + + @Override + public double getDistancePerLiter() { + return MILEAGE; + } + + @Override + public double getTripDistance() { + return distance; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/rentcompany/domain/car/PassengerCar.java b/src/main/java/rentcompany/domain/car/PassengerCar.java new file mode 100644 index 00000000..496b1f26 --- /dev/null +++ b/src/main/java/rentcompany/domain/car/PassengerCar.java @@ -0,0 +1,8 @@ +package rentcompany.domain.car; + +public abstract class PassengerCar implements Car { + + public double getChargeQuantity() { + return getTripDistance() / getDistancePerLiter(); + } +} diff --git a/src/main/java/rentcompany/domain/car/Sonata.java b/src/main/java/rentcompany/domain/car/Sonata.java new file mode 100644 index 00000000..746e41ac --- /dev/null +++ b/src/main/java/rentcompany/domain/car/Sonata.java @@ -0,0 +1,27 @@ +package rentcompany.domain.car; + +public class Sonata extends PassengerCar { + + private static final int MILEAGE = 10; + + private final int distance; + + public Sonata(final int distance) { + this.distance = distance; + } + + @Override + public double getDistancePerLiter() { + return MILEAGE; + } + + @Override + public double getTripDistance() { + return distance; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/test/java/blackjack/domain/CardNumberTest.java b/src/test/java/blackjack/domain/CardNumberTest.java new file mode 100644 index 00000000..7f46d5e0 --- /dev/null +++ b/src/test/java/blackjack/domain/CardNumberTest.java @@ -0,0 +1,22 @@ +package blackjack.domain; + +import blackjack.domain.CardNumber; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CardNumberTest { + + public static Stream makeEnum() { + return Stream.of(Arguments.of(CardNumber.ACE, true), Arguments.of(CardNumber.KING, false)); + } + + @ParameterizedTest + @MethodSource("makeEnum") + public void 카드가_에이스_인지_확인_테스트(CardNumber cardNumber, boolean expect) { + Assertions.assertEquals(cardNumber.isAce(), expect); + } + +} diff --git a/src/test/java/blackjack/domain/DealerTest.java b/src/test/java/blackjack/domain/DealerTest.java new file mode 100644 index 00000000..92b66f1f --- /dev/null +++ b/src/test/java/blackjack/domain/DealerTest.java @@ -0,0 +1,56 @@ +package blackjack.domain; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import blackjack.domain.Card; +import blackjack.domain.CardNumber; +import blackjack.domain.Cards; +import blackjack.domain.Dealer; +import blackjack.domain.Deck; +import blackjack.domain.Suit; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class DealerTest { + + static Stream cardSetCases() { + return Stream.of( + Arguments.of(new Cards(getCards( + new Card(CardNumber.THREE, Suit.DIAMOND), + new Card(CardNumber.NINE, Suit.CLUB) + )), true), + Arguments.of(new Cards(getCards( + new Card(CardNumber.EIGHT, Suit.DIAMOND), + new Card(CardNumber.TEN, Suit.CLUB) + )), false) + ); + } + + @ParameterizedTest + @MethodSource("cardSetCases") + void 총합_16이하일경우_한장_새로_뽑기(Cards cards, boolean expected) { + Dealer dealer = new Dealer("딜러", cards); + + assertEquals(dealer.canDrawCard(), expected); + } + + @Test + void 새로_뽑은_카드_보유_여부_검증() { + Deck deck = Deck.create(); + Dealer dealer = new Dealer("딜러", new Cards(new ArrayList<>())); + dealer.initializeDeck(deck); + dealer.drawCard(deck); + + assertEquals(dealer.cardSize(), 3); + } + + private static List getCards(Card ...cards) { + return Stream.of(cards).collect(Collectors.toList()); + } +} diff --git a/src/test/java/blackjack/domain/DeckTest.java b/src/test/java/blackjack/domain/DeckTest.java new file mode 100644 index 00000000..499f85f4 --- /dev/null +++ b/src/test/java/blackjack/domain/DeckTest.java @@ -0,0 +1,47 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.*; + +import blackjack.domain.Card; +import blackjack.domain.CardNumber; +import blackjack.domain.Deck; +import blackjack.domain.Suit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class DeckTest { + + @Test + void 덱의_사이즈는_52개이다() { + Deck deck = Deck.create(); + + assertThat(deck.deckSize()).isEqualTo(52); + } + + @Test + void 덱에서_한장의_카드를_뽑을_수_있다() { + Card clubEight = new Card(CardNumber.EIGHT, Suit.CLUB); + Card clubSeven = new Card(CardNumber.SEVEN, Suit.CLUB); + + List cards = new ArrayList<>(Arrays.asList(clubEight, clubSeven)); + + Deck deck = new Deck(cards); + + Card card = deck.drawCard(); + Card expected = new Card(CardNumber.EIGHT, Suit.CLUB); + + assertThat(card).isEqualTo(expected); + } + + @Test + void 덱이_비어있을_때는_런타임_예외가_발생한다() { + List cards = new ArrayList<>(); + Deck deck = new Deck(cards); + + assertThatThrownBy(deck::drawCard) + .isInstanceOf(RuntimeException.class) + .hasMessage("더 이상 카드를 뽑을 수 없습니다."); + } +} diff --git a/src/test/java/blackjack/domain/PlayerTest.java b/src/test/java/blackjack/domain/PlayerTest.java new file mode 100644 index 00000000..463e1bcd --- /dev/null +++ b/src/test/java/blackjack/domain/PlayerTest.java @@ -0,0 +1,49 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PlayerTest { + + private Player player; + + static Stream scoreCases() { + return Stream.of( + Arguments.of(Arrays.asList(new Card(CardNumber.FIVE, Suit.HEART)), + Arrays.asList(new Card(CardNumber.TWO, Suit.HEART)), Score.WIN), + Arguments.of(Arrays.asList(new Card(CardNumber.TWO, Suit.HEART)), + Arrays.asList(new Card(CardNumber.TWO, Suit.HEART)), Score.DRAW), + Arguments.of(Arrays.asList(new Card(CardNumber.TWO, Suit.HEART)), + Arrays.asList(new Card(CardNumber.FIVE, Suit.HEART)), Score.LOSE) + ); + } + + @BeforeEach + void init() { + player = new Player("a", new Cards(Arrays.asList(new Card(CardNumber.EIGHT, Suit.CLUB), + new Card(CardNumber.FIVE, Suit.DIAMOND)))); + } + + @Test + void 카드점수_확인_테스트() { + assertThat(player.totalScore()).isEqualTo(13); + } + + @ParameterizedTest + @MethodSource("scoreCases") + void 딜러_플레이어의_점수_비교_테스트(List playerCards, List dealerCards, Score result) { + Player player = new Player("플레이어", new Cards(playerCards)); + Dealer dealer = new Dealer("딜러", new Cards(dealerCards)); + + assertEquals(player.compareScore(dealer), result); + } +} diff --git a/src/test/java/blackjack/domain/PlayersTest.java b/src/test/java/blackjack/domain/PlayersTest.java new file mode 100644 index 00000000..135db577 --- /dev/null +++ b/src/test/java/blackjack/domain/PlayersTest.java @@ -0,0 +1,115 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import blackjack.dto.CardInfo; +import blackjack.dto.NameInfo; +import blackjack.dto.ScoreInfo; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PlayersTest { + + private List playerList; + private Player pobi; + private Player jason; + private Players players; + + @BeforeEach + void setUp() { + pobi = new Player("pobi", new Cards(new ArrayList<>())); + jason = new Player("jason", new Cards(new ArrayList<>())); + + playerList = new ArrayList<>(Arrays.asList(pobi, jason)); + players = new Players(playerList); + } + + @Test + void 플레이어별_카드_초기화_결과_검증() { + Deck deck = Deck.create(); + players.initializeDeck(deck); + + assertAll( + () -> assertEquals(pobi.cardSize(), 2), + () -> assertEquals(jason.cardSize(), 2) + ); + } + + @Test + void 플레이어가_카드를_더_뽑을_수_있는지_판단할_수_있다() { + boolean isPobiCanDraw = players.checkActivePlayerCanDrawCard(); + + assertThat(isPobiCanDraw).isTrue(); + } + + @Test + void 활성화된_플레이어의_카드정보를_가져올_수_있다() { + CardInfo activePlayerCardsInfo = players.getActivePlayerCardInfo(); + CardInfo pobiCardInfo = pobi.openCards(); + + assertThat(activePlayerCardsInfo).isEqualTo(pobiCardInfo); + + players.nextActivePlayer(); + activePlayerCardsInfo = players.getActivePlayerCardInfo(); + CardInfo jasonCardInfo = jason.openCards(); + + assertThat(activePlayerCardsInfo).isEqualTo(jasonCardInfo); + } + + @Test + void 활성화된_플레이어의_이름정보를_가져올_수_있다() { + NameInfo activePlayerNameInfo = players.getActivePlayerNameInfo(); + NameInfo pobiNameInfo = pobi.nameInfo(); + + assertThat(activePlayerNameInfo).isEqualTo(pobiNameInfo); + + players.nextActivePlayer(); + activePlayerNameInfo = players.getActivePlayerNameInfo(); + NameInfo jasonNameInfo = jason.nameInfo(); + + assertThat(activePlayerNameInfo).isEqualTo(jasonNameInfo); + } + + @Test + void 모든_플레이어의_이름정보를_가져올_수_있다() { + List playersName = players.nameInfos(); + + NameInfo pobiNameInfo = pobi.nameInfo(); + NameInfo jasonNameInfo = jason.nameInfo(); + + assertThat(playersName).containsExactly(pobiNameInfo, jasonNameInfo); + } + + @Test + void 플레이어들이_가진_모든_카드정보를_가져올_수_있다() { + List cardInfos = players.openCards(); + + CardInfo pobiCardInfo = pobi.openCards(); + CardInfo jasonCardInfo = jason.openCards(); + + assertThat(cardInfos).containsExactly(pobiCardInfo, jasonCardInfo); + } + + @Test + void 플레이어들의_점수정보를_가져올_수_있다() { + List scoreInfo = players.scoreInfos(); + + ScoreInfo pobiScoreInfo = pobi.scoreInfo(); + ScoreInfo jasonScoreInfo = jason.scoreInfo(); + + assertThat(scoreInfo).containsExactly(pobiScoreInfo, jasonScoreInfo); + } + + @Test + void 더_이상_플레이어가_존재하지_않으면_멈춘다() { + players.nextActivePlayer(); + players.nextActivePlayer(); + + assertThat(players.hasActivePlayer()).isFalse(); + } +} diff --git a/src/test/java/blackjack/domain/ScoreBoardTest.java b/src/test/java/blackjack/domain/ScoreBoardTest.java new file mode 100644 index 00000000..df5ae88e --- /dev/null +++ b/src/test/java/blackjack/domain/ScoreBoardTest.java @@ -0,0 +1,51 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import blackjack.dto.MatchInfo; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ScoreBoardTest { + + Map scoreMap; + Dealer dealer; + Deck deck = Deck.create(); + ScoreBoard scoreBoard; + + @BeforeEach + void setUp() { + dealer = new Dealer(); + dealer.initializeDeck(deck); + + scoreMap = new LinkedHashMap<>() {{ + put(new Player("pobi"), Score.LOSE); + put(new Player("지워니"), Score.WIN); + put(new Player("jason"), Score.LOSE); + }}; + scoreBoard = new ScoreBoard(scoreMap); + } + + @Test + void 딜러의_경기_결과를_얻어낼_수_있다() { + MatchInfo dealerMatchInfo = scoreBoard.dealerMatches(dealer); + MatchInfo matchInfo = new MatchInfo("딜러", "2승 1패"); + + assertThat(dealerMatchInfo.getMatches()).isEqualTo(matchInfo.getMatches()); + } + + @Test + void 모든_플레이어의_경기_결과를_얻어낼_수_있다() { + List playersMatchInfo = scoreBoard.playerMatches(); + MatchInfo pobiMatchInfo = new MatchInfo("pobi", "패"); + MatchInfo jiWoniMatchInfo = new MatchInfo("지워니", "승"); + MatchInfo jasonMatchInfo = new MatchInfo("jason", "패"); + + assertThat(playersMatchInfo).containsExactly(pobiMatchInfo, jiWoniMatchInfo, + jasonMatchInfo); + } + +} diff --git a/src/test/java/blackjack/domain/ScoreTest.java b/src/test/java/blackjack/domain/ScoreTest.java new file mode 100644 index 00000000..60c6ef29 --- /dev/null +++ b/src/test/java/blackjack/domain/ScoreTest.java @@ -0,0 +1,28 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class ScoreTest { + + @Test + void 플레이어와_딜러의_카드를_비교해_승무패를_판단할_수_있다() { + Cards playerCards = new Cards(Arrays.asList(new Card(CardNumber.EIGHT, Suit.CLUB), + new Card(CardNumber.SEVEN, Suit.CLUB))); + Cards dealerCards = new Cards(Arrays.asList(new Card(CardNumber.FIVE, Suit.CLUB), + new Card(CardNumber.FOUR, Suit.CLUB))); + + Score judge = Score.judge(playerCards, dealerCards); + + assertThat(judge).isEqualTo(Score.WIN); + } + + @Test + void 승패_결과를_뒤집는다() { + Score oppositeScore = Score.WIN.oppositeScore(); + + assertThat(oppositeScore).isEqualTo(Score.LOSE); + } +} diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/java/rentcompany/RentCompanyTest.java b/src/test/java/rentcompany/RentCompanyTest.java new file mode 100644 index 00000000..1969ee23 --- /dev/null +++ b/src/test/java/rentcompany/RentCompanyTest.java @@ -0,0 +1,44 @@ +package rentcompany; + +import static org.assertj.core.api.Assertions.assertThat; + +import rentcompany.domain.car.Avante; +import rentcompany.domain.car.K5; +import rentcompany.domain.RentCompany; +import rentcompany.domain.car.Sonata; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RentCompanyTest { + + private static final String NEWLINE = "\n"; + + // RentCompany + // - 자동차 리스트 + // - 연료량 리포트 + + @DisplayName("연료량 리포트 테스트 - 렌트카 업체에 자동차 목록, 대략적인 이동거리를 입력받으면, 주입해야 하는 연료량 확인할 수 있는 보고서가 작성됩니다.") + @Test + void testReportResultValid() { + // Given + RentCompany company = new RentCompany(); + company.addCar(new Sonata(150)); + company.addCar(new Sonata(120)); + company.addCar(new Avante(300)); + company.addCar(new K5(260)); + company.addCar(new K5(390)); + + // When + String report = company.generateReport(); + + // Then + assertThat(report) + .isEqualTo( + "Sonata : 15리터" + NEWLINE + + "Sonata : 12리터" + NEWLINE + + "Avante : 20리터" + NEWLINE + + "K5 : 20리터" + NEWLINE + + "K5 : 30리터" + NEWLINE + ); + } +}