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


# ⚾ 숫자 야구 게임

## 🚀 프로젝트 개요
1부터 9까지의 서로 다른 수로 이루어진 3자리의 수를 맞추는 콘솔 게임입니다.

## 🎯 기능 목록

### 1. 게임 시작 및 초기화
- [ ] **컴퓨터의 수 생성 기능**
- 1에서 9까지의 서로 다른 임의의 수 3개를 선택한다.

### 2. 사용자 입력
- [ ] **숫자 입력 요청 기능**
- "숫자를 입력해주세요 : " 문구를 출력한다.
- 사용자의 입력을 받는다.
- [ ] **재시작/종료 입력 요청 기능**
- 게임 종료 후 "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." 문구를 출력하고 입력을 받는다.

### 3. 입력 유효성 검사 (예외 처리)
- [ ] **사용자 입력 값 검증**
- 사용자가 입력한 값이 유효하지 않을 경우 `IllegalArgumentException`을 발생시키고 `[ERROR]`로 시작하는 에러 메시지를 출력한다.
- 에러 발생 후 게임을 종료하지 않고 다시 입력을 받을 수 있도록 처리한다.
- **검증 조건:**
- [ ] 숫자가 아닌 값이 포함되어 있는가?
- [ ] 3자리가 아닌가?
- [ ] 1~9 사이의 숫자가 아닌가? (0 포함 여부 확인)
- [ ] 중복된 숫자가 있는가?

### 4. 게임 점수 계산 로직
- [ ] **스트라이크/볼 판별 기능**
- 같은 수가 같은 자리에 있으면: **스트라이크**
- 같은 수가 다른 자리에 있으면: **볼**
- 같은 수가 전혀 없으면: **낫싱**

### 5. 결과 출력
- [ ] **라운드 결과 출력 기능**
- 계산된 스트라이크와 볼의 개수를 출력한다.
- 예시: `1볼`, `1스트라이크 1볼`, `3스트라이크`, `낫싱`
- [ ] **게임 종료 문구 출력 기능**
- 3스트라이크일 경우 "3개의 숫자를 모두 맞히셨습니다! 게임 끝"을 출력한다.

### 6. 게임 진행 루프
- [ ] **게임 흐름 제어**
- 3스트라이크가 될 때까지 [입력 -> 계산 -> 출력] 과정을 반복한다.
- 게임 종료 후 재시작(1) 선택 시 게임을 처음부터 다시 시작한다.
- 종료(2) 선택 시 프로그램을 완전히 종료한다.

---

## 🔍 프로그래밍 요구사항1 - 제약사항
1. 자바 코드 컨벤션을 지키면서 프로그래밍한다.
•https://naver.github.io/hackday-conventions-java/<br>

2. indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.<br>
• 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.<br>
• 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.<br>

3. 자바 8에 추가된 stream api를 사용하지 않고 구현해야 한다. 단, 람다는 사용 가능하다.
4. else 예약어를 쓰지 않는다.<br>
•힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
•else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도
허용하지 않는다.<br>
5. 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.<br>
•함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.

---

## 🔍 프로그래밍 요구사항2 - 단위 테스트
1. 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외<br>
•핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.<br>
•힌트는 MVC 패턴 기반으로 구현한 후 View, Controller를 제외한 Model에 대한 단위 테스트를 추가하는 것에 집중한다.<br>
2. JUnit5와 AssertJ 사용법에 익숙하지 않은 개발자는 첨부한 "학습테스트를 통해 JUnit 학습하기.pdf" 문서를 참고해
사용법을 학습한 후 JUnit5 기반 단위 테스트를 구현한다.
11 changes: 11 additions & 0 deletions src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package baseball;

import baseball.controller.BaseballController;

public class Application {
public static void main(String[] args) {

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

import baseball.domain.Computer;
import baseball.domain.Referee;
import baseball.domain.Result;
import baseball.util.InputParser;
import baseball.util.ValidationResult;
import baseball.util.Validator;
import baseball.view.InputView;
import baseball.view.OutputView;

import java.util.List;

public class BaseballController {

private final Referee referee;
private final InputView inputView;
private final OutputView outputView;
private Computer computer;

public BaseballController() {
this.referee = new Referee();
this.inputView = new InputView();
this.outputView = new OutputView();
}

// 프로그램 실행
public void run() {
do {
play();
} while(isRestart());
}

// 게임 시작
private void play() {
computer = new Computer(); // 상대(컴퓨터)가 랜덤 숫자 생성

while (true) {
String input = getValidInput(); // 입력 받기
List<Integer> inputNumbers = InputParser.parseToNumbers(input); // 입력을 List로 변환

Result result = referee.getResult(computer.getNums(), inputNumbers); // 심판에게 결과 받음
outputView.printResult(result); // 받은 결과 출력

// 3스트라이크 시 게임 승리
if (result.isWin()) {
outputView.printFinish();
break;
}
}
}

// 유효한 입력 받기
private String getValidInput() {
String input;
while (true) {
input = inputView.inputNumber();
ValidationResult validationResult = Validator.validateGameNumber(input);
if (validationResult.isValid()) {
return input;
}
outputView.printError(validationResult.getMessage());
}
}

// 재시작 입력 받기
private boolean isRestart() {
String input;
while (true) {
input = inputView.inputRestartNumber();
ValidationResult validationResult = Validator.validateRestartNumber(input);
if (validationResult.isValid()) {
return input.equals("1");
}
outputView.printError(validationResult.getMessage());
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/baseball/domain/Computer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package baseball.domain;

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

public class Computer {

public List<Integer> getNums() {
return nums;
}

private final List<Integer> nums;
private final Random random = new Random();

public Computer() {
this.nums = new ArrayList<>();
generateNumbers();
}

private void generateNumbers() {
while(nums.size() < 3) {
Integer randomNumber = random.nextInt(9) + 1;

if (!nums.contains(randomNumber)) {
nums.add(randomNumber);
}
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/baseball/domain/Referee.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package baseball.domain;

import java.util.List;

public class Referee {

// 해당 라운드 결과 산출
public Result getResult(List<Integer> computer, List<Integer> input) {
int strike = countStrike(computer, input);
int ball = countBall(computer, input);

return new Result(strike, ball);
}

// 스트라이크 갯수 세기
private int countStrike(List<Integer> computer, List<Integer> input) {
int strike = 0;

for (int i = 0; i < 3; i++) {
if (computer.get(i).equals(input.get(i))) strike ++;
}

return strike;
}

// 볼 갯수 세기
private int countBall(List<Integer> computer, List<Integer> input) {
int ball = 0;

for (int i = 0; i < 3; i++) {
if(!computer.get(i).equals(input.get(i)) && input.contains(computer.get(i))) ball ++;
}

return ball;
}
}
23 changes: 23 additions & 0 deletions src/main/java/baseball/domain/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package baseball.domain;

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

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

public int getStrike() {
return strike;
}

public int getBall() {
return ball;
}

public boolean isWin() {
return strike == 3 && ball == 0;
}
}
19 changes: 19 additions & 0 deletions src/main/java/baseball/util/InputParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package baseball.util;

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

public class InputParser {

private InputParser() {}

public static List<Integer> parseToNumbers(String input) {
List<Integer> numbers = new ArrayList<>();

for (char c : input.toCharArray()) {
numbers.add(Character.getNumericValue(c));
}

return numbers;
}
}
27 changes: 27 additions & 0 deletions src/main/java/baseball/util/ValidationResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package baseball.util;

public class ValidationResult {
private final boolean valid;
private final String message;

private ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}

public static ValidationResult ok() {
return new ValidationResult(true, "");
}

public static ValidationResult fail(String message) {
return new ValidationResult(false, message);
}

public boolean isValid() {
return valid;
}

public String getMessage() {
return message;
}
}
62 changes: 62 additions & 0 deletions src/main/java/baseball/util/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package baseball.util;

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

public class Validator {

private Validator() {}
private static final String ERROR_PREFIX = "[ERROR] ";
private static final String INVALID_INPUT = ERROR_PREFIX + "유효하지 않은 입력입니다.";
private static final String INVALID_RESTART_INPUT = ERROR_PREFIX + "1, 2 중 입력하세요.";

// 유효한 입력인지 검증
public static ValidationResult validateGameNumber(String input) {
if (validateIsNumber(input) && validateLength(input) && validateRange(input) && validateDuplicateNumber(input)) {
return ValidationResult.ok();
}

return ValidationResult.fail(INVALID_INPUT);
}

// 유효한 게임 재시작 입력인지 검증
public static ValidationResult validateRestartNumber(String input) {
if (input.equals("1") || input.equals("2")) {
return ValidationResult.ok();
}

return ValidationResult.fail(INVALID_RESTART_INPUT);
}

// 입력받은 숫자가 숫자가 아닌 값이 포함되어 있는지 검증
static boolean validateIsNumber(String input) {
if (!input.matches("^[0-9]*$")) return false;

return true;
}

// 입력받은 숫자가 3자리가 맞는지 검증
static boolean validateLength(String input) {
if (input.length() != 3) return false;

return true;
}

// 입력받은 숫자에 1~9 사이의 숫자가 존재하는 지 검증 (0을 포함하는 지)
static boolean validateRange(String input) {
if (input.contains("0")) return false;

return true;
}

// 입력받은 숫자에 중복된 숫자가 있는지 검증
static boolean validateDuplicateNumber(String input) {
Set<Character> set = new HashSet<>();
for (char num : input.toCharArray()) {
if(set.contains(num)) return false;
set.add(num);
}

return true;
}
}
25 changes: 25 additions & 0 deletions src/main/java/baseball/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package baseball.view;

import java.util.Scanner;

public class InputView {
private final Scanner scanner;

public InputView() {
this.scanner = new Scanner(System.in);
}

// 숫자 입력
public String inputNumber() {
System.out.print("숫자를 입력해주세요 : ");

return scanner.nextLine().trim();
}

// 재시작을 위한 입력
public String inputRestartNumber() {
System.out.print("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요 : ");

return scanner.nextLine().trim();
}
}
Loading