diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..7e42a8b9
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,27 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/java
+{
+ "name": "Java",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "mcr.microsoft.com/devcontainers/java:0-17",
+
+ "features": {
+ "ghcr.io/devcontainers/features/java:1": {
+ "version": "none",
+ "installMaven": "false",
+ "installGradle": "true"
+ }
+ }
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "java -version",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/README.md b/README.md
index 8fe71120..9f26c919 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,133 @@
+## 이전에 지키지 못한 설계 원칙
+* 하나하나 지시하지 말고 요청해라.
+ * 예를들어, 판사가 증인에게 1) 목격했던 장면을 떠올리고, 2) 떠오르는 시간을 순서대로 구성하고, 3) 말로 간결하게 표현해라 라고 요청하지 않는다. 그냥 "증언하라" 라고 요청한다.
+ * 마찬가지로 객체의 설계단계에서도 책임이 있는 객체에 요청만 하도록 설계한다.
+* 역할과 책임을 확실히 분리하기
+ * 이전 코드에서 InputView가 단순 input을 받고 보여주는 역할을 넘어 게임의 로직까지 관리함
+
+
+## 구현
+* Strategy pattern 사용
+* String, int 등의 자료형 사용 x. 다 포장하기
+* 딜러의 수익금 규칙은 명확하지 않으니 spec out
+* 객체의 다형성을 이용해 조건문 줄이기 (아래 UML 사용)
+
+
+ [이미지 출처](https://newwisdom.tistory.com/27)
+* 주요 객체들은 첫 구현 시 equals method & hashCode method를 override 해줄 것
+
+## 주요 객체
+ * User
+ * Player
+ * Dealer
+ * card 관련
+ * enums
+ * Denomination
+ * Suit
+ * Card
+ * Cards
+ * Deck
+ * State
+ * Running
+ * Hit
+ * Finished
+ * Bust
+ * Stay
+ * Blackjack
+ * Controller
+
+## 게임 규칙(from [toney parky](https://github.com/woowacourse/java-blackjack/tree/toneyparky))
+- [x] 플레이어의 이름을 입력 받는다.
+ - [x] 문자열 처리
+ - [x] 참여인원 제한
+ - [x] 플레이어 생성
+- [x] 플레이어마다 베팅 금액을 입력 받는다.
+ - [x] 문자열 처리
+ - [x] 입력 범위 처리
+- [x] 게임 시작시 카드 2장을 딜러와 플레이어에게 나누어 주어야 한다.
+ - [x] Deck에서 2장을 pop해서 준다. (2장 pop하는 메서드도 가능)
+ - [x] Deck을 만들어야 한다.
+- [x] 딜러는 1장, 플레이어는 2장의 카드를 공개한다.
+- [x] 처음에 블랙잭일시 플레이어와 딜러의 패에 따라 경기 처리
+- [x] 플레이어가 카드를 더 받을수 있는지 검사 해야한다.
+ - [x] ACE는 1 또는 11로 계산할 수 있다.
+ - [x] K,Q,J는 10으로 계산한다.
+- [x] 플레이어가 카드를 더 받고 싶어하는지 검사 해야한다.
+- [x] 받을수 있고, 받고자 한다면 카드를 나누어준다.
+- [x] 플레이어가 카드를 받으면 카드의 상태를 출력한다.
+- [x] 플레이어 페이즈가 끝나면 딜러가 카드를 받는다.
+ - [x] 딜러가 16이하의 점수를 갖고 있으면 넘을때 까지 카드를 받는다.
+- [x] 카드 결과를 출력한다.
+- [x] 블랙잭 게임 결과에 따른 베팅 금액을 출력한다.
+ - [x] 딜러가 블랙잭이면 블랙잭을 가진 사람 이외의 플레이어는 전부 패배하여 베팅 금액을 돌려 받지 못한다. (둘다 블랙잭이면 돌려 받음.)
+ - [x] 둘다 버스트면 딜러가 이긴다.
+ - [x] 처음 2장으로 21이 만들어지면 블랙잭이다.
+ - [x] 버스트면 0점으로 간주한다.
+ - [x] 플레이어가 블랙잭으로 승리시 배팅 금액의 1.5배를 받는다.
+ - [x] 플레이어가 승리시 베팅 금액만큼을 받는다.
+ - [x] 플레이어가 패배시 베팅 금액만큼을 잃는다.
+
+## 기능 요구 사항
+블랙잭 게임을 변형한 프로그램을 구현한다. 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다.
+
+- 플레이어는 게임을 시작할 때 배팅 금액을 정해야 한다.
+- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다.
+- 게임을 시작하면 플레이어는 두 장의 카드를 지급 받으며, 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다.
+ - 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다. 단, 카드를 추가로 뽑아 21을 초과할 경우 배팅 금액을 모두 잃게 된다.
+- 처음 두 장의 카드 합이 21일 경우 블랙잭이 되면 베팅 금액의 1.5 배를 딜러에게 받는다.
+ - 딜러와 플레이어가 모두 동시에 블랙잭인 경우 플레이어는 베팅한 금액을 돌려받는다.
+- 딜러는 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다.
+ - 딜러가 21을 초과하면 그 시점까지 남아 있던 플레이어들은 가지고 있는 패에 상관 없이 승리해 베팅 금액을 받는다.
+
+- BlackJack이 아닐 경우, 플레이어는 Hit(카드를 받는다), Stay(그만 받는다) 중 하나를 선택할 수 있다.
+
+- Stay를 선택할 경우 그 판에서는 더이상 카드를 받을 수 없으므로, 게임이 종료된다.
+
+- Hit를 선택할 시, 카드를 한 장 더 받게되고
+이 때, 카드의 합이 21이 초과했을 경우 Bust로 딜러의 카드 합 및 결과와 관계없이 패배한다.
+
+- 21이하인 경우는, 본인 판단하에 Stay를 선택해 게임을 종료하여
+딜러의 카드 합과 플레이어의 카드 합 중 높은 쪽이 승리하게 되는 게임이다.
+
+## 실행결과
+```
+게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)
+pobi,jason
+
+pobi의 배팅 금액은?
+10000
+
+jason의 배팅 금액은?
+20000
+
+딜러와 pobi, jason에게 2장의 나누었습니다.
+딜러: 3다이아몬드
+pobi카드: 2하트, 8스페이드
+jason카드: 7클로버, K스페이드
+
+pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)
+y
+pobi카드: 2하트, 8스페이드, A클로버
+pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)
+n
+jason은 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)
+n
+jason카드: 7클로버, K스페이드
+
+딜러는 16이하라 한장의 카드를 더 받았습니다.
+
+딜러 카드: 3다이아몬드, 9클로버, 8다이아몬드 - 결과: 20
+pobi카드: 2하트, 8스페이드, A클로버 - 결과: 21
+jason카드: 7클로버, K스페이드 - 결과: 17
+
+## 최종 수익
+딜러: 10000
+pobi: 10000
+jason: -20000
+
+```
+
+
## [NEXTSTEP 플레이그라운드의 미션 진행 과정](https://github.com/next-step/nextstep-docs/blob/master/playground/README.md)
---
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c0..41d9927a 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0f80bbf5..aa991fce 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0c..1b6c7873 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,101 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
+APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
+ JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=`expr $i + 1`
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/src/main/java/blackjack/Main.java b/src/main/java/blackjack/Main.java
new file mode 100644
index 00000000..279668ac
--- /dev/null
+++ b/src/main/java/blackjack/Main.java
@@ -0,0 +1,9 @@
+package blackjack;
+
+import blackjack.controller.Controller;
+
+public class Main {
+ public static void main(String[] args) {
+ Controller.run();
+ }
+}
diff --git a/src/main/java/blackjack/controller/Controller.java b/src/main/java/blackjack/controller/Controller.java
new file mode 100644
index 00000000..2e76c3e4
--- /dev/null
+++ b/src/main/java/blackjack/controller/Controller.java
@@ -0,0 +1,78 @@
+package blackjack.controller;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.card.Deck;
+import blackjack.domain.state.State;
+import blackjack.domain.state.StateFactory;
+import blackjack.domain.user.Betting;
+import blackjack.domain.user.Dealer;
+import blackjack.domain.user.Names;
+import blackjack.domain.user.Player;
+import blackjack.domain.user.Players;
+import blackjack.view.InputView;
+import blackjack.view.OutputView;
+
+public class Controller {
+ private final static Deck deck = Deck.getInstance();
+
+ private Controller() {
+ }
+
+ public static void run() {
+ Players players = enrollPlayers();
+ Dealer dealer = enrollDealer();
+
+ OutputView.printInitStatus(players, dealer);
+
+ playerDrawPhase(players);
+ dealerDrawPhase(dealer);
+
+ OutputView.printResult(players, dealer);
+ }
+
+ private static Players enrollPlayers() {
+ Names names = Names.of(InputView.inputPlayerNames());
+ Players players = new Players();
+
+ names.forEach((name) -> {
+ Betting betting = Betting.of(InputView.inputPlayerBettingMoney(name));
+
+ Card card1 = deck.draw();
+ Card card2 = deck.draw();
+ State firstState = StateFactory.firstDraw(card1, card2);
+
+ players.add(new Player(name, betting, firstState));
+ });
+ return players;
+ }
+
+ private static Dealer enrollDealer() {
+ Card card1 = deck.draw();
+ Card card2 = deck.draw();
+ State firstState = StateFactory.firstDraw(card1, card2);
+
+ return new Dealer(firstState);
+ }
+
+ private static void playerDrawPhase(Players players) {
+ players.forEach((player) -> {
+ while (!player.isFinishied() && wantDraw(player)) {
+ player.draw(deck.draw());
+ OutputView.printPlayerStatus(player);
+ }
+ });
+ }
+
+ private static void dealerDrawPhase(Dealer dealer) {
+ OutputView.printDealerDrew(dealer);
+ dealer.draw(deck.draw());
+ }
+
+ private static boolean wantDraw(Player player) {
+ String intention = InputView.inputPlayerIntention(player);
+ boolean drawStops = intention.equals("n");
+ if (drawStops)
+ player.stay();
+ return intention.equals("y");
+ }
+}
diff --git a/src/main/java/blackjack/domain/card/Card.java b/src/main/java/blackjack/domain/card/Card.java
new file mode 100644
index 00000000..189b1da3
--- /dev/null
+++ b/src/main/java/blackjack/domain/card/Card.java
@@ -0,0 +1,42 @@
+package blackjack.domain.card;
+
+import java.util.Objects;
+
+public class Card {
+ private final Denomination denomination;
+ private final Suit suit;
+ private final static String NULL_EXCEPTION_MESSAGE = "Denomination 또는 Suit로 null이 존재합니다.";
+
+ public Card(Denomination denomination, Suit suit) {
+ Objects.requireNonNull(denomination, NULL_EXCEPTION_MESSAGE);
+ Objects.requireNonNull(suit, NULL_EXCEPTION_MESSAGE);
+
+ this.denomination = denomination;
+ this.suit = suit;
+ }
+
+ public Denomination getDenomitation() {
+ return denomination;
+ }
+
+ public Suit getSuit() {
+ return suit;
+ }
+
+ @Override
+ public String toString() {
+ return denomination.getName() + suit.getName();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Card other = (Card) o;
+
+ return this.getDenomitation() == other.getDenomitation()
+ && this.getSuit() == other.getSuit();
+ }
+}
diff --git a/src/main/java/blackjack/domain/card/Cards.java b/src/main/java/blackjack/domain/card/Cards.java
new file mode 100644
index 00000000..0962dffe
--- /dev/null
+++ b/src/main/java/blackjack/domain/card/Cards.java
@@ -0,0 +1,95 @@
+package blackjack.domain.card;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+public class Cards {
+
+ private final List cards;
+ private final static String NULL_CARD_EXCEPTION_MESSAGE = "card의 값은 null이 될 수 없습니다";
+ private final static String DUPLICATED_CARD_EXCEPTION_MESSAGE = "이미 같은 card가 존재합니다";
+ private final static String DELEMITER = ", ";
+ private final static Integer BLACKJACK_SCORE = 21;
+ private final static Random RANDOM = new Random();
+
+ public Cards() {
+ this.cards = new LinkedList<>();
+ }
+
+ public Cards(Card... cards) {
+ this();
+ for (Card card : cards) {
+ this.cards.add(card);
+ }
+ }
+
+ public void add(Card card) {
+ Objects.requireNonNull(card, NULL_CARD_EXCEPTION_MESSAGE);
+ if (cards.contains(card))
+ throw new IllegalArgumentException(DUPLICATED_CARD_EXCEPTION_MESSAGE);
+ cards.add(card);
+ }
+
+ public Integer size() {
+ return cards.size();
+ }
+
+ public Card get(int index) {
+ return cards.get(index);
+ }
+
+ public boolean isBlackjack() {
+ return score() == BLACKJACK_SCORE;
+ }
+
+ public boolean isBust() {
+ return score() > BLACKJACK_SCORE;
+ }
+
+ public Integer score() {
+ int initialScore = cards.stream()
+ .mapToInt(card -> card.getDenomitation().getScore())
+ .sum();
+
+ if (initialScore <= BLACKJACK_SCORE)
+ return initialScore;
+
+ return cards.stream()
+ .mapToInt(card -> card.getDenomitation().getScore(initialScore))
+ .sum();
+ }
+
+ public Card pop() {
+ Integer randomIndex = RANDOM.nextInt(cards.size());
+ Card card = cards.get(randomIndex);
+ cards.remove(card);
+ return card;
+ }
+
+ @Override
+ public String toString() {
+ return cards.stream()
+ .map(Card::toString)
+ .collect(Collectors.joining(DELEMITER));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ Cards other = (Cards) o;
+
+ return Objects.equals(cards, other.cards);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(cards);
+ }
+}
diff --git a/src/main/java/blackjack/domain/card/Deck.java b/src/main/java/blackjack/domain/card/Deck.java
new file mode 100644
index 00000000..ff08d469
--- /dev/null
+++ b/src/main/java/blackjack/domain/card/Deck.java
@@ -0,0 +1,28 @@
+package blackjack.domain.card;
+
+public class Deck {
+ private final static Cards DECK = new Cards();
+ private static Deck instance = null;
+
+ private Deck() {
+ for (Denomination denomination : Denomination.values()) {
+ for (Suit suit : Suit.values()) {
+ DECK.add(new Card(denomination, suit));
+ }
+ }
+ }
+
+ public static Deck getInstance() {
+ if (instance == null)
+ instance = new Deck();
+ return instance;
+ }
+
+ public Card draw() {
+ return DECK.pop();
+ }
+
+ public Integer size() {
+ return DECK.size();
+ }
+}
diff --git a/src/main/java/blackjack/domain/card/Denomination.java b/src/main/java/blackjack/domain/card/Denomination.java
new file mode 100644
index 00000000..4c11d7da
--- /dev/null
+++ b/src/main/java/blackjack/domain/card/Denomination.java
@@ -0,0 +1,59 @@
+package blackjack.domain.card;
+
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public enum Denomination {
+ ACE(11, "A"),
+ 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(10, "J"),
+ QUEEN(10, "Q"),
+ KING(10, "K");
+
+ private final int score;
+ private final String name;
+ private final static Integer BIG_ACE_SCORE = 11;
+ private final static Integer BALCKJACK_SCORE = 21;
+ private final static Predicate scoreBurstPredicate = score -> score + BIG_ACE_SCORE > BALCKJACK_SCORE;
+ private final static Supplier smallAceScoreSupplier = () -> 1;
+ private final static String SCORE_EXCEPTION_MESSAGE = "score는 0보다 작을 수 없습니다.";
+
+ Denomination(final int score, final String name) {
+ this.score = score;
+ this.name = name;
+ }
+
+ public boolean isAce() {
+ return this == ACE;
+ }
+
+ public Integer getScore() {
+ return score;
+ }
+
+ public Integer getScore(int userScore) {
+ if (userScore < 0)
+ throw new IllegalArgumentException(SCORE_EXCEPTION_MESSAGE);
+ if (isAce())
+ return getAceScore(userScore);
+ return score;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private Integer getAceScore(int userScore) {
+ if (scoreBurstPredicate.test(userScore))
+ return smallAceScoreSupplier.get();
+ return BIG_ACE_SCORE;
+ }
+}
diff --git a/src/main/java/blackjack/domain/card/Suit.java b/src/main/java/blackjack/domain/card/Suit.java
new file mode 100644
index 00000000..94eecf47
--- /dev/null
+++ b/src/main/java/blackjack/domain/card/Suit.java
@@ -0,0 +1,15 @@
+package blackjack.domain.card;
+
+public enum Suit {
+ CLUBS("클로버"), DIAMONDS("다이아몬드"), HEARTS("하트"), SPADES("스페이드");
+
+ private final String name;
+
+ Suit (String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/blackjack/domain/state/Started.java b/src/main/java/blackjack/domain/state/Started.java
new file mode 100644
index 00000000..7e7ececc
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/Started.java
@@ -0,0 +1,21 @@
+package blackjack.domain.state;
+
+import blackjack.domain.card.Cards;
+
+public abstract class Started implements State {
+
+ protected final Cards cards;
+
+ public Started(final Cards cards) {
+ this.cards = cards;
+ }
+
+ public Started() {
+ this.cards = new Cards();
+ }
+
+ @Override
+ public Cards cards() {
+ return cards;
+ }
+}
diff --git a/src/main/java/blackjack/domain/state/State.java b/src/main/java/blackjack/domain/state/State.java
new file mode 100644
index 00000000..057541a0
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/State.java
@@ -0,0 +1,18 @@
+package blackjack.domain.state;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.card.Cards;
+import blackjack.domain.user.Betting;
+import blackjack.domain.user.Dealer;
+
+public interface State {
+ State draw(Card card);
+
+ State stay();
+
+ boolean isFinishied();
+
+ Cards cards();
+
+ double profit(Dealer dealer, Betting betting);
+}
diff --git a/src/main/java/blackjack/domain/state/StateFactory.java b/src/main/java/blackjack/domain/state/StateFactory.java
new file mode 100644
index 00000000..60121613
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/StateFactory.java
@@ -0,0 +1,16 @@
+package blackjack.domain.state;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.card.Cards;
+import blackjack.domain.state.finished.Blackjack;
+import blackjack.domain.state.running.Hit;
+
+public class StateFactory {
+ public static State firstDraw(Card card1, Card card2) {
+ Cards cards = new Cards(card1, card2);
+
+ if (cards.isBlackjack())
+ return new Blackjack(cards);
+ return new Hit(cards);
+ }
+}
diff --git a/src/main/java/blackjack/domain/state/finished/Blackjack.java b/src/main/java/blackjack/domain/state/finished/Blackjack.java
new file mode 100644
index 00000000..b2db365f
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/finished/Blackjack.java
@@ -0,0 +1,18 @@
+package blackjack.domain.state.finished;
+
+import blackjack.domain.card.Cards;
+import blackjack.domain.user.Dealer;
+
+// 블랙잭(Blackjack): 처음 두 장의 카드 합이 21인 경우, 베팅 금액의 1.5배
+public class Blackjack extends Finished {
+ public Blackjack(final Cards cards) {
+ super(cards);
+ }
+
+ @Override
+ public double earningRate(Dealer dealer) {
+ if (cards().isBlackjack() && dealer.isBlackjack())
+ return 0;
+ return 1.5;
+ }
+}
diff --git a/src/main/java/blackjack/domain/state/finished/Bust.java b/src/main/java/blackjack/domain/state/finished/Bust.java
new file mode 100644
index 00000000..0346fd1c
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/finished/Bust.java
@@ -0,0 +1,18 @@
+package blackjack.domain.state.finished;
+
+import blackjack.domain.card.Cards;
+import blackjack.domain.user.Dealer;
+
+// 버스트(Bust): 카드 총합이 21을 넘는 경우. 배당금을 잃는다.
+public class Bust extends Finished {
+
+ public Bust(Cards cards) {
+ super(cards);
+ }
+
+ @Override
+ public double earningRate(Dealer dealer) {
+ return 0;
+ }
+
+}
diff --git a/src/main/java/blackjack/domain/state/finished/Finished.java b/src/main/java/blackjack/domain/state/finished/Finished.java
new file mode 100644
index 00000000..a9c65fd6
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/finished/Finished.java
@@ -0,0 +1,39 @@
+package blackjack.domain.state.finished;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.card.Cards;
+import blackjack.domain.state.Started;
+import blackjack.domain.state.State;
+import blackjack.domain.user.Betting;
+import blackjack.domain.user.Dealer;
+
+// 끝난 상태를 표현하기 위한 클래스
+// profit()을 얻을 수 있지만 draw(), stay()는 사용이 불가능 하도록 예외처리
+public abstract class Finished extends Started {
+ public Finished(final Cards cards) {
+ super(cards);
+ }
+
+ @Override
+ public boolean isFinishied() {
+ return true;
+ }
+
+ @Override
+ public double profit(Dealer dealer, Betting betting) {
+ return betting.value() * earningRate(dealer);
+ }
+
+ public abstract double earningRate(Dealer dealer);
+
+ @Override
+ public State draw(final Card card) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public State stay() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/blackjack/domain/state/finished/Stay.java b/src/main/java/blackjack/domain/state/finished/Stay.java
new file mode 100644
index 00000000..68153460
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/finished/Stay.java
@@ -0,0 +1,20 @@
+package blackjack.domain.state.finished;
+
+import blackjack.domain.card.Cards;
+import blackjack.domain.user.Dealer;
+
+// 스테이(Stay): 카드를 더 뽑지 않고 차례를 마치는 것
+public class Stay extends Finished {
+ public Stay(Cards cards) {
+ super(cards);
+ }
+
+ @Override
+ public double earningRate(Dealer dealer) {
+ if (dealer.isBust() || cards().score() > dealer.score())
+ return 1;
+ if (!dealer.isBust() && cards().score() == dealer.score())
+ return 0;
+ return -1;
+ }
+}
diff --git a/src/main/java/blackjack/domain/state/running/Hit.java b/src/main/java/blackjack/domain/state/running/Hit.java
new file mode 100644
index 00000000..6cb3f4bd
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/running/Hit.java
@@ -0,0 +1,28 @@
+package blackjack.domain.state.running;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.card.Cards;
+import blackjack.domain.state.State;
+import blackjack.domain.state.finished.Bust;
+import blackjack.domain.state.finished.Stay;
+
+// 힛(Hit): 처음 2장의 상태에서 카드를 더 뽑는 것
+public class Hit extends Running {
+
+ public Hit(final Cards cards) {
+ super(cards);
+ }
+
+ @Override
+ public State draw(final Card card) {
+ cards.add(card);
+ if (cards.isBust())
+ return new Bust(cards);
+ return new Hit(cards);
+ }
+
+ @Override
+ public State stay() {
+ return new Stay(cards);
+ }
+}
diff --git a/src/main/java/blackjack/domain/state/running/Running.java b/src/main/java/blackjack/domain/state/running/Running.java
new file mode 100644
index 00000000..b43d1425
--- /dev/null
+++ b/src/main/java/blackjack/domain/state/running/Running.java
@@ -0,0 +1,23 @@
+package blackjack.domain.state.running;
+
+import blackjack.domain.card.Cards;
+import blackjack.domain.state.Started;
+import blackjack.domain.user.Betting;
+import blackjack.domain.user.Dealer;
+
+public abstract class Running extends Started {
+
+ public Running(final Cards cards) {
+ super(cards);
+ }
+
+ @Override
+ public boolean isFinishied() {
+ return false;
+ }
+
+ @Override
+ public double profit(Dealer dealer, Betting betting) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/blackjack/domain/user/AbstractUser.java b/src/main/java/blackjack/domain/user/AbstractUser.java
new file mode 100644
index 00000000..52bf5cb6
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/AbstractUser.java
@@ -0,0 +1,55 @@
+package blackjack.domain.user;
+
+import blackjack.domain.card.Cards;
+import blackjack.domain.state.State;
+
+public abstract class AbstractUser implements User {
+ private final Name name;
+ private final Betting betting;
+ protected State state;
+
+ public AbstractUser(Name name, Betting betting, State state) {
+ this.name = name;
+ this.betting = betting;
+ this.state = state;
+ }
+
+ public Name getName() {
+ return name;
+ }
+
+ public Betting getBetting() {
+ return betting;
+ }
+
+ protected void changeState(State state) {
+ this.state = state;
+ }
+
+ public void stay() {
+ changeState(state.stay());
+ }
+
+ public boolean isFinishied() {
+ return state.isFinishied();
+ }
+
+ public boolean isBlackjack() {
+ return getCards().isBlackjack();
+ }
+
+ protected Cards getCards() {
+ return state.cards();
+ }
+
+ public Integer score() {
+ return getCards().score();
+ }
+
+ @Override
+ public String toString() {
+ String cardsInString = getCards().toString();
+
+ return getName().value() + " 카드: " + cardsInString;
+ }
+}
diff --git a/src/main/java/blackjack/domain/user/Betting.java b/src/main/java/blackjack/domain/user/Betting.java
new file mode 100644
index 00000000..523ef963
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/Betting.java
@@ -0,0 +1,26 @@
+package blackjack.domain.user;
+
+public class Betting {
+
+ private final Double betting;
+ private final static String ARGUMENT_EXCEPTION_STRING = "베팅 금액은 0보다 큰 정수여야 합니다.";
+ private final static int MIN_BETTING = 0;
+
+ public Betting(double betting) {
+ validate(betting);
+ this.betting = betting;
+ }
+
+ public static Betting of(String betting) {
+ return new Betting(Double.parseDouble(betting));
+ }
+
+ private void validate(double betting) {
+ if (betting < MIN_BETTING)
+ throw new IllegalArgumentException(ARGUMENT_EXCEPTION_STRING);
+ }
+
+ public Double value() {
+ return betting;
+ }
+}
diff --git a/src/main/java/blackjack/domain/user/Dealer.java b/src/main/java/blackjack/domain/user/Dealer.java
new file mode 100644
index 00000000..2e7144f7
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/Dealer.java
@@ -0,0 +1,34 @@
+package blackjack.domain.user;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.state.State;
+
+public class Dealer extends AbstractUser {
+
+ public static final Integer STANDARD = 16;
+
+ public Dealer(State state) {
+ super(new Name("딜러"), null, state);
+ }
+
+ @Override
+ public void draw(Card card) {
+ if (canDraw()) {
+ changeState(state.draw(card));
+ return;
+ }
+ stay();
+ }
+
+ public String initStatus() {
+ return getName().value() + ": " + getCards().get(0).toString();
+ }
+
+ public boolean isBust() {
+ return getCards().isBust();
+ }
+
+ public boolean canDraw() {
+ return score() <= STANDARD;
+ }
+}
diff --git a/src/main/java/blackjack/domain/user/Name.java b/src/main/java/blackjack/domain/user/Name.java
new file mode 100644
index 00000000..b8e84064
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/Name.java
@@ -0,0 +1,27 @@
+package blackjack.domain.user;
+
+import java.util.Objects;
+
+public class Name {
+
+ private final String name;
+ private final static String NULL_NAME_EXCEPTION_MESSAGE = "이름은 null이될 수 없습니다";
+
+ public Name(String name) {
+ validate(name);
+ this.name = name;
+ }
+
+ public void validate(String name) {
+ Objects.requireNonNull(name, NULL_NAME_EXCEPTION_MESSAGE);
+ }
+
+ public String value() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/src/main/java/blackjack/domain/user/Names.java b/src/main/java/blackjack/domain/user/Names.java
new file mode 100644
index 00000000..3a8680cd
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/Names.java
@@ -0,0 +1,39 @@
+package blackjack.domain.user;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Names implements Iterable {
+ private final List names;
+
+ private static final String SPLIT_DELIMITER = ",";
+
+ public Names(List names) {
+ this.names = names;
+ }
+
+ public static Names of(List names) {
+ return new Names(names);
+ }
+
+ public static Names of(String names) {
+ String trimmedNames = names.trim();
+ return of(Arrays.stream(trimmedNames.split(SPLIT_DELIMITER))
+ .map(String::trim)
+ .map(Name::new)
+ .collect(Collectors.toList()));
+ }
+
+ public List getNames() {
+ return Collections.unmodifiableList(names);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return names.iterator();
+ }
+
+}
diff --git a/src/main/java/blackjack/domain/user/Player.java b/src/main/java/blackjack/domain/user/Player.java
new file mode 100644
index 00000000..361fa367
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/Player.java
@@ -0,0 +1,21 @@
+package blackjack.domain.user;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.state.State;
+
+public class Player extends AbstractUser {
+
+ public Player(Name name, Betting betting, State state) {
+ super(name, betting, state);
+ }
+
+ public double profit(Dealer dealer) {
+ return state.profit(dealer, getBetting());
+ }
+
+ @Override
+ public void draw(Card card) {
+ changeState(state.draw(card));
+ }
+
+}
diff --git a/src/main/java/blackjack/domain/user/Players.java b/src/main/java/blackjack/domain/user/Players.java
new file mode 100644
index 00000000..466745a2
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/Players.java
@@ -0,0 +1,50 @@
+package blackjack.domain.user;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class Players implements Iterable {
+ private final List players;
+
+ public Players() {
+ players = new ArrayList<>();
+ }
+
+ public void add(Player player) {
+ players.add(player);
+ }
+
+ public Player get(int index) {
+ return players.get(index);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return players.iterator();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Players other = (Players) o;
+ return Objects.equals(players, other.players);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(players);
+ }
+
+ @Override
+ public String toString() {
+ return players.stream()
+ .map(Player::toString)
+ .collect(Collectors.joining("\n"));
+ }
+}
diff --git a/src/main/java/blackjack/domain/user/User.java b/src/main/java/blackjack/domain/user/User.java
new file mode 100644
index 00000000..5df4f0a2
--- /dev/null
+++ b/src/main/java/blackjack/domain/user/User.java
@@ -0,0 +1,13 @@
+package blackjack.domain.user;
+
+import blackjack.domain.card.Card;
+
+public interface User {
+ Name getName();
+ Betting getBetting();
+ void draw(Card card);
+ void stay();
+ boolean isFinishied();
+ boolean isBlackjack();
+ Integer 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..b0a0b3f2
--- /dev/null
+++ b/src/main/java/blackjack/view/InputView.java
@@ -0,0 +1,27 @@
+package blackjack.view;
+
+import java.util.Scanner;
+
+import blackjack.domain.user.Name;
+import blackjack.domain.user.Player;
+
+public class InputView {
+ private static final Scanner SCANNER = new Scanner(System.in);
+
+ private InputView() {}
+
+ public static String inputPlayerNames() {
+ System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)");
+ return SCANNER.nextLine();
+ }
+
+ public static String inputPlayerBettingMoney(Name name) {
+ System.out.println("\n" + name + "의 베팅 금액은?");
+ return SCANNER.nextLine();
+ }
+
+ public static String inputPlayerIntention(Player player) {
+ System.out.println(player.getName() + "는 한장의 카드를 더 받겠습니까? (예는 y, 아니오는 n)");
+ return SCANNER.nextLine();
+ }
+}
diff --git a/src/main/java/blackjack/view/OutputView.java b/src/main/java/blackjack/view/OutputView.java
new file mode 100644
index 00000000..e2111f83
--- /dev/null
+++ b/src/main/java/blackjack/view/OutputView.java
@@ -0,0 +1,44 @@
+package blackjack.view;
+
+import blackjack.domain.user.Dealer;
+import blackjack.domain.user.Player;
+import blackjack.domain.user.Players;
+
+public class OutputView {
+ public static void printInitStatus(Players players, Dealer dealer) {
+ printNewLine();
+ System.out.println(dealer.initStatus());
+ System.out.println(players);
+ printNewLine();
+ }
+
+ public static void printPlayerStatus(Player player) {
+ System.out.println(player);
+ }
+
+ public static void printDealerDrew(Dealer dealer) {
+ if (dealer.canDraw()) {
+ printNewLine();
+ System.out.println("딜러는 " + Dealer.STANDARD + "이하라 한장의 카드를 더 받았습니다.");
+ printNewLine();
+ }
+ }
+
+ public static void printResult(Players players, Dealer dealer) {
+ printNewLine();
+ System.out.println(dealer + " - 결과: " + dealer.score());
+ players.forEach(player -> {
+ System.out.println(player + " - 결과: " + player.score());
+ });
+ printNewLine();
+
+ System.out.println("## 최종 수익");
+ players.forEach(player -> {
+ System.out.println(player.getName() + ": " + player.profit(dealer));
+ });
+ }
+
+ private static void printNewLine() {
+ System.out.println();
+ }
+}
diff --git a/src/main/java/nextstep/fp/Conditional.java b/src/main/java/nextstep/fp/Conditional.java
new file mode 100644
index 00000000..49e095c1
--- /dev/null
+++ b/src/main/java/nextstep/fp/Conditional.java
@@ -0,0 +1,5 @@
+package nextstep.fp;
+
+public interface Conditional {
+ boolean test (Integer number);
+}
diff --git a/src/main/java/nextstep/fp/Lambda.java b/src/main/java/nextstep/fp/Lambda.java
index bd68fe1c..70b3260b 100644
--- a/src/main/java/nextstep/fp/Lambda.java
+++ b/src/main/java/nextstep/fp/Lambda.java
@@ -26,31 +26,20 @@ public void run() {
}).start();
}
- public static int sumAll(List numbers) {
+ public static int sumAll(List numbers, Conditional conditon) {
int total = 0;
for (int number : numbers) {
- total += number;
+ if (conditon.test(number))
+ total += number;
}
return total;
}
public static int sumAllEven(List numbers) {
- int total = 0;
- for (int number : numbers) {
- if (number % 2 == 0) {
- total += number;
- }
- }
- return total;
+ return sumAll(numbers, number -> number % 2 == 0);
}
public static int sumAllOverThree(List numbers) {
- int total = 0;
- for (int number : numbers) {
- if (number > 3) {
- total += number;
- }
- }
- return total;
+ return sumAll(numbers, number -> number > 3);
}
}
diff --git a/src/main/java/nextstep/fp/StreamStudy.java b/src/main/java/nextstep/fp/StreamStudy.java
index b446983a..697520a8 100644
--- a/src/main/java/nextstep/fp/StreamStudy.java
+++ b/src/main/java/nextstep/fp/StreamStudy.java
@@ -15,10 +15,7 @@ public static long countWords() throws IOException {
.get("src/main/resources/fp/war-and-peace.txt")), StandardCharsets.UTF_8);
List words = Arrays.asList(contents.split("[\\P{L}]+"));
- long count = 0;
- for (String w : words) {
- if (w.length() > 12) count++;
- }
+ long count = words.stream().filter(w -> w.length() > 12).count();
return count;
}
@@ -28,6 +25,12 @@ public static void printLongestWordTop100() throws IOException {
List words = Arrays.asList(contents.split("[\\P{L}]+"));
// TODO 이 부분에 구현한다.
+ words.stream()
+ .filter(w -> w.length() > 12)
+ .distinct()
+ .sorted()
+ .limit(100)
+ .forEach(w -> System.out.println(w.toLowerCase()));
}
public static List doubleNumbers(List numbers) {
@@ -39,6 +42,6 @@ public static long sumAll(List numbers) {
}
public static long sumOverThreeAndDouble(List numbers) {
- return 0;
+ return numbers.stream().filter(n -> n > 3).map(n -> n * 2).reduce(0, Integer::sum);
}
}
\ No newline at end of file
diff --git a/src/main/java/nextstep/optional/Expression.java b/src/main/java/nextstep/optional/Expression.java
index 1c98cd6a..d455d981 100644
--- a/src/main/java/nextstep/optional/Expression.java
+++ b/src/main/java/nextstep/optional/Expression.java
@@ -1,5 +1,7 @@
package nextstep.optional;
+import java.util.stream.Stream;
+
enum Expression {
PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/");
@@ -14,12 +16,12 @@ private static boolean matchExpression(Expression e, String expression) {
}
static Expression of(String expression) {
- for (Expression v : values()) {
- if (matchExpression(v, expression)) {
- return v;
- }
- }
+ return Stream.of(values())
+ .filter(v -> matchExpression(v, expression))
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalArgumentException(
+ String.format("%s는 사칙연산에 해당하지 않는 표현식입니다.", expression)));
- throw new IllegalArgumentException(String.format("%s는 사칙연산에 해당하지 않는 표현식입니다.", expression));
}
}
diff --git a/src/main/java/nextstep/optional/User.java b/src/main/java/nextstep/optional/User.java
index 9614c2f4..f877348b 100644
--- a/src/main/java/nextstep/optional/User.java
+++ b/src/main/java/nextstep/optional/User.java
@@ -1,5 +1,7 @@
package nextstep.optional;
+import java.util.Optional;
+
public class User {
private String name;
private Integer age;
@@ -26,14 +28,20 @@ public static boolean ageIsInRange1(User user) {
if (user != null && user.getAge() != null
&& (user.getAge() >= 30
- && user.getAge() <= 45)) {
+ && user.getAge() <= 45)) {
isInRange = true;
}
return isInRange;
}
public static boolean ageIsInRange2(User user) {
- return false;
+ Optional optionalUser = Optional.ofNullable(user);
+ if (!optionalUser.isPresent())
+ return false;
+
+ return optionalUser.map(u -> u.getAge())
+ .filter(age -> age >= 30 && age <= 45)
+ .isPresent();
}
@Override
diff --git a/src/main/java/nextstep/optional/Users.java b/src/main/java/nextstep/optional/Users.java
index 6293040d..8a285b68 100644
--- a/src/main/java/nextstep/optional/Users.java
+++ b/src/main/java/nextstep/optional/Users.java
@@ -13,11 +13,9 @@ public class Users {
new User("honux", 45));
User getUser(String name) {
- for (User user : users) {
- if (user.matchName(name)) {
- return user;
- }
- }
- return DEFAULT_USER;
+ return users.stream()
+ .filter(u -> u.matchName(name))
+ .findFirst()
+ .orElseGet(() -> DEFAULT_USER);
}
}
diff --git a/src/test/java/blackjack/domain/Fixture.java b/src/test/java/blackjack/domain/Fixture.java
new file mode 100644
index 00000000..851647e0
--- /dev/null
+++ b/src/test/java/blackjack/domain/Fixture.java
@@ -0,0 +1,14 @@
+package blackjack.domain;
+
+import blackjack.domain.card.Card;
+import blackjack.domain.card.Denomination;
+import blackjack.domain.card.Suit;
+
+public class Fixture {
+ public static final Card ACE_SPADE = new Card(Denomination.ACE, Suit.SPADES);
+ public static final Card TWO_SPADE = new Card(Denomination.TWO, Suit.SPADES);
+ public static final Card KING_SPADE = new Card(Denomination.KING, Suit.SPADES);
+ public static final Card EIGHT_SPADE = new Card(Denomination.EIGHT, Suit.SPADES);
+ public static final Card NINE_SPADE = new Card(Denomination.NINE, Suit.SPADES);
+ public static final Card KING_HEART = new Card(Denomination.KING, Suit.HEARTS);
+}
diff --git a/src/test/java/blackjack/domain/card/CardTest.java b/src/test/java/blackjack/domain/card/CardTest.java
new file mode 100644
index 00000000..8879627c
--- /dev/null
+++ b/src/test/java/blackjack/domain/card/CardTest.java
@@ -0,0 +1,44 @@
+package blackjack.domain.card;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import blackjack.domain.Fixture;
+
+public class CardTest {
+ Card card;
+
+ @BeforeEach
+ void setup() {
+ card = new Card(Denomination.ACE, Suit.CLUBS);
+ }
+
+ @Test
+ void null_argument_test() {
+ assertThatNullPointerException().isThrownBy(() -> {
+ new Card(null, null);
+ });
+
+ assertThatNullPointerException().isThrownBy(() -> {
+ new Card(null, Suit.CLUBS);
+ });
+
+ assertThatNullPointerException().isThrownBy(() -> {
+ new Card(Denomination.ACE, null);
+ });
+ }
+
+ @Test
+ void toString_test() {
+ assertThat(card.toString().equals("A클로버"));
+ }
+
+ @Test
+ void equals_test() {
+ assertThat(card.equals(new Card(Denomination.ACE, Suit.CLUBS))).isTrue();
+ assertThat(card.equals(Fixture.ACE_SPADE)).isFalse();
+ }
+}
diff --git a/src/test/java/blackjack/domain/card/CardsTest.java b/src/test/java/blackjack/domain/card/CardsTest.java
new file mode 100644
index 00000000..c51e8aef
--- /dev/null
+++ b/src/test/java/blackjack/domain/card/CardsTest.java
@@ -0,0 +1,105 @@
+package blackjack.domain.card;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import blackjack.domain.Fixture;
+
+public class CardsTest {
+ Cards cards;
+
+ @BeforeEach
+ void setup() {
+ cards = new Cards();
+ }
+
+ @AfterEach
+ void teardown() {
+ cards = null;
+ }
+
+ @Test
+ void add_null_test() {
+ assertThatNullPointerException().isThrownBy(() -> {
+ cards.add(null);
+ });
+ }
+
+ @Test
+ void add_duplicated_card_test() {
+ cards.add(Fixture.ACE_SPADE);
+ assertThatIllegalArgumentException().isThrownBy(() -> {
+ cards.add(Fixture.ACE_SPADE);
+ });
+ }
+
+ @Test
+ void sum_test_under_bust_with_ace() {
+ cards.add(Fixture.ACE_SPADE);
+ cards.add(Fixture.KING_SPADE);
+
+ assertThat(cards.score()).isEqualTo(21);
+ }
+
+ @Test
+ void sum_test_over_bust_with_ace() {
+ cards.add(Fixture.ACE_SPADE);
+ cards.add(Fixture.KING_SPADE);
+ cards.add(Fixture.KING_HEART);
+
+ assertThat(cards.score()).isEqualTo(21);
+ }
+
+ @Test
+ void isBust_test() {
+ cards.add(Fixture.KING_SPADE);
+ cards.add(Fixture.KING_HEART);
+
+ assertThat(cards.isBust()).isFalse();
+
+ cards.add(new Card(Denomination.TWO, Suit.HEARTS));
+ assertThat(cards.isBust()).isTrue();
+ }
+
+ @Test
+ void pop_test() {
+ Card card = Fixture.KING_SPADE;
+ cards.add(card);
+
+ assertThat(cards.pop().equals(card)).isTrue();
+ assertThat(cards.size()).isEqualTo(0);
+ }
+
+ @Test
+ void equals_test() {
+ Card card1 = Fixture.KING_SPADE;
+ Card card2 = Fixture.KING_HEART;
+ cards.add(card1);
+ cards.add(card2);
+
+ Cards other = new Cards();
+ other.add(card1);
+ other.add(card2);
+
+ assertThat(cards.equals(other)).isTrue();
+
+ cards.add(Fixture.TWO_SPADE);
+
+ assertThat(cards.equals(other)).isFalse();
+ }
+
+ @Test
+ void toString_test() {
+ Card card1 = Fixture.KING_SPADE;
+ Card card2 = Fixture.KING_HEART;
+ cards.add(card1);
+ cards.add(card2);
+
+ assertThat(cards.toString().equals("K스페이드, K하트")).isTrue();
+ }
+}
diff --git a/src/test/java/blackjack/domain/card/DeckTest.java b/src/test/java/blackjack/domain/card/DeckTest.java
new file mode 100644
index 00000000..641d64b3
--- /dev/null
+++ b/src/test/java/blackjack/domain/card/DeckTest.java
@@ -0,0 +1,24 @@
+package blackjack.domain.card;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+public class DeckTest {
+
+ @Test
+ void getInstance_test() {
+ Deck deck = Deck.getInstance();
+ assertThat(deck).isInstanceOf(Deck.class);
+ }
+
+ @Test
+ void draw_test() {
+ Deck deck = Deck.getInstance();
+ Card card = deck.draw();
+ int totalDeckSize = 52;
+
+ assertThat(card).isInstanceOf(Card.class);
+ assertThat(deck.size()).isEqualTo(totalDeckSize - 1);
+ }
+}
diff --git a/src/test/java/blackjack/domain/card/DenominationTest.java b/src/test/java/blackjack/domain/card/DenominationTest.java
new file mode 100644
index 00000000..c128270b
--- /dev/null
+++ b/src/test/java/blackjack/domain/card/DenominationTest.java
@@ -0,0 +1,25 @@
+package blackjack.domain.card;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+import org.junit.jupiter.api.Test;
+
+public class DenominationTest {
+ @Test
+ void getScore_test() {
+ assertThat(Denomination.ACE.getScore(10))
+ .isEqualTo(11);
+ assertThat(Denomination.ACE.getScore(11))
+ .isEqualTo(1);
+ assertThat(Denomination.KING.getScore(11))
+ .isEqualTo(10);
+ }
+
+ @Test
+ void getScore_illegalArgumenttExceptionTest() {
+ assertThatIllegalArgumentException().isThrownBy(() -> {
+ Denomination.SEVEN.getScore(-1);
+ });
+ }
+}
diff --git a/src/test/java/blackjack/domain/state/HitTest.java b/src/test/java/blackjack/domain/state/HitTest.java
new file mode 100644
index 00000000..95ba25bd
--- /dev/null
+++ b/src/test/java/blackjack/domain/state/HitTest.java
@@ -0,0 +1,27 @@
+package blackjack.domain.state;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import blackjack.domain.Fixture;
+import blackjack.domain.card.Cards;
+import blackjack.domain.state.finished.Bust;
+import blackjack.domain.state.running.Hit;
+
+public class HitTest {
+
+ @Test
+ void hit_test() {
+ Hit hit = new Hit(new Cards(Fixture.ACE_SPADE, Fixture.TWO_SPADE));
+ State state = hit.draw(Fixture.KING_HEART);
+ assertThat(state).isInstanceOf(Hit.class);
+ }
+
+ @Test
+ void bust_test() {
+ Hit hit = new Hit(new Cards(Fixture.KING_HEART, Fixture.KING_SPADE, Fixture.ACE_SPADE));
+ State state = hit.draw(Fixture.TWO_SPADE);
+ assertThat(state).isInstanceOf(Bust.class);
+ }
+}
diff --git a/src/test/java/blackjack/domain/user/DealerTest.java b/src/test/java/blackjack/domain/user/DealerTest.java
new file mode 100644
index 00000000..529e3c31
--- /dev/null
+++ b/src/test/java/blackjack/domain/user/DealerTest.java
@@ -0,0 +1,20 @@
+package blackjack.domain.user;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import blackjack.domain.Fixture;
+import blackjack.domain.state.State;
+import blackjack.domain.state.StateFactory;
+
+import org.junit.jupiter.api.Test;
+
+public class DealerTest {
+ @Test
+ void firstPhase() {
+ State firstState = StateFactory.firstDraw(Fixture.KING_HEART, Fixture.KING_SPADE);
+
+ Dealer dealer = new Dealer(firstState);
+
+ assertThat(dealer.initStatus().equals("딜러: K하트")).isTrue();
+ }
+}
diff --git a/src/test/java/blackjack/domain/user/PlayerTest.java b/src/test/java/blackjack/domain/user/PlayerTest.java
new file mode 100644
index 00000000..05ab6a9d
--- /dev/null
+++ b/src/test/java/blackjack/domain/user/PlayerTest.java
@@ -0,0 +1,81 @@
+package blackjack.domain.user;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import blackjack.domain.Fixture;
+import blackjack.domain.state.State;
+import blackjack.domain.state.StateFactory;
+
+public class PlayerTest {
+ Player player;
+ Player blackjackPlayer;
+ Dealer dealer;
+ Dealer blackjackDealer;
+
+ @BeforeEach
+ void setup() {
+ Name name = new Name("hyeonwoo");
+ Betting betting = new Betting(100);
+
+ State firstState = StateFactory.firstDraw(Fixture.EIGHT_SPADE, Fixture.KING_SPADE);
+ player = new Player(name, betting, firstState);
+ player.draw(Fixture.TWO_SPADE);
+
+
+ State blackjack = StateFactory.firstDraw(Fixture.ACE_SPADE, Fixture.KING_SPADE);
+ blackjackPlayer = new Player(name, betting, blackjack);
+
+ State dealerState = StateFactory.firstDraw(Fixture.TWO_SPADE, Fixture.KING_SPADE);
+ dealer = new Dealer(dealerState);
+ dealer.draw(Fixture.ACE_SPADE);
+
+ State blackjackDealerState = StateFactory.firstDraw(Fixture.ACE_SPADE, Fixture.KING_SPADE);
+ blackjackDealer = new Dealer(blackjackDealerState);
+ }
+
+ @Test
+ void draw_isFinished_true() {
+ assertThat(player.isFinishied()).isFalse();
+ }
+
+ @Test
+ void bust() {
+ player.draw(Fixture.KING_HEART);
+
+ assertThat(player.isFinishied()).isTrue();
+ assertThat(player.profit(dealer)).isEqualTo(0.0);
+ }
+
+ @Test
+ void user_toString() {
+ assertThat(player.toString().equals("hyeonwoo 카드: 8스페이드, K스페이드, 2스페이드")).isTrue();
+ }
+
+ @Test
+ void profit_player_score_bigger_than_dealer_score() {
+ player.stay();
+
+ assertThat(player.profit(dealer)).isEqualTo(100.0);
+ }
+
+ @Test
+ void profit_should_be_0_when_both_blackjack_case() {
+ player.stay();
+ assertThat(blackjackPlayer.profit(blackjackDealer)).isEqualTo(0.0);
+ }
+
+ @Test
+ void profit_should_be_negative100_when_only_dealer_is_blackjack() {
+ player.stay();
+ assertThat(player.profit(blackjackDealer)).isEqualTo(-100.0);
+ }
+
+ @Test
+ void profit_should_be_0_when_bust_player_low_dealer_score() {
+ player.draw(Fixture.KING_HEART);
+ assertThat(player.profit(dealer)).isEqualTo(0.0);
+ }
+}
diff --git a/src/test/java/nextstep/fp/CarTest.java b/src/test/java/nextstep/fp/CarTest.java
index 1ab1106f..ecab481a 100644
--- a/src/test/java/nextstep/fp/CarTest.java
+++ b/src/test/java/nextstep/fp/CarTest.java
@@ -8,24 +8,14 @@ public class CarTest {
@Test
public void 이동() {
Car car = new Car("pobi", 0);
- Car actual = car.move(new MoveStrategy() {
- @Override
- public boolean isMovable() {
- return true;
- }
- });
+ Car actual = car.move(() -> true);
assertThat(actual).isEqualTo(new Car("pobi", 1));
}
@Test
public void 정지() {
Car car = new Car("pobi", 0);
- Car actual = car.move(new MoveStrategy() {
- @Override
- public boolean isMovable() {
- return false;
- }
- });
+ Car actual = car.move(() -> false);
assertThat(actual).isEqualTo(new Car("pobi", 0));
}
}
diff --git a/src/test/java/nextstep/fp/LambdaTest.java b/src/test/java/nextstep/fp/LambdaTest.java
index f240ac65..b2c99380 100644
--- a/src/test/java/nextstep/fp/LambdaTest.java
+++ b/src/test/java/nextstep/fp/LambdaTest.java
@@ -33,7 +33,7 @@ public void runThread() throws Exception {
@Test
public void sumAll() throws Exception {
- int sum = Lambda.sumAll(numbers);
+ int sum = Lambda.sumAll(numbers, (number) -> true);
assertThat(sum).isEqualTo(21);
}