diff --git a/README.md b/README.md index d0286c859f..85bdac4faa 100644 --- a/README.md +++ b/README.md @@ -1 +1,78 @@ # java-racingcar-precourse + +# 기능 목록 + 1. 자동차 이름을 입력받기 + 2. 자동차이름 예외사항 예외처리하기(5자 제한 필요) + 3. 시도할 횟수를 입력 받기위한 출력문 출력, 입력 받기 (정수가 아니면 잘못된 입력으로 예외처리) + 4. 시도횟수 만큼 각 자동차의 주사위를 던지고, 전진시키기, 전진 횟수를 Map에 반영하기 + 5. Map을 기반으로 우승자 선별하기, 출력하기 + +# NsTest +NsTest에 정의된 init +```java + @BeforeEach //모든 테스트 수행전에 실행 할것 + protected final void init() { + this.standardOut = System.out; //기존 표준 출력을 저장해놓는다 나중에 여기에 한번에 출력하기 위해서 + this.captor = new ByteArrayOutputStream(); // 출력을 가로채서 담을 ByteStream을 정의한다, 출력 내용을 검증하기 위해서 + System.setOut(new PrintStream(this.captor)); //출력을 가로챈다! + } +``` +NsTest에 정의된 run +```java + protected final void run(String... args) { + try { + this.command(args); //입력 stream 설정 (배열로 전달 된 여러 입력 값을 개행 시켜서 실제 입력처럼 만들기) + this.runMain(); //NsTest를 상속한 클래스에서 구현한 메인 Test를 실행해라 + } finally { + Console.close(); //Console 클래스 안의 Scanner 자원 종료 + } + + } +``` +NsTest에 정의된 command +```java + private void command(String... args) { + byte[] buf = String.join("\n", args).getBytes(); + System.setIn(new ByteArrayInputStream(buf)); + } +``` +1. 배열로 전달된 배열을 개행시켜서 하나의 문자열로 만들기 +2. String -> Byte +3. System.in에 설정 실제 사용자가 입력한것 처럼 +Console 라이브러리는 System.in에서 입력을 받아오기 때문에 System.setIn을 하면 실제 사용자가 입력한것과 같은 효과 + +```java +public class Console { + private static Scanner scanner; + + private Console() { + } + + public static String readLine() { + return getInstance().nextLine(); + } + + public static void close() { + if (scanner != null) { + scanner.close(); + scanner = null; + } + + } + + private static Scanner getInstance() { + if (scanner == null) { + scanner = new Scanner(System.in); // => 요기서 + } + + return scanner; + } +} +``` +NsTest에 정의된 output + +```java + protected final String output() { + return this.captor.toString().trim(); //가로챈 출력을 반환 + } +``` diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..7968185724 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,12 @@ package racingcar; + +import camp.nextstep.edu.missionutils.Console; + public class Application { + private final static RacingCarGame racingCarGame = new RacingCarGame(); + public static void main(String[] args) { - // TODO: 프로그램 구현 + racingCarGame.play(); } } diff --git a/src/main/java/racingcar/RacingCarGame.java b/src/main/java/racingcar/RacingCarGame.java new file mode 100644 index 0000000000..0d4c515412 --- /dev/null +++ b/src/main/java/racingcar/RacingCarGame.java @@ -0,0 +1,93 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Console; +import camp.nextstep.edu.missionutils.Randoms; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RacingCarGame { + Map carMap = new HashMap<>(); + + public Map getCarMap() { + return carMap; + } + + public void play() { + readCarNames(); + int numOfGames = receiceNumOfGames(); + System.out.println("\n실행결과"); + for (int i = 0; i < numOfGames; i++) { + playOneRound(); + } + announceWinners(); + } + + public void playOneRound() { + for (Map.Entry entry : carMap.entrySet()) { + int numOfDice = Randoms.pickNumberInRange(0, 9); + moveForward(entry.getKey(), numOfDice); + System.out.println(entry.getKey() + " : " + "-".repeat(carMap.get(entry.getKey()))); + } + System.out.println("\n"); + } + + private void moveForward(String carName, int numOfDice) { + if (numOfDice >= 4) { + carMap.put(carName, carMap.get(carName) + 1); + } + } + + + public void readCarNames() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String input = Console.readLine(); + String[] carNames = input.split(","); + for (String carName : carNames) { + validateCarName(carName); + carMap.put(carName, 0); + } + } + + private void validateCarName(String carName) { + if (carName.length() > 5) { + throw new IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다."); + } + } + + public int receiceNumOfGames() { + System.out.println("시도할 횟수는 몇 회인가요?"); + String numOfGames = Console.readLine(); + return validateNumOfGames(numOfGames); + } + + private int validateNumOfGames(String numOfGames) { + try { + return Integer.parseInt(numOfGames); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("정수가 아닌 값을 입력했습니다."); + } + } + + private List calculateWinners() { + if (carMap.isEmpty()) return Collections.emptyList(); + int max = carMap.values().stream().mapToInt(Integer::intValue).max().orElse(0); + return carMap.entrySet().stream() + .filter(e -> e.getValue() == max) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public String getWinnersLine() { + List winners = calculateWinners(); + return "최종 우승자 : " + String.join(", ", winners); + } + + + public void announceWinners() { + System.out.println(); + System.out.println(getWinnersLine()); + } +} diff --git a/src/test/java/racingcar/RacingCarGameTest.java b/src/test/java/racingcar/RacingCarGameTest.java new file mode 100644 index 0000000000..a75313cae5 --- /dev/null +++ b/src/test/java/racingcar/RacingCarGameTest.java @@ -0,0 +1,77 @@ +package racingcar; + +import camp.nextstep.edu.missionutils.Console; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class RacingCarGameTest { + + private PrintStream standardOut; + private OutputStream captor; + + RacingCarGame racingCarGame = new RacingCarGame(); + + @BeforeEach + void beforeEach() { + this.standardOut = System.out; + this.captor = new ByteArrayOutputStream(); + System.setOut(new PrintStream(this.captor)); + } + + @AfterEach + void afterEach() { + Console.close(); // Console 내부 Scanner 닫기 + System.setOut(this.standardOut); + } + + + @Test + void 자동차이름_읽기_테스트() { + setInput("pobi,woni"); + racingCarGame.readCarNames(); + Assertions.assertThat(output()).contains("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + Map carMap = racingCarGame.getCarMap(); + Assertions.assertThat(carMap).hasSize(2); + Assertions.assertThat(carMap).containsEntry("pobi", 0); + Assertions.assertThat(carMap).containsEntry("woni", 0); + System.setOut(this.standardOut); + } + + @Test + void 자동차이름_읽기_예외테스트() { + setInput("pobi,sonnii"); + Assertions.assertThatThrownBy(() -> racingCarGame.readCarNames()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("자동차 이름은 5자 이하만 가능합니다."); + } + + @Test + void 게임횟수_읽기_테스트() { + setInput("5"); + Assertions.assertThat(racingCarGame.receiceNumOfGames()).isEqualTo(5); + } + + @Test + void 게임횟수_읽기_예외_테스트() { + setInput("ㅅ"); + Assertions.assertThatThrownBy(() -> racingCarGame.receiceNumOfGames()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("정수가 아닌 값을 입력했습니다."); + } + + private static void setInput(String... args) { + byte[] buf = String.join("\n", args).getBytes(); + System.setIn(new ByteArrayInputStream(buf)); + } + + private final String output() { + return this.captor.toString().trim(); + } +}