Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ca9e7af
feat: 게임 숫자 생성 및 테스트
seong-wooo Mar 24, 2023
bdd44d3
feat: 게임 수들 생성 및 테스트
seong-wooo Mar 24, 2023
e4e71af
feat: 입력값을 게임 수로 변경하는 기능 구현 및 테스트
seong-wooo Mar 24, 2023
3d209ad
feat: Integer값을 게임 수로 변경하는 기능 구현 및 테스트
seong-wooo Mar 24, 2023
de776a1
refactor: 패키지 분리
seong-wooo Mar 24, 2023
b0b21a5
test: GameNumbersTest 테스트 케이스 추가
seong-wooo Mar 24, 2023
79a4760
feat: GameResult 생성 및 테스트
seong-wooo Mar 24, 2023
7a4efb6
feat: 게임 결과 계산 기능 추가 및 테스트
seong-wooo Mar 24, 2023
8a59a93
feat: 게임 숫자 생성 시 예외 사항 추가
seong-wooo Mar 24, 2023
201effb
feat: Application 구현
seong-wooo Mar 24, 2023
a059027
refactor: 일급 컬렉션의 참조를 끊음
seong-wooo Mar 25, 2023
b719d6c
refactor: 메서드 분리
seong-wooo Mar 25, 2023
31a0914
refactor: strike 변수 직접 사용하도록 변경
seong-wooo Mar 25, 2023
5573e23
refactor: 계속 생성될 필요가 없는 객체를 필드 변수로 선언
seong-wooo Mar 25, 2023
33a288f
refactor: 재시작 입력값이 1,2가 아니면 예외가 발생하도록 변경
seong-wooo Mar 25, 2023
0cf7b4c
refactor: 게임 끝 여부를 GameResult 내부로 숨김
seong-wooo Mar 27, 2023
06b7e3c
refactor: 게임 결과 메시지 생성 GameResult 내부로 이동
seong-wooo Mar 27, 2023
2d7c265
refactor: 잘못된 메서드명 수정
seong-wooo Mar 27, 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
34 changes: 33 additions & 1 deletion src/main/java/baseball/Application.java
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

콘솔 입출력과 게임 진행이 모두 App 클래스에 모여있네요.
Controller와 Service를 분리하듯이, 입출력과 게임 진행 로직을 분리하면 어떨까 하는 의견입니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Application 클래스에서 게임 진행 로직과 view 로직을 분리하더라도 진행 흐름에는 변함이 없을 것 같네요.
view 쪽 로직은 크게 신경쓰지 않도록 하겠습니다..ㅎ

Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
package baseball;

import baseball.domain.GameResult;
import baseball.service.GameService;
import camp.nextstep.edu.missionutils.Console;

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
while (true) {
playGame();

System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");

final RestartMessage restartMessage = new RestartMessage(Console.readLine());
if (restartMessage.isEnd()) {
System.out.println("게임 종료");
break;
}
}
}

private static void playGame() {
GameService gameService = new GameService();

System.out.println("숫자 야구 게임을 시작합니다.");
while (true) {
System.out.print("숫자를 입력해주세요 : ");
String playerInput = Console.readLine();

GameResult result = gameService.findResult(playerInput);

System.out.println(result.getResultComment());
if (result.isEnd()) {
break;
}
}
}
}
28 changes: 28 additions & 0 deletions src/main/java/baseball/RestartMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package baseball;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RestartMessage {

private static final String RESTART_MESSAGE = "1";
private static final String END_MESSAGE = "2";
private static final Pattern PATTERN = Pattern.compile(String.format("[%s%s]", RESTART_MESSAGE, END_MESSAGE));
private final String value;

public RestartMessage(String value) {
validate(value);
this.value = value;
}

private void validate(String value) {
final Matcher matcher = PATTERN.matcher(value);
if (!matcher.matches()) {
throw new IllegalArgumentException(String.format("[ERROR] 재시작 입력 값은 %s 일 수 없습니다.", value));
}
}

public boolean isEnd() {
return this.value.equals(END_MESSAGE);
}
}
45 changes: 45 additions & 0 deletions src/main/java/baseball/domain/GameNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package baseball.domain;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.Objects;

public class GameNumber {

private static final int MIN_VALUE = 1;
private static final int MAX_VALUE = 9;

private final int value;

public GameNumber(int value) {
validateRange(value);
this.value = value;
}

private void validateRange(int value) {
if (value < MIN_VALUE || value > MAX_VALUE) {
throw new IllegalArgumentException(String.format("[ERROR] 야구게임 숫자는 %d일 수 없습니다", value));
}
}

public static GameNumber createRandomNumber() {
final int number = Randoms.pickNumberInRange(MIN_VALUE, MAX_VALUE);
return new GameNumber(number);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GameNumber that = (GameNumber) o;
return value == that.value;
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}
65 changes: 65 additions & 0 deletions src/main/java/baseball/domain/GameNumbers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package baseball.domain;

import baseball.domain.gamenumbercreator.GameNumberCreator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

public class GameNumbers {

private static final int MAX_LENGTH = 3;
private final List<GameNumber> numbers;

private GameNumbers(List<GameNumber> numbers) {
validateLength(numbers);
validateDuplicate(numbers);
this.numbers = new ArrayList<>(numbers);
}

public static GameNumbers from(GameNumberCreator numberCreator) {
return new GameNumbers(numberCreator.create(MAX_LENGTH));
}
Comment on lines +21 to +23

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자를 노출하지 않고, 이런 방식으로 객체를 생성하는 이유가 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 생성자는 생성의 역할만 해야된다고 생각해서 이렇게 구현했습니다.
생성자에서 객체의 변환을 해야한다면 정적 팩터리 메서드를 사용해서 변환을 하고 생성자로 넘기는 방식을 사용합니다.


private void validateLength(List<GameNumber> numbers) {
if (numbers.size() != MAX_LENGTH) {
throw new IllegalArgumentException(String.format("[ERROR] 게임 숫자는 %d 자리여야합니다.", MAX_LENGTH));
}
}

private void validateDuplicate(List<GameNumber> numbers) {
if (numbers.size() != new HashSet<>(numbers).size()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HashSet을 사용하니 훨씬 깔끔하군요..! 참고하겠습니다!

throw new IllegalArgumentException("[ERROR] 게임 숫자는 중복된 값을 가질 수 없습니다.");
}
}

public GameResult calculateResult(GameNumbers other) {
int strike = (int) IntStream.range(0, other.numbers.size())
.filter(i -> other.numbers.get(i).equals(this.numbers.get(i)))
.count();

int ball = (int) other.numbers.stream()
.filter(this.numbers::contains)
.count() - strike;

return GameResult.find(strike, ball);
}
Comment on lines +37 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream을 잘 활용하시네요! 저도 더 공부해봐야겠습니다.


@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GameNumbers that = (GameNumbers) o;
return Objects.equals(numbers, that.numbers);
}

@Override
public int hashCode() {
return Objects.hash(numbers);
}
}
55 changes: 55 additions & 0 deletions src/main/java/baseball/domain/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package baseball.domain;

import java.util.Arrays;

public enum GameResult {
ZERO(0, 0),
ZERO_STRIKE_ONE_BALL(0, 1),
ZERO_STRIKE_TWO_BALL(0, 2),
ZERO_STRIKE_THREE_BALL(0, 3),
ONE_STRIKE_ZERO_BALL(1, 0),
ONE_STRIKE_ONE_BALL(1, 1),
ONE_STRIKE_TWO_BALL(1, 2),
TWO_STRIKE_ZERO_BALL(2, 0),
TWO_STRIKE_ONE_BALL(2, 1),
THREE_STRIKE(3, 0);

private final int strike;
private final int ball;

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

public static GameResult find(int strike, int ball) {
return Arrays.stream(GameResult.values())
.filter(gameResult -> gameResult.strike == strike && gameResult.ball == ball)
.findAny()
.orElseThrow(() -> new IllegalArgumentException(
String.format("[ERROR] %d 스트라이크 %d 볼에 해달하는 결과는 존재하지 않습니다.", strike, ball)));
}

public boolean isEnd() {
return this == THREE_STRIKE;
}

public String getResultComment() {
if (strike == 0 && ball == 0) {
return "낫싱";
}

StringBuilder answer = new StringBuilder();
if (ball != 0) {
answer.append(ball);
answer.append("볼 ");
}

if (strike != 0) {
answer.append(strike);
answer.append("스트라이크");
}

return answer.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package baseball.domain.gamenumbercreator;

import baseball.domain.GameNumber;
import java.util.List;

public interface GameNumberCreator {
List<GameNumber> create(int maxLength);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package baseball.domain.gamenumbercreator;

import baseball.domain.GameNumber;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class RandomIntegerToGameNumberCreator implements GameNumberCreator {

@Override
public List<GameNumber> create(int maxLength) {
return Stream.generate(GameNumber::createRandomNumber)
.distinct()
.limit(maxLength)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package baseball.domain.gamenumbercreator;

import baseball.domain.GameNumber;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StringToGameNumberCreator implements GameNumberCreator {

private final String values;

public StringToGameNumberCreator(String values) {
this.values = values;
}

@Override
public List<GameNumber> create(int maxLength) {
validateLength(maxLength);

return Arrays.stream(values.split(""))
.map(this::parseInt)
.map(GameNumber::new)
.collect(Collectors.toList());
}

private void validateLength(int maxLength) {
if (values.length() != maxLength) {
throw new IllegalArgumentException(String.format("[ERROR] %s는 %d 자리수가 아닙니다.", values, maxLength));
}
}

private Integer parseInt(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(String.format("[ERROR] 입력값 %s는 숫자가 아닙니다.", value));
}
}
}
23 changes: 23 additions & 0 deletions src/main/java/baseball/service/GameService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package baseball.service;

import baseball.domain.GameNumbers;
import baseball.domain.GameResult;
import baseball.domain.gamenumbercreator.GameNumberCreator;
import baseball.domain.gamenumbercreator.RandomIntegerToGameNumberCreator;
import baseball.domain.gamenumbercreator.StringToGameNumberCreator;

public class GameService {

private static final GameNumberCreator NUMBER_CREATOR = new RandomIntegerToGameNumberCreator();
private final GameNumbers answerNumbers;

public GameService() {
this.answerNumbers = GameNumbers.from(NUMBER_CREATOR);
}

public GameResult findResult(String playerRequest) {
final StringToGameNumberCreator creator = new StringToGameNumberCreator(playerRequest);
final GameNumbers playerNumbers = GameNumbers.from(creator);
return answerNumbers.calculateResult(playerNumbers);
}
}
6 changes: 3 additions & 3 deletions src/test/java/baseball/ApplicationTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package baseball;

import camp.nextstep.edu.missionutils.test.NsTest;
import org.junit.jupiter.api.Test;

import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest;
import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import camp.nextstep.edu.missionutils.test.NsTest;
import org.junit.jupiter.api.Test;

class ApplicationTest extends NsTest {
@Test
void 게임종료_후_재시작() {
Expand Down
23 changes: 23 additions & 0 deletions src/test/java/baseball/domain/GameNumberTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package baseball.domain;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class GameNumberTest {

@ParameterizedTest
@ValueSource(ints = {0, 10})
void 게임_숫자가_1이상_9이하의_자연수가_아니면_예외를_던진다(int number) {
Assertions.assertThatThrownBy(() -> new GameNumber(number))
.isInstanceOf(IllegalArgumentException.class);
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9})
void 게임_숫자는_1이상_9이하의_자연수여야한다(int number) {
Assertions.assertThatCode(() -> new GameNumber(number))
.doesNotThrowAnyException();
}
}
Loading