From 6f36da9ff4f6275d6c20b7065827aeaae8630346 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:45:12 +0900 Subject: [PATCH 01/31] =?UTF-8?q?docs:=20readme.md=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..5fda768d 100644 --- a/README.md +++ b/README.md @@ -1 +1,120 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +## 목표 + +1부터 9까지 서로 다른 수로 이루어진 3자리 숫자를 맞추는 숫자 야구 게임을 구현한다. + +### 게임 규칙 +- 컴퓨터가 1~9 중 서로 다른 임의의 수 3개를 선택한다. +- 플레이어는 3자리 숫자를 입력하고, 컴퓨터는 힌트를 제공한다. + - **스트라이크**: 같은 수가 같은 자리에 있는 경우 + - **볼**: 같은 수가 다른 자리에 있는 경우 + - **낫싱**: 같은 수가 전혀 없는 경우 +- 3스트라이크 시 게임이 종료되며, 재시작 또는 완전 종료를 선택할 수 있다. +- 잘못된 입력 시 `[ERROR]`로 시작하는 메시지를 출력하고 게임을 계속 진행한다. + +--- + +## 기능 목록 + +### Model + +#### RandomNumberGenerator (랜덤 숫자 생성) +1. `pickNumber` - 숫자 랜덤으로 하나 뽑는 함수 + - 매개변수: 시작숫자, 종료숫자 + - 반환값: 숫자 + - 매개변수의 범위가 올바르지 않을 시 오류 + +2. `pickUniqueNumbers` - 숫자를 중복 없이 N개 뽑는 함수 + - 매개변수: 시작숫자, 종료숫자, 개수 + - 반환값: 숫자 리스트 + - 매개변수의 범위가 올바르지 않을 시 오류 + - 범위보다 요구 개수가 많아도 오류 + +#### GameResult (게임 결과 객체) +- 필드: ball(볼 개수), strike(스트라이크 개수) + +#### BaseballGameJudge (게임 판정) +1. `validateLength` - 길이 검사 함수 + - 매개변수: 숫자 리스트, 숫자 리스트 + - 반환값: boolean + - 두 리스트의 길이가 비교에 유효한지 검사 + - 잘못된 값이면 [ERROR] + +2. `countBall` - 볼 검사 함수 + - 매개변수: 숫자 리스트, 숫자 리스트 + - 반환값: 숫자 + - 두 리스트에서 위치는 다르지만 같은 숫자가 몇 개인지 검사 + +3. `countStrike` - 스트라이크 검사 함수 + - 매개변수: 숫자 리스트, 숫자 리스트 + - 반환값: 숫자 + - 두 리스트에서 동일한 위치에 동일한 숫자가 몇 개인지 검사 + +4. `judge` - 야구게임 결과 함수 + - 매개변수: 숫자 리스트, 숫자 리스트 + - 반환값: GameResult 객체 + +5. `isGameOver` - 게임 종료 여부 판단 함수 + - 매개변수: GameResult 객체 + - 반환값: boolean + +#### InputValidator (입력 검증) +1. `parseToNumbers` - 문자열을 숫자 리스트로 파싱하는 함수 + - 매개변수: 문자열 + - 반환값: 숫자 리스트 + - 숫자 외의 문자가 포함되어 있다면 [ERROR] + +2. `hasNoDuplicates` - 중복 없는 숫자 리스트인지 검증 함수 + - 매개변수: 숫자 리스트 + - 반환값: boolean + - 중복이 있으면 [ERROR] + +3. `validateRestartInput` - 재시작 입력 검증 함수 + - 매개변수: 문자열 + - 반환값: boolean + - 잘못된 값이면 [ERROR] + +--- + +### View + +#### InputView (입력) +1. `readNumber` - 숫자 입력 함수 + - 매개변수: 없음 + - 반환값: 문자열 + - 안내 문구 출력 필요 + +2. `readRestartOption` - 재시작/종료 입력 함수 + - 매개변수: 없음 + - 반환값: 문자열 + +#### OutputView (출력) +1. `printResult` - 게임 결과 출력 함수 + - 매개변수: GameResult 객체 + - 반환값: 없음 + - 결과 문구 출력 + +2. `printError` - 에러 메시지 출력 함수 + - 매개변수: 에러 메시지 + - 반환값: 없음 + +--- + +### Controller + +#### GameController (게임 제어) +1. `run` - 전체 진입 함수 + - 매개변수: 없음 + - 반환값: 없음 + - 최상위 제어, 숫자 생성, 단건 함수 반복 호출, 재시작 함수 호출 등 + +2. `playRound` - 단건 함수 + - 매개변수: 숫자 리스트 + - 반환값: boolean + - 숫자 입력, 결과 출력(검사 로직 포함) 호출, 종료 여부 반환 + +3. `handleRestart` - 재시작 함수 + - 매개변수: 없음 + - 반환값: boolean + - 올바른 입력이 들어올 때까지 반복 From 0df65f810822b6555e4c465dc281c5bb49013f89 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:45:12 +0900 Subject: [PATCH 02/31] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=81=B4=EB=9E=98=EC=8A=A4,=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8(=EB=AF=B8=EA=B5=AC=ED=98=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/GameController.java | 18 +++++++++++++ src/main/java/model/BaseballGameJudge.java | 26 +++++++++++++++++++ src/main/java/model/GameResult.java | 20 ++++++++++++++ src/main/java/model/InputValidator.java | 18 +++++++++++++ .../java/model/RandomNumberGenerator.java | 14 ++++++++++ src/main/java/view/InputView.java | 12 +++++++++ src/main/java/view/OutputView.java | 14 ++++++++++ 7 files changed, 122 insertions(+) create mode 100644 src/main/java/controller/GameController.java create mode 100644 src/main/java/model/BaseballGameJudge.java create mode 100644 src/main/java/model/GameResult.java create mode 100644 src/main/java/model/InputValidator.java create mode 100644 src/main/java/model/RandomNumberGenerator.java create mode 100644 src/main/java/view/InputView.java create mode 100644 src/main/java/view/OutputView.java diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java new file mode 100644 index 00000000..4ac640ba --- /dev/null +++ b/src/main/java/controller/GameController.java @@ -0,0 +1,18 @@ +package controller; + +import java.util.List; + +public class GameController { + + public void run() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public boolean playRound(List answer) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public boolean handleRestart() { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java new file mode 100644 index 00000000..b820ea45 --- /dev/null +++ b/src/main/java/model/BaseballGameJudge.java @@ -0,0 +1,26 @@ +package model; + +import java.util.List; + +public class BaseballGameJudge { + + public boolean validateLength(List answer, List guess) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public int countBall(List answer, List guess) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public int countStrike(List answer, List guess) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public GameResult judge(List answer, List guess) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public boolean isGameOver(GameResult result) { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/src/main/java/model/GameResult.java b/src/main/java/model/GameResult.java new file mode 100644 index 00000000..b3a2c1c0 --- /dev/null +++ b/src/main/java/model/GameResult.java @@ -0,0 +1,20 @@ +package model; + +public class GameResult { + + private int ball; + private int strike; + + public GameResult(int ball, int strike) { + this.ball = ball; + this.strike = strike; + } + + public int getBall() { + return ball; + } + + public int getStrike() { + return strike; + } +} diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java new file mode 100644 index 00000000..2ea15a9f --- /dev/null +++ b/src/main/java/model/InputValidator.java @@ -0,0 +1,18 @@ +package model; + +import java.util.List; + +public class InputValidator { + + public List parseToNumbers(String input) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public boolean hasNoDuplicates(List numbers) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public boolean validateRestartInput(String input) { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/src/main/java/model/RandomNumberGenerator.java b/src/main/java/model/RandomNumberGenerator.java new file mode 100644 index 00000000..2793f877 --- /dev/null +++ b/src/main/java/model/RandomNumberGenerator.java @@ -0,0 +1,14 @@ +package model; + +import java.util.List; + +public class RandomNumberGenerator { + + public int pickNumber(int start, int end) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public List pickUniqueNumbers(int start, int end, int count) { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..4bb179b4 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,12 @@ +package view; + +public class InputView { + + public String readNumber() { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public String readRestartOption() { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..0dc2ade3 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,14 @@ +package view; + +import model.GameResult; + +public class OutputView { + + public void printResult(GameResult result) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + public void printError(String message) { + throw new UnsupportedOperationException("Not implemented yet"); + } +} From bc72b7b4b6630fe1639a7cc1c54f2a357d0bf042 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:45:12 +0900 Subject: [PATCH 03/31] =?UTF-8?q?feat:=20CustomException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=91=20OutputView.printError(e)=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20=ED=98=95?= =?UTF-8?q?=ED=83=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/model/exception/InputErrorCode.java | 27 +++++++++++++++++++ .../exception/InvalidInputException.java | 8 ++++++ .../java/model/exception/UserException.java | 20 ++++++++++++++ src/main/java/view/OutputView.java | 7 +++-- 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/main/java/model/exception/InputErrorCode.java create mode 100644 src/main/java/model/exception/InvalidInputException.java create mode 100644 src/main/java/model/exception/UserException.java diff --git a/README.md b/README.md index 5fda768d..adf8dbf8 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ - 결과 문구 출력 2. `printError` - 에러 메시지 출력 함수 - - 매개변수: 에러 메시지 + - 매개변수: exception 객체 - 반환값: 없음 --- diff --git a/src/main/java/model/exception/InputErrorCode.java b/src/main/java/model/exception/InputErrorCode.java new file mode 100644 index 00000000..3fa3decf --- /dev/null +++ b/src/main/java/model/exception/InputErrorCode.java @@ -0,0 +1,27 @@ +package model.exception; + +public enum InputErrorCode { + + // 입력 관련 + INVALID_INPUT_FORMAT("숫자만 입력할 수 있습니다."), + INVALID_INPUT_EMPTY("입력값이 비어있습니다."), + INVALID_RESTART_INPUT("1 또는 2만 입력할 수 있습니다."), + + // 중복 관련 + DUPLICATE_NUMBER("중복된 숫자가 있습니다."), + + // 길이 관련 + INVALID_LENGTH("입력 길이가 올바르지 않습니다.") + + ; + + private final String message; + + InputErrorCode(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/model/exception/InvalidInputException.java b/src/main/java/model/exception/InvalidInputException.java new file mode 100644 index 00000000..14a398cb --- /dev/null +++ b/src/main/java/model/exception/InvalidInputException.java @@ -0,0 +1,8 @@ +package model.exception; + +public class InvalidInputException extends UserException { + + public InvalidInputException(InputErrorCode code) { + super(code); + } +} diff --git a/src/main/java/model/exception/UserException.java b/src/main/java/model/exception/UserException.java new file mode 100644 index 00000000..d5d111bb --- /dev/null +++ b/src/main/java/model/exception/UserException.java @@ -0,0 +1,20 @@ +package model.exception; + +public abstract class UserException extends RuntimeException { + + private final InputErrorCode inputErrorCode; + + public UserException(InputErrorCode code) { + super(code.getMessage()); + this.inputErrorCode = code; + } + + public InputErrorCode getErrorCode() { + return inputErrorCode; + } + + @Override + public String getMessage(){ + return inputErrorCode.getMessage(); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 0dc2ade3..cd9065e2 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,14 +1,17 @@ package view; import model.GameResult; +import model.exception.UserException; public class OutputView { + private static final String ERROR_PREFIX = "[ERROR] "; + public void printResult(GameResult result) { throw new UnsupportedOperationException("Not implemented yet"); } - public void printError(String message) { - throw new UnsupportedOperationException("Not implemented yet"); + public void printError(UserException e) { + System.out.println(ERROR_PREFIX + e.getMessage()); } } From 8d15860c172dc40cc9c75680521957e8fdfbd0f2 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:45:12 +0900 Subject: [PATCH 04/31] =?UTF-8?q?feat:=20RandomNumberGenerator=EC=97=90=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/RandomNumberGenerator.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/RandomNumberGenerator.java b/src/main/java/model/RandomNumberGenerator.java index 2793f877..ca34bf24 100644 --- a/src/main/java/model/RandomNumberGenerator.java +++ b/src/main/java/model/RandomNumberGenerator.java @@ -1,9 +1,15 @@ package model; -import java.util.List; +import java.util.*; public class RandomNumberGenerator { + private final Random random; + + public RandomNumberGenerator(){ + random = new Random(); + } + public int pickNumber(int start, int end) { throw new UnsupportedOperationException("Not implemented yet"); } From 8b43e46ad38a70e8a1077e8d36b477bd61e43cb8 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:45:13 +0900 Subject: [PATCH 05/31] =?UTF-8?q?feat:=20RandomNumberGenerator.pickNumber?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/RandomNumberGenerator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/RandomNumberGenerator.java b/src/main/java/model/RandomNumberGenerator.java index ca34bf24..a5ba2547 100644 --- a/src/main/java/model/RandomNumberGenerator.java +++ b/src/main/java/model/RandomNumberGenerator.java @@ -11,7 +11,8 @@ public RandomNumberGenerator(){ } public int pickNumber(int start, int end) { - throw new UnsupportedOperationException("Not implemented yet"); + if (start > end) throw new IllegalArgumentException("숫자의 범위가 올바르지 않습니다."); + return random.nextInt(end-start+1) + start; } public List pickUniqueNumbers(int start, int end, int count) { From 461518d2c9454b8035b5dfd4a92be035d3f8b2b8 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:45:13 +0900 Subject: [PATCH 06/31] =?UTF-8?q?feat:=20RandomNumberGenerator.pickUniqueN?= =?UTF-8?q?umbers=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/RandomNumberGenerator.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/RandomNumberGenerator.java b/src/main/java/model/RandomNumberGenerator.java index a5ba2547..beae99c1 100644 --- a/src/main/java/model/RandomNumberGenerator.java +++ b/src/main/java/model/RandomNumberGenerator.java @@ -16,6 +16,18 @@ public int pickNumber(int start, int end) { } public List pickUniqueNumbers(int start, int end, int count) { - throw new UnsupportedOperationException("Not implemented yet"); + if(end-start+1 < count) throw new IllegalArgumentException("원하는 개수의 숫자를 뽑을 수 없습니다"); + if(count<0) throw new IllegalArgumentException("0개 이하의 숫자를 뽑을 수 없습니다."); + List list = new ArrayList<>(); + Set picks = new HashSet<>(); + + while(list.size() < count){ + int pick = pickNumber(start, end); + if(picks.contains(pick)) continue; + list.add(pick); + picks.add(pick); + } + + return list; } } From 0820cd911aa3fcac98addd304908850f1eb5b2dc Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:54:18 +0900 Subject: [PATCH 07/31] =?UTF-8?q?feat:=20BaseballGameJudge.validateLength?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 반환값을 boolean->void로 수정 --- README.md | 2 +- src/main/java/model/BaseballGameJudge.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index adf8dbf8..d724969e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ #### BaseballGameJudge (게임 판정) 1. `validateLength` - 길이 검사 함수 - 매개변수: 숫자 리스트, 숫자 리스트 - - 반환값: boolean + - 반환값: 없음 - 두 리스트의 길이가 비교에 유효한지 검사 - 잘못된 값이면 [ERROR] diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index b820ea45..988bcee0 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -1,11 +1,16 @@ package model; +import model.exception.InvalidInputException; + import java.util.List; +import static model.exception.InputErrorCode.*; + + public class BaseballGameJudge { - public boolean validateLength(List answer, List guess) { - throw new UnsupportedOperationException("Not implemented yet"); + public void validateLength(List answer, List guess) { + if(answer.size() != guess.size()) throw new InvalidInputException(INVALID_LENGTH); } public int countBall(List answer, List guess) { From 5bb501e6e83e8bd002931a0f296128c4bd337c44 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 16:59:25 +0900 Subject: [PATCH 08/31] =?UTF-8?q?feat:=20BaseballGameJudge.countBall=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/BaseballGameJudge.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index 988bcee0..2cf864cb 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -14,7 +14,14 @@ public void validateLength(List answer, List guess) { } public int countBall(List answer, List guess) { - throw new UnsupportedOperationException("Not implemented yet"); + int ball = 0; + for (int i = 0; i < guess.size(); i++) { + int num = guess.get(i); + if (answer.contains(num) && !answer.get(i).equals(num)) { + ball++; + } + } + return ball; } public int countStrike(List answer, List guess) { From f9ec4fdc933f42c21f85cc5b42ed76d59c3feaa3 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:01:42 +0900 Subject: [PATCH 09/31] =?UTF-8?q?feat:=20BaseballGameJudge.countStrike=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/BaseballGameJudge.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index 2cf864cb..059adb68 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -25,7 +25,13 @@ public int countBall(List answer, List guess) { } public int countStrike(List answer, List guess) { - throw new UnsupportedOperationException("Not implemented yet"); + int strike = 0; + for (int i = 0; i < guess.size(); i++) { + if (answer.get(i).equals(guess.get(i))) { + strike++; + } + } + return strike; } public GameResult judge(List answer, List guess) { From d0a99e6f0fb9fa5be14147ca1c4fdc9738fa23c1 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:03:46 +0900 Subject: [PATCH 10/31] =?UTF-8?q?feat:=20BaseballGameJudge.judge=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/BaseballGameJudge.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index 059adb68..35d5205f 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -13,7 +13,7 @@ public void validateLength(List answer, List guess) { if(answer.size() != guess.size()) throw new InvalidInputException(INVALID_LENGTH); } - public int countBall(List answer, List guess) { + int countBall(List answer, List guess) { int ball = 0; for (int i = 0; i < guess.size(); i++) { int num = guess.get(i); @@ -24,7 +24,7 @@ public int countBall(List answer, List guess) { return ball; } - public int countStrike(List answer, List guess) { + int countStrike(List answer, List guess) { int strike = 0; for (int i = 0; i < guess.size(); i++) { if (answer.get(i).equals(guess.get(i))) { @@ -35,7 +35,7 @@ public int countStrike(List answer, List guess) { } public GameResult judge(List answer, List guess) { - throw new UnsupportedOperationException("Not implemented yet"); + return new GameResult(countBall(answer, guess), countStrike(answer, guess)); } public boolean isGameOver(GameResult result) { From 48117774b97920ba8453fbbc96f8eef2a4268cb1 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:07:23 +0900 Subject: [PATCH 11/31] =?UTF-8?q?feat:=20BaseballGameJudge.judge=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - validateLength를 public->default로 변경 --- src/main/java/model/BaseballGameJudge.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index 35d5205f..9e4b774d 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -9,7 +9,7 @@ public class BaseballGameJudge { - public void validateLength(List answer, List guess) { + void validateLength(List answer, List guess) { if(answer.size() != guess.size()) throw new InvalidInputException(INVALID_LENGTH); } @@ -35,6 +35,7 @@ int countStrike(List answer, List guess) { } public GameResult judge(List answer, List guess) { + validateLength(answer, guess); return new GameResult(countBall(answer, guess), countStrike(answer, guess)); } From fc8e0026e0bd2bc50745515c1d2e85b990efbee1 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:22:50 +0900 Subject: [PATCH 12/31] =?UTF-8?q?feat:=20BaseballGameJudge=20=ED=8C=90?= =?UTF-8?q?=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - countBall: 볼 개수 계산 - countStrike: 스트라이크 개수 계산 - judge: 결과 판정 후 GameResult 반환 - isGameOver를 GameResult로 이동 (자기 상태 판단 책임) - GameResult에 goal 필드 추가 --- src/main/java/model/BaseballGameJudge.java | 5 +---- src/main/java/model/GameResult.java | 9 ++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index 9e4b774d..264319fc 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -36,10 +36,7 @@ int countStrike(List answer, List guess) { public GameResult judge(List answer, List guess) { validateLength(answer, guess); - return new GameResult(countBall(answer, guess), countStrike(answer, guess)); + return new GameResult(answer.size(), countBall(answer, guess), countStrike(answer, guess)); } - public boolean isGameOver(GameResult result) { - throw new UnsupportedOperationException("Not implemented yet"); - } } diff --git a/src/main/java/model/GameResult.java b/src/main/java/model/GameResult.java index b3a2c1c0..99d6385b 100644 --- a/src/main/java/model/GameResult.java +++ b/src/main/java/model/GameResult.java @@ -2,10 +2,12 @@ public class GameResult { + private int goal; private int ball; private int strike; - public GameResult(int ball, int strike) { + public GameResult(int goal, int ball, int strike) { + this.goal = goal; this.ball = ball; this.strike = strike; } @@ -17,4 +19,9 @@ public int getBall() { public int getStrike() { return strike; } + + public boolean isGameOver() { + return goal == strike; + } + } From 091985926e4edbd399358abe39a61163ece2d339 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:26:35 +0900 Subject: [PATCH 13/31] =?UTF-8?q?docs:=20isGameOver=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99=EC=9D=84=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=ED=95=98=EC=97=AC=20readme=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d724969e..54609dd5 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,11 @@ - 범위보다 요구 개수가 많아도 오류 #### GameResult (게임 결과 객체) -- 필드: ball(볼 개수), strike(스트라이크 개수) +- 필드: goal(목표 개수), ball(볼 개수), strike(스트라이크 개수) + +1. `isGameOver` - 게임 종료 여부 판단 함수 + - 매개변수: 없음 + - 반환값: boolean #### BaseballGameJudge (게임 판정) 1. `validateLength` - 길이 검사 함수 @@ -55,10 +59,6 @@ - 매개변수: 숫자 리스트, 숫자 리스트 - 반환값: GameResult 객체 -5. `isGameOver` - 게임 종료 여부 판단 함수 - - 매개변수: GameResult 객체 - - 반환값: boolean - #### InputValidator (입력 검증) 1. `parseToNumbers` - 문자열을 숫자 리스트로 파싱하는 함수 - 매개변수: 문자열 From b0dba37111592e59ba8969289134092239cd8a6c Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:29:32 +0900 Subject: [PATCH 14/31] =?UTF-8?q?feat:=20InputValidator.parseToNumbers=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/InputValidator.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java index 2ea15a9f..2abf2b33 100644 --- a/src/main/java/model/InputValidator.java +++ b/src/main/java/model/InputValidator.java @@ -1,11 +1,28 @@ package model; +import model.exception.InvalidInputException; + +import java.util.ArrayList; import java.util.List; +import static model.exception.InputErrorCode.*; + public class InputValidator { public List parseToNumbers(String input) { - throw new UnsupportedOperationException("Not implemented yet"); + if (input.isEmpty()) { + throw new InvalidInputException(INVALID_INPUT_EMPTY); + } + + List numbers = new ArrayList<>(); + for (char c : input.toCharArray()) { + if (!Character.isDigit(c)) { + throw new InvalidInputException(INVALID_INPUT_FORMAT); + } + numbers.add(c - '0'); + } + + return numbers; } public boolean hasNoDuplicates(List numbers) { From 24c51e88530206433b85dea9c66977fa46fd3293 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:34:11 +0900 Subject: [PATCH 15/31] =?UTF-8?q?feat:=20InputValidator.validateNoDuplicat?= =?UTF-8?q?es=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hasNoDuplicates에서 이름 변경 (validate 패턴 적용) - parseToNumbers에서 validateNoDuplicates 호출 추가 - docs: README 함수명 및 반환값 수정 --- README.md | 4 ++-- src/main/java/model/InputValidator.java | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 54609dd5..649f82e4 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,9 @@ - 반환값: 숫자 리스트 - 숫자 외의 문자가 포함되어 있다면 [ERROR] -2. `hasNoDuplicates` - 중복 없는 숫자 리스트인지 검증 함수 +2. `validateNoDuplicates` - 중복 없는 숫자 리스트인지 검증 함수 - 매개변수: 숫자 리스트 - - 반환값: boolean + - 반환값: 없음 - 중복이 있으면 [ERROR] 3. `validateRestartInput` - 재시작 입력 검증 함수 diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java index 2abf2b33..7742f7d9 100644 --- a/src/main/java/model/InputValidator.java +++ b/src/main/java/model/InputValidator.java @@ -3,7 +3,9 @@ import model.exception.InvalidInputException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static model.exception.InputErrorCode.*; @@ -22,11 +24,17 @@ public List parseToNumbers(String input) { numbers.add(c - '0'); } + validateNoDuplicates(numbers); return numbers; } - public boolean hasNoDuplicates(List numbers) { - throw new UnsupportedOperationException("Not implemented yet"); + void validateNoDuplicates(List numbers) { + Set seen = new HashSet<>(); + for (Integer num : numbers) { + if (!seen.add(num)) { + throw new InvalidInputException(DUPLICATE_NUMBER); + } + } } public boolean validateRestartInput(String input) { From 28d4ba3929b5b56512a258dafc9c623e5fdfcbd8 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:37:22 +0900 Subject: [PATCH 16/31] =?UTF-8?q?feat:=20InputValidator.validateRestartInp?= =?UTF-8?q?ut=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/InputValidator.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java index 7742f7d9..24a6447f 100644 --- a/src/main/java/model/InputValidator.java +++ b/src/main/java/model/InputValidator.java @@ -38,6 +38,15 @@ void validateNoDuplicates(List numbers) { } public boolean validateRestartInput(String input) { - throw new UnsupportedOperationException("Not implemented yet"); + if (input.isEmpty()) { + throw new InvalidInputException(INVALID_INPUT_EMPTY); + } + if (input.equals("1")) { + return true; + } + if (input.equals("2")) { + return false; + } + throw new InvalidInputException(INVALID_RESTART_INPUT); } } From 2deac30a347c57fe258c12ec53885417939eae1a Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:41:04 +0900 Subject: [PATCH 17/31] =?UTF-8?q?feat:=20InputView=EC=97=90=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/InputView.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 4bb179b4..6fac67d4 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,7 +1,16 @@ package view; +import java.io.BufferedReader; +import java.io.InputStreamReader; + public class InputView { + private final BufferedReader br; + + public InputView(){ + this.br = new BufferedReader(new InputStreamReader(System.in)); + } + public String readNumber() { throw new UnsupportedOperationException("Not implemented yet"); } From eb11c5b928d42c3c75846616a1a48848567413ec Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:44:54 +0900 Subject: [PATCH 18/31] =?UTF-8?q?feat:=20InputView.readNumber=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20input=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/InputView.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 6fac67d4..d24762a1 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,6 +1,7 @@ package view; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; public class InputView { @@ -11,8 +12,17 @@ public InputView(){ this.br = new BufferedReader(new InputStreamReader(System.in)); } + String input(){ + try{ + return br.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public String readNumber() { - throw new UnsupportedOperationException("Not implemented yet"); + System.out.print("숫자를 입력해주세요 : "); + return input(); } public String readRestartOption() { From 50e278dc7b65d7800152d20526bd5f266d0fc41b Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 17:47:00 +0900 Subject: [PATCH 19/31] =?UTF-8?q?feat:=20InputView.readRestartOption=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/InputView.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index d24762a1..1a5fb139 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,8 +1,11 @@ package view; +import model.GameResult; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.sql.SQLOutput; public class InputView { @@ -26,6 +29,7 @@ public String readNumber() { } public String readRestartOption() { - throw new UnsupportedOperationException("Not implemented yet"); + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + return input(); } } From be5eff85a9942f44ed720701e38c2ceb5602c699 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 18:06:41 +0900 Subject: [PATCH 20/31] =?UTF-8?q?feat:=20OutputView=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - printResult: 게임 결과 출력 - printGameEnd: 게임 종료 메시지 출력 - GameResult.getGoal getter 추가 - docs: README에 printGameEnd 추가 --- README.md | 6 +++++- src/main/java/model/GameResult.java | 4 ++++ src/main/java/view/OutputView.java | 16 +++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 649f82e4..df66369f 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,11 @@ - 반환값: 없음 - 결과 문구 출력 -2. `printError` - 에러 메시지 출력 함수 +2. `printGameEnd` - 게임 종료 메시지 출력 함수 + - 매개변수: GameResult 객체 + - 반환값: 없음 + +3. `printError` - 에러 메시지 출력 함수 - 매개변수: exception 객체 - 반환값: 없음 diff --git a/src/main/java/model/GameResult.java b/src/main/java/model/GameResult.java index 99d6385b..d163a17e 100644 --- a/src/main/java/model/GameResult.java +++ b/src/main/java/model/GameResult.java @@ -20,6 +20,10 @@ public int getStrike() { return strike; } + public int getGoal() { + return goal; + } + public boolean isGameOver() { return goal == strike; } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index cd9065e2..0da1de6f 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -7,8 +7,22 @@ public class OutputView { private static final String ERROR_PREFIX = "[ERROR] "; + private String formatResult(GameResult result) { + if (result.getBall() + result.getStrike() == 0) { + return "낫싱"; + } + String s = ""; + if (result.getStrike() != 0) s += result.getStrike() + "스트라이크 "; + if (result.getBall() != 0) s += result.getBall() + "볼"; + return s.trim(); + } + public void printResult(GameResult result) { - throw new UnsupportedOperationException("Not implemented yet"); + System.out.println(formatResult(result)); + } + + public void printGameEnd(GameResult result){ + System.out.println(result.getGoal() + "개의 숫자를 모두 맞히셨습니다! 게임 끝"); } public void printError(UserException e) { From 4144f327862b10ecbd0b6a658ceaf6208d0404a3 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Wed, 28 Jan 2026 18:10:31 +0900 Subject: [PATCH 21/31] =?UTF-8?q?feat:=20GameController=EC=97=90=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/GameController.java | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 4ac640ba..c9da5f22 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -1,9 +1,31 @@ package controller; +import model.BaseballGameJudge; +import model.InputValidator; +import model.RandomNumberGenerator; +import view.InputView; +import view.OutputView; + import java.util.List; public class GameController { + private final RandomNumberGenerator randomNumberGenerator; + private final BaseballGameJudge baseballGameJudge; + private final InputValidator inputValidator; + + private final InputView inputView; + private final OutputView outputView; + + public GameController() { + this.randomNumberGenerator = new RandomNumberGenerator(); + this.baseballGameJudge = new BaseballGameJudge(); + this.inputValidator = new InputValidator(); + + this.inputView = new InputView(); + this.outputView = new OutputView(); + } + public void run() { throw new UnsupportedOperationException("Not implemented yet"); } From 05589033e53e5a7a7588abae9a17ac2b9efa4b2a Mon Sep 17 00:00:00 2001 From: un-known0 Date: Thu, 29 Jan 2026 10:00:29 +0900 Subject: [PATCH 22/31] =?UTF-8?q?refactor:=20GameResult=20goal=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20View=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GameResult: goal 필드 제거, isGameOver(int) 매개변수 방식 변경 - BaseballGameJudge.judge() 호출 수정 - OutputView.printGameEnd(int) 매개변수 변경 - InputView 불필요한 import 제거 - docs: README 반영 --- README.md | 6 +++--- src/main/java/model/BaseballGameJudge.java | 2 +- src/main/java/model/GameResult.java | 12 +++--------- src/main/java/view/InputView.java | 3 --- src/main/java/view/OutputView.java | 4 ++-- 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index df66369f..56568d3d 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ - 범위보다 요구 개수가 많아도 오류 #### GameResult (게임 결과 객체) -- 필드: goal(목표 개수), ball(볼 개수), strike(스트라이크 개수) +- 필드: ball(볼 개수), strike(스트라이크 개수) 1. `isGameOver` - 게임 종료 여부 판단 함수 - - 매개변수: 없음 + - 매개변수: 목표 개수 - 반환값: boolean #### BaseballGameJudge (게임 판정) @@ -96,7 +96,7 @@ - 결과 문구 출력 2. `printGameEnd` - 게임 종료 메시지 출력 함수 - - 매개변수: GameResult 객체 + - 매개변수: 목표 개수 - 반환값: 없음 3. `printError` - 에러 메시지 출력 함수 diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index 264319fc..a3c560a3 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -36,7 +36,7 @@ int countStrike(List answer, List guess) { public GameResult judge(List answer, List guess) { validateLength(answer, guess); - return new GameResult(answer.size(), countBall(answer, guess), countStrike(answer, guess)); + return new GameResult(countBall(answer, guess), countStrike(answer, guess)); } } diff --git a/src/main/java/model/GameResult.java b/src/main/java/model/GameResult.java index d163a17e..70ab45a9 100644 --- a/src/main/java/model/GameResult.java +++ b/src/main/java/model/GameResult.java @@ -2,12 +2,10 @@ public class GameResult { - private int goal; private int ball; private int strike; - public GameResult(int goal, int ball, int strike) { - this.goal = goal; + public GameResult(int ball, int strike) { this.ball = ball; this.strike = strike; } @@ -20,12 +18,8 @@ public int getStrike() { return strike; } - public int getGoal() { - return goal; - } - - public boolean isGameOver() { - return goal == strike; + public boolean isGameOver(int numberSize) { + return numberSize == strike; } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 1a5fb139..fa11bd7e 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,11 +1,8 @@ package view; -import model.GameResult; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.sql.SQLOutput; public class InputView { diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 0da1de6f..6f7ec3bd 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -21,8 +21,8 @@ public void printResult(GameResult result) { System.out.println(formatResult(result)); } - public void printGameEnd(GameResult result){ - System.out.println(result.getGoal() + "개의 숫자를 모두 맞히셨습니다! 게임 끝"); + public void printGameEnd(int numberSize){ + System.out.println(numberSize + "개의 숫자를 모두 맞히셨습니다! 게임 끝"); } public void printError(UserException e) { From 75fbfcca1fa3c9a782975989765a6183efa15413 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Thu, 29 Jan 2026 10:01:40 +0900 Subject: [PATCH 23/31] =?UTF-8?q?feat:=20GameController=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - run: 게임 루프 및 재시작 처리 - playRound: 단일 라운드 진행 - handleRestart: 재시작 입력 처리 --- src/main/java/controller/GameController.java | 34 ++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index c9da5f22..53d6376a 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -1,8 +1,10 @@ package controller; import model.BaseballGameJudge; +import model.GameResult; import model.InputValidator; import model.RandomNumberGenerator; +import model.exception.UserException; import view.InputView; import view.OutputView; @@ -10,6 +12,10 @@ public class GameController { + private static final int NUMBER_SIZE = 3; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 9; + private final RandomNumberGenerator randomNumberGenerator; private final BaseballGameJudge baseballGameJudge; private final InputValidator inputValidator; @@ -27,14 +33,36 @@ public GameController() { } public void run() { - throw new UnsupportedOperationException("Not implemented yet"); + do{ + List result = randomNumberGenerator.pickUniqueNumbers(MIN_NUMBER, MAX_NUMBER, NUMBER_SIZE); +// List result = List.of(1,2,3); + while(!playRound(result)){ + } + outputView.printGameEnd(NUMBER_SIZE); + }while(handleRestart()); } public boolean playRound(List answer) { - throw new UnsupportedOperationException("Not implemented yet"); + try{ + String input = inputView.readNumber(); + List inputList = inputValidator.parseToNumbers(input); + GameResult judge = baseballGameJudge.judge(answer, inputList); + outputView.printResult(judge); + return judge.isGameOver(NUMBER_SIZE); + }catch(UserException e){ + outputView.printError(e); + } + return false; } public boolean handleRestart() { - throw new UnsupportedOperationException("Not implemented yet"); + while(true){ + try{ + String s = inputView.readRestartOption(); + return inputValidator.validateRestartInput(s); + }catch(UserException e){ + outputView.printError(e); + } + } } } From d4704f70bb4e3cdad66627801263ff2912379c2f Mon Sep 17 00:00:00 2001 From: un-known0 Date: Thu, 29 Jan 2026 10:02:37 +0900 Subject: [PATCH 24/31] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=ED=98=B8=EC=B6=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?BaseballGame=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/BaseballGame.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/BaseballGame.java diff --git a/src/main/java/BaseballGame.java b/src/main/java/BaseballGame.java new file mode 100644 index 00000000..9351df1e --- /dev/null +++ b/src/main/java/BaseballGame.java @@ -0,0 +1,8 @@ +import controller.GameController; + +public class BaseballGame { + public static void main(String[] args) { + GameController controller = new GameController(); + controller.run(); + } +} From 6146e0bc4477b5707ba4a1e10a5f00c4eac0418a Mon Sep 17 00:00:00 2001 From: un-known0 Date: Thu, 29 Jan 2026 10:44:22 +0900 Subject: [PATCH 25/31] =?UTF-8?q?chore:=20InputValidator=EA=B0=80=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=EC=97=B4=20=EB=82=B4=20=EC=95=9E=EB=92=A4=20?= =?UTF-8?q?=EA=B3=B5=EB=B0=B1=EC=9D=84=20=EB=AC=B4=EC=8B=9C=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/InputValidator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java index 24a6447f..8b875f08 100644 --- a/src/main/java/model/InputValidator.java +++ b/src/main/java/model/InputValidator.java @@ -17,7 +17,7 @@ public List parseToNumbers(String input) { } List numbers = new ArrayList<>(); - for (char c : input.toCharArray()) { + for (char c : input.trim().toCharArray()) { if (!Character.isDigit(c)) { throw new InvalidInputException(INVALID_INPUT_FORMAT); } @@ -41,10 +41,10 @@ public boolean validateRestartInput(String input) { if (input.isEmpty()) { throw new InvalidInputException(INVALID_INPUT_EMPTY); } - if (input.equals("1")) { + if (input.trim().equals("1")) { return true; } - if (input.equals("2")) { + if (input.trim().equals("2")) { return false; } throw new InvalidInputException(INVALID_RESTART_INPUT); From 8032de05453702fae4cbca928424a5fad1b27cf7 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Sun, 1 Feb 2026 13:58:07 +0900 Subject: [PATCH 26/31] =?UTF-8?q?chore:=20InputView.input=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=97=90=20flush=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/InputView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index fa11bd7e..05de8bf7 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -14,6 +14,7 @@ public InputView(){ String input(){ try{ + System.out.flush(); return br.readLine(); } catch (IOException e) { throw new RuntimeException(e); From 76d6de2559ae1fc26a4e7ff437092a66fb10f1f3 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Sun, 1 Feb 2026 14:46:10 +0900 Subject: [PATCH 27/31] =?UTF-8?q?fix:=20InputValidator.parseToNumbers?= =?UTF-8?q?=EA=B0=80=20trim=20=EC=9D=B4=ED=9B=84=20empty=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EB=A5=BC=20=ED=8C=90=EB=8B=A8=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/InputValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java index 8b875f08..4c96a584 100644 --- a/src/main/java/model/InputValidator.java +++ b/src/main/java/model/InputValidator.java @@ -12,7 +12,7 @@ public class InputValidator { public List parseToNumbers(String input) { - if (input.isEmpty()) { + if (input.trim().isEmpty()) { throw new InvalidInputException(INVALID_INPUT_EMPTY); } From b5d958b5004f6bf45727448c2bdc19772301a921 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Sun, 1 Feb 2026 16:52:53 +0900 Subject: [PATCH 28/31] =?UTF-8?q?refector:=20validateLength=EB=A5=BC=20Inp?= =?UTF-8?q?utValidator=EB=A1=9C=20=EC=9D=B4=EB=8F=99,=20validateRange=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/GameController.java | 2 +- src/main/java/model/BaseballGameJudge.java | 5 ---- src/main/java/model/InputValidator.java | 24 ++++++++++++++++--- .../java/model/exception/InputErrorCode.java | 3 +++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/main/java/controller/GameController.java b/src/main/java/controller/GameController.java index 53d6376a..b5513926 100644 --- a/src/main/java/controller/GameController.java +++ b/src/main/java/controller/GameController.java @@ -45,7 +45,7 @@ public void run() { public boolean playRound(List answer) { try{ String input = inputView.readNumber(); - List inputList = inputValidator.parseToNumbers(input); + List inputList = inputValidator.parseToNumbers(input, MIN_NUMBER, MAX_NUMBER, NUMBER_SIZE); GameResult judge = baseballGameJudge.judge(answer, inputList); outputView.printResult(judge); return judge.isGameOver(NUMBER_SIZE); diff --git a/src/main/java/model/BaseballGameJudge.java b/src/main/java/model/BaseballGameJudge.java index a3c560a3..3fd9bfe5 100644 --- a/src/main/java/model/BaseballGameJudge.java +++ b/src/main/java/model/BaseballGameJudge.java @@ -9,10 +9,6 @@ public class BaseballGameJudge { - void validateLength(List answer, List guess) { - if(answer.size() != guess.size()) throw new InvalidInputException(INVALID_LENGTH); - } - int countBall(List answer, List guess) { int ball = 0; for (int i = 0; i < guess.size(); i++) { @@ -35,7 +31,6 @@ int countStrike(List answer, List guess) { } public GameResult judge(List answer, List guess) { - validateLength(answer, guess); return new GameResult(countBall(answer, guess), countStrike(answer, guess)); } diff --git a/src/main/java/model/InputValidator.java b/src/main/java/model/InputValidator.java index 4c96a584..7971df46 100644 --- a/src/main/java/model/InputValidator.java +++ b/src/main/java/model/InputValidator.java @@ -11,8 +11,8 @@ public class InputValidator { - public List parseToNumbers(String input) { - if (input.trim().isEmpty()) { + public List parseToNumbers(String input, int min, int max, int size) { + if (input == null || input.trim().isEmpty()) { throw new InvalidInputException(INVALID_INPUT_EMPTY); } @@ -24,10 +24,28 @@ public List parseToNumbers(String input) { numbers.add(c - '0'); } - validateNoDuplicates(numbers); + validateInput(numbers, min, max, size); return numbers; } + void validateInput(List numbers, int min, int max, int size){ + validateRange(numbers, min, max); + validateNoDuplicates(numbers); + validateLength(numbers, size); + } + + void validateLength(List numbers, int size) { + if(numbers.size() != size) throw new InvalidInputException(INVALID_LENGTH); + } + + void validateRange(List numbers, int min, int max) { + for (Integer num : numbers) { + if (num < min || num > max) { + throw new InvalidInputException(INVALID_NUMBER_RANGE); + } + } + } + void validateNoDuplicates(List numbers) { Set seen = new HashSet<>(); for (Integer num : numbers) { diff --git a/src/main/java/model/exception/InputErrorCode.java b/src/main/java/model/exception/InputErrorCode.java index 3fa3decf..fe84bd4a 100644 --- a/src/main/java/model/exception/InputErrorCode.java +++ b/src/main/java/model/exception/InputErrorCode.java @@ -7,6 +7,9 @@ public enum InputErrorCode { INVALID_INPUT_EMPTY("입력값이 비어있습니다."), INVALID_RESTART_INPUT("1 또는 2만 입력할 수 있습니다."), + // 범위 관련 + INVALID_NUMBER_RANGE("범위를 벗어난 숫자가 있습니다."), + // 중복 관련 DUPLICATE_NUMBER("중복된 숫자가 있습니다."), From 206db3e75a661e827692d32ecdc4d31f9558e8ad Mon Sep 17 00:00:00 2001 From: un-known0 Date: Sun, 1 Feb 2026 17:05:07 +0900 Subject: [PATCH 29/31] =?UTF-8?q?fix:=20RandomNumberGenerator.pickUniqueNu?= =?UTF-8?q?mbers=EC=97=90=20=EC=9E=98=EB=AA=BB=EB=90=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=84=A4=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/model/RandomNumberGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/model/RandomNumberGenerator.java b/src/main/java/model/RandomNumberGenerator.java index beae99c1..8302223c 100644 --- a/src/main/java/model/RandomNumberGenerator.java +++ b/src/main/java/model/RandomNumberGenerator.java @@ -17,7 +17,7 @@ public int pickNumber(int start, int end) { public List pickUniqueNumbers(int start, int end, int count) { if(end-start+1 < count) throw new IllegalArgumentException("원하는 개수의 숫자를 뽑을 수 없습니다"); - if(count<0) throw new IllegalArgumentException("0개 이하의 숫자를 뽑을 수 없습니다."); + if(count<0) throw new IllegalArgumentException("0개 미만의 숫자를 뽑을 수 없습니다."); List list = new ArrayList<>(); Set picks = new HashSet<>(); From aee1cb5344899643a6dbbbeb2f806e4b31e554e6 Mon Sep 17 00:00:00 2001 From: un-known0 Date: Thu, 5 Feb 2026 11:39:50 +0900 Subject: [PATCH 30/31] =?UTF-8?q?docs:=20=EA=B5=AC=ED=98=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20readme=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 56568d3d..70483e9d 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,12 @@ 1. `pickNumber` - 숫자 랜덤으로 하나 뽑는 함수 - 매개변수: 시작숫자, 종료숫자 - 반환값: 숫자 - - 매개변수의 범위가 올바르지 않을 시 오류 + - 시작숫자가 종료숫자보다 큰 경우 오류 2. `pickUniqueNumbers` - 숫자를 중복 없이 N개 뽑는 함수 - 매개변수: 시작숫자, 종료숫자, 개수 - 반환값: 숫자 리스트 - - 매개변수의 범위가 올바르지 않을 시 오류 - - 범위보다 요구 개수가 많아도 오류 + - 범위보다 요구 개수가 많거나 개수가 0 미만이면 오류 #### GameResult (게임 결과 객체) - 필드: ball(볼 개수), strike(스트라이크 개수) @@ -39,38 +38,44 @@ - 반환값: boolean #### BaseballGameJudge (게임 판정) -1. `validateLength` - 길이 검사 함수 - - 매개변수: 숫자 리스트, 숫자 리스트 - - 반환값: 없음 - - 두 리스트의 길이가 비교에 유효한지 검사 - - 잘못된 값이면 [ERROR] - -2. `countBall` - 볼 검사 함수 +1. `countBall` - 볼 검사 함수 - 매개변수: 숫자 리스트, 숫자 리스트 - 반환값: 숫자 - 두 리스트에서 위치는 다르지만 같은 숫자가 몇 개인지 검사 -3. `countStrike` - 스트라이크 검사 함수 +2. `countStrike` - 스트라이크 검사 함수 - 매개변수: 숫자 리스트, 숫자 리스트 - 반환값: 숫자 - 두 리스트에서 동일한 위치에 동일한 숫자가 몇 개인지 검사 -4. `judge` - 야구게임 결과 함수 +3. `judge` - 야구게임 결과 함수 - 매개변수: 숫자 리스트, 숫자 리스트 - 반환값: GameResult 객체 #### InputValidator (입력 검증) 1. `parseToNumbers` - 문자열을 숫자 리스트로 파싱하는 함수 - - 매개변수: 문자열 + - 매개변수: 문자열, 최소값, 최대값, 길이 - 반환값: 숫자 리스트 - 숫자 외의 문자가 포함되어 있다면 [ERROR] + - 입력값이 비어있으면 [ERROR] + - 범위/길이/중복 검증을 함께 수행 + +2. `validateRange` - 숫자 범위 검증 함수 + - 매개변수: 숫자 리스트, 최소값, 최대값 + - 반환값: 없음 + - 범위를 벗어난 숫자가 있으면 [ERROR] + +3. `validateLength` - 길이 검사 함수 + - 매개변수: 숫자 리스트, 길이 + - 반환값: 없음 + - 길이가 다르면 [ERROR] -2. `validateNoDuplicates` - 중복 없는 숫자 리스트인지 검증 함수 +4. `validateNoDuplicates` - 중복 없는 숫자 리스트인지 검증 함수 - 매개변수: 숫자 리스트 - 반환값: 없음 - 중복이 있으면 [ERROR] -3. `validateRestartInput` - 재시작 입력 검증 함수 +5. `validateRestartInput` - 재시작 입력 검증 함수 - 매개변수: 문자열 - 반환값: boolean - 잘못된 값이면 [ERROR] From 9aa606d58f82957aae13985c4ff107abade6be5f Mon Sep 17 00:00:00 2001 From: un-known0 Date: Fri, 6 Feb 2026 14:50:24 +0900 Subject: [PATCH 31/31] =?UTF-8?q?test:=20model=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/model/BaseballGameJudgeTest.java | 73 ++++++++ src/test/java/model/GameResultTest.java | 42 +++++ src/test/java/model/InputValidatorTest.java | 163 ++++++++++++++++++ src/test/java/model/MODEL_TEST_CASES.md | 79 +++++++++ .../java/model/RandomNumberGeneratorTest.java | 94 ++++++++++ 5 files changed, 451 insertions(+) create mode 100644 src/test/java/model/BaseballGameJudgeTest.java create mode 100644 src/test/java/model/GameResultTest.java create mode 100644 src/test/java/model/InputValidatorTest.java create mode 100644 src/test/java/model/MODEL_TEST_CASES.md create mode 100644 src/test/java/model/RandomNumberGeneratorTest.java diff --git a/src/test/java/model/BaseballGameJudgeTest.java b/src/test/java/model/BaseballGameJudgeTest.java new file mode 100644 index 00000000..fef3cf74 --- /dev/null +++ b/src/test/java/model/BaseballGameJudgeTest.java @@ -0,0 +1,73 @@ +package model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class BaseballGameJudgeTest { + + @Test + @DisplayName("countBall: 위치 다른 동일 숫자만 카운트") + void countBall_countsOnlyDifferentPositions() { + BaseballGameJudge judge = new BaseballGameJudge(); + + int ball = judge.countBall(List.of(1, 2, 3), List.of(2, 1, 3)); + + assertThat(ball).isEqualTo(2); + } + + @Test + @DisplayName("countBall: 스트라이크는 볼로 카운트하지 않음") + void countBall_doesNotCountStrikeAsBall() { + BaseballGameJudge judge = new BaseballGameJudge(); + + int ball = judge.countBall(List.of(1, 2, 3), List.of(1, 3, 2)); + + assertThat(ball).isEqualTo(2); + } + + @Test + @DisplayName("countStrike: 같은 위치 같은 숫자 카운트") + void countStrike_countsSamePositionSameNumber() { + BaseballGameJudge judge = new BaseballGameJudge(); + + int strike = judge.countStrike(List.of(1, 2, 3), List.of(1, 4, 3)); + + assertThat(strike).isEqualTo(2); + } + + @Test + @DisplayName("countStrike: 전부 일치 시 길이 반환") + void countStrike_returnsLengthWhenAllMatch() { + BaseballGameJudge judge = new BaseballGameJudge(); + + int strike = judge.countStrike(List.of(7, 8, 9), List.of(7, 8, 9)); + + assertThat(strike).isEqualTo(3); + } + + @Test + @DisplayName("judge: 볼/스트라이크 계산") + void judge_returnsBallAndStrikeCounts() { + BaseballGameJudge judge = new BaseballGameJudge(); + + GameResult result = judge.judge(List.of(4, 2, 5), List.of(2, 4, 5)); + + assertThat(result.getBall()).isEqualTo(2); + assertThat(result.getStrike()).isEqualTo(1); + } + + @Test + @DisplayName("judge: 일치 없음이면 0/0") + void judge_returnsZeroWhenNoMatches() { + BaseballGameJudge judge = new BaseballGameJudge(); + + GameResult result = judge.judge(List.of(1, 2, 3), List.of(4, 5, 6)); + + assertThat(result.getBall()).isZero(); + assertThat(result.getStrike()).isZero(); + } +} diff --git a/src/test/java/model/GameResultTest.java b/src/test/java/model/GameResultTest.java new file mode 100644 index 00000000..95cfa8cf --- /dev/null +++ b/src/test/java/model/GameResultTest.java @@ -0,0 +1,42 @@ +package model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.assertj.core.api.Assertions.assertThat; + +class GameResultTest { + + @Test + @DisplayName("getter: 생성자 값 반환") + void getters_returnConstructorValues() { + GameResult result = new GameResult(2, 1); + + assertThat(result.getBall()).isEqualTo(2); + assertThat(result.getStrike()).isEqualTo(1); + } + + @Test + @DisplayName("isGameOver: strike==size면 true") + void isGameOver_returnsTrueWhenStrikeMatchesSize() { + GameResult result = new GameResult(0, 3); + + assertThat(result.isGameOver(3)).isTrue(); + } + + @Test + @DisplayName("isGameOver: strike!=size면 false") + void isGameOver_returnsFalseWhenStrikeDoesNotMatchSize() { + GameResult result = new GameResult(1, 2); + + assertThat(result.isGameOver(3)).isFalse(); + } + + @Test + @DisplayName("isGameOver: size=0, strike=0이면 true") + void isGameOver_returnsTrueWhenSizeIsZeroAndStrikeIsZero() { + GameResult result = new GameResult(0, 0); + + assertThat(result.isGameOver(0)).isTrue(); + } +} diff --git a/src/test/java/model/InputValidatorTest.java b/src/test/java/model/InputValidatorTest.java new file mode 100644 index 00000000..50a6ff11 --- /dev/null +++ b/src/test/java/model/InputValidatorTest.java @@ -0,0 +1,163 @@ +package model; + +import model.exception.InputErrorCode; +import model.exception.InvalidInputException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class InputValidatorTest { + + private final InputValidator validator = new InputValidator(); + + @Test + @DisplayName("parseToNumbers: 숫자 문자열 파싱") + void parseToNumbers_parsesDigits() { + List numbers = validator.parseToNumbers("123", 1, 9, 3); + + assertThat(numbers).containsExactly(1, 2, 3); + } + + @Test + @DisplayName("parseToNumbers: 앞뒤 공백 제거") + void parseToNumbers_trimsInput() { + List numbers = validator.parseToNumbers(" 123 ", 1, 9, 3); + + assertThat(numbers).containsExactly(1, 2, 3); + } + + @Test + @DisplayName("parseToNumbers: null이면 예외") + void parseToNumbers_throwsOnNull() { + assertInvalidInput(() -> validator.parseToNumbers(null, 1, 9, 3), InputErrorCode.INVALID_INPUT_EMPTY); + } + + @Test + @DisplayName("parseToNumbers: 공백/빈 문자열이면 예외") + void parseToNumbers_throwsOnBlank() { + assertInvalidInput(() -> validator.parseToNumbers(" ", 1, 9, 3), InputErrorCode.INVALID_INPUT_EMPTY); + } + + @Test + @DisplayName("parseToNumbers: 숫자 외 문자가 있으면 예외") + void parseToNumbers_throwsOnNonDigit() { + assertInvalidInput(() -> validator.parseToNumbers("12a", 1, 9, 3), InputErrorCode.INVALID_INPUT_FORMAT); + } + + @Test + @DisplayName("parseToNumbers: 길이 불일치면 예외") + void parseToNumbers_throwsOnInvalidLength() { + assertInvalidInput(() -> validator.parseToNumbers("12", 1, 9, 3), InputErrorCode.INVALID_LENGTH); + } + + @Test + @DisplayName("parseToNumbers: 길이 초과면 예외") + void parseToNumbers_throwsOnTooLongLength() { + assertInvalidInput(() -> validator.parseToNumbers("1234", 1, 9, 3), InputErrorCode.INVALID_LENGTH); + } + + @Test + @DisplayName("parseToNumbers: 범위 밖 숫자면 예외") + void parseToNumbers_throwsOnOutOfRange() { + assertInvalidInput(() -> validator.parseToNumbers("109", 1, 9, 3), InputErrorCode.INVALID_NUMBER_RANGE); + } + + @Test + @DisplayName("parseToNumbers: 중복이면 예외") + void parseToNumbers_throwsOnDuplicate() { + assertInvalidInput(() -> validator.parseToNumbers("112", 1, 9, 3), InputErrorCode.DUPLICATE_NUMBER); + } + + @Test + @DisplayName("validateRange: 범위 내면 통과") + void validateRange_allowsValuesWithinRange() { + validator.validateRange(List.of(1, 2, 3), 1, 3); + } + + @Test + @DisplayName("validateRange: 범위 밖이면 예외") + void validateRange_throwsOnOutOfRange() { + assertInvalidInput(() -> validator.validateRange(List.of(1, 4, 3), 1, 3), InputErrorCode.INVALID_NUMBER_RANGE); + } + + @Test + @DisplayName("validateLength: 길이 일치하면 통과") + void validateLength_allowsMatchingLength() { + validator.validateLength(List.of(1, 2, 3), 3); + } + + @Test + @DisplayName("validateLength: 길이 다르면 예외") + void validateLength_throwsOnMismatchedLength() { + assertInvalidInput(() -> validator.validateLength(List.of(1, 2), 3), InputErrorCode.INVALID_LENGTH); + } + + @Test + @DisplayName("validateNoDuplicates: 중복 없으면 통과") + void validateNoDuplicates_allowsUniqueValues() { + validator.validateNoDuplicates(List.of(1, 2, 3)); + } + + @Test + @DisplayName("validateNoDuplicates: 중복이면 예외") + void validateNoDuplicates_throwsOnDuplicate() { + assertInvalidInput(() -> validator.validateNoDuplicates(List.of(1, 2, 2)), InputErrorCode.DUPLICATE_NUMBER); + } + + @Test + @DisplayName("validateRestartInput: '1'이면 true") + void validateRestartInput_returnsTrueForOne() { + assertThat(validator.validateRestartInput("1")).isTrue(); + } + + @Test + @DisplayName("validateRestartInput: '2'이면 false") + void validateRestartInput_returnsFalseForTwo() { + assertThat(validator.validateRestartInput("2")).isFalse(); + } + + @Test + @DisplayName("validateRestartInput: 앞뒤 공백 허용") + void validateRestartInput_allowsWhitespaceAroundInput() { + assertThat(validator.validateRestartInput(" 1 ")).isTrue(); + assertThat(validator.validateRestartInput(" 2 ")).isFalse(); + } + + @Test + @DisplayName("validateRestartInput: 빈 문자열이면 예외") + void validateRestartInput_throwsOnEmpty() { + assertInvalidInput(() -> validator.validateRestartInput(""), InputErrorCode.INVALID_INPUT_EMPTY); + } + + @Test + @DisplayName("validateRestartInput: null이면 NPE") + void validateRestartInput_throwsOnNull() { + assertThatThrownBy(() -> validator.validateRestartInput(null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + @DisplayName("validateRestartInput: 공백만 있으면 예외") + void validateRestartInput_throwsOnWhitespaceOnly() { + assertInvalidInput(() -> validator.validateRestartInput(" "), InputErrorCode.INVALID_RESTART_INPUT); + } + + @Test + @DisplayName("validateRestartInput: 그 외 값이면 예외") + void validateRestartInput_throwsOnOtherValues() { + assertInvalidInput(() -> validator.validateRestartInput("3"), InputErrorCode.INVALID_RESTART_INPUT); + } + + private void assertInvalidInput(Runnable action, InputErrorCode expectedCode) { + assertThatThrownBy(action::run) + .isInstanceOf(InvalidInputException.class) + .satisfies(e -> { + InvalidInputException ex = (InvalidInputException) e; + assertThat(ex.getErrorCode()).isEqualTo(expectedCode); + }); + } +} diff --git a/src/test/java/model/MODEL_TEST_CASES.md b/src/test/java/model/MODEL_TEST_CASES.md new file mode 100644 index 00000000..32d24163 --- /dev/null +++ b/src/test/java/model/MODEL_TEST_CASES.md @@ -0,0 +1,79 @@ +# Model JUnit5 테스트 케이스 목록 + +이 문서는 `model` 패키지에 대한 JUnit5 테스트 케이스를 정리한다. + +## RandomNumberGenerator + +### pickNumber(start, end) +- 정상 범위: `start == end`일 때 반환값이 그 값인지 +- 정상 범위: `start < end`일 때 반환값이 `start..end` 범위 내인지 (여러 번 반복) +- 예외: `start > end`이면 `IllegalArgumentException` 발생 + +### pickUniqueNumbers(start, end, count) +- 정상: `count == 0`이면 빈 리스트 반환 +- 정상: `count > 0`이고 범위가 충분할 때 리스트 크기가 `count`인지 +- 정상: 반환 리스트가 모두 `start..end` 범위 내인지 +- 정상: 반환 리스트에 중복이 없는지 +- 예외: `end - start + 1 < count`이면 `IllegalArgumentException` 발생 +- 예외: `count < 0`이면 `IllegalArgumentException` 발생 +- 참고: `start > end`인 경우는 별도 검증이 없으므로 `count > 0`일 때는 `pickNumber`에서 예외가 발생함 + +## GameResult + +### constructor / getters +- 정상: 생성 시 전달한 `ball`, `strike`가 그대로 반환되는지 + +### isGameOver(numberSize) +- 정상: `numberSize == strike`이면 `true` +- 정상: `numberSize != strike`이면 `false` +- 경계: `numberSize == 0`일 때 `strike == 0`이면 `true` + +## BaseballGameJudge + +### countBall(answer, guess) +- 정상: 모든 숫자가 위치만 다를 때 볼 개수 정확히 계산 +- 정상: 같은 숫자가 같은 위치면 볼로 카운트하지 않음 +- 정상: 중복이 없는 입력에서 볼 계산이 기대값인지 +- 참고: 입력 리스트 길이 검증이 없으므로 길이가 다른 경우는 현재 구현상 정의되지 않음 + +### countStrike(answer, guess) +- 정상: 같은 위치 같은 숫자 개수 계산 +- 정상: 전부 일치 시 `strike == 길이` + +### judge(answer, guess) +- 정상: `countBall`과 `countStrike` 결과로 `GameResult` 생성되는지 +- 정상: 볼/스트라이크가 모두 0이면 0/0 반환되는지 + +## InputValidator + +### parseToNumbers(input, min, max, size) +- 정상: 숫자 문자열이 리스트로 변환되는지 +- 정상: 앞뒤 공백이 있는 경우에도 정상 파싱되는지 +- 예외: `null` 입력이면 `InvalidInputException(INVALID_INPUT_EMPTY)` +- 예외: 빈 문자열/공백 문자열이면 `InvalidInputException(INVALID_INPUT_EMPTY)` +- 예외: 숫자 외 문자가 포함되면 `InvalidInputException(INVALID_INPUT_FORMAT)` +- 예외: 길이가 `size`와 다르면 `InvalidInputException(INVALID_LENGTH)` +- 예외: 길이가 `size`보다 길면 `InvalidInputException(INVALID_LENGTH)` +- 예외: 범위 밖 숫자가 있으면 `InvalidInputException(INVALID_NUMBER_RANGE)` +- 예외: 중복이 있으면 `InvalidInputException(DUPLICATE_NUMBER)` + +### validateRange(numbers, min, max) +- 정상: 모두 범위 내면 예외 없음 +- 예외: 범위 밖 숫자가 하나라도 있으면 `InvalidInputException(INVALID_NUMBER_RANGE)` + +### validateLength(numbers, size) +- 정상: 길이가 같으면 예외 없음 +- 예외: 길이가 다르면 `InvalidInputException(INVALID_LENGTH)` + +### validateNoDuplicates(numbers) +- 정상: 중복 없으면 예외 없음 +- 예외: 중복 있으면 `InvalidInputException(DUPLICATE_NUMBER)` + +### validateRestartInput(input) +- 정상: `"1"`이면 `true` +- 정상: `"2"`이면 `false` +- 정상: 앞뒤 공백이 있어도 `"1"`/`"2"`는 허용 +- 예외: `null`이면 `NullPointerException` +- 예외: 빈 문자열이면 `InvalidInputException(INVALID_INPUT_EMPTY)` +- 예외: 공백만 있는 문자열이면 `InvalidInputException(INVALID_RESTART_INPUT)` +- 예외: 그 외 문자열이면 `InvalidInputException(INVALID_RESTART_INPUT)` diff --git a/src/test/java/model/RandomNumberGeneratorTest.java b/src/test/java/model/RandomNumberGeneratorTest.java new file mode 100644 index 00000000..c7acaa1b --- /dev/null +++ b/src/test/java/model/RandomNumberGeneratorTest.java @@ -0,0 +1,94 @@ +package model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RandomNumberGeneratorTest { + + @Test + @DisplayName("pickNumber: 범위 내 값 반환") + void pickNumber_returnsValueWithinRange() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + for (int i = 0; i < 100; i++) { + int value = generator.pickNumber(1, 9); + assertThat(value).isBetween(1, 9); + } + } + + @Test + @DisplayName("pickNumber: start==end이면 해당 값 반환") + void pickNumber_returnsStartWhenRangeIsSingleValue() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + int value = generator.pickNumber(5, 5); + + assertThat(value).isEqualTo(5); + } + + @Test + @DisplayName("pickNumber: start가 end보다 크면 예외") + void pickNumber_throwsWhenStartGreaterThanEnd() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + assertThatThrownBy(() -> generator.pickNumber(5, 3)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("pickUniqueNumbers: count가 0이면 빈 리스트") + void pickUniqueNumbers_returnsEmptyListWhenCountIsZero() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + List numbers = generator.pickUniqueNumbers(1, 9, 0); + + assertThat(numbers).isEmpty(); + } + + @Test + @DisplayName("pickUniqueNumbers: 범위 내 중복 없는 리스트") + void pickUniqueNumbers_returnsUniqueNumbersWithinRange() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + List numbers = generator.pickUniqueNumbers(1, 9, 3); + + assertThat(numbers).hasSize(3); + assertThat(numbers).allSatisfy(n -> assertThat(n).isBetween(1, 9)); + Set unique = new HashSet<>(numbers); + assertThat(unique).hasSize(3); + } + + @Test + @DisplayName("pickUniqueNumbers: 범위보다 많은 개수면 예외") + void pickUniqueNumbers_throwsWhenCountExceedsRange() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + assertThatThrownBy(() -> generator.pickUniqueNumbers(1, 2, 3)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("pickUniqueNumbers: count가 음수면 예외") + void pickUniqueNumbers_throwsWhenCountIsNegative() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + assertThatThrownBy(() -> generator.pickUniqueNumbers(1, 9, -1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("pickUniqueNumbers: start>end이고 count>0이면 예외") + void pickUniqueNumbers_throwsWhenStartGreaterThanEndAndCountPositive() { + RandomNumberGenerator generator = new RandomNumberGenerator(); + + assertThatThrownBy(() -> generator.pickUniqueNumbers(5, 3, 1)) + .isInstanceOf(IllegalArgumentException.class); + } +}