Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
17
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,37 @@
# java-baseball-precourse
# 숫자 야구 게임

## 프로젝트 개요
1부터 9까지 서로 다른 숫자로 이루어진 3자리 수를 맞추는 숫자 야구 게임이다.
컴퓨터가 임의의 숫자 3개를 생성하고, 사용자는 숫자를 입력해 힌트를 받으며 정답을 맞힌다.
모든 숫자를 맞히면 게임이 종료되며, 이후 재시작 또는 종료를 선택할 수 있다.

간단한 흐름
- 숫자 입력 → 힌트 출력(스트라이크/볼/낫싱) → 정답 시 종료 메시지 출력 → 재시작/종료 선택

## 구현(레이어드) 체크리스트
### Domain
- [x] ComputerNumbers: 1~9 서로 다른 숫자 3개 생성
- [x] ComputerNumbersTest: 범위/중복 생성 검증
- [x] PlayerNumbers: 입력 파싱 및 검증(길이/숫자/범위/중복)
- [x] PlayerNumbersTest: 파싱 및 예외 케이스 검증
- [x] Judgement: 스트라이크/볼 계산
- [x] JudgementTest: 스트라이크/볼/낫싱 케이스 검증
- [x] HintResult: 판정 결과 값 객체(스트라이크/볼/낫싱)
- [x] HintResultTest: 결과 값 객체 규칙 검증

### Application
- [x] GameService: 정답 생성과 판정 흐름 연결
- [x] GameService: 3스트라이크 종료 판단
- [x] GameService: 재시작/종료 분기 처리

### UI
- [x] InputView: 숫자 입력, 재시작 선택 입력
- [x] OutputView: 힌트/종료 메시지 출력
- [x] ErrorHandler: `[ERROR]` 메시지 출력 후 진행 유지

## 제약사항
- indent depth 2 이하
- `else`/`switch` 미사용
- 메서드 길이 15라인 이하
- Stream API 미사용(람다 가능)
- 도메인 로직과 UI 로직 분리
82 changes: 82 additions & 0 deletions src/main/java/baseball/BaseballApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package baseball;

import baseball.application.GameService;
import baseball.domain.exceptions.BaseballException;
import baseball.domain.models.ComputerNumbers;
import baseball.domain.models.HintResult;
import baseball.domain.models.PlayerNumbers;
import baseball.ui.ErrorHandler;
import baseball.ui.InputView;
import baseball.ui.OutputView;

public class BaseballApplication {
private final GameService gameService;
private final InputView inputView;
private final OutputView outputView;
private final ErrorHandler errorHandler;

public BaseballApplication() {
this(new GameService(new ComputerNumbers()), new InputView(), new OutputView(), new ErrorHandler());
}

public BaseballApplication(GameService gameService, InputView inputView, OutputView outputView,
ErrorHandler errorHandler) {
this.gameService = gameService;
this.inputView = inputView;
this.outputView = outputView;
this.errorHandler = errorHandler;
}

public static void main(String[] args) {
new BaseballApplication().run();
}

public void run() {
gameService.startNewGame();
playLoop();
}

private void playLoop() {
while (true) {
HintResult result = playRound();
if (!gameService.isGameOver(result)) {
continue;
}
outputView.printGameOver();
if (restartGame()) {
gameService.startNewGame();
continue;
}
return;
}
}

private HintResult playRound() {
PlayerNumbers playerNumbers = readPlayerNumbers();
HintResult result = gameService.judge(playerNumbers);
outputView.printHint(result);
return result;
}

private PlayerNumbers readPlayerNumbers() {
while (true) {
outputView.printInputPrompt();
try {
return PlayerNumbers.from(inputView.readNumbers());
} catch (BaseballException exception) {
errorHandler.printError(exception);
}
}
}

private boolean restartGame() {
while (true) {
outputView.printRestartPrompt();
try {
return gameService.shouldRestart(inputView.readRestartOption());
} catch (BaseballException exception) {
errorHandler.printError(exception);
}
}
}
}
66 changes: 66 additions & 0 deletions src/main/java/baseball/application/GameService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package baseball.application;

import java.util.List;

import baseball.domain.exceptions.GameNotStartedException;
import baseball.domain.exceptions.InvalidRestartOptionException;
import baseball.domain.models.ComputerNumbers;
import baseball.domain.models.HintResult;
import baseball.domain.models.Judgement;
import baseball.domain.models.PlayerNumbers;

public class GameService {
private final ComputerNumbers computerNumbers;
private List<Integer> answerNumbers;

public GameService(ComputerNumbers computerNumbers) {
this.computerNumbers = computerNumbers;
}

public void startNewGame() {
answerNumbers = computerNumbers.generate();
}

public HintResult judge(PlayerNumbers playerNumbers) {
validateStarted();
Judgement judgement = new Judgement(answerNumbers, playerNumbers.numbers());
return new HintResult(judgement.countStrike(), judgement.countBall());
}

public boolean isGameOver(HintResult hintResult) {
return hintResult.strikeCount() == 3;
}

public boolean shouldRestart(String input) {
validateRestartOption(input);
return isRestartOption(input);
}

private void validateStarted() {
if (answerNumbers != null) {
return;
}
throw new GameNotStartedException("게임을 먼저 시작해주세요.");
}

private void validateRestartOption(String input) {
if (input == null) {
throw new InvalidRestartOptionException();
}
if (isRestartOption(input)) {
return;
}
if (isExitOption(input)) {
return;
}
throw new InvalidRestartOptionException();
}

private boolean isRestartOption(String input) {
return "1".equals(input);
}

private boolean isExitOption(String input) {
return "2".equals(input);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package baseball.domain.exceptions;

public class BaseballException extends IllegalArgumentException {
private static final String ERROR_PREFIX = "[ERROR] ";

public BaseballException(String message) {
super(ERROR_PREFIX + message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package baseball.domain.exceptions;

public class GameNotStartedException extends BaseballException {
public GameNotStartedException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package baseball.domain.exceptions;

public class InvalidHintResultException extends BaseballException {
public InvalidHintResultException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package baseball.domain.exceptions;

public class InvalidPlayerNumbersException extends BaseballException {
public InvalidPlayerNumbersException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package baseball.domain.exceptions;

public class InvalidRestartOptionException extends BaseballException {
private static final String MESSAGE = "재시작은 1, 종료는 2를 입력해주세요.";

public InvalidRestartOptionException() {
super(MESSAGE);
}
}
29 changes: 29 additions & 0 deletions src/main/java/baseball/domain/models/ComputerNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package baseball.domain.models;

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

public class ComputerNumbers {
private final Random random;

public ComputerNumbers() {
this(new Random());
}

public ComputerNumbers(Random random) {
this.random = random;
}

public List<Integer> generate() {
List<Integer> numbers = new ArrayList<>();
while (numbers.size() < 3) {
int candidate = random.nextInt(9) + 1;
if (numbers.contains(candidate)) {
continue;
}
numbers.add(candidate);
}
return numbers;
}
}
61 changes: 61 additions & 0 deletions src/main/java/baseball/domain/models/HintResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package baseball.domain.models;

import baseball.domain.exceptions.InvalidHintResultException;

public class HintResult {
private static final int MAX_COUNT = 3;
private static final String NOTHING_MESSAGE = "낫싱";
private static final String STRIKE_SUFFIX = "스트라이크";
private static final String BALL_SUFFIX = "볼";

private final int strikeCount;
private final int ballCount;

public HintResult(int strikeCount, int ballCount) {
validateCount(strikeCount);
validateCount(ballCount);
validateTotal(strikeCount, ballCount);
this.strikeCount = strikeCount;
this.ballCount = ballCount;
}

public int strikeCount() {
return strikeCount;
}

public int ballCount() {
return ballCount;
}

public String message() {
if (strikeCount == 0 && ballCount == 0) {
return NOTHING_MESSAGE;
}
StringBuilder builder = new StringBuilder();
appendCount(builder, strikeCount, STRIKE_SUFFIX);
appendCount(builder, ballCount, BALL_SUFFIX);
return builder.toString();
}

private void appendCount(StringBuilder builder, int count, String suffix) {
if (count == 0) {
return;
}
if (builder.length() > 0) {
builder.append(" ");
}
builder.append(count).append(suffix);
}

private void validateCount(int count) {
if (count < 0 || count > MAX_COUNT) {
throw new InvalidHintResultException("카운트는 0에서 3 사이여야 해요.");
}
}

private void validateTotal(int strikeCount, int ballCount) {
if (strikeCount + ballCount > MAX_COUNT) {
throw new InvalidHintResultException("스트라이크와 볼의 합은 3을 넘을 수 없어요.");
}
}
}
41 changes: 41 additions & 0 deletions src/main/java/baseball/domain/models/Judgement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package baseball.domain.models;

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

public class Judgement {
private final List<Integer> computerNumbers;
private final List<Integer> playerNumbers;

public Judgement(List<Integer> computerNumbers, List<Integer> playerNumbers) {
this.computerNumbers = new ArrayList<>(computerNumbers);
this.playerNumbers = new ArrayList<>(playerNumbers);
}

public int countStrike() {
int strikes = 0;
for (int index = 0; index < playerNumbers.size(); index++) {
if (isStrike(index)) {
strikes++;
}
}
return strikes;
}

public int countBall() {
int balls = 0;
for (int index = 0; index < playerNumbers.size(); index++) {
if (isStrike(index)) {
continue;
}
if (computerNumbers.contains(playerNumbers.get(index))) {
balls++;
}
}
return balls;
}

private boolean isStrike(int index) {
return playerNumbers.get(index).equals(computerNumbers.get(index));
}
}
Loading