diff --git a/README.md b/README.md index fcf3f057..271e8b33 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,22 @@ ## 과제 제출 과정 * [과제 제출 방법](https://github.com/next-step/nextstep-docs/tree/master/ent-precourse) + +### 기능 요구사항 +- 1부터 9까지 서로 다른 수로 이루어진 3자리 수를 맞추는 게임 +- 사용자는 3자리 수를 제시 +- 사용자가 제시한 수에 대한 응답 + - n개의 (존재하는 수 & 자리 일치) : n 스트라이크 + - n개의 (존재하는 수 & 자리 불일치) : n 볼 + - 하나도 일치하지 않는 경우 : 낫싱 +- 정답을 맞추면 게임 종료 or 게임 재시작 가능 + + +### 구현할 기능 목록 +[x] 랜덤 세 자리 숫자 생성 기능 +[x] 사용자 추측 숫자 입력 기능 +[x] 사용자 입력 추측 숫자에 대한 유효성 검사, 재입력 기능 +[x] 추측 숫자에 대한 결과 도출 및 출력 기능 +[x] 정답이 나올 때까지 추측 반복 기능 +[x] 정답시 게임 재시작 or 종료 기능 +[x] 사용자 게임 재시작 입력 숫자에 대한 유효성 검사, 재입력 기능 \ No newline at end of file diff --git a/src/main/java/NumberBaseBallGameApplication.java b/src/main/java/NumberBaseBallGameApplication.java new file mode 100644 index 00000000..3b9ed97a --- /dev/null +++ b/src/main/java/NumberBaseBallGameApplication.java @@ -0,0 +1,9 @@ +import game.NumberBaseBallGameManager; + +public class NumberBaseBallGameApplication { + + public static void main(String[] args) { + NumberBaseBallGameManager numberBaseBallGameManager = new NumberBaseBallGameManager(); + numberBaseBallGameManager.start(); + } +} diff --git a/src/main/java/config/GlobalData.java b/src/main/java/config/GlobalData.java new file mode 100644 index 00000000..8bfa8085 --- /dev/null +++ b/src/main/java/config/GlobalData.java @@ -0,0 +1,9 @@ +package config; + +import domain.ballnumber.generator.BallNumberGenMode; + +public class GlobalData { + + public static final int BALL_NUMBER_LENGTH = 3; + public static final BallNumberGenMode BALL_NUMBER_GEN_MODE = BallNumberGenMode.RANDOM; +} diff --git a/src/main/java/domain/ballnumber/BallNumber.java b/src/main/java/domain/ballnumber/BallNumber.java new file mode 100644 index 00000000..a62da7e2 --- /dev/null +++ b/src/main/java/domain/ballnumber/BallNumber.java @@ -0,0 +1,23 @@ +package domain.ballnumber; + +import java.util.ArrayList; +import java.util.List; + +public class BallNumber { + + private List digits; + + public BallNumber(List numbers) { + this.digits = new ArrayList<>(); + this.digits.addAll(numbers); + } + + public int getLength() { + return digits.size(); + } + + public List getDigits() { + return this.digits; + } + +} diff --git a/src/main/java/domain/ballnumber/BallResult.java b/src/main/java/domain/ballnumber/BallResult.java new file mode 100644 index 00000000..45753c53 --- /dev/null +++ b/src/main/java/domain/ballnumber/BallResult.java @@ -0,0 +1,51 @@ +package domain.ballnumber; + +import config.GlobalData; + +import java.util.HashSet; +import java.util.Set; + +public class BallResult { + + private int strike; + private int ball; + + public BallResult() { + this.strike = 0; + this.ball = 0; + } + + public int getStrike() { + return strike; + } + + public int getBall() { + return ball; + } + + public static BallResult createBallResult(BallNumber correctBallNumber, BallNumber guessedBallNumber) { + BallResult ballResult = new BallResult(); + Set correctBallNumberSet = new HashSet(correctBallNumber.getDigits()); + for(int i=0; i numberSet = new LinkedHashSet<>(); + while(numberSet.size() < length) { + numberSet.add((int)(Math.random()*1000)%9+1); + } + return new BallNumber(new ArrayList<>(numberSet)); + } +} diff --git a/src/main/java/domain/game/GameResult.java b/src/main/java/domain/game/GameResult.java new file mode 100644 index 00000000..25b13754 --- /dev/null +++ b/src/main/java/domain/game/GameResult.java @@ -0,0 +1,5 @@ +package domain.game; + +public enum GameResult { + RESTART, EXIT +} diff --git a/src/main/java/domain/game/GameStatus.java b/src/main/java/domain/game/GameStatus.java new file mode 100644 index 00000000..90409056 --- /dev/null +++ b/src/main/java/domain/game/GameStatus.java @@ -0,0 +1,5 @@ +package domain.game; + +public enum GameStatus { + READY, PLAYING, FINISH +} diff --git a/src/main/java/game/NumberBaseBallGame.java b/src/main/java/game/NumberBaseBallGame.java new file mode 100644 index 00000000..74e6097e --- /dev/null +++ b/src/main/java/game/NumberBaseBallGame.java @@ -0,0 +1,55 @@ +package game; + +import config.GlobalData; +import domain.ballnumber.BallNumber; +import domain.ballnumber.BallResult; +import domain.ballnumber.generator.BallNumberGenMode; +import domain.ballnumber.generator.BallNumberGenerator; +import domain.ballnumber.generator.RandomBallNumberGenerator; +import domain.game.GameStatus; + +public class NumberBaseBallGame { + + private GameStatus gameStatus; + private BallNumberGenerator ballNumberGenerator; + private BallNumber correctBallNumber; + + private NumberBaseBallGame() { + this.gameStatus = GameStatus.READY; + } + + public NumberBaseBallGame(BallNumberGenMode mode) { + this(); + if (BallNumberGenMode.RANDOM.equals(mode)) + this.ballNumberGenerator = new RandomBallNumberGenerator(); + } + + public GameStatus getGameStatus() { + return this.gameStatus; + } + + public BallNumber getCorrectBallNumber() { + return this.correctBallNumber; + } + + public void initGame() { + generateBallNumber(); + this.gameStatus = GameStatus.PLAYING; + } + + public void generateBallNumber() { + if(this.ballNumberGenerator==null) return; + this.correctBallNumber = this.ballNumberGenerator.generate(GlobalData.BALL_NUMBER_LENGTH); + } + + public BallResult throwBall(BallNumber guessedBallNumber) { + BallResult ballResult = compareBallNumber(guessedBallNumber); + if(ballResult.isAllStrike()) this.gameStatus = GameStatus.FINISH; + return ballResult; + } + + public BallResult compareBallNumber(BallNumber guessedBallNumber) { + return BallResult.createBallResult(this.correctBallNumber, guessedBallNumber); + } + +} diff --git a/src/main/java/game/NumberBaseBallGameManager.java b/src/main/java/game/NumberBaseBallGameManager.java new file mode 100644 index 00000000..fc0b4530 --- /dev/null +++ b/src/main/java/game/NumberBaseBallGameManager.java @@ -0,0 +1,43 @@ +package game; + +import config.GlobalData; +import domain.ballnumber.BallNumber; +import domain.game.GameResult; +import domain.game.GameStatus; +import view.InputManager; +import view.OutputView; + +public class NumberBaseBallGameManager { + + private NumberBaseBallGame numberBaseBallGame; + private InputManager inputManager; + private OutputView outputView; + + public NumberBaseBallGameManager() { + this.numberBaseBallGame = new NumberBaseBallGame(GlobalData.BALL_NUMBER_GEN_MODE); + this.inputManager = new InputManager(); + this.outputView = new OutputView(); + } + + public void start() { + while(!GameResult.EXIT.equals(play())){} + } + + public GameResult play() { + this.numberBaseBallGame.initGame(); + while(isGamePlaying()) { + outputView.showBallResult(numberBaseBallGame.throwBall(guessBallNumber())); + } + outputView.showGameFinished(); + return inputManager.inputGameResult(); + } + + public boolean isGamePlaying() { + return GameStatus.PLAYING.equals(numberBaseBallGame.getGameStatus()); + } + + public BallNumber guessBallNumber() { + return inputManager.inputGuessedBallNumber(); + } + +} diff --git a/src/main/java/view/InputManager.java b/src/main/java/view/InputManager.java new file mode 100644 index 00000000..58c28aa7 --- /dev/null +++ b/src/main/java/view/InputManager.java @@ -0,0 +1,49 @@ +package view; + +import domain.ballnumber.BallNumber; +import domain.game.GameResult; +import view.InputView; + +public class InputManager { + + private InputView inputView; + + public InputManager() { + this.inputView = new InputView(); + } + + public BallNumber inputGuessedBallNumber() { + BallNumber guessedBallNumber = null; + while((guessedBallNumber=tryInputGuessedBallNumber())==null){} + return guessedBallNumber; + } + + public BallNumber tryInputGuessedBallNumber() { + BallNumber guessedBallNumber = null; + try { + guessedBallNumber = inputView.inputGuessedBallNumber(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + return guessedBallNumber; + } + } + + public GameResult inputGameResult() { + GameResult gameResult = null; + while((gameResult=tryInputGameResult())==null){} + return gameResult; + } + + public GameResult tryInputGameResult() { + GameResult gameResult = null; + try { + gameResult = inputView.inputGameResult(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } finally { + return gameResult; + } + } + +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..dab81e01 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,63 @@ +package view; + +import config.GlobalData; +import domain.ballnumber.BallNumber; +import domain.game.GameResult; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.*; + +public class InputView { + + private BufferedReader br; + + public InputView() { + this.br = new BufferedReader(new InputStreamReader(System.in)); + } + + public BallNumber inputGuessedBallNumber() throws Exception{ + System.out.print("숫자를 입력해주세요 : "); + String input = br.readLine(); + validateBallNumberInput(input); + return new BallNumber(stringToIntList(input)); + } + + public void validateBallNumberInput(String input) { + if(input.length()!= GlobalData.BALL_NUMBER_LENGTH) + throw new IllegalArgumentException(GlobalData.BALL_NUMBER_LENGTH+" 자리 숫자를 입력해주세요."); + Set numberSet = new HashSet<>(); + for(char c : input.toCharArray()) { + validateBallNumberChar(c); + numberSet.add(c); + } + if(numberSet.size()!=GlobalData.BALL_NUMBER_LENGTH) + throw new IllegalArgumentException("숫자는 중복되지 않습니다."); + } + + public void validateBallNumberChar(char c) { + if(c<'1' || c>'9') throw new IllegalArgumentException("각 자리는 1 이상 9 이하의 숫자로 이루어져야 합니다."); + } + + public List stringToIntList(String input) { + List result = new ArrayList<>(); + for(char c : input.toCharArray()) { + result.add(c-'0'); + } + return result; + } + + public GameResult inputGameResult() throws Exception{ + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + String input = br.readLine(); + validateGameResultInput(input); + int selected = input.charAt(0)-'0'; + if(selected==1) return GameResult.RESTART; + return GameResult.EXIT; + } + + public void validateGameResultInput(String input) { + if(input.length()!=1 || (input.charAt(0)<'1' || input.charAt(0)>'2')) + throw new IllegalArgumentException("1 과 2 중에서 선택해주세요."); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..54ef075d --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,22 @@ +package view; + +import config.GlobalData; +import domain.ballnumber.BallResult; + +public class OutputView { + + public void showBallResult(BallResult ballResult) { + StringBuilder sb = new StringBuilder(); + if(ballResult.getStrike()>0) + sb.append(ballResult.getStrike()).append(" 스트라이크 "); + if(ballResult.getBall()>0) + sb.append(ballResult.getBall()).append(" 볼"); + if(sb.length()==0) + sb.append("낫싱"); + System.out.println(sb); + } + + public void showGameFinished() { + System.out.println(GlobalData.BALL_NUMBER_LENGTH+"개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } +} diff --git a/src/test/java/BallResultTest.java b/src/test/java/BallResultTest.java new file mode 100644 index 00000000..225dbb92 --- /dev/null +++ b/src/test/java/BallResultTest.java @@ -0,0 +1,70 @@ +import config.GlobalData; +import domain.ballnumber.BallNumber; +import domain.ballnumber.BallResult; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; + +public class BallResultTest { + + @Test + @DisplayName("추측한 숫자에 대한 전체 스트라이크 테스트") + public void checkAllStrike(){ + //given + BallNumber correctBallNumber = new BallNumber(new ArrayList<>(List.of(5,3,7))); + BallNumber guessedBallNumber = new BallNumber(new ArrayList<>(List.of(5,3,7))); + + // when + BallResult ballResult = BallResult.createBallResult(correctBallNumber,guessedBallNumber); + + //then + assertTrue(ballResult.isAllStrike()); + } + + @Test + @DisplayName("추측한 숫자에 대한 전체 볼 테스트") + public void checkAllBall(){ + //given + BallNumber correctBallNumber = new BallNumber(new ArrayList<>(List.of(5,3,7))); + BallNumber guessedBallNumber = new BallNumber(new ArrayList<>(List.of(7,5,3))); + + // when + BallResult ballResult = BallResult.createBallResult(correctBallNumber,guessedBallNumber); + + //then + assertEquals(ballResult.getBall(), GlobalData.BALL_NUMBER_LENGTH); + } + + @Test + @DisplayName("추측한 숫자에 대한 1 스트라이크 2볼 테스트") + public void check1Strike2Ball(){ + //given + BallNumber correctBallNumber = new BallNumber(new ArrayList<>(List.of(5,3,7))); + BallNumber guessedBallNumber = new BallNumber(new ArrayList<>(List.of(7,3,5))); + + // when + BallResult ballResult = BallResult.createBallResult(correctBallNumber,guessedBallNumber); + + //then + assertEquals(ballResult.getStrike(),1); + assertEquals(ballResult.getBall(),2); + } + + @Test + @DisplayName("추측한 숫자에 대한 전체 불일치 테스트") + public void checkNothing(){ + //given + BallNumber correctBallNumber = new BallNumber(new ArrayList<>(List.of(5,3,7))); + BallNumber guessedBallNumber = new BallNumber(new ArrayList<>(List.of(1,9,2))); + + // when + BallResult ballResult = BallResult.createBallResult(correctBallNumber,guessedBallNumber); + + //then + assertEquals(ballResult.getStrike(),0); + assertEquals(ballResult.getBall(),0); + } +} diff --git a/src/test/java/RandomBallNumberGeneratorTest.java b/src/test/java/RandomBallNumberGeneratorTest.java new file mode 100644 index 00000000..eff49df3 --- /dev/null +++ b/src/test/java/RandomBallNumberGeneratorTest.java @@ -0,0 +1,28 @@ +import domain.ballnumber.BallNumber; +import domain.ballnumber.generator.RandomBallNumberGenerator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; + +import static org.junit.jupiter.api.Assertions.*; + +public class RandomBallNumberGeneratorTest { + + @Test + @DisplayName("랜덤으로 생성한 숫자 테스트") + void generateRandomBallNumber(){ + //given + RandomBallNumberGenerator randomBallNumberGenerator = new RandomBallNumberGenerator(); + int length = 3; + + //when + BallNumber ballNumber = randomBallNumberGenerator.generate(length); + + //then + // 길이 일치 + assertEquals(ballNumber.getLength(),length); + // 중복 없음 + assertEquals(new HashSet<>(ballNumber.getDigits()).size(), length); + } +} diff --git a/src/test/java/game/NumberBaseBallGameTest.java b/src/test/java/game/NumberBaseBallGameTest.java new file mode 100644 index 00000000..a5f26455 --- /dev/null +++ b/src/test/java/game/NumberBaseBallGameTest.java @@ -0,0 +1,40 @@ +package game; + +import domain.ballnumber.BallNumber; +import domain.ballnumber.generator.BallNumberGenMode; +import domain.game.GameStatus; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class NumberBaseBallGameTest { + + @Test + @DisplayName("정답시 게임 중지 테스트") + public void correctNumberGameStopTest() { + //given + NumberBaseBallGame numberBaseBallGame = new NumberBaseBallGame(BallNumberGenMode.RANDOM); + + //when + numberBaseBallGame.throwBall(numberBaseBallGame.getCorrectBallNumber()); + + //then + assertEquals(numberBaseBallGame.getGameStatus(), GameStatus.FINISH); + } + + @Test + @DisplayName("오답시 게임 계속 진행 테스트") + public void wrongNumberGameKeepGoingTest() { + //given + NumberBaseBallGame numberBaseBallGame = new NumberBaseBallGame(BallNumberGenMode.RANDOM); + + //when + numberBaseBallGame.throwBall(new BallNumber(List.of(1,1,1))); + + //then + assertEquals(numberBaseBallGame.getGameStatus(), GameStatus.PLAYING); + } +} diff --git a/src/test/java/study/StringTest.java b/src/test/java/study/StringTest.java deleted file mode 100644 index 43e47d90..00000000 --- a/src/test/java/study/StringTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package study; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StringTest { - @Test - void replace() { - String actual = "abc".replace("b", "d"); - assertThat(actual).isEqualTo("adc"); - } -}