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

## 기능 목록

### 1. 야구 숫자 (Model - BaseballNumber)

- [x] 1에서 9 사이의 서로 다른 3자리 숫자를 저장한다.
- [x] 입력받은 숫자의 유효성을 검사한다.
- [x] 3자리 숫자인지 확인
- [x] 1~9 사이의 숫자인지 확인
- [x] 중복된 숫자가 없는지 확인
- [x] 1에서 9 사이의 서로 다른 임의의 수 3개를 생성한다.

### 2. 점수 계산 (Model - Score)

- [x] 스트라이크와 볼의 개수를 저장한다.
- [x] 모든 숫자를 맞혔는지(3스트라이크) 확인한다.
- [x] 하나도 맞히지 못했는지(낫싱) 확인한다.

### 3. 정답 및 비교 로직 (Model - Answer)

- [x] 컴퓨터의 정답 숫자를 관리한다.
- [x] 사용자의 입력 숫자와 비교하여 스트라이크/볼 결과를 도출한다.

### 4. 게임 종료 명령어 (Model - GameCommand)

- [x] 게임 종료 후 재시작(1) 또는 종료(2) 입력을 관리한다.
- [x] 1 또는 2 이외의 입력에 대해 예외를 발생시킨다.

### 5. 입출력 (View)

- [x] 안내 메시지 및 게임 결과를 출력한다.
- [x] 게임 시작 메시지 출력
- [x] 입력 유도 메시지 출력
- [x] 판정 결과(n볼 n스트라이크, 낫싱 등) 출력
- [x] 게임 종료 및 재시작 안내 메시지 출력
- [x] 사용자로부터 문자열을 입력받는다.

### 6. 게임 흐름 제어 (Controller & Application)

- [x] 전체적인 게임 루프를 관리한다.
- [x] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시키고, "[ERROR]"로 시작하는 메시지를 출력한 후 입력을 다시 받는다.
12 changes: 12 additions & 0 deletions src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package baseball;

import baseball.application.BaseballRunner;
import baseball.config.AppConfig;

public class Application {
public static void main(String[] args) {
AppConfig config = new AppConfig();
BaseballRunner runner = new BaseballRunner(config);
runner.run();
}
}
68 changes: 68 additions & 0 deletions src/main/java/baseball/application/BaseballRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package baseball.application;

import baseball.config.AppConfig;
import baseball.controller.BaseballController;
import baseball.model.Answer;
import baseball.model.GameCommand;
import baseball.model.Score;
import baseball.utils.ConsoleInputReader;
import baseball.view.*;

public class BaseballRunner {
private final BaseballController baseballController;
private final ConsoleInputReader input;
private final ConsoleViewRenderer renderer;

public BaseballRunner(AppConfig config) {
this.baseballController = config.baseballController();
this.input = config.inputReader();
this.renderer = config.viewRenderer();
}

public void run() {
while (true) {
playOneGame(Answer.random());

if (isEnd()) {
return;
}
}
}

private void playOneGame(Answer answer) {
while (true) {
if (playOneTurn(answer)) {
break;
}
}
}

private boolean playOneTurn(Answer answer) {
renderer.render(new InputConsoleView());
String inputNumber = input.readLine();

try {
Score score = baseballController.guess(answer, inputNumber);
renderer.render(new ResultConsoleView(score));

if (score.isAllStrike()) {
renderer.render(new EndConsoleView(score));
return true;
}
} catch (IllegalArgumentException e) {
renderer.render(new ErrorConsoleView(e.getMessage()));
}
return false;
}

private boolean isEnd() {
while (true) {
try {
GameCommand cmd = baseballController.decideNextAction(input.readLine());
return cmd == GameCommand.EXIT;
} catch (IllegalArgumentException e) {
renderer.render(new ErrorConsoleView(e.getMessage()));
}
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/baseball/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package baseball.config;

import baseball.controller.BaseballController;
import baseball.utils.ConsoleInputReader;
import baseball.view.ConsoleViewRenderer;

import java.util.Scanner;

public class AppConfig {
private Scanner scanner() {
return new Scanner(System.in);
}

public ConsoleInputReader inputReader() {
return new ConsoleInputReader(scanner());
}

public ConsoleViewRenderer viewRenderer() {
return new ConsoleViewRenderer();
}

public BaseballController baseballController() {
return new BaseballController();
}
}
17 changes: 17 additions & 0 deletions src/main/java/baseball/controller/BaseballController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package baseball.controller;

import baseball.model.Answer;
import baseball.model.BaseballNumber;
import baseball.model.GameCommand;
import baseball.model.Score;

public class BaseballController {
public Score guess(Answer answer, String number) {
BaseballNumber guessNumber = BaseballNumber.fromString(number);
return answer.compare(guessNumber);
}

public GameCommand decideNextAction(String input) {
return GameCommand.from(input);
}
}
52 changes: 52 additions & 0 deletions src/main/java/baseball/model/Answer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package baseball.model;

public class Answer {
private final BaseballNumber number;

private Answer(BaseballNumber number) {
this.number = number;
}

public static Answer random() {
return new Answer(BaseballNumber.random());
}

public static Answer from(BaseballNumber number) {
return new Answer(number);
}

public Score compare(BaseballNumber guess) {
int strikes = countStrikes(guess);
int balls = countBalls(guess);

return Score.of(strikes, balls, guess.size());
}

private int countStrikes(BaseballNumber guess) {
int count = 0;
for (int i = 0; i < number.size(); i++) {
if (isStrike(i, guess.digitAt(i))) {
count++;
}
}
return count;
}

private int countBalls(BaseballNumber guess) {
int count = 0;
for (int i = 0; i < number.size(); i++) {
if (isBall(i, guess.digitAt(i))) {
count++;
}
}
return count;
}

private boolean isStrike(int index, int digit) {
return number.digitAt(index) == digit;
}

private boolean isBall(int index, int digit) {
return !isStrike(index, digit) && number.contains(digit);
}
}
77 changes: 77 additions & 0 deletions src/main/java/baseball/model/BaseballNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package baseball.model;


import java.util.*;

public class BaseballNumber {
static final int LENGTH = 3;
private final List<Integer> digits;

private BaseballNumber(List<Integer> digits) {
this.digits = List.copyOf(digits);
}

public static BaseballNumber fromString(String raw) {
String input = raw.trim();
validate(input);

List<Integer> digits = new ArrayList<>();
for (char c : input.toCharArray()) {
digits.add(c - '0');
}
return new BaseballNumber(digits);
}

public static BaseballNumber random() {
List<Integer> pool = new ArrayList<>();
for (int i = 1; i <= 9; i++) {
pool.add(i);
}
Collections.shuffle(pool);
return new BaseballNumber(pool.subList(0, LENGTH));
}

private static void validate(String input) {
validateLength(input);
validateElements(input);
}

private static void validateLength(String input) {
if (input.length() != LENGTH) {
throw new IllegalArgumentException("입력 숫자는 " + LENGTH + "자리여야 합니다.");
}
}

private static void validateElements(String input) {
Set<Character> uniqueChars = new HashSet<>();
for (char c : input.toCharArray()) {
validateRange(c);
validateUniqueness(uniqueChars, c);
uniqueChars.add(c);
}
}

private static void validateRange(char c) {
if (c < '1' || c > '9') {
throw new IllegalArgumentException("1 에서 9 사이의 숫자만 입력 가능합니다.");
}
}

private static void validateUniqueness(Set<Character> uniqueChars, char c) {
if (uniqueChars.contains(c)) {
throw new IllegalArgumentException("중복되지 않은 숫자여야 합니다.");
}
}

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

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

public boolean contains(int digit) {
return digits.contains(digit);
}
}
27 changes: 27 additions & 0 deletions src/main/java/baseball/model/GameCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package baseball.model;

public enum GameCommand {
RESTART("1"),
EXIT("2");

private final String code;

GameCommand(String code) {
this.code = code;
}

public static GameCommand from(String rawInput) {
String input = rawInput.trim();

for (GameCommand command : values()) {
if (command.code.equals(input)) {
return command;
}
}
throw new IllegalArgumentException("1(재시작) 또는 2(종료)를 입력하세요.");
}

public String getCode() {
return code;
}
}
36 changes: 36 additions & 0 deletions src/main/java/baseball/model/Score.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package baseball.model;

public class Score {
private final int strikes;
private final int balls;
private final int length;

private Score(int strikes, int balls, int length) {
this.strikes = strikes;
this.balls = balls;
this.length = length;
}

public static Score of(int strikes, int balls, int length) {
if (strikes < 0 || balls < 0) {
throw new IllegalArgumentException("스트라이크/볼은 음수일 수 없습니다.");
}
return new Score(strikes, balls, length);
}

public int getStrikes() {
return strikes;
}

public int getBalls() {
return balls;
}

public boolean isAllStrike() {
return strikes == length;
}

public boolean isNothing() {
return strikes == 0 && balls == 0;
}
}
15 changes: 15 additions & 0 deletions src/main/java/baseball/utils/ConsoleInputReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package baseball.utils;

import java.util.Scanner;

public class ConsoleInputReader {
private final Scanner scanner;

public ConsoleInputReader(Scanner scanner) {
this.scanner = scanner;
}

public String readLine() {
return scanner.nextLine();
}
}
5 changes: 5 additions & 0 deletions src/main/java/baseball/view/ConsoleView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package baseball.view;

public interface ConsoleView {
String asString();
}
7 changes: 7 additions & 0 deletions src/main/java/baseball/view/ConsoleViewRenderer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package baseball.view;

public class ConsoleViewRenderer {
public void render(ConsoleView cv) {
System.out.print(cv.asString());
}
}
Loading