Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
88bdaa5
docs: 기능 요구사항 README에 정리
sapiensnoah Jan 28, 2026
cf5eca7
feat: 랜덤 수 생성
sapiensnoah Jan 28, 2026
957b293
feat: 랜덤 수 생성 검증로직
sapiensXXV Jan 28, 2026
edada6c
feat: 비교로직 결과 출력
sapiensXXV Jan 28, 2026
7aa1606
feat: 게임 판정 로직
sapiensXXV Jan 28, 2026
ec9efce
add: 게임 결과 DTO
sapiensXXV Jan 28, 2026
ae602f6
feat: 사용자 입력
sapiensXXV Jan 28, 2026
265cdf5
feat: 메세지 출력 뷰
sapiensXXV Jan 28, 2026
6079eb4
chore: 생성자 누락 수정
sapiensXXV Jan 28, 2026
09561be
feat: 사용자 입력 클래스
sapiensXXV Jan 28, 2026
e6d3e7d
chore: 불변 리스트 반환 메서드
sapiensXXV Jan 28, 2026
42fd136
feat: 게임 컨트롤러
sapiensXXV Jan 28, 2026
2c20035
feat: 애플리케이션 진입점
sapiensXXV Jan 28, 2026
80b5877
feat: 재시도 메세지 출력
sapiensXXV Jan 28, 2026
cdc080e
feat: 게임시작
sapiensXXV Jan 28, 2026
5706973
fix: 메세지 오타 수정
sapiensXXV Jan 28, 2026
be8efd8
add: 에러메세지 상수 클래스
sapiensXXV Jan 28, 2026
7fa2b0d
fix: 패키지 구조 변경
sapiensXXV Jan 28, 2026
2774dcf
chore: 불필요한 주석 삭제
sapiensXXV Jan 29, 2026
dde095c
feat: 숫자 입력 검증로직
sapiensXXV Jan 30, 2026
db94990
test: input_converter 테스트
sapiensXXV Jan 30, 2026
e89ed85
test: referee 테스트
sapiensXXV Jan 30, 2026
e8013d4
test: random_number_generator test
sapiensXXV Jan 30, 2026
427983d
test: baseball_number test
sapiensXXV Jan 30, 2026
a0ea641
refactor: gamecontroller di
sapiensXXV Jan 30, 2026
f00409f
refactor: 입출력 및 숫자 생성 인터페이스 추출 & 의존성 주입 구조로 변경
sapiensXXV Jan 31, 2026
44ab66b
test: gamecontroller test
sapiensXXV Jan 31, 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
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
# java-baseball-precourse
# java-baseball-precourse

크게 게임 초기화(컴퓨터), 사용자 입력, 게임 로직(판정), 게임 종료 및 재시작, 예외처리 5가지로 기능을 나누었습니다.

1. 게임 초기화 (컴퓨터 숫자 생성)
2. 사용자 입력
3. 게임 판정 및 힌트 출력
4. 게임 종료 및 재시작
5. 예외 처리 및 유효성 검사

## 기능 설명
### 1. 게임 초기화 (컴퓨터 숫자 생성)
게임이 시작되면 컴퓨터는 아래의 규칙에 따라 숫자를 생성해야합니다.
- [x] 랜덤 수 생성: 1부터 9까지의 서로 다른 수로 이루어진 3자리 수를 임의로 선택합니다.
- [x] 각 자리의 숫자가 중복될 수 없다.
- [x] 0 은 포함되지 않는다.

### 2. 사용자 입력
사용자로부터 3자리의 숫자를 입력받는 기능입니다.
- [ ] 입력 요청: `숫자를 입력 해주세요`와 같은 문구로 사용자의 입력을 유도합니다.
- [ ] 입력값 수신: 사용자가 입력한 문자열(숫자)를 받아옵니다.

### 3. 게임 판정 및 힌트 출력
컴퓨터의 수 플레이어의 수를 비교하여 결과를 계산하고 출력합니다.

- [x] 비교 로직
- [x] `스트라이크`: 같은 수가 같은 자리에 있는 경우
- [x] `볼`: 같은 수가 다른 자리에 있는 경우
- [x] `낫싱`: 같은 수가 전혀 없는 경우
- [x] 결과 출력
- [x] 계산된 볼과 스트라이크 개수를 출력합니다.
- [x] 하나도 맞지 않을 경우 `낫싱`을 출력합니다.
- [x] 스트라이크와 볼이 같이 있는 경우, 보통 볼을 먼저 출력하고 스트라이크를 나중에 출력하는 형식을 따릅니다.

### 4. 게임 종료 및 재시작
3개의 숫자를 모두 맞혔을 때의 처리 로직입니다.
- [ ] 정답 처리: 3스트라이크일 경우, `3개의 숫자를 모두 맞히셨습니다! 게임종료` 메세지를 출력합니다.
- [ ] 재시작/종료 선택: 게임 종료 후 재시작 여부를 묻습니다.
- [ ] `1` 입력 시: 게임을 처음부터 다시 시작. 새로운 랜덤 숫자 생성
- [ ] `2` 입력 시: 애플리케이션 완전히 종료

### 5. 예외 처리 및 유효성 검사
사용자가 잘못된 값을 입력했을 때 프로그램이 종료되지 않고 알림을 준 뒤 다시 진행되어야 합니다.
- [x] 숫자가 아닌 문자가 포함된 경우.
- [x] 3자리가 아닌 경우 (2자리 이하 또는 4자리 이상)
- [x] 중복된 숫자가 있는 경우 (예: 112)
- [x] 1~9 범위를 벗어난 숫자(0)가 포함된 경우

에러처리는 `[ERROR]`로 시작하는 에러 메시지를 출력합니다.

에러 발생 후 프로그램이 강제 종료되지 않아야 하고, 다시 `플레이어 입력 기능`으로 돌아가야합니다.
18 changes: 18 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import controller.GameController;
import domain.RandomNumberGenerator;
import domain.Referee;
import view.ConsoleInputView;
import view.ConsoleOutputView;

public class Application {
public static void main(String[] args) {
GameController gameController = new GameController(
new ConsoleInputView(),
new ConsoleOutputView(),
new RandomNumberGenerator(),
new Referee()
);

gameController.run();
}
}
11 changes: 11 additions & 0 deletions src/main/java/constant/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package constant;

public class ErrorMessage {
public static final String ERROR_PREFIX = "[ERROR] ";
public static final String NOT_NUMBER = ERROR_PREFIX + "숫자만 입력해주세요";
public static final String OUT_OF_RANGE = ERROR_PREFIX + "숫자는 1부터 9까지의 수여야 합니다.";
public static final String DUPLICATE_NUMBER = ERROR_PREFIX + "숫자는 중복될 수 없습니다.";
public static final String INVALID_SIZE = ERROR_PREFIX + "숫자는 3자리여야 합니다.";

private ErrorMessage() { } // 인스턴스 생성 방지
}
8 changes: 8 additions & 0 deletions src/main/java/constant/GameConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package constant;

public class GameConfig {

public static final int NUMBERS_SIZE = 3;
public static final int NUMBER_MIN_RANGE = 1;
public static final int NUMBER_MAX_RANGE = 9;
}
70 changes: 70 additions & 0 deletions src/main/java/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package controller;

import domain.NumberGenerator;
import dto.GameResult;
import domain.BaseballNumber;
import utils.InputConverter;
import domain.Referee;
import view.InputView;
import view.OutputView;


public class GameController {

private final InputView inputView;
private final OutputView outputView;
private final NumberGenerator randomNumberGenerator;
private final Referee referee;

public GameController(
InputView inputView,
OutputView outputView,
NumberGenerator randomNumberGenerator,
Referee referee
) {
this.inputView = inputView;
this.outputView = outputView;
this.randomNumberGenerator = randomNumberGenerator;
this.referee = referee;
}

public void run() {
outputView.printMessage("숫자 야구 게임을 시작하겠습니다.");
// 게임 전체 반복 (재시작 로직)
while (true) {
// 1. 컴퓨터 숫자 생성
BaseballNumber computerNumber = randomNumberGenerator.generate();

// 2. 한 판 플레이 (3스트라이크 맞출 때까지 반복)
play(computerNumber);

// 3. 게임 종료 후 재시작 여부 확인
// "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."
String restartCommand = inputView.inputRestartCommand();

if (restartCommand.equals("2")) {
break; // while문 탈출 -> 프로그램 종료
}
// 1번이면 while문 처음으로 돌아가서 새 숫자 생성
}
}

private void play(BaseballNumber computerNumber) {
while(true) {
try {
String inputString = inputView.inputNumbers();
BaseballNumber userNumber = new BaseballNumber(InputConverter.convertStringToIntegerList(inputString));
GameResult result = referee.judge(computerNumber, userNumber);
outputView.printResult(result);

if (result.isThreeStrike()) {
outputView.printGameSuccess();
break;
}
} catch (IllegalArgumentException e) {
outputView.printMessage(e.getMessage());
}
}
}

}
66 changes: 66 additions & 0 deletions src/main/java/domain/BaseballNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package domain;

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

import static constant.ErrorMessage.*;
import static constant.GameConfig.*;

/**
* 숫자 3개를 담고 있는 객체
* Computer, Referee, View 등의 객체에서 사용됩니다.
*/
public class BaseballNumber {
// 생성한 숫자라는 것은 숫자를 픽한것 뿐만 아니라 순서 정보도 가지고 있어야한다.
private final List<Integer> numbers;

public BaseballNumber(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
}

private void validate(List<Integer> numbers) {
validateSize(numbers);
validateRange(numbers);
validateDuplicate(numbers);
}

private void validateSize(List<Integer> numbers) {
if (numbers.size() != NUMBERS_SIZE) {
throw new IllegalArgumentException(INVALID_SIZE);
}
}

private void validateRange(List<Integer> numbers) {
for (int number: numbers) {
if (number < NUMBER_MIN_RANGE || number > NUMBER_MAX_RANGE) {
throw new IllegalArgumentException(OUT_OF_RANGE);
}
}
}

private void validateDuplicate(List<Integer> numbers) {
Set<Integer> nonDuplicateNumbers = new HashSet<>(numbers);
if (nonDuplicateNumbers.size() != NUMBERS_SIZE) {
throw new IllegalArgumentException(DUPLICATE_NUMBER);
}
}

// 유틸리티 메서드들

//특정 위치의 숫자를 가져오기
public int getNumber(int index) {
return numbers.get(index);
}

// 특정 숫자가 포함되어 있는지 확인
public boolean contain(int number) {
return numbers.contains(number);
}

public List<Integer> getNumbers() {
return Collections.unmodifiableList(numbers);
}
}
6 changes: 6 additions & 0 deletions src/main/java/domain/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package domain;

public interface NumberGenerator {

public BaseballNumber generate();
}
21 changes: 21 additions & 0 deletions src/main/java/domain/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain;

import java.util.*;

public class RandomNumberGenerator implements NumberGenerator {

private final Random random;

public RandomNumberGenerator() {
this.random = new Random();
}

public BaseballNumber generate() {
Set<Integer> uniqueNumbers = new LinkedHashSet<>();
while (uniqueNumbers.size() < 3) {
uniqueNumbers.add(random.nextInt(9) + 1);
}

return new BaseballNumber(new ArrayList<>(uniqueNumbers));
}
}
27 changes: 27 additions & 0 deletions src/main/java/domain/Referee.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package domain;

import constant.GameConfig;
import dto.GameResult;

public class Referee {

public GameResult judge(BaseballNumber computer, BaseballNumber user) {
int balls = 0;
int strikes = 0;

for (int i = 0; i < GameConfig.NUMBERS_SIZE; i++) {
int userNumber = user.getNumber(i);

// 스트라이크인지 확인 (위치와 숫자가 모두 같음);
if (computer.getNumber(i) == userNumber) {
strikes++;
continue; // 볼 확인은 건너뜀
}
if (computer.contain(userNumber)) {
balls++;
}
}

return new GameResult(balls, strikes);
}
}
38 changes: 38 additions & 0 deletions src/main/java/dto/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dto;

public class GameResult {
private final int ball;
private final int strike;

public GameResult(int ball, int strike) {
this.ball = ball;
this.strike = strike;
}

public boolean isNothing() {
return ball == 0 && strike == 0;
}

public boolean isThreeStrike() {
return strike == 3;
}

public int getBall() {
return ball;
}

public int getStrike() {
return strike;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (isThreeStrike()) {
sb.append("3스트라이크");
} else {
sb.append(getBall() + "볼 " + getStrike() + "스트라이크");
}
return sb.toString();
}
}
54 changes: 54 additions & 0 deletions src/main/java/utils/InputConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package utils;

import constant.ErrorMessage;

import java.util.ArrayList;
import java.util.List;

import static constant.ErrorMessage.*;

public class InputConverter {

private InputConverter() {}

public static List<Integer> convertStringToIntegerList(String input) {
// null 및 공백처리
if (input == null || input.isBlank()) {
throw new IllegalArgumentException(NOT_NUMBER);
}

String trimmedInput = input.trim();

// 길이검증
validateLength(trimmedInput);
List<Integer> numbers = new ArrayList<>();

for (char character: trimmedInput.toCharArray()) {
validateDigit(character);
int number = Character.getNumericValue(character);
validateRange(number);
numbers.add(number);
}
return numbers;
}

private static void validateLength(String input) {
if (input.length() != 3) {
throw new IllegalArgumentException(INVALID_SIZE);
}
}

private static void validateDigit(char number) {
if (!Character.isDigit(number)) {
throw new IllegalArgumentException(OUT_OF_RANGE);
}
}

private static void validateRange(int number) {
if (number == 0) {
throw new IllegalArgumentException(OUT_OF_RANGE);
}
}


}
Loading