diff --git a/src/main/java/baseball/domain/Judgement.java b/src/main/java/baseball/domain/Judgement.java new file mode 100644 index 0000000..f03b4ba --- /dev/null +++ b/src/main/java/baseball/domain/Judgement.java @@ -0,0 +1,31 @@ +package baseball.domain; + + +import baseball.domain.ball.Ball; +import baseball.domain.ball.BallList; +import baseball.domain.count.Count; +import java.util.List; +import java.util.stream.Collectors; + +public class Judgement { + + public List judgeAllBalls(BallList userBalls, BallList comBalls) { + return userBalls.getBalls().stream() + .map(userBall -> judgeOneBall(userBall, comBalls)) + .collect(Collectors.toList()); + } + + public Count judgeOneBall(Ball userBall, BallList comBalls) { + Ball comBall = comBalls.getBalls().get(userBall.getPosition()); + + if (comBall.equals(userBall)) { + return Count.STRIKE; + } + + if (comBalls.containsBall(userBall)) { + return Count.BALL; + } + + return Count.NOTHING; + } +} diff --git a/src/main/java/baseball/domain/Score.java b/src/main/java/baseball/domain/Score.java new file mode 100644 index 0000000..56ae9c9 --- /dev/null +++ b/src/main/java/baseball/domain/Score.java @@ -0,0 +1,42 @@ +package baseball.domain; + +import baseball.domain.count.Count; +import baseball.domain.count.Counts; + +public class Score { + + private final Counts counts; + + private Score(Counts counts) { + this.counts = counts; + } + + public static Score from(Counts counts) { + return new Score(counts); + } + + public String getScreenMessage() { + if (isStrikeCountGreaterThanZero() && isBallCountGreaterThanZero()) { + return counts.getStrikeCount() + Count.STRIKE.description + " " + counts.getBallCount() + Count.BALL.description; + } + + if (isStrikeCountGreaterThanZero()) { + return counts.getStrikeCount() + Count.STRIKE.description; + } + + if (isBallCountGreaterThanZero()) { + return counts.getBallCount() + Count.BALL.description; + } + + return Count.NOTHING.description; + } + + private boolean isBallCountGreaterThanZero() { + return counts.getBallCount() > 0; + } + + private boolean isStrikeCountGreaterThanZero() { + return counts.getStrikeCount() > 0; + } + +} diff --git a/src/main/java/baseball/domain/User.java b/src/main/java/baseball/domain/User.java new file mode 100644 index 0000000..d93f097 --- /dev/null +++ b/src/main/java/baseball/domain/User.java @@ -0,0 +1,41 @@ +package baseball.domain; + +import baseball.domain.ball.Ball; +import baseball.domain.ball.BallList; +import baseball.shared.MESSAGES; +import baseball.util.BaseballUtils; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class User { + + private final BallList balls; + + private User(List balls) { + this.balls = convertStringsToBallList(balls); + } + + public static User of(String ballNumbers) { + if (ballNumbers.length() != 3) { + throw new IllegalArgumentException(MESSAGES.USER_INSTANCE_ERROR_MESSAGE.getMessage()); + } + + List ballStringNumbers = BaseballUtils.splitByBlank(ballNumbers); + List ballIntegerNumbers = BaseballUtils.convertDataTypesIntoListData(ballStringNumbers); + + return new User(ballIntegerNumbers); + } + + public BallList toBallList() { + return balls; + } + + private BallList convertStringsToBallList(List ballNumbers) { + List ballList = IntStream.range(0, ballNumbers.size()) + .mapToObj(position -> Ball.of(position, ballNumbers.get(position))) + .collect(Collectors.toList()); + return BallList.of(ballList); + + } +} diff --git a/src/main/java/baseball/domain/ball/Ball.java b/src/main/java/baseball/domain/ball/Ball.java new file mode 100644 index 0000000..f8bfccf --- /dev/null +++ b/src/main/java/baseball/domain/ball/Ball.java @@ -0,0 +1,62 @@ +package baseball.domain.ball; + +import baseball.shared.MESSAGES; +import java.util.Objects; + +public class Ball { + + private static final int MIN = 1; + private static final int MAX = 9; + private final int number; + private final int position; + + private Ball(int position, int number) { + if (ballPositionValidation(position) || ballNumberValidation(number)) { + throw new IllegalArgumentException(MESSAGES.BALL_INSTANCE_ERROR.getMessage()); + } + + this.position = position; + this.number = number; + } + + public static Ball of(int position, int number) { + return new Ball(position, number); + } + + public boolean isAnotherPositionSameNumber(Ball targetBall) { + return (this.position != targetBall.getPosition()) && (this.number == targetBall.getNumber()); + } + + public int getNumber() { + return number; + } + + public int getPosition() { + return position; + } + + private boolean ballNumberValidation(int inputNumber) { + return inputNumber < MIN || inputNumber > MAX; + } + + private boolean ballPositionValidation(int inputPosition) { + return inputPosition < 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Ball ball = (Ball) o; + return number == ball.number && position == ball.position; + } + + @Override + public int hashCode() { + return Objects.hash(number, position); + } +} diff --git a/src/main/java/baseball/domain/ball/BallList.java b/src/main/java/baseball/domain/ball/BallList.java new file mode 100644 index 0000000..6a95853 --- /dev/null +++ b/src/main/java/baseball/domain/ball/BallList.java @@ -0,0 +1,52 @@ +package baseball.domain.ball; + +import baseball.shared.MESSAGES; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class BallList { + + public static final int MAX_SIZE = 3; + + private final List balls; + + public List getBalls() { + return new ArrayList<>(balls); + } + + private BallList(List balls) { + if (balls.size() != MAX_SIZE) { + throw new IllegalArgumentException(MESSAGES.BALL_LIST_INSTANCE_ERROR.getMessage()); + } + + this.balls = balls; + } + + public static BallList of(List balls) { + return new BallList(balls); + } + + public boolean containsBall(Ball ball) { + return balls.stream() + .anyMatch(b -> b.isAnotherPositionSameNumber(ball)); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BallList ballList = (BallList) o; + return Objects.equals(balls, ballList.balls); + } + + @Override + public int hashCode() { + return Objects.hashCode(balls); + } +} diff --git a/src/main/java/baseball/domain/count/Count.java b/src/main/java/baseball/domain/count/Count.java new file mode 100644 index 0000000..c4dd35a --- /dev/null +++ b/src/main/java/baseball/domain/count/Count.java @@ -0,0 +1,14 @@ +package baseball.domain.count; + +public enum Count { + + BALL("볼"), + STRIKE("스트라이크"), + NOTHING("낫띵"); + + public String description; + + Count(String description) { + this.description = description; + } +} diff --git a/src/main/java/baseball/domain/count/Counts.java b/src/main/java/baseball/domain/count/Counts.java new file mode 100644 index 0000000..f0fe0dc --- /dev/null +++ b/src/main/java/baseball/domain/count/Counts.java @@ -0,0 +1,44 @@ +package baseball.domain.count; + +import java.util.List; +import java.util.Objects; + +public class Counts { + + private final long strikeCount; + private final long ballCount; + + private Counts(List counts) { + this.strikeCount = counts.stream().filter(count -> count == Count.STRIKE).count(); + this.ballCount = counts.stream().filter(count -> count == Count.BALL).count(); + } + + public static Counts of(Count first, Count second, Count third) { + return new Counts(List.of(first, second, third)); + } + + public long getStrikeCount() { + return strikeCount; + } + + public long getBallCount() { + return ballCount; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Counts counts = (Counts) o; + return strikeCount == counts.strikeCount && ballCount == counts.ballCount; + } + + @Override + public int hashCode() { + return Objects.hash(strikeCount, ballCount); + } +} diff --git a/src/main/java/baseball/shared/MESSAGES.java b/src/main/java/baseball/shared/MESSAGES.java new file mode 100644 index 0000000..3b7bae5 --- /dev/null +++ b/src/main/java/baseball/shared/MESSAGES.java @@ -0,0 +1,17 @@ +package baseball.shared; + +public enum MESSAGES { + USER_INSTANCE_ERROR_MESSAGE("3개의 숫자를 입력하세요"), + BALL_INSTANCE_ERROR("올바르지 않은 입력입니다."), + BALL_LIST_INSTANCE_ERROR("BallNumber는 3개가 최대 입니다."); + + private String message; + + MESSAGES(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/baseball/util/BaseballUtils.java b/src/main/java/baseball/util/BaseballUtils.java new file mode 100644 index 0000000..b0aa9d1 --- /dev/null +++ b/src/main/java/baseball/util/BaseballUtils.java @@ -0,0 +1,19 @@ +package baseball.util; + +import java.util.List; +import java.util.stream.Collectors; + +public class BaseballUtils { + + public static List splitByBlank(String number) { + + return List.of(number.split("")); + } + + + public static List convertDataTypesIntoListData(List strings) { + return strings.stream() + .map(Integer::parseInt) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/baseball/domain/BallListTest.java b/src/test/java/baseball/domain/BallListTest.java new file mode 100644 index 0000000..a4131a8 --- /dev/null +++ b/src/test/java/baseball/domain/BallListTest.java @@ -0,0 +1,40 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import baseball.domain.ball.Ball; +import baseball.domain.ball.BallList; +import baseball.shared.MESSAGES; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + + +public class BallListTest { + + @Test + @DisplayName("BallList 컬렉션의 요소는 3개를 넘을 수 없다") + void BallList_요소_갯수_초과() { + assertThatThrownBy(() -> BallList.of(List.of( + Ball.of(0, 1), + Ball.of(1, 2), + Ball.of(2, 3), + Ball.of(3, 4) + ))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(MESSAGES.BALL_LIST_INSTANCE_ERROR.getMessage()); + + } + + @Test + @DisplayName("BallList 컬렉션의 요소는 3개 보다 적을 수 없다") + void BallList_요소_갯수_미만() { + assertThatThrownBy(() -> BallList.of(List.of( + Ball.of(0, 1), + Ball.of(1, 2) + ))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(MESSAGES.BALL_LIST_INSTANCE_ERROR.getMessage()); + + } +} diff --git a/src/test/java/baseball/domain/BallTest.java b/src/test/java/baseball/domain/BallTest.java new file mode 100644 index 0000000..86f492e --- /dev/null +++ b/src/test/java/baseball/domain/BallTest.java @@ -0,0 +1,41 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import baseball.domain.ball.Ball; +import baseball.shared.MESSAGES; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class BallTest { + + @ParameterizedTest + @DisplayName("Ball 객체의 number 인자는 1부터 9까지의 숫자만 입력이 가능하다") + @CsvSource({"0,0", "0, 10"}) + void test_Ball_객체_number_인자_오류(int position, int number) { + assertThatThrownBy(() -> { + Ball.of(position, number); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(MESSAGES.BALL_INSTANCE_ERROR.getMessage()); + + } + + @Test + @DisplayName("Ball 객체의 position 인자는 0보다 낮을 수 없다") + void test_Ball_객체_position_인자_오류() { + assertThatThrownBy(() -> Ball.of(-1, 1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(MESSAGES.BALL_INSTANCE_ERROR.getMessage()); + + } + + @ParameterizedTest + @DisplayName("Ball 객체는 0보다 큰 position과 1부터 9까지의 number만 입력이 가능하다") + @CsvSource({"0, 1", "0, 9"}) + void test_Ball_객체_생성_성공(int position, int number) { + assertThat(Ball.of(position, number)).isInstanceOf(Ball.class); + } +} diff --git a/src/test/java/baseball/domain/JudgementTest.java b/src/test/java/baseball/domain/JudgementTest.java new file mode 100644 index 0000000..0baab1e --- /dev/null +++ b/src/test/java/baseball/domain/JudgementTest.java @@ -0,0 +1,110 @@ +package baseball.domain; + + +import static org.assertj.core.api.Assertions.*; + +import baseball.domain.ball.Ball; +import baseball.domain.ball.BallList; +import baseball.domain.count.Count; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class JudgementTest { + + private static Judgement judgement; + private static BallList comBalls; + + @BeforeAll + public static void setUp() { + judgement = new Judgement(); + comBalls = BallList.of( + List.of( + Ball.of(0, 4), + Ball.of(1, 5), + Ball.of(2, 6) + ) + ); + } + + @Test + @DisplayName("유저와 컴퓨터의 Ball 속성 값 중 number만 같으면 볼이다") + void test_Ball_비교_1볼() { + assertThat( + judgement.judgeOneBall( + Ball.of(0, 5), + comBalls + ) + ).isEqualTo(Count.BALL); + } + + @Test + @DisplayName("유저와 컴퓨터의 Ball 속성 값이 모두 같으면 스트라이크이다") + void test_Ball_비교_1스트라이크() { + assertThat( + judgement.judgeOneBall( + Ball.of(0, 4), + comBalls + ) + ).isEqualTo(Count.STRIKE); + } + + @Test + @DisplayName("유저와 컴퓨터의 Ball 속성 값이 모두 다르면 낫띵이다") + void test_Ball_비교_낫띵() { + assertThat( + judgement.judgeOneBall( + Ball.of(0, 3), + comBalls + ) + ).isEqualTo(Count.NOTHING); + } + + @Test + @DisplayName("유저와 컴퓨터의 BallList 요소의 속성 값이 모두 다르면 낫띵") + void test_BallList_비교_낫띵() { + BallList userBalls = BallList.of( + List.of( + Ball.of(0, 1), + Ball.of(1, 2), + Ball.of(2, 3) + ) + ); + + assertThat(judgement.judgeAllBalls(userBalls, comBalls)).isEqualTo( + List.of(Count.NOTHING, Count.NOTHING, Count.NOTHING)); + + } + + @Test + @DisplayName("유저와 컴퓨터의 BallList 요소의 속성 값 중 같은 요소가 있다면 스트라이크") + void test_BallList_비교_스트라이크() { + BallList userBalls = BallList.of( + List.of( + Ball.of(0, 4), + Ball.of(1, 2), + Ball.of(2, 3) + ) + ); + + assertThat(judgement.judgeAllBalls(userBalls, comBalls)).isEqualTo( + List.of(Count.STRIKE, Count.NOTHING, Count.NOTHING)); + } + + @Test + @DisplayName("유저와 컴퓨터의 BallList 요소의 속성 값 중 같은 포지션만 같다면 볼") + void test_BallList_비교_볼() { + BallList userBalls = BallList.of( + List.of( + Ball.of(0, 1), + Ball.of(1, 4), + Ball.of(2, 3) + ) + ); + + assertThat(judgement.judgeAllBalls(userBalls, comBalls)).isEqualTo( + List.of(Count.NOTHING, Count.BALL, Count.NOTHING)); + } +} + diff --git a/src/test/java/baseball/domain/ScoreTest.java b/src/test/java/baseball/domain/ScoreTest.java new file mode 100644 index 0000000..29e4383 --- /dev/null +++ b/src/test/java/baseball/domain/ScoreTest.java @@ -0,0 +1,103 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import baseball.domain.count.Count; +import baseball.domain.count.Counts; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class ScoreTest { + + private List setCounts; + + @BeforeEach + void setUp() { + setCounts = new ArrayList<>(); + setCounts.add(Count.NOTHING); + setCounts.add(Count.NOTHING); + setCounts.add(Count.NOTHING); + } + + @Test + @DisplayName("Counts 객체는 매번 새로운 인스턴스가 생성되어야 한다") + void test_counts_객체_생성_검증() { + Counts counts1 = Counts.of(Count.NOTHING, Count.NOTHING, Count.NOTHING); + Counts counts2 = Counts.of(Count.NOTHING, Count.NOTHING, Count.NOTHING); + + assertThat(counts1).isNotSameAs(counts2); + } + + @Test + @DisplayName("카운트 리스트 스코어 낫띵") + void test_스코어_낫띵() { + Score score = Score.from( + Counts.of( + Count.NOTHING, + Count.NOTHING, + Count.NOTHING + ) + ); + assertThat(score.getScreenMessage()).isEqualTo("낫띵"); + } + + + @ParameterizedTest + @DisplayName("카운트 리스트 스코어 스트라이크 카운트 검증") + @CsvSource(value = {"1,1스트라이크", "2,2스트라이크", "3,3스트라이크"}) + void test_스코어_스트라이크_메세지(int strikeCount, String scoreMessage) { + for (int i = 0; i < strikeCount; i++) { + setCounts.set(i, Count.STRIKE); + } + + Score score = Score.from( + Counts.of( + setCounts.get(0), + setCounts.get(1), + setCounts.get(2) + ) + ); + + assertThat(score.getScreenMessage()).isEqualTo(scoreMessage); + } + + + @ParameterizedTest + @DisplayName("카운트 리스트 스코어 볼 카운트 검증") + @CsvSource(value = {"1,1볼", "2,2볼", "3,3볼"}) + void test_스코어_볼_메세지(int ballCount, String scoreMessage) { + + for (int i = 0; i < ballCount; i++) { + setCounts.set(i, Count.BALL); + } + + Score score = Score.from( + Counts.of( + setCounts.get(0), + setCounts.get(1), + setCounts.get(2) + ) + ); + + assertThat(score.getScreenMessage()).isEqualTo(scoreMessage); + } + + @Test + @DisplayName("카운트 리스트 스코어 1볼 1스트라이크") + void test_스코어_1볼_1스트라이크() { + Score score = Score.from( + Counts.of( + Count.BALL, + Count.STRIKE, + Count.NOTHING + ) + ); + + assertThat(score.getScreenMessage()).isEqualTo("1스트라이크 1볼"); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/domain/UserTest.java b/src/test/java/baseball/domain/UserTest.java new file mode 100644 index 0000000..00dadc3 --- /dev/null +++ b/src/test/java/baseball/domain/UserTest.java @@ -0,0 +1,47 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import baseball.shared.MESSAGES; +import baseball.domain.ball.Ball; +import baseball.domain.ball.BallList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class UserTest { + + @ParameterizedTest + @DisplayName("유저는 문자열 타입의 숫자 3개를 입력 받아야한다") + @ValueSource(strings = {"1", "12", "1234"}) + void test_유저_유효성_검증(String ballNumber) { + + assertThatThrownBy(() -> { + User.of(ballNumber); + } + ) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(MESSAGES.USER_INSTANCE_ERROR_MESSAGE.getMessage()); + } + + @Test + @DisplayName("유저는 문자열 타입의 숫자 3개를 입력 받아 BallList 값의 동등성이 보장된다") + void test_유저_볼_동등성_검증() { + assertThat(User.of("123").toBallList()).isEqualTo( + BallList.of(List.of(Ball.of(0, 1), Ball.of(1, 2), Ball.of(2, 3))) + ); + } + + + @Test + @DisplayName("유저는 문자열 타입의 숫자를 입력 받으면 숫자 자료형으로 변환 하여 Ball 객체를 생성한다") + void test_유저_볼_자료형_검증() { + List userBalls = User.of("123").toBallList().getBalls(); + + assertThat(userBalls).extracting(Ball::getNumber).containsExactly(1, 2, 3); + } + +} + diff --git a/src/test/java/baseball/util/BaseballUtilTest.java b/src/test/java/baseball/util/BaseballUtilTest.java new file mode 100644 index 0000000..8f1de43 --- /dev/null +++ b/src/test/java/baseball/util/BaseballUtilTest.java @@ -0,0 +1,22 @@ +package baseball.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BaseballUtilTest { + + @Test + @DisplayName("3개의 숫자를 입력 하면 문자열 리스트를 반환한다") + void test_문자열을_리스트로_변환() { + assertThat(BaseballUtils.splitByBlank("123")).isEqualTo(List.of("1", "2", "3")); + } + + @Test + @DisplayName("리스트 객체의 요소 타입을 문자열에서 숫자형으로 변환한다") + void test_리스트_요소_타입변경() { + assertThat(BaseballUtils.convertDataTypesIntoListData(List.of("1", "2", "3"))).isEqualTo(List.of(1, 2, 3)); + } +}