diff --git a/README.md b/README.md index d0286c859f..57834eb7a0 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ # java-racingcar-precourse + +## 🎯 κΈ°λŠ₯ λͺ©λ‘ + +### 1. μž…λ ₯ +- [ ] "κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”.(이름은 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ ꡬ뢄)" 문ꡬλ₯Ό 좜λ ₯ν•˜κ³ , μ‚¬μš©μžλ‘œλΆ€ν„° μžλ™μ°¨ 이름듀을 μž…λ ₯λ°›λŠ”λ‹€. (`Console.readLine()` ν™œμš©) +- [ ] "μ‹œλ„ν•  νšŸμˆ˜λŠ” λͺ‡ νšŒμΈκ°€μš”?" 문ꡬλ₯Ό 좜λ ₯ν•˜κ³ , μ‚¬μš©μžλ‘œλΆ€ν„° μ‹œλ„ν•  횟수λ₯Ό μž…λ ₯λ°›λŠ”λ‹€. (`Console.readLine()` ν™œμš©) +- [ ] [Test] `ApplicationTest`에 μž…λ ₯ ν”„λ‘¬ν”„νŠΈκ°€ μ •μƒμ μœΌλ‘œ 좜λ ₯λ˜λŠ”μ§€ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€. + +### 2. μ˜ˆμ™Έ 처리 +- [ ] [μ˜ˆμ™Έ] μ‚¬μš©μžκ°€ 잘λͺ»λœ 값을 μž…λ ₯ν•  경우 `IllegalArgumentException`을 λ°œμƒμ‹œν‚¨ ν›„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ’…λ£Œν•œλ‹€. + - [ ] μžλ™μ°¨ 이름이 5자λ₯Ό μ΄ˆκ³Όν•˜λŠ” 경우 + - [ ] μžλ™μ°¨ 이름 μž…λ ₯을 λΉ„μ–΄μžˆκ²Œ ν•˜κ±°λ‚˜, μ‰Όν‘œλ§Œ μž…λ ₯ν•˜λŠ” 경우 (e.g., `,,`) + - [ ] μ‹œλ„ν•  νšŸμˆ˜κ°€ μˆ«μžκ°€ μ•„λ‹Œ 경우 (e.g., "a") + - [ ] μ‹œλ„ν•  νšŸμˆ˜κ°€ 1 미만의 μ •μˆ˜μΈ 경우 (e.g., "0" λ˜λŠ” "-1") +- [ ] [Test] `ApplicationTest`의 `μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ()`λ₯Ό 톡해 각 μ˜ˆμ™Έ 상황을 κ²€μ¦ν•˜λŠ” ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€. + +### 3. 핡심 둜직 +- [ ] μž…λ ₯받은 μžλ™μ°¨ 이름듀을 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ λΆ„λ¦¬ν•˜μ—¬ 각 μžλ™μ°¨ 객체λ₯Ό μƒμ„±ν•œλ‹€. (1μ£Ό μ°¨ ν”Όλ“œλ°±: `List` λ“± μ»¬λ ‰μ…˜ μ‚¬μš©) +- [ ] 각 μžλ™μ°¨λŠ” 이름과 ν˜„μž¬ μœ„μΉ˜(position)λ₯Ό μƒνƒœλ‘œ κ°€μ§„λ‹€. +- [ ] 각 μžλ™μ°¨λ³„λ‘œ 0μ—μ„œ 9 μ‚¬μ΄μ˜ λ¬΄μž‘μœ„ 값을 κ΅¬ν•œλ‹€. (`Randoms.pickNumberInRange(0, 9)` ν™œμš©) +- [ ] λ¬΄μž‘μœ„ 값이 4 이상일 경우, ν•΄λ‹Ή μžλ™μ°¨μ˜ μœ„μΉ˜λ₯Ό 1 μ¦κ°€μ‹œν‚¨λ‹€. (μ „μ§„) +- [ ] λ¬΄μž‘μœ„ 값이 3 μ΄ν•˜μΌ 경우, μžλ™μ°¨μ˜ μœ„μΉ˜λŠ” λ³€ν•˜μ§€ μ•ŠλŠ”λ‹€. (멈좀) +- [ ] μž…λ ₯받은 μ‹œλ„ν•  횟수만큼 λͺ¨λ“  μžλ™μ°¨μ— λŒ€ν•΄ μ „μ§„ λ˜λŠ” 멈좀 λ‘œμ§μ„ λ°˜λ³΅ν•œλ‹€. + +### 4. 좜λ ₯ +- [ ] "μ‹€ν–‰ κ²°κ³Ό" 문ꡬλ₯Ό 좜λ ₯ν•œλ‹€. +- [ ] 각 차수(λΌμš΄λ“œ)κ°€ 끝날 λ•Œλ§ˆλ‹€ λͺ¨λ“  μžλ™μ°¨μ˜ ν˜„μž¬ μƒνƒœ(이름 : -)λ₯Ό 좜λ ₯ν•œλ‹€. (예: `pobi : --`) +- [ ] λͺ¨λ“  μ‹œλ„κ°€ λλ‚œ ν›„, μ΅œμ’… 우승자λ₯Ό κ²°μ •ν•œλ‹€. (κ°€μž₯ 멀리 μ΄λ™ν•œ μžλ™μ°¨) +- [ ] μ΅œμ’… 우승자 μ•ˆλ‚΄ 문ꡬλ₯Ό 좜λ ₯ν•œλ‹€. (예: "μ΅œμ’… 우승자 : pobi" λ˜λŠ” "μ΅œμ’… 우승자 : pobi, jun") + - (1μ£Ό μ°¨ ν”Όλ“œλ°±: 곡동 μš°μŠΉμžκ°€ μ—¬λŸ¬ λͺ…일 경우 `String.join()`을 ν™œμš©ν•˜μ—¬ μ‰Όν‘œ(,)둜 ꡬ뢄) +- [ ] [Test] `ApplicationTest`의 `κΈ°λŠ₯_ν…ŒμŠ€νŠΈ()`λ₯Ό 톡해 μ΅œμ’… μ‹€ν–‰ κ²°κ³Όκ°€ μ˜ˆμƒκ³Ό λ™μΌν•œμ§€ κ²€μ¦ν•˜λŠ” ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€. \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..18dcafa862 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,164 @@ package racingcar; +import camp.nextstep.edu.missionutils.Console; +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + public class Application { public static void main(String[] args) { - // TODO: ν”„λ‘œκ·Έλž¨ κ΅¬ν˜„ + String carNamesInput = getCarNamesInput(); + List cars = createCarsFromInput(carNamesInput); + + String attemptCountInput = getAttemptCountInput(); + int attemptCount = parseAndValidateAttemptCount(attemptCountInput); + + System.out.println("\nμ‹€ν–‰ κ²°κ³Ό"); + runRacingGame(cars, attemptCount); + + printFinalWinners(cars); // --- μ΅œμ’… 우승자 좜λ ₯ 둜직 μΆ”κ°€ --- + } + + private static String getCarNamesInput() { + System.out.println("κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”.(이름은 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ ꡬ뢄)"); + return Console.readLine(); + } + + private static String getAttemptCountInput() { + System.out.println("μ‹œλ„ν•  νšŸμˆ˜λŠ” λͺ‡ νšŒμΈκ°€μš”?"); + return Console.readLine(); + } + + // --- μžλ™μ°¨ 생성 및 검증 둜직 --- + + private static List createCarsFromInput(String carNamesInput) { + validateCarNamesNotEmpty(carNamesInput); + String[] names = carNamesInput.split(","); + + return Arrays.stream(names) + .map(Application::validateAndCreateCar) + .collect(Collectors.toList()); + } + + private static void validateCarNamesNotEmpty(String carNamesInput) { + if (carNamesInput.isEmpty()) { + throw new IllegalArgumentException("μžλ™μ°¨ 이름이 μž…λ ₯λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."); + } + } + + private static Car validateAndCreateCar(String name) { + validateNameLength(name); + return new Car(name); + } + + private static void validateNameLength(String name) { + if (name.isEmpty()) { + throw new IllegalArgumentException("μžλ™μ°¨ 이름은 곡백일 수 μ—†μŠ΅λ‹ˆλ‹€."); + } + if (name.length() > 5) { + throw new IllegalArgumentException("μžλ™μ°¨ 이름은 5자 μ΄ν•˜λ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€."); + } + } + + // --- μ‹œλ„ 횟수 νŒŒμ‹± 및 검증 둜직 --- + + private static int parseAndValidateAttemptCount(String attemptCountInput) { + validateAttemptCountNumeric(attemptCountInput); + + int count = Integer.parseInt(attemptCountInput); + validateAttemptCountRange(count); + + return count; + } + + private static void validateAttemptCountNumeric(String attemptCountInput) { + if (attemptCountInput.isEmpty()) { + throw new IllegalArgumentException("μ‹œλ„ νšŸμˆ˜λŠ” 1 μ΄μƒμ˜ μ •μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + for (char c : attemptCountInput.toCharArray()) { + validateIsDigit(c); + } + } + + private static void validateIsDigit(char c) { + if (!Character.isDigit(c)) { + throw new IllegalArgumentException("μ‹œλ„ νšŸμˆ˜λŠ” 숫자만 κ°€λŠ₯ν•©λ‹ˆλ‹€."); + } + } + + private static void validateAttemptCountRange(int count) { + if (count < 1) { + throw new IllegalArgumentException("μ‹œλ„ νšŸμˆ˜λŠ” 1 μ΄μƒμ˜ μ •μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€."); + } + } + + // --- λ ˆμ΄μ‹± 둜직 --- + + private static void runRacingGame(List cars, int attemptCount) { + for (int i = 0; i < attemptCount; i++) { + runSingleRound(cars); + printRoundResult(cars); + } + } + + private static void runSingleRound(List cars) { + for (Car car : cars) { + int randomNumber = Randoms.pickNumberInRange(0, 9); + car.tryMove(randomNumber); + } + } + + // --- 좜λ ₯ 둜직 --- + + private static void printRoundResult(List cars) { + for (Car car : cars) { + String positionBar = generatePositionBar(car.getPosition()); + System.out.println(car.getName() + " : " + positionBar); + } + System.out.println(); + } + + private static String generatePositionBar(int position) { + StringBuilder bar = new StringBuilder(); + for (int i = 0; i < position; i++) { + bar.append("-"); + } + return bar.toString(); + } + + // --- μ΅œμ’… 우승자 κ²°μ • 및 좜λ ₯ --- + + /** + * 4. 좜λ ₯ - μ΅œμ’… 우승자 좜λ ₯ + */ + private static void printFinalWinners(List cars) { + List winnerNames = findWinners(cars); + String winnerString = String.join(", ", winnerNames); // 곡동 우승자 처리 + System.out.println("μ΅œμ’… 우승자 : " + winnerString); + } + + /** + * 4. 좜λ ₯ - μ΅œμ’… 우승자 κ²°μ • (κ°€μž₯ 멀리 μ΄λ™ν•œ μžλ™μ°¨) + */ + private static List findWinners(List cars) { + int maxPosition = findMaxPosition(cars); + + // Java API(stream)λ₯Ό ν™œμš©ν•˜μ—¬ maxPositionκ³Ό λ™μΌν•œ λͺ¨λ“  우승자λ₯Ό 찾음 + return cars.stream() + .filter(car -> car.getPosition() == maxPosition) + .map(Car::getName) + .collect(Collectors.toList()); + } + + private static int findMaxPosition(List cars) { + int maxPosition = 0; + for (Car car : cars) { + if (car.getPosition() > maxPosition) { + maxPosition = car.getPosition(); + } + } + return maxPosition; } -} +} \ No newline at end of file diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java new file mode 100644 index 0000000000..cb13e8c2bb --- /dev/null +++ b/src/main/java/racingcar/Car.java @@ -0,0 +1,31 @@ +package racingcar; + +public class Car { + private static final int MOVING_FORWARD_THRESHOLD = 4; // μ „μ§„ 쑰건 + + private final String name; + private int position; + + public Car(String name) { + this.name = name; + this.position = 0; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + /** + * λ¬΄μž‘μœ„ 값을 λ°›μ•„ 4 이상이면 μ „μ§„, 3 μ΄ν•˜λ©΄ μ •μ§€ν•œλ‹€. + * @param randomNumber 0μ—μ„œ 9 μ‚¬μ΄μ˜ λ¬΄μž‘μœ„ κ°’ + */ + public void tryMove(int randomNumber) { + if (randomNumber >= MOVING_FORWARD_THRESHOLD) { + this.position++; + } + } +} \ No newline at end of file diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..41efff2276 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -15,24 +15,69 @@ class ApplicationTest extends NsTest { @Test void κΈ°λŠ₯_ν…ŒμŠ€νŠΈ() { assertRandomNumberInRangeTest( - () -> { - run("pobi,woni", "1"); - assertThat(output()).contains("pobi : -", "woni : ", "μ΅œμ’… 우승자 : pobi"); - }, - MOVING_FORWARD, STOP + () -> { + run("pobi,woni", "1"); + assertThat(output()).contains("pobi : -", "woni : ", "μ΅œμ’… 우승자 : pobi"); + }, + MOVING_FORWARD, STOP ); } @Test void μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("pobi,javaji", "1")) - .isInstanceOf(IllegalArgumentException.class) + assertThatThrownBy(() -> runException("pobi,javaji", "1")) + .isInstanceOf(IllegalArgumentException.class) ); } + @Test + void μž…λ ₯_ν”„λ‘¬ν”„νŠΈ_ν…ŒμŠ€νŠΈ() { + assertSimpleTest(() -> { + run("pobi", "1"); // ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ μž„μ‹œ μž…λ ₯κ°’ 제곡 + assertThat(output()).contains( + "κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”.(이름은 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ ꡬ뢄)", + "μ‹œλ„ν•  νšŸμˆ˜λŠ” λͺ‡ νšŒμΈκ°€μš”?" + ); + }); + } + + @Test + void μžλ™μ°¨_이름_곡백_μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { + assertSimpleTest(() -> { + // 이름이 μ‰Όν‘œ(,) μ‚¬μ΄μ—μ„œ λΉ„μ–΄μžˆλŠ” 경우 (e.g., ",,pobi") + assertThatThrownBy(() -> runException(",,pobi", "1")) + .isInstanceOf(IllegalArgumentException.class); + + // 이름이 μ•„μ˜ˆ λΉ„μ–΄μžˆλŠ” 경우 + assertThatThrownBy(() -> runException("", "1")) + .isInstanceOf(IllegalArgumentException.class); + }); + } + + @Test + void μ‹œλ„_횟수_숫자_μ•„λ‹˜_μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { + assertSimpleTest(() -> { + assertThatThrownBy(() -> runException("pobi,woni", "a")) + .isInstanceOf(IllegalArgumentException.class); + }); + } + + @Test + void μ‹œλ„_횟수_1_미만_μ˜ˆμ™Έ_ν…ŒμŠ€νŠΈ() { + assertSimpleTest(() -> { + // μ‹œλ„ νšŸμˆ˜κ°€ λΉ„μ–΄μžˆλŠ” 경우 + assertThatThrownBy(() -> runException("pobi,woni", "")) + .isInstanceOf(IllegalArgumentException.class); + + // μ‹œλ„ νšŸμˆ˜κ°€ 0인 경우 + assertThatThrownBy(() -> runException("pobi,woni", "0")) + .isInstanceOf(IllegalArgumentException.class); + }); + } + @Override public void runMain() { Application.main(new String[]{}); } -} +} \ No newline at end of file diff --git a/src/test/java/racingcar/CarTest.java b/src/test/java/racingcar/CarTest.java new file mode 100644 index 0000000000..c741fa1cb3 --- /dev/null +++ b/src/test/java/racingcar/CarTest.java @@ -0,0 +1,45 @@ +package racingcar; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +class CarTest { + @Test + void μžλ™μ°¨_생성_ν…ŒμŠ€νŠΈ() { + // given + String carName = "pobi"; + + // when + Car car = new Car(carName); + + // then + assertThat(car.getName()).isEqualTo(carName); + assertThat(car.getPosition()).isEqualTo(0); + } + + @Test + void μžλ™μ°¨_μ „μ§„_ν…ŒμŠ€νŠΈ() { + // given + Car car = new Car("pobi"); + int forwardNumber = 4; // 4 이상은 μ „μ§„ + + // when + car.tryMove(forwardNumber); + + // then + assertThat(car.getPosition()).isEqualTo(1); + } + + @Test + void μžλ™μ°¨_μ •μ§€_ν…ŒμŠ€νŠΈ() { + // given + Car car = new Car("pobi"); + int stopNumber = 3; // 3 μ΄ν•˜λŠ” μ •μ§€ + + // when + car.tryMove(stopNumber); + + // then + assertThat(car.getPosition()).isEqualTo(0); + } +} \ No newline at end of file