Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[전남대 BE_오명헌] 미션 제출합니다. #163

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
# java-baseball-precourse
# 숫자 야구

1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.

## 기능 요구 사항

---

- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.

```markdown
e.g.
상대방(컴퓨터)의 수가 425일 때,
- 123을 제시한 경우 : 1스트라이크
- 456을 제시한 경우 : 1볼 1스트라이크
- 789를 제시한 경우 : 낫싱
```

- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

## 실행 결과

---

```markdown
숫자를 입력해 주세요 : 123
1볼 1스트라이크
숫자를 입력해 주세요 : 145
1볼
숫자를 입력해 주세요 : 671
2볼
숫자를 입력해 주세요 : 216
1스트라이크
숫자를 입력해 주세요 : 713
3스트라이크
3개의 숫자를 모두 맞히셨습니다! 게임 종료
게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.
1
숫자를 입력해 주세요 : 123
1볼
...
```

- 프로그래밍 요구 사항 1
- JDK 17 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 Application 의 main() 이다.
- build.gradle 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 System.exit() 를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 프로그래밍 요구 사항 2
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Google Java Style Guide을 원칙으로 한다.
- 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.
- JUnit 5 User Guide
- AssertJ User Guide
- AssertJ Exception Assertions
- Guide to JUnit 5 Parameterized Test

## 기능 목록

---

1. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다.
2. 사용자로부터 3자리의 수를 입력 받는다.
- 사용자가 잘못된 값을 입력할 경우 예외 발생 후 실행 종료
- 3자리가 아닌 경우
- 숫자가 아닌 경우
- 0이 입력된 경우
- 중복된 숫자가 입력된 경우
3. 입력 값에 따라 힌트를 제공한다.
- 동일한 자리 - 동일한 수 → 스트라이크
- 다른 자리 - 동일한 수 → 볼
- 다른 수 → 낫싱
4. 정답을 맞추면 게임을 종료한다.
- 3스트라이크인 경우 → 게임 종료
- 3스트라이크가 아닌 경우 → 게임 진행
5. 게임 종료 후, 게임을 다시 시작하거나 완전히 종료할 수 있다.
- 1 입력 → 게임 새로 시작
- 2 입력 → 게임 완전히 종료
- 사용자가 잘못된 값을 입력할 경우 예외 발생 후 실행 종료
- 1, 2가 아닌 값을 입력할 경우
10 changes: 10 additions & 0 deletions src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package baseball;

import baseball.game.Game;

public class Application {
public static void main(String[] args) {
Game game = new Game();
game.startGame();
}
}
42 changes: 42 additions & 0 deletions src/main/java/baseball/domain/GameRules.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package baseball.domain;

public class GameRules {
public String generateHint(String secretNumber, String userGuess) {
int strike = 0;
int ball = 0;
for (int i = 0; i < 3; i++) {
if (userGuess.charAt(i) == secretNumber.charAt(i)) {
strike++;
} else if (secretNumber.contains(userGuess.substring(i, i+1))) {
ball++;
}
}
return createHintMessage(strike, ball);
}

private String createHintMessage(int strike, int ball) {
String result = "";
if (strike == 0 && ball == 0) {
result = "낫싱";
} else {
if (ball > 0) {
result += ball + "볼 ";
}
if (strike > 0) {
result += strike + "스트라이크";
}
}
return result.trim();
}

public Boolean decideRestart(String input) {
switch (input) {
case "1":
return true;
case "2":
return false;
default:
return null;
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/baseball/domain/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package baseball.domain;

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

public class NumberGenerator {

public static String generateNumber() {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 9; i++) {
numbers.add(i);
}
Collections.shuffle(numbers);
return "" + numbers.get(0) + numbers.get(1) + numbers.get(2);
}
}
63 changes: 63 additions & 0 deletions src/main/java/baseball/game/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package baseball.game;

import baseball.domain.GameRules;
import baseball.domain.NumberGenerator;
import baseball.view.GameView;

public class Game {
private GameView gameView;
private NumberGenerator numberGenerator;
private GameRules gameRules;
private String computerNumber;

public Game() {
this.gameView = new GameView();
this.numberGenerator = new NumberGenerator();
this.gameRules = new GameRules();
this.computerNumber = numberGenerator.generateNumber();
}

public void startGame() {
boolean gameContinues = true;
try {
while (gameContinues) {
gameContinues = game();
}
} catch (IllegalArgumentException e) {
gameView.displayError(e.getMessage());
}
}

private boolean game() {
String userGuess = gameView.promptForGuess();
if (!InputValidator.isValidInput(userGuess)) {
throw new IllegalArgumentException("잘못된 값을 입력했습니다.");
}
String result = gameRules.generateHint(computerNumber, userGuess);
gameView.displayResult(result);
if (result.equals("3스트라이크")) {
gameView.displayGameEnd();
return promptForRestart();
}
return true;
}

private boolean promptForRestart() {
while (true) {
String input = gameView.promptForRestart();
Boolean decision = gameRules.decideRestart(input);
if (decision == null) {
throw new IllegalArgumentException("잘못된 값을 입력했습니다.");
} else {
if (decision) {
resetGame();
}
return decision;
}
}
}

private void resetGame() {
computerNumber = numberGenerator.generateNumber();
}
}
32 changes: 32 additions & 0 deletions src/main/java/baseball/game/InputValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package baseball.game;

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

public class InputValidator {
public static boolean isValidInput(String input) {
return isProperLength(input) && isAllDigits(input) && hasNoZero(input) && hasUniqueDigits(input);
}

private static boolean isProperLength(String input) {
return input.length() == 3;
}

private static boolean isAllDigits(String input) {
return input.chars().allMatch(Character::isDigit);
}

private static boolean hasNoZero(String input) {
return !input.contains("0");
}

private static boolean hasUniqueDigits(String input) {
Set<Character> digits = new HashSet<>();
for (char c : input.toCharArray()) {
if (!digits.add(c)) {
return false;
}
}
return true;
}
}
33 changes: 33 additions & 0 deletions src/main/java/baseball/view/GameView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package baseball.view;

import java.util.Scanner;

public class GameView {
private Scanner scanner;

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

public String promptForGuess() {
System.out.println("숫자를 입력해 주세요: ");
return scanner.nextLine();
}

public void displayResult(String result) {
System.out.println(result);
}

public void displayGameEnd() {
System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
}

public String promptForRestart() {
System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
return scanner.nextLine();
}

public void displayError(String message) {
System.out.println(message);
}
}
62 changes: 62 additions & 0 deletions src/test/java/baseball/domain/GameRulesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package baseball.domain;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

class GameRulesTest {

@Test
public void testGenerateHint_AllStrikes() {
GameRules rules = new GameRules();
String hint = rules.generateHint("123", "123");
assertThat(hint).isEqualTo("3스트라이크");
}

@Test
public void testGenerateHint_SomeBalls() {
GameRules rules = new GameRules();
String hint = rules.generateHint("123", "231");
assertThat(hint).isEqualTo("3볼");
}

@Test
public void testGenerateHint_Mixed() {
GameRules rules = new GameRules();
String hint = rules.generateHint("123", "132");
assertThat(hint).isEqualTo("2볼 1스트라이크");
}

@Test
public void testGenerateHint_Nothing() {
GameRules rules = new GameRules();
String hint = rules.generateHint("123", "456");
assertThat(hint).isEqualTo("낫싱");
}

@Test
public void testDecideRestart_ReturnsTrueFor1() {
GameRules rules = new GameRules();
Boolean result = rules.decideRestart("1");
assertThat(result).isTrue();
}

@Test
public void testDecideRestart_ReturnsFalseFor2() {
GameRules rules = new GameRules();
Boolean result = rules.decideRestart("2");
assertThat(result).isFalse();
}

@Test
public void testDecideRestart_ReturnsNullForOtherInputs() {
GameRules rules = new GameRules();
Boolean result = rules.decideRestart("3");
assertThat(result).isNull();

result = rules.decideRestart("hello");
assertThat(result).isNull();

result = rules.decideRestart("");
assertThat(result).isNull();
}
}
18 changes: 18 additions & 0 deletions src/test/java/baseball/domain/NumberGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package baseball.domain;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

class NumberGeneratorTest {

@Test
@DisplayName("testGenerateNumber() : 컴퓨터는 1-9까지 서로 다른 임의의 수를 3개를 선택한다.")
public void testGenerateNumber() {
String number = NumberGenerator.generateNumber();
assertThat(number).hasSize(3);
assertThat(number).matches("[1-9]+");
assertThat(number.chars().distinct().count()).isEqualTo(3);
}

}
Loading