Skip to content
Open
178 changes: 177 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,177 @@
# java-baseball-precourse
# java-baseball-precourse

## Commit 1. 컴퓨터 숫자 생성 규칙 구현

### 목표

컴퓨터가 사용하는 3자리 숫자의 생성 규칙을 도메인으로 분리한다.

### 구현 내용

1~9 사이의 숫자 중 서로 다른 숫자 3개를 생성한다.

생성된 숫자는 게임 도중 변경되지 않는다.

랜덤 생성 로직은 별도의 생성 책임을 가지는 클래스로 분리한다.

### 의도

숫자 생성 규칙을 UI 및 게임 흐름과 분리해 테스트 가능하도록 한다.

## Commit 2. 판정 결과 모델(Hint) 구현

### 목표

스트라이크/볼 판정 결과를 표현하는 객체를 도입한다.

### 구현 내용

strike, ball 값을 가진 불변 객체를 생성한다.

3스트라이크 여부를 판단하는 책임을 가진다.

스트라이크와 볼이 모두 0인 경우(낫싱)를 판단할 수 있다.

### 의도

단순한 숫자 계산 결과를 의미 있는 도메인 객체로 표현한다.

## Commit 3. 스트라이크/볼 판정 로직 구현

### 목표

컴퓨터 숫자와 사용자 입력을 비교하는 핵심 판정 로직을 구현한다.

### 구현 내용

같은 숫자가 같은 자리에 있으면 스트라이크를 증가시킨다.

같은 숫자가 다른 자리에 있으면 볼을 증가시킨다.

판정 결과를 Hint 객체로 반환한다.

### 의도

판정 로직을 Controller에서 분리하여 재사용성과 테스트 용이성을 확보한다.

## Commit 4. 사용자 입력 파싱 및 검증 도메인 구현

### 목표

사용자 입력에 대한 모든 검증 로직을 도메인에서 처리한다.

### 구현 내용

입력값은 문자열로 전달받아 도메인 객체로 변환한다.

다음 조건을 만족하지 않으면 예외를 발생시킨다.

길이가 3이 아닌 경우

숫자가 아닌 문자가 포함된 경우

1~9 범위를 벗어난 숫자가 있는 경우 (0 포함)

중복된 숫자가 있는 경우

### 의도

“잘못된 입력”이라는 규칙을 UI나 Controller가 아닌 도메인에서 책임지도록 한다.

## Commit 5. 도메인 단위 테스트 작성

### 목표

핵심 로직의 안정성을 단위 테스트로 검증한다.

### 구현 내용

컴퓨터 숫자 생성 규칙 테스트

스트라이크/볼 판정 로직 테스트

사용자 입력 검증 테스트

JUnit5 + AssertJ 기반으로 테스트를 작성한다.

UI 관련 코드는 테스트 대상에서 제외한다.

### 의도

요구사항을 만족하는지 코드 레벨에서 명확히 검증한다.

## Commit 6. 입출력(View) 구현

### 목표

사용자와의 입출력을 담당하는 View 계층을 구현한다.

### 구현 내용

사용자 입력을 받는 기능을 분리한다.

판정 결과를 요구사항에 맞는 형식으로 출력한다.

잘못된 입력에 대해 [ERROR]로 시작하는 메시지를 출력한다.

### 의도

출력 형식 변경이 도메인에 영향을 주지 않도록 한다.

## Commit 7. 게임 진행 흐름(Controller) 구현

### 목표

전체 게임 흐름을 제어하는 Controller를 구현한다.

### 구현 내용

숫자 입력 → 검증 → 판정 → 출력 과정을 반복한다.

3스트라이크가 될 때까지 게임을 진행한다.

도메인에서 발생한 예외를 처리해 게임이 종료되지 않도록 한다.

### 의도

게임의 “흐름”과 “규칙”을 분리한다.

## Commit 8. 게임 재시작 및 종료 처리

### 목표

게임 종료 후 재시작 또는 완전 종료 기능을 구현한다.

### 구현 내용

게임 종료 후 선택지를 출력한다.

1 입력 시 새로운 게임을 시작한다.

2 입력 시 프로그램을 종료한다.

잘못된 입력 시 예외를 발생시키고 다시 입력받는다.

### 의도

한 번의 게임 로직을 재사용 가능한 구조로 만든다.

## Commit 9. 제약사항 리팩토링

### 목표

모든 프로그래밍 요구사항을 최종적으로 만족시킨다.

### 구현 내용

indent depth를 2 이하로 조정한다.

else, switch/case 제거

메서드 길이 15라인 이하로 분리

하나의 메서드가 하나의 책임만 갖도록 리팩토링

### 의도

가독성과 유지보수성을 높인다.
21 changes: 21 additions & 0 deletions src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package baseball;

import baseball.controller.GameController;
import baseball.domain.NumbersGenerator;
import baseball.domain.Umpire;
import baseball.view.InputView;
import baseball.view.OutputView;

public class Application {

public static void main(String[] args) {
GameController gameController = new GameController(
new NumbersGenerator(),
new Umpire(),
new InputView(),
new OutputView()
);

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

import baseball.domain.Hint;
import baseball.domain.NumbersGenerator;
import baseball.domain.PlayerGuess;
import baseball.domain.Umpire;
import baseball.view.InputView;
import baseball.view.OutputView;

public class GameController {

private final NumbersGenerator numbersGenerator;
private final Umpire umpire;
private final InputView inputView;
private final OutputView outputView;

public GameController(NumbersGenerator numbersGenerator, Umpire umpire,
InputView inputView, OutputView outputView) {
this.numbersGenerator = numbersGenerator;
this.umpire = umpire;
this.inputView = inputView;
this.outputView = outputView;
}

public void run() {
while (true) {
playOneGame();

if (isRestart()) {
continue;
}
return;
}
}

private void playOneGame() {
int[] answer = numbersGenerator.generate();

while (true) {
Hint hint = playOneTurn(answer);

if (hint == null) {
continue;
}

if (hint.isThreeStrike()) {
outputView.printGameEnd();
return;
}
}
}

private Hint playOneTurn(int[] answer) {
try {
PlayerGuess guess = readGuess();
Hint hint = umpire.judge(answer, guess.asArray());
outputView.printHint(hint);
return hint;
} catch (IllegalArgumentException e) {
outputView.printErrorMessage();
return null;
}
}

private PlayerGuess readGuess() {
String input = inputView.readGuess();
return PlayerGuess.from(input);
}

private boolean isRestart() {
try {
String input = inputView.readRestartCommand();
return parseRestartCommand(input);
} catch (IllegalArgumentException e) {
outputView.printErrorMessage();
return isRestart();
}
}

private boolean parseRestartCommand(String input) {
if ("1".equals(input)) {
return true;
}

if ("2".equals(input)) {
return false;
}

throw new IllegalArgumentException();
}
}
28 changes: 28 additions & 0 deletions src/main/java/baseball/domain/Hint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package baseball.domain;

public class Hint {

private final int strike;
private final int ball;

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

public int getStrike() {
return strike;
}

public int getBall() {
return ball;
}

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

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

public class NumbersGenerator {

public int[] generate() {
int[] numbers = new int[3];
for (int i = 0; i < 3; i++) {
int num = pickNumber();
if (contains(numbers, i, num)) {
i--;
continue;
}
numbers[i] = num;
}

return numbers;
}

private int pickNumber() {
return (int) (Math.random() * 9) + 1;
}

private boolean contains(int[] numbers, int size, int candidate) {
for (int i = 0; i < size; i++) {
if (numbers[i] == candidate) {
return true;
}
}
return false;
}
}
Loading