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
61 changes: 60 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,60 @@
# java-baseball-precourse
# java-baseball-precourse

## 기능 목록

숫자야구 게임 기능을 구현해야 한다.

### 게임 시작 및 상대방(컴퓨터) 숫자 생성
- 1에서 9까지의 서로 다른 임의의 수 3개를 생성하는 기능
- 게임 시작 문구 출력 ("숫자 야구 게임을 시작합니다.")
- 컴퓨터의 랜덤 숫자 생성 로직 구현

### 사용자 입력 및 예외 처리
- 사용자로부터 3자리 숫자를 입력받는 기능
- 잘못된 값을 입력할 경우 [ERROR]로 시작하는 에러 메시지를 출력
- 3자리 숫자가 아닌 경우
- 숫자가 아닌 문자가 포함된 경우
- 중복된 숫자가 있는 경우
- 0이 포함된 경우 (문제 조건에 따라 1~9 사이인지 확인)
- 사용자 입력 기능 및 유효성 검사 로직 구현

### 게임 결과 판정 (힌트 생성)
- 입력한 숫자와 컴퓨터 숫자를 비교하여 결과 계산
- 스트라이크: 같은 수가 같은 자리에 있는 경우
- 볼: 같은 수가 다른 자리에 있는 경우
- 낫싱: 같은 수가 전혀 없는 경우

- 판정 결과를 텍스트로 변환 (예: "1볼 1스트라이크", "낫싱")
- 스트라이크/볼 카운트 및 판정 결과 출력 로직 구현

### 게임 종료 및 재시작/완전 종료
- 3개 숫자를 모두 맞혔을 경우 안내 문구 출력 ("3스트라이크 \n 3개의 숫자를 모두 맞히셨습니다! 게임 종료")
- 게임 종료 후 재시작(1) 또는 종료(2)를 선택받는 기능
- 1 입력 시: 새로운 숫자를 생성하고 게임 다시 시작
- 2 입력 시: 애플리케이션 완전 종료
- 게임 종료 조건 확인 및 재시작/종료 선택 로직 구현

---
## 프로그래밍 요구사항1 - 제약사항

- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- https://naver.github.io/hackday-conventions-java/
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
- 자바 8에 추가된 stream api를 사용하지 않고 구현해야 한다. 단, 람다는 사용 가능하다.
- else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다

---
## 프로그래밍 요구사항2 - 단위 테스트

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

public class BaseballGameClient {

public static void main(String[] args) {
BaseballGameController gameController = new BaseballGameController();
gameController.start();
}
}

59 changes: 59 additions & 0 deletions src/main/java/controller/BaseballGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package controller;

import domain.BaseballNumbers;
import service.BaseballService;
import utils.RandomNumberGenerator;
import view.InputView;
import view.OutputView;

public class BaseballGameController {
private final BaseballService baseballService = new BaseballService();
private final InputView inputView = new InputView();
private final OutputView outputView = new OutputView();

public void start() {
boolean isRunning = true;
while (isRunning) {
playOneGame();
isRunning = askRestart();
}
}

private void playOneGame() {
outputView.printStartMessage();
BaseballNumbers computer = new BaseballNumbers(RandomNumberGenerator.generate());
boolean isGameEnd = false;
while (!isGameEnd) {
isGameEnd = playTurn(computer);
}
outputView.printGameEnd();
}

private boolean playTurn(BaseballNumbers computer) {
BaseballNumbers player = askPlayerNumbers();
String result = baseballService.playRound(computer, player);
outputView.printResult(result);
return result.equals("3스트라이크");
}

private BaseballNumbers askPlayerNumbers() {
while (true) {
try {
return new BaseballNumbers(inputView.readInput());
} catch (IllegalArgumentException e) {
outputView.printErrorMessage(e.getMessage());
}
}
}

private boolean askRestart() {
while (true) {
try {
String input = inputView.readRestartInput();
return input.equals("1");
} catch (IllegalArgumentException e) {
outputView.printErrorMessage(e.getMessage());
}
}
}
}
95 changes: 95 additions & 0 deletions src/main/java/domain/BaseballNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package domain;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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

public BaseballNumbers(String numbers) {
validateLength(numbers);
this.numbers = parseInput(numbers);
}

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

private List<Integer> parseInput(String input) {
List<Integer> numbers = new ArrayList<>();

for (char c : input.toCharArray()) {
validateNumeric(c);
int number = Character.getNumericValue(c);
validateNumberRange(number);

numbers.add(number);
}

validateDuplicate(numbers);
return numbers;
}

private void validateDuplicate(List<Integer> numbers) {
HashSet<Integer> compare = new HashSet<>(numbers);

if (numbers.size() != compare.size()) {
throw new IllegalArgumentException("중복된 숫자가 있습니다.");
}
}

private void validateLength(String numbers) {
if (numbers.length() != 3) throw new IllegalArgumentException("입력은 세 글자만 가능합니다.");
}

private void validateNumeric(char c) {
if (!Character.isDigit(c)) {
throw new IllegalArgumentException("숫자 이외의 문자는 입력할 수 없습니다.");
}
}

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

public int countStrike(BaseballNumbers other) {
int strike = 0;
for (int i = 0; i < numbers.size(); i++) {
strike += checkStrikeAt(other, i);
}
return strike;
}

private int checkStrikeAt(BaseballNumbers other, int index) {
if (this.numbers.get(index).equals(other.numbers.get(index))) {
return 1;
}
return 0;
}

public int countBall(BaseballNumbers other) {
int totalMatching = 0;
Set<Integer> computerSet = new HashSet<>(this.numbers);

for (int number : other.numbers) {
totalMatching += checkMatching(computerSet, number);
}

return totalMatching - countStrike(other);
}

private int checkMatching(Set<Integer> set, int number) {
if (set.contains(number)) {
return 1;
}
return 0;
}

public List<Integer> getNumbers() {
return numbers;
}
}
31 changes: 31 additions & 0 deletions src/main/java/service/BaseballService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package service;

import domain.BaseballNumbers;


public class BaseballService {

public String playRound(BaseballNumbers computer, BaseballNumbers player) {
int strike = computer.countStrike(player);
int ball = computer.countBall(player);
return getResultMessage(strike, ball);
}

public String getResultMessage(int strike, int ball) {
if (strike == 0 && ball == 0) {
return "낫싱";
}
return buildStrikeBallMessage(strike, ball).trim();
}

private String buildStrikeBallMessage(int strike, int ball) {
StringBuilder message = new StringBuilder();
if (ball > 0) {
message.append(ball).append("볼 ");
}
if (strike > 0) {
message.append(strike).append("스트라이크");
}
return message.toString();
}
}
39 changes: 39 additions & 0 deletions src/main/java/utils/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package utils;

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

public class RandomNumberGenerator {
private static final Random random = new Random();

public static List<Integer> generate() {
List<Integer> numbers = new ArrayList<>();
int index = 0;

while (index < 3) {
int num = getNumber();
if (isDuplicate(numbers, num)){
continue;
}

numbers.add(num);
index++;
}

return numbers;
}

private static int getNumber(){
return random.nextInt(1, 10);
}

private static boolean isDuplicate(List<Integer> numbers, int num){
for (Integer number : numbers) {
if (number == num)
return true;
}

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

import java.util.Scanner;

public class InputView {
Scanner sc = new Scanner(System.in);

public String readInput() {
return sc.nextLine();
}

public String readRestartInput() {
String input = readInput(); // 공통 입력 메서드 호출
validateRestart(input);
return input;
}

void validateRestart(String input) {
if (!input.equals("1") && !input.equals("2")) {
throw new IllegalArgumentException("1 또는 2만 입력 가능합니다.");
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package view;

public class OutputView {
private static final String ERROR_PREFIX = "[ERROR] ";
private static final String GAME_START_MESSAGE = "숫자 야구 게임을 시작합니다.";
private static final String GAME_END_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료";
private static final String RESTART_GUIDE_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.";

public void printStartMessage() {
System.out.println(GAME_START_MESSAGE);
}

public void printResult(String resultMessage) {
System.out.println(resultMessage);
}

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

public void printErrorMessage(String message) {
System.out.println(ERROR_PREFIX + message);
}
}
Loading