diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..adf1469b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(git add:*)", + "Bash(git rm:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/README.md b/README.md index 8d7e8aee..37d1ca95 100644 --- a/README.md +++ b/README.md @@ -1 +1,73 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +## 기능 요구사항 + +- [ ] 기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. +- [ ] 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 포볼 또는 낫싱이란 힌트를 얻고, + 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. + - [예] 상대방(컴퓨터)의 수가 425일 때, + - 123을 제시한 경우 : 1스트라이크 + - 456을 제시한 경우 : 1스트라이크 1볼 + - 789를 제시한 경우 : 낫싱 +- [ ] 위 숫자 야구게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. + 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다. +- [ ] 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. +- [ ] 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. +- [ ] 사용자가 잘못된 값을 입력할 경우 [ERROR]로 시작하는 에러 메시지를 출력하고 게임을 계속 진행할 수 있어야 한다. + +--- + +## 프로그램 실행 결과 + +``` +숫자를 입력해주세요 : 123 +1스트라이크 1볼 +숫자를 입력해주세요 : 145 +1볼 +숫자를 입력해주세요 : 671 +2볼 +숫자를 입력해주세요 : 216 +1스트라이크 +숫자를 입력해주세요 : 713 +3스트라이크 +3개의 숫자를 모두 맞히셨습니다! 게임 끝 +게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요. +1 +숫자를 입력해주세요 : 123 +1볼 +``` + +--- + +## 프로그래밍 요구사항1 - 제약사항 + +- [ ] 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - https://naver.github.io/hackday-conventions-java/ +- [ ] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. +- [ ] 자바 8에 추가된 stream api를 사용하지 않고 구현해야 한다. 단, 람다는 사용 가능하다. +- [ ] else 예약어를 쓰지 않는다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. + - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. +- [ ] 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다. + - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. + +--- + +## 프로그래밍 요구사항2 - 단위 테스트 + +- [ ] 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. + - [ ] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. + - [ ] 힌트는 MVC 패턴 기반으로 구현한 후 View, Controller를 제외한 Model에 대한 단위 테스트를 추가하는 것에 집중한다. +- [ ] JUnit5와 AssertJ 사용법에 익숙하지 않은 개발자는 첨부한 "학습테스트를 통해 JUnit 학습하기.pdf" 문서를 참고해 사용법을 학습한 후 JUnit5 기반 단위 테스트를 구현한다. + +--- + +## 과제 진행 요구사항 + +- [ ] 미션은 https://github.com/next-step/java-baseball-precourse 저장소를 fork/clone해 시작한다. +- [ ] 기능을 구현하기 전에 java-baseball-precourse/README.md 파일에 구현할 기능 목록을 정리해 추가한다. +- [ ] git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위 또는 의미 있는 단위로 Commit한다. + - [ ] AngularJS Commit Message Conventions 참고해 commit log를 남기려고 노력해 본다. +- [ ] 과제 진행 및 제출 방법은 프리코스 과제 제출 문서를 참고한다. diff --git a/build.gradle b/build.gradle index 20a92c9e..f1bc0591 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' } -group = 'camp.nextstep.edu' +group = 'com.kakao.onboarding.precourse.dukeyun' version = '1.0-SNAPSHOT' java { diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/App.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/App.java new file mode 100644 index 00000000..02094cb4 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/App.java @@ -0,0 +1,12 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.GameSystem; + +public class App { + + public static void main(String[] args) { + GameSystem baseballGameSystem = GameFactory.createBaseballGameSystem(); + baseballGameSystem.run(); + } + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/GameFactory.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/GameFactory.java new file mode 100644 index 00000000..bbda4af9 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/GameFactory.java @@ -0,0 +1,35 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball; + +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameConfig; +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameConsoleView; +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameService; +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.RandomBaseballNumberGenerator; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.Game; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnBaseGame; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.GameSystem; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.ReplayableGameSystem; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem.ReplayableGameSystemConsoleView; + +public class GameFactory { + private GameFactory() { + } + + public static BaseballGameConfig baseballGameConfig() { + return new BaseballGameConfig(3, 1, 9); + } + + public static GameSystem createBaseballGameSystem() { + return createBaseballGameSystem(baseballGameConfig()); + } + + public static GameSystem createBaseballGameSystem(BaseballGameConfig config) { + return new ReplayableGameSystem(new ReplayableGameSystemConsoleView(), createBaseballGame(config)); + } + + private static Game createBaseballGame(BaseballGameConfig config) { + return new TurnBaseGame( + new BaseballGameConsoleView(config), + new BaseballGameService(config, new RandomBaseballNumberGenerator(config)) + ); + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameConfig.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameConfig.java new file mode 100644 index 00000000..07b8f282 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameConfig.java @@ -0,0 +1,49 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +public class BaseballGameConfig { + + private static final int DEFAULT_LENGTH = 3; + private static final int DEFAULT_MIN_RANGE = 1; + private static final int DEFAULT_MAX_RANGE = 9; + + private final int length; + private final int minRange; + private final int maxRange; + + public BaseballGameConfig(int length, int minRange, int maxRange) { + validateConfig(length, minRange, maxRange); + this.length = length; + this.minRange = minRange; + this.maxRange = maxRange; + } + + public static BaseballGameConfig ofDefault() { + return new BaseballGameConfig(DEFAULT_LENGTH, DEFAULT_MIN_RANGE, DEFAULT_MAX_RANGE); + } + + private void validateConfig(int length, int minRange, int maxRange) { + if (length <= 0) { + throw new RuntimeException("길이는 양수여야 합니다."); + } + if (minRange >= maxRange) { + throw new RuntimeException("최소값은 최대값보다 작아야 합니다."); + } + int availableNumbers = maxRange - minRange + 1; + if (length > availableNumbers) { + throw new RuntimeException( + String.format("길이(%d)가 사용 가능한 숫자의 개수(%d)보다 큽니다.", length, availableNumbers)); + } + } + + public int getLength() { + return length; + } + + public int getMinRange() { + return minRange; + } + + public int getMaxRange() { + return maxRange; + } +} \ No newline at end of file diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameConsoleView.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameConsoleView.java new file mode 100644 index 00000000..c454554b --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameConsoleView.java @@ -0,0 +1,77 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnBaseGameView; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnInput; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnResult; +import com.kakao.onboarding.precourse.dukeyun.baseball.utils.Console; + +public class BaseballGameConsoleView implements TurnBaseGameView { + + private static final String REQUEST_INPUT_MESSAGE = "숫자를 입력해주세요 : "; + private static final String CLEAR_MESSAGE_FORMAT = "%d개의 숫자를 모두 맞히셨습니다! 게임 끝"; + + private static final String BALL_UNIT = "볼"; + private static final String STRIKE_UNIT = "스트라이크"; + private static final String NOTHING = "낫싱"; + + private final BaseballGameConfig config; + + public BaseballGameConsoleView(BaseballGameConfig config) { + this.config = config; + } + + private static void printNumStrike(int numStrike) { + System.out.printf("%d%s", numStrike, STRIKE_UNIT); + } + + private static void printNumBall(int numBall) { + System.out.printf("%d%s ", numBall, BALL_UNIT); + } + + private static void printNewLine() { + System.out.println(); + } + + private static void printNothing() { + System.out.print(NOTHING); + } + + @Override + public void printStart() { + } + + @Override + public TurnInput requestTurnInput() { + System.out.print(REQUEST_INPUT_MESSAGE); + return new BaseballGameTurnInput(config, Console.readLine()); + } + + @Override + public void printClear() { + System.out.printf(CLEAR_MESSAGE_FORMAT + "%n", config.getLength()); + } + + @Override + public void printResult(TurnResult turnResult) { + BaseballGameTurnResult baseballGameTurnResult = (BaseballGameTurnResult)turnResult; + + int numBall = baseballGameTurnResult.getNumBall(); + int numStrike = baseballGameTurnResult.getNumStrike(); + + if (numBall == 0 && numStrike == 0) { + printNothing(); + printNewLine(); + return; + } + + if (numBall != 0) { + printNumBall(numBall); + } + + if (numStrike != 0) { + printNumStrike(numStrike); + } + + printNewLine(); + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameService.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameService.java new file mode 100644 index 00000000..53305bfa --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameService.java @@ -0,0 +1,41 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnBaseGameService; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnInput; +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnResult; + +public class BaseballGameService implements TurnBaseGameService { + + private final BaseballGameConfig config; + private final BaseballNumberGenerator baseballNumberGenerator; + private BaseballNumber answer; + + public BaseballGameService(BaseballGameConfig config, BaseballNumberGenerator baseballNumberGenerator) { + this.config = config; + this.baseballNumberGenerator = baseballNumberGenerator; + } + + @Override + public void init() { + this.answer = baseballNumberGenerator.generate(); + } + + @Override + public TurnResult playTurn(TurnInput turnInput) { + BaseballGameTurnInput baseballGameTurnInput = (BaseballGameTurnInput)turnInput; + BaseballNumber baseballNumber = baseballGameTurnInput.getBaseballNumber(); + + return calculateResult(baseballNumber); + } + + private TurnResult calculateResult(BaseballNumber baseballNumber) { + int numStrike = answer.countStrike(baseballNumber); + int numBall = answer.countBall(baseballNumber); + + return new BaseballGameTurnResult(isClear(numStrike), numStrike, numBall); + } + + private boolean isClear(int numStrike) { + return numStrike == config.getLength(); + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnInput.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnInput.java new file mode 100644 index 00000000..b2ca7a4d --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnInput.java @@ -0,0 +1,40 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import java.util.ArrayList; +import java.util.List; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnInput; + +public class BaseballGameTurnInput implements TurnInput { + + private final BaseballNumber baseballNumber; + + public BaseballGameTurnInput(BaseballGameConfig config, String input) { + valid(input); + this.baseballNumber = createBaseballNumber(config, input); + } + + private BaseballNumber createBaseballNumber(BaseballGameConfig config, String input) { + List numbers = new ArrayList<>(); + for (char ch : input.toCharArray()) { + numbers.add(ch - '0'); + } + return new BaseballNumber(config, numbers); + } + + private void valid(String input) { + for (char ch : input.toCharArray()) { + validIsNumber(ch); + } + } + + private void validIsNumber(char ch) { + if (!Character.isDigit(ch)) { + throw new IllegalArgumentException("입력은 숫자여야 합니다."); + } + } + + public BaseballNumber getBaseballNumber() { + return baseballNumber; + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnResult.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnResult.java new file mode 100644 index 00000000..c58548b5 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnResult.java @@ -0,0 +1,29 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn.TurnResult; + +public class BaseballGameTurnResult implements TurnResult { + + private final boolean clear; + private final int numStrike; + private final int numBall; + + public BaseballGameTurnResult(boolean clear, int numStrike, int numBall) { + this.clear = clear; + this.numStrike = numStrike; + this.numBall = numBall; + } + + @Override + public boolean isGameCleared() { + return clear; + } + + public int getNumStrike() { + return numStrike; + } + + public int getNumBall() { + return numBall; + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumber.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumber.java new file mode 100644 index 00000000..c63ca3e2 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumber.java @@ -0,0 +1,79 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import java.util.List; + +public class BaseballNumber { + + private final BaseballGameConfig config; + private final List numbers; + + public BaseballNumber(BaseballGameConfig config, List numbers) { + this.config = config; + checkLength(numbers); + checkNumbersRange(numbers); + checkDuplication(numbers); + this.numbers = numbers; + } + + private void checkDuplication(List numbers) { + int[] freq = new int[config.getMaxRange() - config.getMinRange() + 1]; + for (int number : numbers) { + int index = number - config.getMinRange(); + freq[index]++; + if (freq[index] > 1) { + throw new RuntimeException(String.format("숫자가 중복 되었습니다.(중복 숫자: %d)", number)); + } + } + } + + private void checkNumbersRange(List numbers) { + for (int number : numbers) { + checkNumberRange(number); + } + } + + private void checkNumberRange(int number) { + if (number < config.getMinRange() || config.getMaxRange() < number) { + throw new IllegalArgumentException( + String.format("숫자 범위는 %d 이상 %d 이하여야 합니다. (문제 숫자: %d)", config.getMinRange(), config.getMaxRange(), + number)); + } + } + + private void checkLength(List numbers) { + if (numbers.size() != config.getLength()) { + throw new IllegalArgumentException( + String.format("숫자야구 길이는 %d이어야 합니다.(현재 길이 : %d)", config.getLength(), numbers.size())); + } + } + + public int countStrike(BaseballNumber baseballNumber) { + int count = 0; + + for (int index = 0; index < config.getLength(); index++) { + if (numbers.get(index).equals(baseballNumber.numbers.get(index))) { + count++; + } + } + + return count; + } + + public int countBall(BaseballNumber baseballNumber) { + int count = 0; + + for (int pivotIndex = 0; pivotIndex < config.getLength(); pivotIndex++) { + for (int index = 0; index < config.getLength(); index++) { + if (pivotIndex == index) { + continue; + } + + if (numbers.get(pivotIndex).equals(baseballNumber.numbers.get(index))) { + count++; + } + } + } + + return count; + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumberGenerator.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumberGenerator.java new file mode 100644 index 00000000..25d63605 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumberGenerator.java @@ -0,0 +1,5 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +public interface BaseballNumberGenerator { + BaseballNumber generate(); +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/RandomBaseballNumberGenerator.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/RandomBaseballNumberGenerator.java new file mode 100644 index 00000000..a418d923 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/RandomBaseballNumberGenerator.java @@ -0,0 +1,30 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class RandomBaseballNumberGenerator implements BaseballNumberGenerator { + + private static final Random RANDOM = new Random(); + + private final BaseballGameConfig config; + + public RandomBaseballNumberGenerator(BaseballGameConfig config) { + this.config = config; + } + + @Override + public BaseballNumber generate() { + List numbers = new ArrayList<>(); + while (numbers.size() < config.getLength()) { + int randomNumber = RANDOM.nextInt(config.getMinRange(), config.getMaxRange() + 1); + if (!numbers.contains(randomNumber)) { + numbers.add(randomNumber); + } + } + + return new BaseballNumber(config, numbers); + } + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/Game.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/Game.java new file mode 100644 index 00000000..8e992f85 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/Game.java @@ -0,0 +1,9 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.game; + +public interface Game { + + void init(); + + void play(); + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGame.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGame.java new file mode 100644 index 00000000..53120b99 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGame.java @@ -0,0 +1,37 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.Game; + +public class TurnBaseGame implements Game { + + private final TurnBaseGameView turnBaseGameView; + private final TurnBaseGameService turnBaseGameService; + + public TurnBaseGame(TurnBaseGameView turnBaseGameView, TurnBaseGameService turnBaseGameService) { + this.turnBaseGameView = turnBaseGameView; + this.turnBaseGameService = turnBaseGameService; + } + + @Override + public void init() { + turnBaseGameService.init(); + } + + @Override + public void play() { + turnBaseGameView.printStart(); + + while (true) { + try { + TurnResult turnResult = turnBaseGameService.playTurn(turnBaseGameView.requestTurnInput()); + turnBaseGameView.printResult(turnResult); + if (turnResult.isGameCleared()) { + turnBaseGameView.printClear(); + return; + } + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + } + } + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGameService.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGameService.java new file mode 100644 index 00000000..eae18abf --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGameService.java @@ -0,0 +1,9 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn; + +public interface TurnBaseGameService { + + void init(); + + TurnResult playTurn(TurnInput turnInput); + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGameView.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGameView.java new file mode 100644 index 00000000..4c883dd5 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnBaseGameView.java @@ -0,0 +1,13 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn; + +public interface TurnBaseGameView { + + void printStart(); + + TurnInput requestTurnInput(); + + void printClear(); + + void printResult(TurnResult turnResult); + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnInput.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnInput.java new file mode 100644 index 00000000..714d476b --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnInput.java @@ -0,0 +1,4 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn; + +public interface TurnInput { +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnResult.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnResult.java new file mode 100644 index 00000000..f2791dda --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/game/turn/TurnResult.java @@ -0,0 +1,7 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.turn; + +public interface TurnResult { + + boolean isGameCleared(); + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/GameSystem.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/GameSystem.java new file mode 100644 index 00000000..df241c1c --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/GameSystem.java @@ -0,0 +1,6 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem; + +public interface GameSystem { + + void run(); +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystem.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystem.java new file mode 100644 index 00000000..8f6e24d9 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystem.java @@ -0,0 +1,33 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem; + +import com.kakao.onboarding.precourse.dukeyun.baseball.framework.game.Game; + +public class ReplayableGameSystem implements GameSystem { + + private final ReplayableGameSystemView systemView; + private final Game game; + + public ReplayableGameSystem(ReplayableGameSystemView systemView, Game game) { + this.systemView = systemView; + this.game = game; + } + + @Override + public void run() { + boolean replay = true; + while (replay) { + game.init(); + game.play(); + replay = inputReplay(); + } + } + + private boolean inputReplay() { + try { + return systemView.requestReplayInput(); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + return inputReplay(); + } + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystemConsoleView.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystemConsoleView.java new file mode 100644 index 00000000..82549076 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystemConsoleView.java @@ -0,0 +1,28 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem; + +import com.kakao.onboarding.precourse.dukeyun.baseball.utils.Console; + +public class ReplayableGameSystemConsoleView implements ReplayableGameSystemView { + + private static final String RESTART = "1"; + private static final String EXIT = "2"; + private static final String REPLAY_REQUEST_PROMPT = "게임을 새로 시작하려면 " + RESTART + ", 종료하려면 " + EXIT + "를 입력하세요."; + + @Override + public boolean requestReplayInput() { + System.out.println(REPLAY_REQUEST_PROMPT); + String input = Console.readLine(); + validInput(input); + return isReplay(input); + } + + private void validInput(String input) { + if (!input.equals(RESTART) && !input.equals(EXIT)) { + throw new IllegalArgumentException("입력은 " + RESTART + " 이거나" + EXIT + " 여야 합니다."); + } + } + + private boolean isReplay(String input) { + return input.equals(RESTART); + } +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystemView.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystemView.java new file mode 100644 index 00000000..0c21fc75 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/framework/gamesystem/ReplayableGameSystemView.java @@ -0,0 +1,7 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.framework.gamesystem; + +public interface ReplayableGameSystemView { + + boolean requestReplayInput(); + +} diff --git a/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/utils/Console.java b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/utils/Console.java new file mode 100644 index 00000000..9205ada8 --- /dev/null +++ b/src/main/java/com/kakao/onboarding/precourse/dukeyun/baseball/utils/Console.java @@ -0,0 +1,12 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.utils; + +import java.util.Scanner; + +public class Console { + + private static final Scanner scanner = new Scanner(System.in); + + public static String readLine() { + return scanner.nextLine().trim(); + } +} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameServiceTest.java b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameServiceTest.java new file mode 100644 index 00000000..0e43dad4 --- /dev/null +++ b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameServiceTest.java @@ -0,0 +1,84 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import com.kakao.onboarding.precourse.dukeyun.baseball.mock.TestBaseballNumberGenerator; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class BaseballGameServiceTest { + + private BaseballGameService baseballGameService; + private BaseballGameConfig baseballGameConfig; + + @BeforeEach + public void init() { + baseballGameConfig = new BaseballGameConfig(3, 1, 9); + + baseballGameService = new BaseballGameService(baseballGameConfig, + new TestBaseballNumberGenerator(List.of(1, 2, 3))); + baseballGameService.init(); + } + + @Test + public void 정답_시_결과_객체에_클리어_정보를_반환한다() throws Exception { + // given + BaseballGameTurnInput input = new BaseballGameTurnInput(baseballGameConfig, "123"); + + // when + BaseballGameTurnResult result = (BaseballGameTurnResult)baseballGameService.playTurn(input); + + //then + assertThat(result.isGameCleared()).isTrue(); + assertThat(result.getNumBall()).isEqualTo(0); + assertThat(result.getNumStrike()).isEqualTo(3); + } + + @Test + public void 정답이_아닐_시_결과_객체에_클리어_실패_정보를_반환한다() throws Exception { + // given + BaseballGameTurnInput input = new BaseballGameTurnInput(baseballGameConfig, "132"); + + // when + BaseballGameTurnResult result = (BaseballGameTurnResult)baseballGameService.playTurn(input); + + //then + assertThat(result.isGameCleared()).isFalse(); + assertThat(result.getNumBall()).isEqualTo(2); + assertThat(result.getNumStrike()).isEqualTo(1); + } + + @Test + public void 모두_틀리면_볼과_스트라이크_모두_0이다() throws Exception { + // given + BaseballGameTurnInput input = new BaseballGameTurnInput(baseballGameConfig, "456"); + + // when + BaseballGameTurnResult result = (BaseballGameTurnResult)baseballGameService.playTurn(input); + + //then + assertThat(result.isGameCleared()).isFalse(); + assertThat(result.getNumBall()).isEqualTo(0); + assertThat(result.getNumStrike()).isEqualTo(0); + } + + @Test + public void 모두_자리만_다르면_3볼이다() throws Exception { + // given + BaseballGameTurnInput input = new BaseballGameTurnInput(baseballGameConfig, "312"); + + // when + BaseballGameTurnResult result = (BaseballGameTurnResult)baseballGameService.playTurn(input); + + //then + assertThat(result.isGameCleared()).isFalse(); + assertThat(result.getNumBall()).isEqualTo(3); + assertThat(result.getNumStrike()).isEqualTo(0); + } +} \ No newline at end of file diff --git a/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnInputTest.java b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnInputTest.java new file mode 100644 index 00000000..768bf9f3 --- /dev/null +++ b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballGameTurnInputTest.java @@ -0,0 +1,18 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class BaseballGameTurnInputTest { + + @Test + public void 숫자가_아니면_예외를_발생시킨다() throws Exception { + assertThatThrownBy(() -> { + new BaseballGameTurnInput(new BaseballGameConfig(3, 1, 9), "1aa"); + }).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumberTest.java b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumberTest.java new file mode 100644 index 00000000..406772e6 --- /dev/null +++ b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/baseball/BaseballNumberTest.java @@ -0,0 +1,84 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.baseball; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BaseballNumberTest { + + private BaseballGameConfig config; + + @BeforeEach + void setup() { + config = new BaseballGameConfig(3, 1, 9); + } + + @Test + void 기준_길이보다_길면_생성이_안된다() { + assertThatThrownBy(() -> + new BaseballNumber(config, List.of(1, 2, 3, 4)) + ).isInstanceOf(RuntimeException.class); + } + + @Test + void 기준_길이보다_짧으면_생성이_안된다() { + assertThatThrownBy(() -> + new BaseballNumber(config, List.of(1, 2)) + ).isInstanceOf(RuntimeException.class); + } + + @Test + void 숫자가_범위가_최소_허용_범위보다_작으면_생성이_안된다() { + assertThatThrownBy(() -> + new BaseballNumber(config, List.of(0, 2, 3)) + ).isInstanceOf(RuntimeException.class); + } + + @Test + void 숫자가_범위가_최소_허용_범위보다_크면_생성이_안된다() { + assertThatThrownBy(() -> + new BaseballNumber(config, List.of(10, 2, 3)) + ).isInstanceOf(RuntimeException.class); + } + + @Test + void 중복된_숫자로_생성할_수_없다() { + assertThatThrownBy(() -> + new BaseballNumber(config, List.of(1, 1, 3)) + ).isInstanceOf(RuntimeException.class); + } + + @Test + void 올바른_조건으로_생성할_수_있다() { + assertThatCode( + () -> new BaseballNumber(config, List.of(1, 2, 3)) + ).doesNotThrowAnyException(); + } + + @Test + void 스트라이크_갯수를_계산할_수_있다() { + BaseballNumber systemNumber = new BaseballNumber(config, List.of(1, 2, 3)); + + assertThat(systemNumber.countStrike(new BaseballNumber(config, List.of(1, 2, 3)))).isEqualTo(3); + assertThat(systemNumber.countStrike(new BaseballNumber(config, List.of(1, 2, 4)))).isEqualTo(2); + assertThat(systemNumber.countStrike(new BaseballNumber(config, List.of(1, 3, 4)))).isEqualTo(1); + assertThat(systemNumber.countStrike(new BaseballNumber(config, List.of(3, 2, 1)))).isEqualTo(1); + assertThat(systemNumber.countStrike(new BaseballNumber(config, List.of(4, 5, 6)))).isEqualTo(0); + assertThat(systemNumber.countStrike(new BaseballNumber(config, List.of(3, 1, 2)))).isEqualTo(0); + } + + @Test + void 볼_갯수를_계산할_수_있다() { + BaseballNumber systemNumber = new BaseballNumber(config, List.of(1, 2, 3)); + + assertThat(systemNumber.countBall(new BaseballNumber(config, List.of(1, 2, 3)))).isEqualTo(0); + assertThat(systemNumber.countBall(new BaseballNumber(config, List.of(1, 2, 4)))).isEqualTo(0); + assertThat(systemNumber.countBall(new BaseballNumber(config, List.of(1, 3, 4)))).isEqualTo(1); + assertThat(systemNumber.countBall(new BaseballNumber(config, List.of(3, 2, 1)))).isEqualTo(2); + assertThat(systemNumber.countBall(new BaseballNumber(config, List.of(4, 5, 6)))).isEqualTo(0); + assertThat(systemNumber.countBall(new BaseballNumber(config, List.of(3, 1, 2)))).isEqualTo(3); + } +} \ No newline at end of file diff --git a/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/mock/TestBaseballNumberGenerator.java b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/mock/TestBaseballNumberGenerator.java new file mode 100644 index 00000000..494f5603 --- /dev/null +++ b/src/test/java/com/kakao/onboarding/precourse/dukeyun/baseball/mock/TestBaseballNumberGenerator.java @@ -0,0 +1,20 @@ +package com.kakao.onboarding.precourse.dukeyun.baseball.mock; + +import java.util.List; + +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballGameConfig; +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballNumber; +import com.kakao.onboarding.precourse.dukeyun.baseball.baseball.BaseballNumberGenerator; + +public class TestBaseballNumberGenerator implements BaseballNumberGenerator { + private final List fixedNumbers; + + public TestBaseballNumberGenerator(List fixedNumbers) { + this.fixedNumbers = fixedNumbers; + } + + @Override + public BaseballNumber generate() { + return new BaseballNumber(new BaseballGameConfig(3, 1, 9), fixedNumbers); + } +} \ No newline at end of file