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

[야구 게임] 김민겸 미션 제출합니다. #2794

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fb6f312
docs: 기능 구현 목록 작성
Mingyum-Kim Dec 13, 2023
c4397fb
feat: 콘솔에 출력하기 위한 클래스 생성
Mingyum-Kim Dec 13, 2023
d504a06
feat: 커스텀 예외 처리 클래스 생성
Mingyum-Kim Dec 13, 2023
b8c8715
feat: 게임을 시작하는 기능 구현
Mingyum-Kim Dec 13, 2023
6cf7c04
feat: 숫자를 세 개 생성하여 컴퓨터에 저장하는 기능 구현
Mingyum-Kim Dec 13, 2023
376503a
feat: 플레이어가 숫자를 입력하고 검증하는 기능 구현
Mingyum-Kim Dec 13, 2023
bbb317b
feat: 힌트를 생성하는 기능 구현
Mingyum-Kim Dec 13, 2023
df056dd
feat: 힌트를 출력하는 기능 구현
Mingyum-Kim Dec 13, 2023
233a282
feat: 게임을 계속 진행하기 위한 반복문 구현
Mingyum-Kim Dec 13, 2023
f0ac6e9
feat: 게임의 재시작 혹은 종료 여부를 입력할 Enum 클래스 생성
Mingyum-Kim Dec 13, 2023
0308d4f
feat: 입력의 숫자와 범위를 검증하는 Validator 생성
Mingyum-Kim Dec 13, 2023
2ef10d1
feat: 에러 메시지를 저장하는 Enum 클래스 구현
Mingyum-Kim Dec 13, 2023
f3a1f43
feat: 어플리케이션 실행
Mingyum-Kim Dec 13, 2023
7cf4bec
docs: 기능 구현 목록 체크
Mingyum-Kim Dec 13, 2023
ccd2854
refactor: 게임을 다시 시작할 때 랜덤 숫자를 초기화
Mingyum-Kim Dec 13, 2023
88c62a8
refactor: 마지막 공백까지 인식되는 현상 제거
Mingyum-Kim Dec 13, 2023
8f2f600
refactor: 랜덤 숫자 생성 함수를 분리
Mingyum-Kim Dec 13, 2023
0b66264
feat: 에러 발생 시 재입력받는 기능 구현
Mingyum-Kim Dec 14, 2023
18c84a8
feat: 숫자를 저장하는 래퍼 클래스와 일급 컬렉션 생성
Mingyum-Kim Dec 14, 2023
071004d
refactor: 일급 컬렉션을 활용한 인자 전달 구현
Mingyum-Kim Dec 14, 2023
b3b4d1a
feat: 검증 로직을 도메인과 분산하는 것으로 수정
Mingyum-Kim Dec 14, 2023
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
37 changes: 37 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 💪 프로젝트 개요

컴퓨터가 생성한 숫자를 맞추기 위해 플레이어가 숫자를 입력한다.
컴퓨터는 플레이어의 숫자에 대해 스트라이크, 볼, 낫싱으로 힌트를 제공한다.

# 📝 구현 기능 목록

### 컴퓨터가 숫자를 생성하는 기능

- [x] `숫자 야구 게임을 시작합니다.`를 출력한다.
- [x] 1부터 9까지의 숫자 세 개를 생성한다.

### 플레이어가 숫자를 입력하는 기능

- [x] `숫자를 입력해주세요 : `를 출력한다.
- [x] 숫자를 입력받는다.
- [x] 빈 문자열이 아님을 검증한다.
- [x] 길이가 3인 문자열임을 검증한다.
- [x] 숫자로 이루어진 문자열임을 검증한다.
- [x] 숫자가 1 이상, 9 이하임을 검증한다.
- [x] 서로 다른 숫자임을 검증한다.

### 힌트를 생성하는 기능

- [x] 컴퓨터에 저장한 숫자와 입력된 숫자를 비교한다.
- [x] 스트라이크와 볼 개수를 계산하여 반환한다.

### 힌트를 출력하는 기능

- [x] `1볼 1스트라이크`와 같이 볼 개수와 스트라이크 개수를 출력한다.

### 게임을 새로 시작하거나 종료하는 기능

- [x] 스트라이크가 3인 경우 게임을 종료한다.
- [x] `3개의 숫자를 모두 맞히셨습니다! 게임 종료 \n 게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.`를 출력한다.
- [x] 1 혹은 2의 입력을 받는다.
- [x] 입력에 따라 게임을 새로 시작하거나 종료한다.
7 changes: 6 additions & 1 deletion src/main/java/baseball/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package baseball;

import baseball.controller.GameManager;
import camp.nextstep.edu.missionutils.Console;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
GameManager gameManager = new GameManager();
gameManager.run();
Console.close();
}
}
61 changes: 61 additions & 0 deletions src/main/java/baseball/controller/GameManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package baseball.controller;

import baseball.domain.Computer;
import baseball.domain.HintResult;
import baseball.domain.Numbers;
import baseball.view.InputView;
import baseball.view.OutputView;
import baseball.view.console.ConsoleWriter;
import java.util.function.Supplier;

public class GameManager {
private final InputView inputView;
private final OutputView outputView;
private final Computer computer;

public GameManager() {
this.inputView = new InputView();
this.outputView = new OutputView();
this.outputView.start();
this.computer = new Computer();
}

public void run() {
boolean isRunning = true;
while (isRunning) {
computer.generate();
isRunning = play();
}
}

private boolean play() {
while (true) {
Numbers numbers = retry(() -> {
return new Numbers(inputView.enterNumbers());
});
HintResult hintResult = computer.generateHintResult(numbers);
outputView.printHintResult(hintResult);
if (isSuccess(hintResult)) {
break;
}
}
outputView.printGameOver();
return retry(() -> {
return inputView.enterRestartOrQuit().isRunning();
});
}

private boolean isSuccess(HintResult hintResult) {
return hintResult.strike() == 3;
}

private static <T> T retry(Supplier<T> supplier) {
while (true) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
ConsoleWriter.printlnMessage(e.getMessage());
}
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/baseball/domain/Computer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package baseball.domain;

import baseball.global.util.RandomNumberGenerator;
import java.util.ArrayList;
import java.util.List;

public class Computer {
private List<Integer> numbers;

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

public void generate() {
numbers = RandomNumberGenerator.generateRandomNumber();
}

public HintResult generateHintResult(Numbers givenNumbers) {
int strike = 0;
int ball = 0;
for (int i = 0; i < numbers.size(); i++) {
if (numbers.get(i) == givenNumbers.get(i)) {
strike++;
}
ball += isBallIndex(i, givenNumbers);
}
return new HintResult(strike, ball);
}

private int isBallIndex(int i, Numbers givenNumbers) {
for (int j = 0; j < givenNumbers.size(); j++) {
if (i != j && numbers.get(i) == givenNumbers.get(j)) {
return 1;
}
}
return 0;
}
}
29 changes: 29 additions & 0 deletions src/main/java/baseball/domain/GameCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package baseball.domain;

import baseball.global.exception.CustomException;
import baseball.global.exception.ErrorMessage;
import java.util.Arrays;

public enum GameCommand {
RESTART("1", true),
QUIT("2", false);

private final String command;
private final boolean isRunning;

GameCommand(String command, boolean isRunning) {
this.command = command;
this.isRunning = isRunning;
}

public static GameCommand from(String command) {
return Arrays.stream(GameCommand.values())
.filter(element -> element.command.equals(command))
.findFirst()
.orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_NUMBER_ERROR));
}

public boolean isRunning() {
return isRunning;
}
}
7 changes: 7 additions & 0 deletions src/main/java/baseball/domain/HintResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package baseball.domain;

public record HintResult(
int strike,
int ball
) {
}
24 changes: 24 additions & 0 deletions src/main/java/baseball/domain/Number.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package baseball.domain;

import static baseball.global.validator.Validator.validateRange;

public class Number {
private static final int START = 1;
private static final int END = 9;
private Integer value;

public Number(Integer value) {
Validator.validate(value);
this.value = value;
}

public int getValue() {
return value;
}

private static class Validator {
public static void validate(int value) {
validateRange(value, 1, 9);
}
}
}
54 changes: 54 additions & 0 deletions src/main/java/baseball/domain/Numbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package baseball.domain;

import baseball.global.exception.CustomException;
import baseball.global.exception.ErrorMessage;
import java.util.List;

public class Numbers {
private static final int COUNT = 3;
private List<Number> numbers;

public Numbers(List<Integer> numbers) {
this.numbers = Validator.validate(numbers);
}

public int get(int index) {
return numbers.get(index).getValue();
}

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

private static class Validator {
private static List<Number> validate(List<Integer> numbers) {
validateLength(numbers);
validateDuplicated(numbers);
return numbers.stream()
.map(Number::new)
.toList();
}

private static void validateLength(List<Integer> numbers) {
if (numbers.size() != COUNT) {
throw CustomException.from(ErrorMessage.INVALID_NUMBER_ERROR);
}
}

private static void validateDuplicated(List<Integer> items) {
if (hasDuplicatedItem(items)) {
throw CustomException.from(ErrorMessage.INVALID_NUMBER_ERROR);
}
}

private static boolean hasDuplicatedItem(List<Integer> items) {
return items.size() != calculateUniqueItemsCount(items);
}

private static int calculateUniqueItemsCount(List<Integer> items) {
return (int) items.stream()
.distinct()
.count();
}
}
}
13 changes: 13 additions & 0 deletions src/main/java/baseball/global/exception/CustomException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package baseball.global.exception;

public class CustomException extends IllegalArgumentException {
private static final String PREFIX = "[ERROR] ";

private CustomException(ErrorMessage errorMessage) {
super(PREFIX + errorMessage.getMessage());
}

public static CustomException from(ErrorMessage errorMessage) {
return new CustomException(errorMessage);
}
}
18 changes: 18 additions & 0 deletions src/main/java/baseball/global/exception/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package baseball.global.exception;

public enum ErrorMessage {
BLANK_INPUT_ERROR("빈 문자열이 입력되었습니다."),
NOT_NUMBER_ERROR("숫자만을 입력해주세요."),
INVALID_NUMBER_ERROR("숫자를 다시 입력해주세요."),
INVALID_RANGE_ERROR("올바른 범위의 숫자를 입력해주세요.");

private final String message;

ErrorMessage(String message) {
this.message = message;
}

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

import camp.nextstep.edu.missionutils.Randoms;
import java.util.ArrayList;
import java.util.List;

public class RandomNumberGenerator {
public static List<Integer> generateRandomNumber() {
List<Integer> computer = new ArrayList<>();
while (computer.size() < 3) {
int randomNumber = Randoms.pickNumberInRange(1, 9);
if (!computer.contains(randomNumber)) {
computer.add(randomNumber);
}
}
return computer;
}
}
27 changes: 27 additions & 0 deletions src/main/java/baseball/global/validator/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package baseball.global.validator;

import baseball.global.exception.CustomException;
import baseball.global.exception.ErrorMessage;

public class Validator {
public static int validateNumber(String message) {
if (isNotNumber(message)) {
throw CustomException.from(ErrorMessage.NOT_NUMBER_ERROR);
}
return Integer.parseInt(message);
}

private static boolean isNotNumber(String str) {
return !str.matches("-?\\d+");
}

public static void validateRange(int number, int start, int end) {
if (isInvalidRange(number, start, end)) {
throw CustomException.from(ErrorMessage.INVALID_RANGE_ERROR);
}
}

private static boolean isInvalidRange(int number, int start, int end) {
return number < start || number > end;
}
}
37 changes: 37 additions & 0 deletions src/main/java/baseball/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package baseball.view;

import baseball.domain.GameCommand;
import baseball.view.console.ConsoleReader;
import baseball.view.console.ConsoleWriter;
import java.util.Arrays;
import java.util.List;

public class InputView {
public List<Integer> enterNumbers() {
ConsoleWriter.printMessage("숫자를 입력해주세요 : ");
return Validator.validateNumbers(ConsoleReader.enterMessage());
}

public GameCommand enterRestartOrQuit() {
ConsoleWriter.printlnMessage("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
return GameCommand.from(ConsoleReader.enterMessage());
}

private static class Validator {
private static List<Integer> validateNumbers(String message) {
List<String> numbers = parseStringToList(message, "");

return numbers.stream()
.map(baseball.global.validator.Validator::validateNumber)
.toList();
}

private static List<String> parseStringToList(String message, String separator) {
return Arrays.stream(split(message, separator)).toList();
}

private static String[] split(String message, String separator) {
return message.split(separator);
}
}
}
Loading