Skip to content
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# java-baseball-precourse
## 구현할 기능 목록

### 1. 게임 실행 기능
- [x] 숫자 생성 기능
- [x] 스트라이크, 볼 판별 로직
- [x] 스트라이크, 볼 관련 결과값 저장

### 2. 입력 및 출력 관련
- [x] 숫자 입력 및 결과값 출력
- [x] 숫자 입력 관련 유효성 검증 로직 도입

### 3. 단위 테스트 계획
- [x] `NumberGeneratorTest`
- [x] 3자리의 숫자인지 검증
- [x] 각 자리수가 중복되지 않는지 검증
- [x] 각 자리수가 1-9 사이의 숫자인지 검증
- [x] `BaseballGameTest` (target: "123")
- [x] 3 스트라이크 (e.g., "123")
- [x] 3 볼 (e.g., "312")
- [x] 1 스트라이크 2 볼 (e.g., "132")
- [x] 낫싱 (e.g., "456")
- [x] `ResultTest`
- [x] 3 스트라이크일 때 `isWin()`이 true인지 검증
- [x] 3 스트라이크가 아닐 때 `isWin()`이 false인지 검증
- [x] 0 스트라이크, 0 볼일 때 `isNothing()`이 true인지 검증
135 changes: 135 additions & 0 deletions src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package baseball;

import java.util.Scanner;

public class Application {
private static final int GAME_SIZE = 3;
private static final String ERROR_PREFIX = "[ERROR] ";
private static final String INPUT_PROMPT = "숫자를 입력해주세요 : ";
private static final String RESTART_PROMPT = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.";
private static final String GAME_OVER_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료";

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

public void run() {
try (Scanner scanner = new Scanner(System.in)) {
play(scanner);
}
}

private void play(Scanner scanner) {
boolean restart = true;
while (restart) {
String targetNumber = new NumberGenerator().generate();
playOneGame(scanner, targetNumber);
restart = shouldRestart(scanner);
}
}

private void playOneGame(Scanner scanner, String targetNumber) {
BaseballGame game = new BaseballGame(targetNumber);
boolean isFinished = false;
while (!isFinished) {
String inputNumber = readUserNumber(scanner);
if (!isValidUserNumber(inputNumber)) {
continue;
}
Result result = game.play(inputNumber);
isFinished = processGameResult(result);
}
}

private boolean processGameResult(Result result) {
System.out.println(formatResult(result));
if (result.isWin()) {
System.out.println(GAME_OVER_MESSAGE);
return true;
}
return false;
}

private String readUserNumber(Scanner scanner) {
System.out.print(INPUT_PROMPT);
return scanner.nextLine();
}

private boolean isValidUserNumber(String inputNumber) {
if (inputNumber == null) {
System.out.println(ERROR_PREFIX + "입력값이 올바르지 않습니다.");
return false;
}
if (inputNumber.length() != GAME_SIZE) {
System.out.println(ERROR_PREFIX + "3자리 숫자를 입력해야 합니다.");
return false;
}
if (!areDigitsValid(inputNumber)) {
return false;
}
if (hasDuplicateDigits(inputNumber)) {
System.out.println(ERROR_PREFIX + "서로 다른 숫자를 입력해야 합니다.");
return false;
}
return true;
}

private boolean areDigitsValid(String inputNumber) {
for (int i = 0; i < GAME_SIZE; i++) {
char c = inputNumber.charAt(i);
if (c < '1' || c > '9') {
System.out.println(ERROR_PREFIX + "1부터 9까지 숫자만 입력해야 합니다.");
return false;
}
}
return true;
}

private boolean hasDuplicateDigits(String inputNumber) {
for (int i = 0; i < GAME_SIZE; i++) {
if (isCharDuplicatedInRestOfString(inputNumber, i)) {
return true;
}
}
return false;
}

private boolean isCharDuplicatedInRestOfString(String inputNumber, int index) {
char currentChar = inputNumber.charAt(index);
for (int j = index + 1; j < GAME_SIZE; j++) {
if (currentChar == inputNumber.charAt(j)) {
return true;
}
}
return false;
}

private boolean shouldRestart(Scanner scanner) {
while (true) {
System.out.println(RESTART_PROMPT);
String input = scanner.nextLine();
if ("1".equals(input)) {
return true;
}
if ("2".equals(input)) {
return false;
}
System.out.println(ERROR_PREFIX + "1 또는 2를 입력해야 합니다.");
}
}

private String formatResult(Result result) {
if (result.isNothing()) {
return "낫싱";
}
StringBuilder res = new StringBuilder();
if (result.getStrikeCount() > 0) {
res.append(result.getStrikeCount()).append(" 스트라이크 ");
}
if (result.getBallCount() > 0) {
res.append(result.getBallCount()).append(" 볼");
}
return res.toString().trim();
}
}
55 changes: 55 additions & 0 deletions src/main/java/baseball/BaseballGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package baseball;

public class BaseballGame {
private static final int GAME_SIZE = 3;

private final String targetNumber;

public BaseballGame(String targetNumber) {
this.targetNumber = targetNumber;
}

public Result play(String inputNumber) {
int strikeCount = countStrikes(inputNumber);
int ballCount = countBalls(inputNumber);
return new Result(strikeCount, ballCount);
}

private int countStrikes(String inputNumber) {
int strikeCount = 0;
for (int i = 0; i < GAME_SIZE; i++) {
strikeCount += getStrikeIncrement(inputNumber.charAt(i), i);
}
return strikeCount;
}

private int getStrikeIncrement(char inputChar, int index) {
if (isStrike(inputChar, index)) {
return 1;
}
return 0;
}

private int countBalls(String inputNumber) {
int ballCount = 0;
for (int i = 0; i < GAME_SIZE; i++) {
ballCount += getBallIncrement(inputNumber.charAt(i), i);
}
return ballCount;
}

private int getBallIncrement(char inputChar, int index) {
if (isBall(inputChar, index)) {
return 1;
}
return 0;
}

private boolean isStrike(char inputChar, int index) {
return targetNumber.charAt(index) == inputChar;
}

private boolean isBall(char inputChar, int index) {
return !isStrike(inputChar, index) && targetNumber.contains(String.valueOf(inputChar));
}
}
20 changes: 20 additions & 0 deletions src/main/java/baseball/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package baseball;

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

public class NumberGenerator {
public String generate() {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 9; i++) {
numbers.add(i);
}
Collections.shuffle(numbers);
StringBuilder result = new StringBuilder();
for (int i = 0; i < 3; i++) {
result.append(numbers.get(i));
}
return result.toString();
}
}
28 changes: 28 additions & 0 deletions src/main/java/baseball/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package baseball;

public class Result {
private final int strikeCount;
private final int ballCount;

public Result(int strikeCount, int ballCount) {
this.strikeCount = strikeCount;
this.ballCount = ballCount;
}

public int getStrikeCount() {
return strikeCount;
}

public int getBallCount() {
return ballCount;
}

public boolean isNothing() {
return strikeCount == 0 && ballCount == 0;
}

public boolean isWin() {
return strikeCount == 3;
}
}

40 changes: 40 additions & 0 deletions src/test/java/baseball/BaseballGameTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package baseball;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

class BaseballGameTest {

private final BaseballGame game = new BaseballGame("123");

@ParameterizedTest
@MethodSource("gameScenarios")
@DisplayName("스트라이크와 볼 개수를 정확히 계산한다")
void play_calculatesStrikesAndBallsCorrectly(String input, int expectedStrikes, int expectedBalls) {
Result result = game.play(input);

assertThat(result.getStrikeCount()).isEqualTo(expectedStrikes);
assertThat(result.getBallCount()).isEqualTo(expectedBalls);
}

static Stream<Arguments> gameScenarios() {
return Stream.of(
// 계획된 테스트 케이스
Arguments.of("123", 3, 0), // 3 스트라이크
Arguments.of("312", 0, 3), // 3 볼
Arguments.of("132", 1, 2), // 1 스트라이크 2 볼
Arguments.of("456", 0, 0), // 낫싱

// 견고성을 위한 추가 케이스
Arguments.of("124", 2, 0), // 2 스트라이크
Arguments.of("415", 0, 1), // 1 볼
Arguments.of("142", 1, 1) // 1 스트라이크 1 볼
);
}
}
41 changes: 41 additions & 0 deletions src/test/java/baseball/NumberGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package baseball;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;

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

import static org.assertj.core.api.Assertions.assertThat;

class NumberGeneratorTest {

private final NumberGenerator generator = new NumberGenerator();

@RepeatedTest(10)
@DisplayName("생성된 숫자는 3자리이다")
void generates_a_3_digit_number() {
String number = generator.generate();
assertThat(number).hasSize(3);
}

@RepeatedTest(10)
@DisplayName("생성된 숫자는 1-9 사이의 값이다")
void generates_digits_between_1_and_9() {
String number = generator.generate();
for (char c : number.toCharArray()) {
assertThat(c).isGreaterThanOrEqualTo('1').isLessThanOrEqualTo('9');
}
}

@RepeatedTest(10)
@DisplayName("생성된 숫자는 서로 다른 수로 이루어져 있다")
void generates_a_number_with_unique_digits() {
String number = generator.generate();
Set<Character> uniqueDigits = new HashSet<>();
for (char c : number.toCharArray()) {
uniqueDigits.add(c);
}
assertThat(uniqueDigits).hasSize(3);
}
}
Loading