Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c3dc259
docs: readme 파일 추가
kakao-daniel-hoon Jan 28, 2026
c082f32
feat: main 함수 추가
kakao-daniel-hoon Jan 28, 2026
4e90c1b
feat: main 내 게임 함수 코드 추가
kakao-daniel-hoon Jan 28, 2026
8a9bad8
feat: 사용자 입력 설정
kakao-daniel-hoon Jan 28, 2026
205546c
feat: 사용자 출력 설정
kakao-daniel-hoon Jan 28, 2026
74e2b6d
feat: 애플리케이션 실행 시 숫자 야구 게임 실행 로직 구현
kakao-daniel-hoon Jan 28, 2026
4ddd39c
feat: 컴퓨터의 정답 3자리 숫자 생성 로직 구현
kakao-daniel-hoon Jan 28, 2026
d1fdb88
feat: 사용자 입력값 받기 로직 구현
kakao-daniel-hoon Jan 28, 2026
96ced91
feat: 게임 진행 로직 (입력 검증, 스트라이크 여부, 에러 반환)
kakao-daniel-hoon Jan 30, 2026
d198934
feat: 사용자 입력 클래스 구현
kakao-daniel-hoon Jan 30, 2026
8f26e18
feat: Result 클래스 구현
kakao-daniel-hoon Jan 30, 2026
0983316
feat: Judge 클래스 구현
kakao-daniel-hoon Jan 30, 2026
1389c2f
feat: ComputerNumber 클래스 내 랜덤 숫자 생성 로직 구현
kakao-daniel-hoon Jan 30, 2026
f7ac0fc
feat: exception 클래스 구현
kakao-daniel-hoon Jan 30, 2026
1f4ce23
feat: 재시작 로직 구현
kakao-daniel-hoon Jan 30, 2026
912d540
feat: 재시작 입력값 검증 로직 구현
kakao-daniel-hoon Jan 30, 2026
b491cf4
feat: 게임 진행 중 output 로직 구현
kakao-daniel-hoon Jan 30, 2026
e003a9e
fix: 끝 메시지 출력 및 Tree 오타 수정
kakao-daniel-hoon Jan 30, 2026
12fd3fe
feat: run 메소드, 게임 시작 로직 구현
kakao-daniel-hoon Jan 30, 2026
bcbd937
feat: lombok AllArgs에서 RequiredArgs로 변경
kakao-daniel-hoon Jan 30, 2026
0af19ce
chore: gradle 내 lombok 버저닝 추가
kakao-daniel-hoon Feb 2, 2026
b0c7e54
fix: git new line 에러 수정
kakao-daniel-hoon Feb 3, 2026
aa65957
feat: ErrorCode, CommonErrorCode 클래스 구현
kakao-daniel-hoon Feb 6, 2026
d79e6f8
feat: Exception 하드코딩 제거 / enum 파일로 분리
kakao-daniel-hoon Feb 6, 2026
ed0108a
refactor: UserGuess 클래스 내 하드코딩 숫자 제거
kakao-daniel-hoon Feb 6, 2026
cab89ad
refactor: Result 클래스 내 하드코딩 숫자 제거
kakao-daniel-hoon Feb 6, 2026
4a256af
refactor: baseball로 폴더 병합 / 패키지 경로 변경
kakao-daniel-hoon Feb 7, 2026
05beac4
feat: 변수를 고정하기 위해 Controller 내 정답 생성 함수를 주입해주는 코드 추가
kakao-daniel-hoon Feb 7, 2026
f39657f
test: UserGuess 검증 테스트 코드 추가
kakao-daniel-hoon Feb 7, 2026
745ec95
test: ComputerNumberTest 검증 테스트 코드 추가
kakao-daniel-hoon Feb 7, 2026
95ca810
feat: ComputerNumber에 JudgeTest 테스트 코드 작성을 위한 테스트용 생성자 추가
kakao-daniel-hoon Feb 7, 2026
8b21baa
test: JudgeTest 검증 테스트 코드 추가
kakao-daniel-hoon Feb 7, 2026
7eebd76
test: ResultTest 검증 테스트 코드 추가
kakao-daniel-hoon Feb 7, 2026
52378d3
docs: README 내 UML 추가
kdaehun00 Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# java-baseball-precourse
# java-baseball-precourse

1. 게임 시작 및 초기화
- 애플리케이션 실행 시 숫자 야구 게임을 시작한다.
- 컴퓨터의 정답 숫자 3자리를 생성한다.
- 1~9 사이의 숫자만 사용한다.
- 중복되지 않는 서로 다른 숫자 3개로 구성한다.
- 3자리 수(문자열 또는 리스트 등)로 관리한다.

---
2. 사용자 입력 처리
- 사용자로부터 3자리 숫자를 입력받는다.
- 입력값 검증을 수행한다.
- 길이가 3인지 확인한다.
- 모두 숫자인지 확인한다.
- 각 자리가 1~9인지 확인한다.
- 중복 숫자가 없는지 확인한다.
- 잘못된 입력이면 예외 처리 후 에러 메시지를 출력한다.
- [ERROR]로 시작하는 메시지를 출력한다.
- 에러 이후에도 게임은 종료되지 않고 다시 입력을 받는다.

---
3. 결과(힌트) 판정
- 사용자 입력과 컴퓨터 정답을 비교하여 스트라이크/볼/낫싱을 계산한다.
- 같은 숫자가 같은 자리면 스트라이크
- 같은 숫자가 다른 자리면 볼
- 공통 숫자가 하나도 없으면 낫싱
- 판정 결과를 출력한다.
- 스트라이크/볼이 존재하면 조합 형태로 출력한다. (예: 1스트라이크 1볼)
- 아무것도 없으면 낫싱을 출력한다.

---
4. 게임 진행 루프
- 사용자가 정답 3자리를 모두 맞힐 때까지(3스트라이크) 입력 → 판정 → 출력 과정을 반복한다.
- 3스트라이크가 되면 게임 종료 메시지를 출력한다.

---
5. 게임 종료 후 재시작/종료
- 게임 종료 후 사용자에게 재시작/종료 입력을 받는다.
- 1 입력 시 게임을 다시 시작한다 (정답 숫자 재생성).
- 2 입력 시 프로그램을 완전히 종료한다.
- 재시작/종료 입력값이 잘못된 경우에도 예외 처리한다.
- [ERROR]로 시작하는 메시지를 출력한다.
- 다시 입력을 받는다.

## UML
<img width="450" height="752" alt="Image" src="https://github.com/user-attachments/assets/7bcdc840-fd2d-43ec-9042-f14188f5df25" />
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ repositories {
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testImplementation 'org.assertj:assertj-core:3.25.3'
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
}

test {
Expand Down
Empty file removed src/main/java/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import baseball.controller.BaseballGame;
import baseball.model.ComputerNumber;
import baseball.view.InputView;
import baseball.view.OutputView;

public class Application {

public static void main(String[] args) {
BaseballGame controller = new BaseballGame(new InputView(), new OutputView(), ComputerNumber::createRandom);
controller.run();
}
}
72 changes: 72 additions & 0 deletions src/main/java/baseball/controller/BaseballGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package baseball.controller;

import baseball.exception.CommonErrorCode;
import baseball.exception.InvalidInputException;
import lombok.RequiredArgsConstructor;
import baseball.model.ComputerNumber;
import baseball.model.Judge;
import baseball.model.Result;
import baseball.model.UserGuess;
import baseball.view.InputView;
import baseball.view.OutputView;

import java.util.function.Supplier;

@RequiredArgsConstructor
public class BaseballGame {
private final InputView inputView;
private final OutputView outputView;
private final Supplier<ComputerNumber> computerNumberSupplier;

public void run() {
while (true) {
playSingleGame();
if (!askRestart()) {
return;
}
}
}

private void playSingleGame() {
ComputerNumber targetNum = computerNumberSupplier.get();

outputView.printStartMessage();

while (true) {
try {
String raw = inputView.readGuess();
UserGuess guess = UserGuess.from(raw);

Result result = Judge.judge(targetNum, guess);
outputView.printHint(result);

if (result.isThreeStrike()) {
outputView.printGameEndMessage();
break;
}
} catch (InvalidInputException e) {
outputView.printError(e.getMessage());
}

}
}

private boolean askRestart() {
while (true) {
try {
String cmd = inputView.readRestartCommand();
validateRestartCommand(cmd);

return "1".equals(cmd);
} catch (InvalidInputException e) {
outputView.printError(e.getMessage());
}
}
}

private void validateRestartCommand(String cmd) {
if (!"1".equals(cmd) && !"2".equals(cmd)) {
throw new InvalidInputException(CommonErrorCode.INVALID_COMMAND);
}
}
}
19 changes: 19 additions & 0 deletions src/main/java/baseball/exception/CommonErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package baseball.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum CommonErrorCode implements ErrorCode {

EMPTY_INPUT("EMPTY_INPUT", "입력값이 비어있습니다."),
INVALID_LENGTH("INVALID_LENGTH", "3자리 숫자를 입력해야 합니다."),
NOT_A_NUMBER("NOT_A_NUMBER", "숫자만 입력해야 합니다."),
NUMBER_OUT_OF_RANGE("NUMBER_OUT_OF_RANGE", "각 자리는 1~9 사이여야 합니다."),
DUPLICATED_NUMBER("DUPLICATED_NUMBER", "중복되지 않는 숫자 3개여야 합니다."),
INVALID_COMMAND("INVALID_COMMAND", "1 또는 2를 입력해야 합니다.");

private final String code;
private final String message;
}
6 changes: 6 additions & 0 deletions src/main/java/baseball/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package baseball.exception;

public interface ErrorCode {
String getCode();
String getMessage();
}
13 changes: 13 additions & 0 deletions src/main/java/baseball/exception/InvalidInputException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package baseball.exception;

import lombok.Getter;

@Getter
public class InvalidInputException extends IllegalArgumentException {
private final CommonErrorCode commonErrorCode;

public InvalidInputException(CommonErrorCode commonErrorCode) {
super(commonErrorCode.getMessage());
this.commonErrorCode = commonErrorCode;
}
}
40 changes: 40 additions & 0 deletions src/main/java/baseball/model/ComputerNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package baseball.model;

import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

@RequiredArgsConstructor
public final class ComputerNumber {
private static final Random RANDOM = new Random();
private final List<Integer> digits;

public static ComputerNumber createRandom() {
Set<Integer> set = new LinkedHashSet<>();
while (set.size() < 3) {
int n = RANDOM.nextInt(9) + 1; // 1~9
set.add(n); // 중복 자동 제거
}
return new ComputerNumber(new ArrayList<>(set));
}

public int digitAt(int index) {
return digits.get(index);
}

public boolean contains(int digit) {
return digits.contains(digit);
}

public int size() {
return digits.size();
}

public static ComputerNumber of(List<Integer> digits) {
return new ComputerNumber(digits);
}
}
21 changes: 21 additions & 0 deletions src/main/java/baseball/model/Judge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package baseball.model;

public final class Judge {
private Judge() { }

public static Result judge(ComputerNumber answer, UserGuess guess) {
int strike = 0;
int ball = 0;

for (int i = 0; i < answer.size(); i++) {
int g = guess.digitAt(i);

if (answer.digitAt(i) == g) {
strike++;
} else if (answer.contains(g)) {
ball++;
}
}
return new Result(strike, ball);
}
}
9 changes: 9 additions & 0 deletions src/main/java/baseball/model/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package baseball.model;

public record Result(int strike, int ball) {
private static final int DIGIT_COUNT = 3;

public boolean isThreeStrike() {
return strike == DIGIT_COUNT;
}
}
53 changes: 53 additions & 0 deletions src/main/java/baseball/model/UserGuess.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package baseball.model;

import baseball.exception.CommonErrorCode;
import baseball.exception.InvalidInputException;
import lombok.AllArgsConstructor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@AllArgsConstructor
public class UserGuess {
private final List<Integer> digits;

private static final int DIGIT_COUNT = 3;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 9;

public static UserGuess from(String input) {
if (input == null) {
throw new InvalidInputException(CommonErrorCode.EMPTY_INPUT);
}
if (input.length() != DIGIT_COUNT) {
throw new InvalidInputException(CommonErrorCode.INVALID_LENGTH);
}

List<Integer> digits = new ArrayList<>(DIGIT_COUNT);
Set<Integer> dupCheck = new HashSet<>();

for (int i = 0; i < DIGIT_COUNT; i++) {
char c = input.charAt(i);
if (!Character.isDigit(c)) {
throw new InvalidInputException(CommonErrorCode.NOT_A_NUMBER);
}

int d = c - '0';
if (d < MIN_NUMBER || d > MAX_NUMBER) {
throw new InvalidInputException(CommonErrorCode.NUMBER_OUT_OF_RANGE);
}
if (!dupCheck.add(d)) {
throw new InvalidInputException(CommonErrorCode.DUPLICATED_NUMBER);
}
digits.add(d);
}

return new UserGuess(digits);
}

public int digitAt(int index) {
return digits.get(index);
}
}
17 changes: 17 additions & 0 deletions src/main/java/baseball/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package baseball.view;

import java.util.Scanner;

public class InputView {
private final Scanner scanner = new Scanner(System.in);

public String readGuess() {
System.out.print("숫자를 입력해주세요: ");
return scanner.nextLine();
}

public String readRestartCommand() {
System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
return scanner.nextLine();
}
}
32 changes: 32 additions & 0 deletions src/main/java/baseball/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package baseball.view;

import baseball.model.Result;

public class OutputView {
public void printStartMessage() {
System.out.println("숫자 야구 게임을 시작합니다.");
}

public void printHint(Result result) {
int strike = result.strike();
int ball = result.ball();

if (strike == 0 && ball == 0) {
System.out.println("낫싱");
return;
}

StringBuilder sb = new StringBuilder();
if (ball > 0) sb.append(ball).append("볼 ");
if (strike > 0) sb.append(strike).append("스트라이크");
System.out.println(sb.toString().trim());
}

public void printGameEndMessage() {
System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
}

public void printError(String message) {
System.out.println("[ERROR] " + message);
}
}
Empty file removed src/test/java/.gitkeep
Empty file.
Loading