diff --git a/build.gradle b/build.gradle index 8172fb73f..d1dd395bc 100644 --- a/build.gradle +++ b/build.gradle @@ -6,14 +6,15 @@ version = '1.0.0' sourceCompatibility = '1.8' repositories { - mavenCentral() + mavenCentral() } dependencies { testImplementation "org.junit.jupiter:junit-jupiter:5.7.2" testImplementation "org.assertj:assertj-core:3.19.0" + implementation 'org.openjdk.jol:jol-core:0.16' } test { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/baseball/console/Application.java b/src/main/java/baseball/console/Application.java new file mode 100644 index 000000000..6580fcc8c --- /dev/null +++ b/src/main/java/baseball/console/Application.java @@ -0,0 +1,17 @@ +package baseball.console; + +import baseball.core.Game; +import baseball.core.InputView; +import baseball.core.OutputView; +import baseball.core.RandomBallsGenerator; + +public class Application { + public static void main(String[] args) { + InputView inputView = new ConsoleInputView(); + OutputView outputView = new ConsoleOutputView(); + RandomBallsGenerator randomBallsGenerator = new DefaultRandomBallsGenerator(); + Game game = new Game(inputView, outputView, randomBallsGenerator); + + game.play(); + } +} diff --git a/src/main/java/baseball/console/ConsoleInputView.java b/src/main/java/baseball/console/ConsoleInputView.java new file mode 100644 index 000000000..c9cc0d57c --- /dev/null +++ b/src/main/java/baseball/console/ConsoleInputView.java @@ -0,0 +1,43 @@ +package baseball.console; + +import baseball.core.Balls; +import baseball.core.InputView; + +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +public class ConsoleInputView implements InputView { + private final Scanner scanner = new Scanner(System.in); + + @Override + public Balls getBalls(String message) { + Balls result = null; + do { + System.out.print(message); + String input = scanner.nextLine(); + List numbers = input.chars().boxed().map(c -> c - '0').collect(Collectors.toList()); + + try { + result = new Balls(numbers); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } while (result == null); + return result; + } + + @Override + public boolean shouldRetry(String message) { + while (true) { + System.out.print(message); + String input = scanner.nextLine().trim(); + if (input.equals("1")) { + return true; + } + if (input.equals("2")) { + return false; + } + } + } +} diff --git a/src/main/java/baseball/console/ConsoleOutputView.java b/src/main/java/baseball/console/ConsoleOutputView.java new file mode 100644 index 000000000..1c218408a --- /dev/null +++ b/src/main/java/baseball/console/ConsoleOutputView.java @@ -0,0 +1,10 @@ +package baseball.console; + +import baseball.core.OutputView; + +public class ConsoleOutputView implements OutputView { + @Override + public void write(String message) { + System.out.print(message); + } +} diff --git a/src/main/java/baseball/console/DefaultRandomBallsGenerator.java b/src/main/java/baseball/console/DefaultRandomBallsGenerator.java new file mode 100644 index 000000000..406bba661 --- /dev/null +++ b/src/main/java/baseball/console/DefaultRandomBallsGenerator.java @@ -0,0 +1,23 @@ +package baseball.console; + +import baseball.core.Balls; +import baseball.core.RandomBallsGenerator; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Random; +import java.util.Set; + +public class DefaultRandomBallsGenerator implements RandomBallsGenerator { + private static final Random random = new Random(); + + @Override + public Balls generateBalls() { + Set numbers = new LinkedHashSet<>(); + + while (numbers.size() != Balls.BALL_COUNT) { + numbers.add(random.nextInt(9) + 1); + } + return new Balls(new ArrayList<>(numbers)); + } +} diff --git a/src/main/java/baseball/core/Ball.java b/src/main/java/baseball/core/Ball.java new file mode 100644 index 000000000..fc12c7117 --- /dev/null +++ b/src/main/java/baseball/core/Ball.java @@ -0,0 +1,46 @@ +package baseball.core; + +import java.util.Objects; + +public class Ball { + private static final int VALUE_MIN = 1; + private static final int VALUE_MAX = 9; + private static final int POSITION_MIN = 1; + private static final int POSITION_MAX = 3; + private final int value; + private final int position; + + public Ball(int value, int position) { + if (value < VALUE_MIN || VALUE_MAX < value) { + throw new IllegalArgumentException(String.format("value should be between %d to %d", VALUE_MIN, VALUE_MAX)); + } + if (position < POSITION_MIN || POSITION_MAX < position) { + throw new IllegalArgumentException(String.format("position should be between %d to %d", POSITION_MIN, POSITION_MAX)); + } + this.value = value; + this.position = position; + } + + public BallStatus play(Ball other) { + if (this.equals(other)) { + return BallStatus.STRIKE; + } + if (this.value == other.value) { + return BallStatus.BALL; + } + return BallStatus.NOTHING; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Ball ball = (Ball) o; + return value == ball.value && position == ball.position; + } + + @Override + public int hashCode() { + return Objects.hash(value, position); + } +} diff --git a/src/main/java/baseball/core/BallStatus.java b/src/main/java/baseball/core/BallStatus.java new file mode 100644 index 000000000..20ffdf929 --- /dev/null +++ b/src/main/java/baseball/core/BallStatus.java @@ -0,0 +1,13 @@ +package baseball.core; + +public enum BallStatus { + BALL, NOTHING, STRIKE; + + public boolean isStrike() { + return this.equals(BallStatus.STRIKE); + } + + public boolean isBall() { + return this.equals(BallStatus.BALL); + } +} diff --git a/src/main/java/baseball/core/Balls.java b/src/main/java/baseball/core/Balls.java new file mode 100644 index 000000000..ac3405f01 --- /dev/null +++ b/src/main/java/baseball/core/Balls.java @@ -0,0 +1,67 @@ +package baseball.core; + +import java.util.*; + +public class Balls { + public static final int BALL_COUNT = 3; + private final List balls; + + public Balls(List numbers) { + if (numbers == null || numbers.size() != BALL_COUNT) { + throw new IllegalArgumentException("Balls의 크기는 " + BALL_COUNT + "이어야 합니다."); + } + this.balls = convertIntegerToBall(numbers); + } + + private static List convertIntegerToBall(List numbers) { + List balls = new ArrayList<>(); + int position = 1; + for (int number : numbers) { + balls.add(new Ball(number, position++)); + } + return balls; + } + + public PlayResult play(Balls other) { + int strikeCount = 0; + int ballCount = 0; + for (Ball ball : other.balls) { + BallStatus ballStatus = this.play(ball); + if (ballStatus.isStrike()) { + strikeCount += 1; + } + if (ballStatus.isBall()) { + ballCount += 1; + } + } + + return new PlayResult(strikeCount, ballCount); + } + + private BallStatus play(Ball other) { + Set result = new HashSet<>(); + for (Ball ball : this.balls) { + result.add(ball.play(other)); + } + if (result.contains(BallStatus.STRIKE)) { + return BallStatus.STRIKE; + } + if (result.contains(BallStatus.BALL)) { + return BallStatus.BALL; + } + return BallStatus.NOTHING; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Balls balls1 = (Balls) o; + return Objects.equals(balls, balls1.balls); + } + + @Override + public int hashCode() { + return Objects.hash(balls); + } +} diff --git a/src/main/java/baseball/core/Game.java b/src/main/java/baseball/core/Game.java new file mode 100644 index 000000000..8f42c99c5 --- /dev/null +++ b/src/main/java/baseball/core/Game.java @@ -0,0 +1,38 @@ +package baseball.core; + +public class Game { + private static final String NEW_LINE = System.lineSeparator(); + private static final String ENTER_INPUT_MESSAGE = "숫자를 입력해 주세요 : "; + private static final String WIN_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료" + NEW_LINE; + private static final String RETRY_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." + NEW_LINE; + + private final InputView inputView; + private final OutputView outputView; + private final RandomBallsGenerator randomBallsGenerator; + private Balls answer; + + public Game(InputView inputView, OutputView outputView, RandomBallsGenerator randomBallsGenerator) { + this.inputView = inputView; + this.outputView = outputView; + this.randomBallsGenerator = randomBallsGenerator; + this.answer = randomBallsGenerator.generateBalls(); + } + + public void play() { + while (true) { + Balls balls = this.inputView.getBalls(ENTER_INPUT_MESSAGE); + PlayResult playResult = this.answer.play(balls); + this.outputView.write(playResult.getResultMessage() + NEW_LINE); + + if (!playResult.isFinished()) { + continue; + } + + this.outputView.write(WIN_MESSAGE); + if (!this.inputView.shouldRetry(RETRY_MESSAGE)) { + return; + } + this.answer = this.randomBallsGenerator.generateBalls(); + } + } +} diff --git a/src/main/java/baseball/core/InputView.java b/src/main/java/baseball/core/InputView.java new file mode 100644 index 000000000..1a2fe36f9 --- /dev/null +++ b/src/main/java/baseball/core/InputView.java @@ -0,0 +1,7 @@ +package baseball.core; + +public interface InputView { + Balls getBalls(String message); + + boolean shouldRetry(String message); +} diff --git a/src/main/java/baseball/core/OutputView.java b/src/main/java/baseball/core/OutputView.java new file mode 100644 index 000000000..025a90602 --- /dev/null +++ b/src/main/java/baseball/core/OutputView.java @@ -0,0 +1,5 @@ +package baseball.core; + +public interface OutputView { + void write(String message); +} diff --git a/src/main/java/baseball/core/PlayResult.java b/src/main/java/baseball/core/PlayResult.java new file mode 100644 index 000000000..eba2b2430 --- /dev/null +++ b/src/main/java/baseball/core/PlayResult.java @@ -0,0 +1,43 @@ +package baseball.core; + +import java.util.Objects; + +public class PlayResult { + private final int strikeCount; + private final int ballCount; + + public PlayResult(int strikeCount, int ballCount) { + this.strikeCount = strikeCount; + this.ballCount = ballCount; + } + + public boolean isFinished() { + return this.strikeCount == Balls.BALL_COUNT; + } + + public String getResultMessage() { + if (0 < this.ballCount && 0 < this.strikeCount) { + return this.ballCount + "볼 " + this.strikeCount + "스트라이크"; + } + if (0 < this.strikeCount) { + return this.strikeCount + "스트라이크"; + } + if (0 < this.ballCount) { + return this.ballCount + "볼"; + } + return "낫싱"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlayResult that = (PlayResult) o; + return strikeCount == that.strikeCount && ballCount == that.ballCount; + } + + @Override + public int hashCode() { + return Objects.hash(strikeCount, ballCount); + } +} diff --git a/src/main/java/baseball/core/RandomBallsGenerator.java b/src/main/java/baseball/core/RandomBallsGenerator.java new file mode 100644 index 000000000..0578060ad --- /dev/null +++ b/src/main/java/baseball/core/RandomBallsGenerator.java @@ -0,0 +1,5 @@ +package baseball.core; + +public interface RandomBallsGenerator { + Balls generateBalls(); +} diff --git a/src/test/java/baseball/core/BallTest.java b/src/test/java/baseball/core/BallTest.java new file mode 100644 index 000000000..7071f2a85 --- /dev/null +++ b/src/test/java/baseball/core/BallTest.java @@ -0,0 +1,54 @@ +package baseball.core; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class BallTest { + @ParameterizedTest + @ValueSource(ints = {0, 4}) + void invalidPosition(int position) { + Assertions.assertThatThrownBy(() -> new Ball(1, position)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(ints = {0, 10}) + void invalidValue(int value) { + Assertions.assertThatThrownBy(() -> new Ball(value, 1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void strike() { + Ball ball1 = new Ball(1, 1); + Ball ball2 = new Ball(1, 1); + + BallStatus actual = ball1.play(ball2); + + Assertions.assertThat(actual).isEqualTo(BallStatus.STRIKE); + } + + @Test + void ball() { + Ball ball1 = new Ball(1, 1); + Ball ball2 = new Ball(1, 2); + + BallStatus actual = ball1.play(ball2); + + Assertions.assertThat(actual).isEqualTo(BallStatus.BALL); + } + + @Test + void nothing() { + Ball ball1 = new Ball(2, 1); + Ball ball2 = new Ball(1, 2); + + BallStatus actual = ball1.play(ball2); + + Assertions.assertThat(actual).isEqualTo(BallStatus.NOTHING); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/core/BallsTest.java b/src/test/java/baseball/core/BallsTest.java new file mode 100644 index 000000000..e24d56db0 --- /dev/null +++ b/src/test/java/baseball/core/BallsTest.java @@ -0,0 +1,67 @@ +package baseball.core; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BallsTest { + @Test + void invalidConstructorInput() { + assertThatThrownBy(() -> new Balls(Arrays.asList(1, 2, 3, 4))).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new Balls(Arrays.asList(1, 2))).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void equals() { + Balls balls1 = new Balls(Arrays.asList(1, 2, 3)); + Balls balls2 = new Balls(Arrays.asList(1, 2, 3)); + + assertThat(balls1).isEqualTo(balls2); + + Balls balls3 = new Balls(Arrays.asList(2, 1, 3)); + assertThat(balls1).isNotEqualTo(balls3); + } + + @Test + void playTest() { + Balls balls1 = new Balls(Arrays.asList(1, 2, 3)); + Balls balls2 = new Balls(Arrays.asList(1, 2, 3)); + + PlayResult result = balls1.play(balls2); + + assertThat(result).isEqualTo(new PlayResult(3, 0)); + } + + @Test + void playTest2() { + Balls balls1 = new Balls(Arrays.asList(1, 2, 3)); + Balls balls2 = new Balls(Arrays.asList(2, 1, 3)); + + PlayResult result = balls1.play(balls2); + + assertThat(result).isEqualTo(new PlayResult(1, 2)); + } + + @Test + void playTest3() { + Balls balls1 = new Balls(Arrays.asList(1, 2, 3)); + Balls balls2 = new Balls(Arrays.asList(5, 3, 1)); + + PlayResult result = balls1.play(balls2); + + assertThat(result).isEqualTo(new PlayResult(0, 2)); + } + + @Test + void playTest4() { + Balls balls1 = new Balls(Arrays.asList(1, 2, 3)); + Balls balls2 = new Balls(Arrays.asList(6, 4, 5)); + + PlayResult result = balls1.play(balls2); + + assertThat(result).isEqualTo(new PlayResult(0, 0)); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/core/GameTest.java b/src/test/java/baseball/core/GameTest.java new file mode 100644 index 000000000..36477ef34 --- /dev/null +++ b/src/test/java/baseball/core/GameTest.java @@ -0,0 +1,77 @@ +package baseball.core; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; + +class GameTest { + + class TestInputView implements InputView { + private Queue ballss; + + public TestInputView(Queue ballss) { + this.ballss = ballss; + } + + @Override + public Balls getBalls(String message) { + return ballss.poll(); + } + + @Override + public boolean shouldRetry(String message) { + return false; + } + } + + class TestOutputView implements OutputView { + private String buffer = ""; + + @Override + public void write(String message) { + buffer += message; + } + + public String getBuffer() { + return buffer; + } + } + + class TestAnswerGenerator implements RandomBallsGenerator { + private Balls answer; + + public TestAnswerGenerator(Balls answer) { + this.answer = answer; + } + + @Override + public Balls generateBalls() { + return this.answer; + } + } + + + @Test + void name() { + TestInputView inputView = new TestInputView(new LinkedList<>( + Arrays.asList( + new Balls(Arrays.asList(3, 4, 1)), + new Balls(Arrays.asList(1, 2, 3)) + ) + )); + TestOutputView outputView = new TestOutputView(); + Balls answer = new Balls(Arrays.asList(1, 2, 3)); + TestAnswerGenerator randomBallsGenerator = new TestAnswerGenerator(answer); + + Game sut = new Game(inputView, outputView, randomBallsGenerator); + sut.play(); + + String[] outputMessages = outputView.getBuffer().split("\n"); + Assertions.assertThat(outputMessages[0]).isEqualTo("2볼"); + Assertions.assertThat(outputMessages[1]).isEqualTo("3스트라이크"); + Assertions.assertThat(outputMessages[2]).isEqualTo("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/core/PlayResultTest.java b/src/test/java/baseball/core/PlayResultTest.java new file mode 100644 index 000000000..8065faeef --- /dev/null +++ b/src/test/java/baseball/core/PlayResultTest.java @@ -0,0 +1,41 @@ +package baseball.core; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlayResultTest { + @Test + void isFinishedTest() { + PlayResult playResult1 = new PlayResult(3, 0); + assertThat(playResult1.isFinished()).isTrue(); + + PlayResult playResult2 = new PlayResult(1, 0); + assertThat(playResult2.isFinished()).isFalse(); + } + + @Test + void equalsTest() { + assertThat(new PlayResult(1, 2)).isEqualTo(new PlayResult(1, 2)); + } + + @Test + void strikeOnlyString() { + assertThat(new PlayResult(1, 0).getResultMessage()).isEqualTo("1스트라이크"); + } + + @Test + void ballOnlyString() { + assertThat(new PlayResult(0, 2).getResultMessage()).isEqualTo("2볼"); + } + + @Test + void strikeAndBallString() { + assertThat(new PlayResult(1, 2).getResultMessage()).isEqualTo("2볼 1스트라이크"); + } + + @Test + void nothingString() { + assertThat(new PlayResult(0, 0).getResultMessage()).isEqualTo("낫싱"); + } +} \ No newline at end of file