diff --git a/README.md b/README.md index d0286c859f..ee2949ac05 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# java-racingcar-precourse +# πŸ£μžλ™μ°¨ κ²½μ£Ό κ²Œμž„ +## κΈ°λŠ₯ μš”κ΅¬ 사항 +- 주어진 횟수 λ™μ•ˆ nλŒ€μ˜ μžλ™μ°¨λŠ” 전진 λ˜λŠ” 멈좜 수 μžˆλ‹€. +- 각 μžλ™μ°¨μ— 이름을 λΆ€μ—¬ν•  수 μžˆλ‹€. μ „μ§„ν•˜λŠ” μžλ™μ°¨λ₯Ό 좜λ ₯ν•  λ•Œ μžλ™μ°¨ 이름을 같이 좜λ ₯ν•œλ‹€. +- μžλ™μ°¨ 이름은 μ‰Όν‘œ(,)λ₯Ό κΈ°μ€€μœΌλ‘œ κ΅¬λΆ„ν•˜λ©° 이름은 5자 μ΄ν•˜λ§Œ κ°€λŠ₯ν•˜λ‹€. +- μ‚¬μš©μžλŠ” λͺ‡ 번의 이동을 ν•  것인지λ₯Ό μž…λ ₯ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€. +- μ „μ§„ν•˜λŠ” 쑰건은 0μ—μ„œ 9 μ‚¬μ΄μ—μ„œ λ¬΄μž‘μœ„ 값을 κ΅¬ν•œ ν›„ λ¬΄μž‘μœ„ 값이 4 이상일 κ²½μš°μ΄λ‹€. +- μžλ™μ°¨ κ²½μ£Ό κ²Œμž„μ„ μ™„λ£Œν•œ ν›„ λˆ„κ°€ μš°μŠΉν–ˆλŠ”μ§€λ₯Ό μ•Œλ €μ€€λ‹€. μš°μŠΉμžλŠ” ν•œ λͺ… 이상일 수 μžˆλ‹€. +- μš°μŠΉμžκ°€ μ—¬λŸ¬ λͺ…일 경우 μ‰Όν‘œ(,)λ₯Ό μ΄μš©ν•˜μ—¬ κ΅¬λΆ„ν•œλ‹€. +- μ‚¬μš©μžκ°€ 잘λͺ»λœ 값을 μž…λ ₯ν•  경우 IllegalArgumentException을 λ°œμƒμ‹œν‚¨ ν›„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ μ’…λ£Œλ˜μ–΄μ•Ό ν•œλ‹€. + +## βœ… κΈ°λŠ₯ λͺ©λ‘ +- [X] μ‰Όν‘œ(,)둜 ꡬ뢄할 수 μžˆλŠ” {\$μžλ™μ°¨ 이름 λͺ©λ‘}κ³Ό {\$κ²Œμž„νšŸμˆ˜}λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. +- [X] 각 {\$μžλ™μ°¨ 이름}의 길이가 5 초과일 경우 μ˜ˆμ™Έ(IllegalArgumentException)둜 μ²˜λ¦¬ν•œλ‹€. +- [X] 0~9의 λ²”μœ„μ—μ„œ 랜덀으둜 λΆˆλŸ¬μ™€ 4이상일 κ²½μš°μ—λ§Œ μžλ™μ°¨κ°€ μ „μ§„ν•œλ‹€. +- [X] κ²Œμž„ λΌμš΄λ“œλ³„ μ‹€ν–‰κ²°κ³Όλ₯Ό 좜λ ₯ν•œλ‹€. +- [X] μ‹€ν–‰κ²°κ³Όκ°€ λλ‚˜λ©΄ 우승자λ₯Ό 좜λ ₯ν•œλ‹€. "μ΅œμ’… 우승자 : {\$μžλ™μ°¨ 이름}" ν˜•μ‹μœΌλ‘œ 좜λ ₯ν•œλ‹€. +- [X] λ§Œμ•½ μ΅œμ’… μš°μŠΉμžκ°€ μ—¬λŸ¬ λͺ…일 경우 "μ΅œμ’… 우승자 : {\$μžλ™μ°¨ 이름}, {\$μžλ™μ°¨ 이름}" κ³Ό 같이 μ‰Όν‘œλ‘œ κ΅¬λΆ„ν•˜μ—¬ 좜λ ₯ν•œλ‹€. \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..5339df25ec 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,12 @@ package racingcar; + +import racingcar.controller.RacingCarController; + public class Application { public static void main(String[] args) { // TODO: ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„ + RacingCarController racingCarController = new RacingCarController(); + racingCarController.race(); } } diff --git a/src/main/java/racingcar/RacingCar.java b/src/main/java/racingcar/RacingCar.java new file mode 100644 index 0000000000..d691f022cc --- /dev/null +++ b/src/main/java/racingcar/RacingCar.java @@ -0,0 +1,52 @@ +package racingcar; + +import java.util.*; +import java.util.stream.Collectors; + +import camp.nextstep.edu.missionutils.Randoms; + +public class RacingCar { + public static final HashMap carList = new HashMap<>(); + public static final String moveString = "-"; + public static List Winner = new ArrayList<>(); + //public static StringBuilder resultString = new StringBuilder("μ΅œμ’… 우승자 : "); + + public static void run(final String carNameString, final String roundString){ + if (carNameString==null || carNameString.isBlank()){ + throw new IllegalArgumentException(); + } + + if (roundString==null || roundString.isBlank()){ + throw new IllegalArgumentException(); + } + int round = Integer.parseInt(roundString); + + for(final String carNameSplit: carNameString.split(",")){ + if (carNameSplit.length()>5){ + throw new IllegalArgumentException(); + } + carList.put(carNameSplit, 0); + } + + System.out.println("μ‹€ν–‰κ²°κ³Ό"); + for(int i=0; i= 4) { + carList.put(carName, carList.get(carName) + 1); + } + System.out.println(carName + " : " + moveString.repeat(carList.get(carName))); + } + System.out.println(); + } + + int winValue = Collections.max(carList.values()); + List findWinner = carList.entrySet().stream() + .filter(e -> e.getValue() == winValue) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + System.out.println("μ΅œμ’… 우승자 : " + String.join(",", findWinner)); + } +} + diff --git a/src/main/java/racingcar/controller/RacingCarController.java b/src/main/java/racingcar/controller/RacingCarController.java new file mode 100644 index 0000000000..882d335082 --- /dev/null +++ b/src/main/java/racingcar/controller/RacingCarController.java @@ -0,0 +1,50 @@ +package racingcar.controller; + +import racingcar.service.RacingCarService; +import racingcar.util.InputParser; +import racingcar.view.InputView; +import racingcar.view.OutputView; +import java.util.List; +import java.util.Map; +import static racingcar.service.RacingCarService.*; + +public class RacingCarController { + + private final InputView inputView; + private final OutputView outputView; + private final RacingCarService service; + + public RacingCarController() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + this.service = new RacingCarService(); + } + + public void race() { + final String carNameString = getInputCarNameString(); + final List carNameList = InputParser.splitCarNameString(carNameString); + final String roundCountString = getInputRoundCount(); + final int roundCount = InputParser.getIntRoundCount(roundCountString); + + run(carNameList, roundCount); + } + + private String getInputCarNameString() { + return inputView.getCarString(); + } + + private String getInputRoundCount() { + return inputView.getRoundCount(); + } + + public void run(List carNameList, int roundCount) { + final Map raceResultList = service.raceAllRound(carNameList, roundCount); + printRaceResult(raceResultList); + } + + public void printRaceResult(Map raceResultList) { + outputView.printResultMessage(); + final List winnerList = getWinnerList(raceResultList); + outputView.printWinner(winnerList); + } +} diff --git a/src/main/java/racingcar/exception/ErrorMessage.java b/src/main/java/racingcar/exception/ErrorMessage.java new file mode 100644 index 0000000000..5af13f12f3 --- /dev/null +++ b/src/main/java/racingcar/exception/ErrorMessage.java @@ -0,0 +1,18 @@ +package racingcar.exception; + +public enum ErrorMessage { + INPUT_MUST_BE_NOT_NULL("곡백은 μž…λ ₯ν•  수 μ—†μŠ΅λ‹ˆλ‹€."), + CAR_NAME_LENGTH_OVER_FIVE("μžλ™μ°¨μ˜ 이름은 5자 μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."), + CAR_NAME_MUST_UNIQUE("μ°¨ 이름은 쀑볡할 수 μ—†μŠ΅λ‹ˆλ‹€."), + INPUT_ROUND_COUNT_MUST_BE_NUMERIC("μˆ«μžκ°€ μ•„λ‹Œ λ¬Έμžκ°€ ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€."), + INPUT_ROUND_COUNT_MUST_BE_POSITIVE("κ²Œμž„μ˜ νšŸμˆ˜λŠ” 1 이상이어야 ν•©λ‹ˆλ‹€."); + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/racingcar/service/RacingCarService.java b/src/main/java/racingcar/service/RacingCarService.java new file mode 100644 index 0000000000..2c44d42c34 --- /dev/null +++ b/src/main/java/racingcar/service/RacingCarService.java @@ -0,0 +1,65 @@ +package racingcar.service; + +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 RacingCarService { + public static Map carMap = new HashMap<>(); + + + public static Map raceAllRound(List carNameList, int roundCount){ + Map carMap = initCarList(carNameList); + + for(int i=0; i initCarList(List carNameList){ + for(final String carName: carNameList){ + carMap.put(carName, 0); + } + return carMap; + } + + public static Map raceOneRound(Map carMap) { + for (String carName : carMap.keySet()) { + if (isMove()){ + carMap.put(carName, carMap.get(carName) + 1); + printOneRaceResult(carName, carMap.get(carName)); + } + } + return carMap; + } + + public static boolean isMove(){ + int randomMove = Randoms.pickNumberInRange(0, 9); + if (randomMove >= 4) { + return true; + } + else{ + return false; + } + } + + public static List getWinnerList(Map carList){ + int winValue = Collections.max(carList.values()); + List findWinner = carList.entrySet().stream() + .filter(e -> e.getValue() == winValue) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + return findWinner; + } + + public static void printOneRaceResult(String carName, int n){ + System.out.println(carName + " : " + "-".repeat(n)); + } +} diff --git a/src/main/java/racingcar/util/InputParser.java b/src/main/java/racingcar/util/InputParser.java new file mode 100644 index 0000000000..98ff8cbe67 --- /dev/null +++ b/src/main/java/racingcar/util/InputParser.java @@ -0,0 +1,31 @@ +package racingcar.util; + +import racingcar.validator.Validator; + +import java.util.Arrays; +import java.util.List; + +import static racingcar.validator.Validator.*; + +public class InputParser { + + public static List splitCarNameString(String carNameString) { + List carNames = Arrays.stream(carNameString.split(",", -1)) + .map(String::trim) + .peek(Validator::carNameStringNotNull) + .peek(Validator::carNameLength) + .toList(); + Validator.carNameDuplicated(carNames); + + return carNames; + } + + public static int getIntRoundCount(String rountCountString) { + roundCountStringNotNull(rountCountString); + isRoundCountNumeric(rountCountString); + final int roundCount = Integer.parseInt(rountCountString); + roundCountPositive(roundCount); + + return roundCount; + } +} diff --git a/src/main/java/racingcar/validator/Validator.java b/src/main/java/racingcar/validator/Validator.java new file mode 100644 index 0000000000..dce4e45ef8 --- /dev/null +++ b/src/main/java/racingcar/validator/Validator.java @@ -0,0 +1,50 @@ +package racingcar.validator; + +import racingcar.exception.ErrorMessage; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Validator { + + public static void carNameStringNotNull(String carNameString) { + if (carNameString == null || carNameString.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.INPUT_MUST_BE_NOT_NULL.getMessage()); + } + } + + public static void carNameLength(String carName) { + if (carName.length() > 5) { + throw new IllegalArgumentException(ErrorMessage.CAR_NAME_LENGTH_OVER_FIVE.getMessage()); + } + } + + public static void carNameDuplicated(List CarNameList) { + Set uniqueCarNames = new HashSet<>(CarNameList); + + if (CarNameList.size() > uniqueCarNames.size()) { + throw new IllegalArgumentException(ErrorMessage.CAR_NAME_MUST_UNIQUE.getMessage()); + } + } + + public static void roundCountStringNotNull(String roundCountString) { + if (roundCountString == null || roundCountString.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.INPUT_MUST_BE_NOT_NULL.getMessage()); + } + } + + public static void isRoundCountNumeric(String roundCountString) { + try { + Integer.parseInt(roundCountString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.INPUT_ROUND_COUNT_MUST_BE_NUMERIC.getMessage()); + } + } + + public static void roundCountPositive(int roundCount) { + if (roundCount < 1) { + throw new IllegalArgumentException(ErrorMessage.INPUT_ROUND_COUNT_MUST_BE_POSITIVE.getMessage()); + } + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000000..63c01d439b --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,16 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + public String getCarString() { + System.out.println("κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”.(이름은 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ ꡬ뢄)"); + return Console.readLine(); + } + + public String getRoundCount() { + System.out.println("μ‹œλ„ν•  νšŸμˆ˜λŠ” λͺ‡ νšŒμΈκ°€μš”?"); + return Console.readLine(); + } +} diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000000..7bf14a973b --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,14 @@ +package racingcar.view; + +import java.util.List; + +public class OutputView { + + public void printResultMessage() { + System.out.println("μ‹€ν–‰ κ²°κ³Ό"); + } + + public void printWinner(List winnerList) { + System.out.println("μ΅œμ’… 우승자 : " + String.join(",", winnerList)); + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..76ac6ec8d0 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -1,33 +1,61 @@ package racingcar; import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.InputStream; +import java.util.HashMap; + 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 static org.junit.jupiter.api.Assertions.assertEquals; class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; private static final int STOP = 3; + @BeforeEach + void setUp() { + RacingCar.carList.clear(); + } + @Test - void κΈ°λŠ₯_ν…ŒμŠ€νŠΈ() { - assertRandomNumberInRangeTest( - () -> { - run("pobi,woni", "1"); - assertThat(output()).contains("pobi : -", "woni : ", "μ΅œμ’… 우승자 : pobi"); - }, - MOVING_FORWARD, STOP - ); + void μœ νš¨ν•œ_μž…λ ₯을_λ°›μ•˜μ„_λ•Œ_carList_μž…λ ₯_확인() { + // given + String carNames = "car1,car2,car3"; + String rounds = "5"; + + // when + RacingCar.run(carNames, rounds); + + // then + HashMap expectedCarList = new HashMap<>(); + expectedCarList.put("car1", 0); + expectedCarList.put("car2", 0); + expectedCarList.put("car3", 0); + + assertEquals(expectedCarList.keySet(), RacingCar.carList.keySet()); } @Test - void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { + void μžλ™μ°¨_μ΄λ¦„μ˜_길이가_5이상이면_μ˜ˆμ™Έλ₯Ό_λ°œμƒμ‹œν‚¨λ‹€(){ assertSimpleTest(() -> - assertThatThrownBy(() -> runException("pobi,javaji", "1")) - .isInstanceOf(IllegalArgumentException.class) + assertThatThrownBy(() -> runException("pobi,javaji", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void μœ νš¨ν•œ_μž…λ ₯이면_λΌμš΄λ“œλ³„_κ²°κ³Όλ₯Ό_좜λ ₯ν•œλ‹€() { + assertRandomNumberInRangeTest( + () -> { + RacingCar.run("pobi,woni", "3"); + assertThat(output()).contains("pobi : -", "woni : ", "μ΅œμ’… 우승자 : pobi"); + }, + MOVING_FORWARD, STOP ); } diff --git a/src/test/java/racingcar/inputView/InputViewTest.java b/src/test/java/racingcar/inputView/InputViewTest.java new file mode 100644 index 0000000000..c414ce320d --- /dev/null +++ b/src/test/java/racingcar/inputView/InputViewTest.java @@ -0,0 +1,43 @@ +package racingcar.inputView; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import racingcar.exception.ErrorMessage; +import racingcar.util.InputParser; +import racingcar.validator.Validator; +import racingcar.view.InputView; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static java.beans.Beans.isInstanceOf; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class InputViewTest { + + private InputStream originalIn; + + @BeforeEach + void setUp() { + originalIn = System.in; + } + + @AfterEach + void tearDown() { + System.setIn(originalIn); + } + + @Test + void λΌμš΄λ“œ_횟수_μœ νš¨ν•œ_μž…λ ₯_ν…ŒμŠ€νŠΈ() { + String mockInput = "5\n"; + System.setIn(new ByteArrayInputStream(mockInput.getBytes())); + + // InputView μΈμŠ€ν„΄μŠ€ 생성 + InputView inputView = new InputView(); + + // λ©”μ„œλ“œ 호좜 및 검증 + String roundCount = inputView.getRoundCount(); + assertEquals("5", roundCount); + } + +}