Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
311c841
[DOCS]: gitkeep파일 제거
jungseokyoung-cloud Jan 28, 2026
3847fff
[FEAT]: inputView 추가
jungseokyoung-cloud Jan 28, 2026
c8f1550
[FEAT]: GameController 추가
jungseokyoung-cloud Jan 28, 2026
19255e0
feat(domain): BaseballNumbers 도메인 클래스 추가
jungseokyoung-cloud Jan 28, 2026
d79fc59
feat(domain): 중복 없는 숫자 생성기(NumberGenerator) 구현
jungseokyoung-cloud Jan 28, 2026
60ceb8f
feat(domain): 스트라이크/볼 판정 로직 및 GameResult 클래스 구현
jungseokyoung-cloud Jan 28, 2026
b0efd3d
chore: 불필요해진 .gitkeep 파일 삭제
jungseokyoung-cloud Jan 28, 2026
a1dcb0d
feat(domain): BaseballNumbers 숫자 리스트 조회 기능 추가
jungseokyoung-cloud Jan 28, 2026
b157ad5
test(domain): BaseballNumbers 판정 로직 및 GameResult 상태 검증 테스트 추가
jungseokyoung-cloud Jan 28, 2026
d587cf5
test(domain): GameResult 상태 확인 로직 단위 테스트 추가
jungseokyoung-cloud Jan 28, 2026
e213a10
feat(domain): 사용자 입력 유효성 검증을 위한 Validator 구현
jungseokyoung-cloud Jan 28, 2026
2854280
test(domain): Validator 입력값 및 재시작 명령어 검증 테스트 추가
jungseokyoung-cloud Jan 28, 2026
68af746
feat(controller): 게임 실행 루프 및 재시작/종료 로직 구현
jungseokyoung-cloud Jan 28, 2026
9b486fa
feat(view): 게임 결과 출력을 위한 OutputView 구현
jungseokyoung-cloud Jan 28, 2026
a534cf2
feat(controller): 숫자 야구 게임 메인 로직(playGame) 구현
jungseokyoung-cloud Jan 28, 2026
4e43573
feat: 프로그램 시작점(Application) 구현 및 의존성 주입
jungseokyoung-cloud Jan 28, 2026
2541784
[REFACT]: output의 message print하도록 수정
jungseokyoung-cloud Feb 1, 2026
d4fc0d0
[STYLE]: package 네임 대문자에서 소문자로 변경
jungseokyoung-cloud Feb 2, 2026
3b7ab08
[STYLE]: else 문 제거
jungseokyoung-cloud Feb 2, 2026
1a287c5
[STYLE]: Validator에서 stream api 제거
jungseokyoung-cloud Feb 2, 2026
a064b57
[STYLE]: 메서드 길이가 길어져서 메서드 분리
jungseokyoung-cloud Feb 2, 2026
9c392c9
docs: 기능 요구사항 작성
jungseokyoung-cloud Feb 2, 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
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# java-baseball-precourse
# java-baseball-precourse

1. [domains] Baseball 정답 생성기
- 1에서 9까지 서로 다른 임의의 수 3개를 선택하여 저장한다.

2. [domains] Baseball 판독기 (핵심 로직)
- 플레이어가 입력한 수와 컴퓨터의 수를 비교한다.

- 같은 수가 같은 자리에 있으면 스트라이크 개수를 센다.

- 같은 수가 다른 자리에 있으면 볼 개수를 센다.

- 두 숫자의 비교 결과를 GameResult와 같은 객체로 반환한다.

3. [controllers] Baseball 게임 진행
- 3 스트라이크인 경우 게임 종료를 판정한다.
- 게임 종료 후 재시작(1) 또는 완전히 종료(2) 여부를 판단한다.

4. [validator] 입력기 및 에러 핸들러
- 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨다.
- 숫자만 입력했는지, 3자리인지, 중복이 없는지 검증한다.
- 예외 발생 시 [ERROR]로 시작하는 메시지를 출력하고 게임을 지속한다.

5. [View] 출력기
- 입력한 숫자에 대한 결과(볼, 스트라이크, 낫싱)를 형식에 맞춰 출력한다.
- 게임 종료 시와 에러 시에 적절한 메시지를 화면에 출력한다.
Empty file removed src/main/java/.gitkeep
Empty file.
14 changes: 14 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import controllers.GameController;
import views.InputView;
import views.OutputView;

public class Application {
public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();

GameController controller = new GameController(inputView, outputView);

controller.run();
}
}
72 changes: 72 additions & 0 deletions src/main/java/controllers/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package controllers;

import domains.BaseballNumbers;
import domains.GameResult;
import domains.NumberGenerator;
import domains.Validator;
import views.InputView;
import views.OutputView;

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

public class GameController {
private final InputView inputView;
private final OutputView outputView; // 출력 담당 클래스 (가정)
private BaseballNumbers computerNumbers;

public GameController(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void run() {
do {
playGame();
} while (isRestartRequested());
}

private void playGame() {
// 1. 컴퓨터 숫자 생성
computerNumbers = NumberGenerator.generate();
boolean isGameWon = false;
// 2. 맞출 때까지 반복
while (!isGameWon) {
isGameWon = playTurn(computerNumbers); // 루프 내부 로직 분리 (15라인 제한 준수)
}
outputView.printGameEnd();
}

private boolean playTurn(BaseballNumbers computerNumbers) {
try {
String input = inputView.readNumbers();
Validator.validateInput(input);
GameResult result = computerNumbers.compare(parseInput(input));
outputView.printResult(result);
return result.isThreeStrike();
} catch (IllegalArgumentException e) {
outputView.printErrorMessage(e.getMessage()); // UI 일관성 유지
return false;
}
}

private boolean isRestartRequested() {
try {
String command = inputView.readRestartCommand();
Validator.validateRestartCommand(command);
return command.equals("1");
} catch (IllegalArgumentException e) {
System.out.println("[ERROR] " + e.getMessage());
return isRestartRequested(); // 잘못 입력하면 재귀적으로 다시 물어봄
}
}

private List<Integer> parseInput(String input) {
List<Integer> numbers = new ArrayList<>();
for (char c : input.toCharArray()) {
// 문자 '1'을 숫자 1로 변환해서 리스트에 추가
numbers.add(Character.getNumericValue(c));
}
return numbers;
}
}
42 changes: 42 additions & 0 deletions src/main/java/domains/BaseballNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package domains;

import java.util.List;
import java.util.Collections;

public class BaseballNumbers {
private final List<Integer> numbers;

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

public GameResult compare(List<Integer> otherNumbers) {
int strikes = 0;
int balls = 0;

for (int i = 0; i < numbers.size(); i++) {
if (isStrike(i, otherNumbers)) {
strikes++;
}
}

for (int i = 0; i < numbers.size(); i++) {
if (isBall(i, otherNumbers)) {
balls++;
}
}
return new GameResult(strikes, balls);
}

public List<Integer> getNumbers() {
return Collections.unmodifiableList(numbers);
}

private boolean isStrike(int index, List<Integer> otherNumbers) {
return numbers.get(index).equals(otherNumbers.get(index));
}

private boolean isBall(int index, List<Integer> otherNumbers) {
return !isStrike(index, otherNumbers) && numbers.contains(otherNumbers.get(index));
}
}
28 changes: 28 additions & 0 deletions src/main/java/domains/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package domains;

public class GameResult {
private final int strikes;
private final int balls;

public GameResult(int strikes, int balls) {
this.strikes = strikes;
this.balls = balls;
}

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

public boolean isNothing() {
return strikes == 0 && balls == 0;
}

// OutputView에서 사용하기 편하도록 Getter 제공
public int getStrikes() {
return strikes;
}

public int getBalls() {
return balls;
}
}
24 changes: 24 additions & 0 deletions src/main/java/domains/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domains;

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

public class NumberGenerator {
private static final int NUMBER_COUNT = 3;
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 9;

public static BaseballNumbers generate() {
Set<Integer> uniqueNumbers = new LinkedHashSet<>();
Random random = new Random();

while (uniqueNumbers.size() < NUMBER_COUNT) {
int randomNumber = random.nextInt(MAX_NUMBER - MIN_NUMBER + 1) + MIN_NUMBER;
uniqueNumbers.add(randomNumber);
}

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

import java.util.HashSet;
import java.util.Set;

public class Validator {
public static void validateRestartCommand(String input) {
if (!input.equals("1") && !input.equals("2")) {
throw new IllegalArgumentException("[ERROR] 1 또는 2만 입력해야 합니다.");
}
}

public static void validateInput(String input) {
if (!isNumeric(input)) {
throw new IllegalArgumentException("[ERROR] 숫자만 입력 가능합니다.");
}
if (input.length() != 3) {
throw new IllegalArgumentException("[ERROR] 3자리 숫자여야 합니다.");
}
if (hasDuplicate(input)) {
throw new IllegalArgumentException("[ERROR] 중복된 숫자가 있습니다.");
}
}

private static boolean isNumeric(String str) {
return str.matches("^[1-9]+$"); // 1-9 사이의 숫자인지 확인
}

private static boolean hasDuplicate(String str) {
Set<Character> uniqueChars = new HashSet<>();
for (char c : str.toCharArray()) {
uniqueChars.add(c);
}
return uniqueChars.size() != str.length();
}
}
19 changes: 19 additions & 0 deletions src/main/java/views/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package views;

import java.util.Scanner;

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

// 3자리 숫자 입력 받기
public String readNumbers() {
System.out.print("숫자를 입력해주세요 : ");
return scanner.nextLine();
}

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

import domains.GameResult;

public class OutputView {
private static final String BALL = "볼";
private static final String STRIKE = "스트라이크";
private static final String NOTHING = "낫싱";
private static final String GAME_END_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료";

public void printResult(GameResult result) {
if (result.isNothing()) {
System.out.println(NOTHING);
return;
}

System.out.println(formatResult(result.getBalls(), result.getStrikes()));
}

private String formatResult(int balls, int strikes) {
StringBuilder sb = new StringBuilder();

if (balls > 0) {
sb.append(balls).append(BALL).append(" ");
}
if (strikes > 0) {
sb.append(strikes).append(STRIKE);
}

return sb.toString().trim();
}

public void printGameEnd() {
System.out.println(GAME_END_MESSAGE);
}

public void printErrorMessage(String message) {
System.out.println("[ERROR] " + message);
}
}
Empty file removed src/test/java/.gitkeep
Empty file.
36 changes: 36 additions & 0 deletions src/test/java/GameResultTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package domains;

import static org.assertj.core.api.Assertions.*;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class GameResultTest {
@DisplayName("3 스트라이크일 때 승리 여부를 정확히 판정하는지 확인한다.")
@Test
void isThreeStrike_True_WhenStrikesIsThree() {
GameResult result = new GameResult(3, 0);
assertThat(result.isThreeStrike()).isTrue();
}

@DisplayName("스트라이크와 볼이 모두 0이면 '낫싱'으로 판정한다.")
@Test
void isNothing_True_WhenNoStrikeAndNoBall() {
GameResult result = new GameResult(0, 0);
assertThat(result.isNothing()).isTrue();
}

@DisplayName("하나라도 맞으면 '낫싱'이 아니다.")
@ParameterizedTest
@CsvSource({
"1, 0",
"0, 1",
"1, 1"
})
void isNothing_False_WhenAnyMatch(int strikes, int balls) {
GameResult result = new GameResult(strikes, balls);
assertThat(result.isNothing()).isFalse();
}
}
40 changes: 40 additions & 0 deletions src/test/java/NumberGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package domains;

import static org.assertj.core.api.Assertions.*;

import java.util.List;
import java.util.Set;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;

class NumberGeneratorTest {

@DisplayName("생성된 숫자는 정확히 3개여야 한다.")
@Test
void generateNumberCountTest() {
BaseballNumbers numbers = NumberGenerator.generate();
assertThat(numbers.getNumbers()).hasSize(3);
}

@DisplayName("생성된 숫자는 1에서 9 사이의 범위여야 한다.")
@RepeatedTest(100) // 난수 생성이므로 여러 번 반복해서 검증
void generateNumberRangeTest() {
BaseballNumbers numbers = NumberGenerator.generate();

for (int number : numbers.getNumbers()) {
assertThat(number).isBetween(1, 9);
}
}

@DisplayName("생성된 숫자들 사이에는 중복이 없어야 한다.")
@Test
void generateUniqueNumberTest() {
BaseballNumbers numbers = NumberGenerator.generate();
List<Integer> numberList = numbers.getNumbers();

// Set에 넣었을 때도 크기가 3이라면 중복이 없는 것
assertThat(Set.copyOf(numberList)).hasSize(3);
}
}
Loading