diff --git a/docs/README.md b/docs/README.md index e69de29bb2..33d7c9b435 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,20 @@ +# 기능 목록 + +입출력 +- [ ] 사용자 입력 + - [ ] 숫자 입력 + - [ ] 예외처리 + - [ ] 게임 진행(1 or 2) 입력 + - [ ] 예외처리 +- [ ] 메시지 출력 + - [ ] 시작 메시지 + - [ ] (b, s) -> 볼/스트라이크 출력 + - [ ] 종료 메시지 + +게임 진행 +- [ ] main + - [ ] 게임 시작 + - [ ] 게임 종료, 결과 출력 +- [ ] singleGame + - [ ] init : 난수 생성 + - [ ] singleTurn (xxx) -> 판정 후 결과 리턴 diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index dd95a34214..cb5c594f56 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,7 +1,22 @@ package baseball; +import baseball.game.model.GameCommand; +import baseball.game.model.GameStatus; +import baseball.game.GameService; +import baseball.inout.UserInput; +import baseball.inout.UserOutput; + +import java.util.List; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + GameCommand command = GameCommand.CONTINUE; + + while (command.isContinue()){ + GameService gameService = new GameService(); + gameService.playSingleGame(); + command = gameService.getCommand(); + } } + } diff --git a/src/main/java/baseball/game/GameService.java b/src/main/java/baseball/game/GameService.java new file mode 100644 index 0000000000..bcb0dc3726 --- /dev/null +++ b/src/main/java/baseball/game/GameService.java @@ -0,0 +1,37 @@ +package baseball.game; + +import baseball.game.model.GameBall; +import baseball.game.model.GameCommand; +import baseball.game.model.GameStatus; +import baseball.inout.UserInput; +import baseball.inout.UserOutput; +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.List; + +public class GameService { + + GameBall computer; + + public GameService() { + computer = GameBall.createRandomBall(); + } + + public void playSingleGame() { + boolean isContinue = true; + + while (isContinue) { + GameBall playNum = GameBall.createBall(UserInput.getNum()); + GameStatus gameStatus = computer.getStatus(playNum); + UserOutput.printStatus(gameStatus); + isContinue = gameStatus.isContinue(); + } + UserOutput.printEndMessage(); + } + + public GameCommand getCommand() { + return GameCommand.getCommand(UserInput.isContinue()); + } + +} diff --git a/src/main/java/baseball/game/model/GameBall.java b/src/main/java/baseball/game/model/GameBall.java new file mode 100644 index 0000000000..5f939e4900 --- /dev/null +++ b/src/main/java/baseball/game/model/GameBall.java @@ -0,0 +1,74 @@ +package baseball.game.model; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.*; + +public class GameBall { + + private final static int BALL_MAX = 9; + private final static int BALL_MIN = 1; + private final static int BALL_NUM = 3; + + private List ballNumbers; + + private GameBall(List ballNumbers) { + this.ballNumbers = ballNumbers; + } + + public static GameBall createBall(String userInput){ + if (isInvalidLength(userInput)) + throw new IllegalArgumentException("3자리의 숫자를 입력해야 합니다."); + if (isInvalidRange(userInput)) + throw new IllegalArgumentException("숫자의 범위가 올바르지 않습니다."); + + List ball = new ArrayList<>(); + for (int i = 0; i < 3; i++) + ball.add(userInput.charAt(i) - '0'); + + if (isDuplicated(ball)) + throw new IllegalArgumentException("중복이 없는 3자리의 숫자를 입력해야 합니다."); + + return new GameBall(ball); + } + + public static GameBall createRandomBall (){ + Set ball = new LinkedHashSet<>(); + + while(ball.size() < BALL_NUM) { + int randomNumber = Randoms.pickNumberInRange(1, 9); + ball.add(randomNumber); + } + + return new GameBall(new ArrayList<>(ball)); + } + + public GameStatus getStatus(GameBall other) { + int strike = 0; + int ball = 0; + + for (int i = 0; i < this.ballNumbers.size(); i++) { + if (other.ballNumbers.get(i).equals(this.ballNumbers.get(i))) strike++; + else if (other.ballNumbers.contains(this.ballNumbers.get(i))) ball++; + } + + return new GameStatus(ball, strike); + } + + private static boolean isInvalidLength(String input) { + return input.length() != BALL_NUM; + } + + private static boolean isDuplicated(List input) { + return input.size() != (new HashSet(input)).size(); + } + + private static boolean isInvalidRange(String input) { + for (int i = 0; i < input.length(); i++) { + int num = input.charAt(i) - '0'; + if (num > BALL_MAX || num < BALL_MIN) return true; + } + return false; + } + +} diff --git a/src/main/java/baseball/game/model/GameCommand.java b/src/main/java/baseball/game/model/GameCommand.java new file mode 100644 index 0000000000..06d74b9f50 --- /dev/null +++ b/src/main/java/baseball/game/model/GameCommand.java @@ -0,0 +1,35 @@ +package baseball.game.model; + +import java.util.Arrays; + +public enum GameCommand { + QUIT(2), + CONTINUE(1); + + private final int number; + + GameCommand(int number) { + this.number = number; + } + + public static GameCommand getCommand(String userInput) { + int commandNumber; + try { + commandNumber = Integer.parseInt(userInput); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("숫자를 입력해야 합니다."); + } + + GameCommand command = Arrays.stream(values()) + .filter(value -> value.number == commandNumber) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("올바른 숫자가 입력되지 않았습니다.")); + + return command; + } + + public boolean isContinue() { + return this == CONTINUE; + } + +} diff --git a/src/main/java/baseball/game/model/GameStatus.java b/src/main/java/baseball/game/model/GameStatus.java new file mode 100644 index 0000000000..3e343a3f3c --- /dev/null +++ b/src/main/java/baseball/game/model/GameStatus.java @@ -0,0 +1,31 @@ +package baseball.game.model; + +import java.util.Objects; + +public class GameStatus { + public int ball; + public int strike; + + public GameStatus(int ball, int strike) { + this.ball = ball; + this.strike = strike; + } + + public boolean isContinue() { + return !(strike == 3 && ball == 0); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GameStatus that = (GameStatus) o; + return ball == that.ball && strike == that.strike; + } + + @Override + public int hashCode() { + return Objects.hash(ball, strike); + } + +} diff --git a/src/main/java/baseball/inout/UserInput.java b/src/main/java/baseball/inout/UserInput.java new file mode 100644 index 0000000000..16216d48f4 --- /dev/null +++ b/src/main/java/baseball/inout/UserInput.java @@ -0,0 +1,29 @@ +package baseball.inout; + + +import camp.nextstep.edu.missionutils.Console; + +public class UserInput { + + private UserInput() { + } + + /** + * 숫자 입력 + */ + public static String getNum() { + System.out.print("숫자를 입력해주세요 : "); + String input = Console.readLine().trim(); + return input; + } + + /** + * 게임 진행여부 입력 + */ + public static String isContinue() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + String input = Console.readLine().trim(); + return input; + } + +} diff --git a/src/main/java/baseball/inout/UserOutput.java b/src/main/java/baseball/inout/UserOutput.java new file mode 100644 index 0000000000..34a54e88c2 --- /dev/null +++ b/src/main/java/baseball/inout/UserOutput.java @@ -0,0 +1,30 @@ +package baseball.inout; + +import baseball.game.model.GameStatus; + +public class UserOutput { + + private UserOutput() { + } + + public static void initMessage() { + System.out.println("숫자 야구 게임을 시작합니다."); + } + + public static void printStatus(GameStatus status) { + if (status.ball == 0 && status.strike == 0) + System.out.println("낫싱"); + else if (status.ball == 0) + System.out.println(status.strike + "스트라이크"); + else if (status.strike == 0) + System.out.println(status.ball + "볼"); + else + System.out.println(status.ball + "볼 " + status.strike + "스트라이크"); + + } + + public static void printEndMessage() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } + +} diff --git a/src/test/java/baseball/game/model/GameBallTest.java b/src/test/java/baseball/game/model/GameBallTest.java new file mode 100644 index 0000000000..123c4040e6 --- /dev/null +++ b/src/test/java/baseball/game/model/GameBallTest.java @@ -0,0 +1,59 @@ +package baseball.game.model; + +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; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +class GameBallTest { + + @ParameterizedTest + @DisplayName("GameBall 생성 동작 테스트") + @ValueSource(strings = {"123", "145", "671"}) + void createBall(String input) { + // given + + // when & then + assertThatCode( () -> { + GameBall.createBall(input); + }).doesNotThrowAnyException(); + } + + + @ParameterizedTest + @DisplayName("GameBall 생성 예외 테스트") + @ValueSource(strings = {"111", "122", "1325", "a11"}) + void createBallErrors(String input) { + // given + + // when & then + assertThatThrownBy( () -> { + GameBall.createBall(input); + }).isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @DisplayName("getStatus 생성 동작 테스트") + @CsvSource(value = {"456:0:0", + "516:1:0", "156:0:1", "231:3:0", "123:0:3"}, delimiter = ':' + ) + void getStatus(String input, int ball, int strike) { + // given + GameBall computer = GameBall.createBall("123"); + GameBall player = GameBall.createBall(input); + + // when + GameStatus gameStatus = computer.getStatus(player); + + // then + assertThat(gameStatus).isEqualTo(new GameStatus(ball, strike)); + } + +} \ No newline at end of file diff --git a/src/test/java/baseball/game/model/GameCommandTest.java b/src/test/java/baseball/game/model/GameCommandTest.java new file mode 100644 index 0000000000..e3dfe968dd --- /dev/null +++ b/src/test/java/baseball/game/model/GameCommandTest.java @@ -0,0 +1,52 @@ +package baseball.game.model; + +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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +class GameCommandTest { + + @Test + void getCommand() { + } + + @Test + void isContinue() { + } + + + + @ParameterizedTest + @DisplayName("게임진행 입력 동작 테스트") + @ValueSource(strings = {"1", "2"}) + void getCommand(String input) { + // given + + // when + GameCommand cmd = GameCommand.getCommand(input); + + // then + if (input.equals("1")) + assertThat(cmd).isEqualTo(GameCommand.CONTINUE); + else + assertThat(cmd).isEqualTo(GameCommand.QUIT); + } + + + @ParameterizedTest + @DisplayName("게임진행 입력 예외 테스트") + @ValueSource(strings = {"111", "3", "0", "a"}) + void getCommandError(String input) { + // given + + // then + assertThatThrownBy( () -> { + GameCommand.getCommand(input); + }).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/baseball/inout/UserOutputTest.java b/src/test/java/baseball/inout/UserOutputTest.java new file mode 100644 index 0000000000..c79efa9e91 --- /dev/null +++ b/src/test/java/baseball/inout/UserOutputTest.java @@ -0,0 +1,79 @@ +package baseball.inout; + +import baseball.game.model.GameStatus; +import org.junit.jupiter.api.AfterEach; +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.ValueSource; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.assertj.core.api.Assertions.assertThat; + +class UserOutputTest { + private static ByteArrayOutputStream outputMessage; + private static UserOutput output; + + @BeforeEach + void setUpStreams() { + outputMessage = new ByteArrayOutputStream(); // OutputStream 생성 + System.setOut(new PrintStream(outputMessage)); // 생성한 OutputStream 으로 설정 + } + + @AfterEach + void restoresStreams() { + System.setOut(System.out); // 원상복귀 + } + + @Test + void initMessage() { + // when + UserOutput.initMessage(); + + // then + assertThat(outputMessage.toString().trim()).isEqualTo("숫자 야구 게임을 시작합니다."); + } + + + @ParameterizedTest + @DisplayName("statusMessage 테스트 - 1볼") + @ValueSource(ints = {0, 1, 2}) + void statusMessage(int strike) { + // when + UserOutput.printStatus(new GameStatus(1, strike)); + + // then + if (strike == 0) + assertThat(outputMessage.toString().trim()).isEqualTo("1볼"); + else + assertThat(outputMessage.toString().trim()).isEqualTo("1볼 " + strike + "스트라이크"); + } + + + @ParameterizedTest + @DisplayName("statusMessage 테스트 - 3스트라이크, 낫싱") + @ValueSource(ints = {0, 3}) + void statusMessage2(int strike) { + // when + UserOutput.printStatus(new GameStatus(0, strike)); + + // then + if (strike == 0) + assertThat(outputMessage.toString().trim()).isEqualTo("낫싱"); + else + assertThat(outputMessage.toString().trim()).isEqualTo("3스트라이크"); + } + + @Test + void endMessage() { + // when + UserOutput.printEndMessage(); + + // then + assertThat(outputMessage.toString().trim()).isEqualTo("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } + +} \ No newline at end of file