diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..75e35d8430 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,102 @@ package racingcar; +import java.util.ArrayList; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + + // 입력받기 + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉽표(,) 기준으로 구분)"); + String[] cars = camp.nextstep.edu.missionutils.Console.readLine().split(","); + + System.out.println("시도할 횟수는 몇 회인가요?"); + int maxTryCnt = Integer.parseInt(camp.nextstep.edu.missionutils.Console.readLine()); + int currentTryCnt = 0; + int[] currentRecord = new int[]{0, 0, 0}; + + System.out.println(); + System.out.println("실행결과"); + + testValidInput(cars); + + while (currentTryCnt < maxTryCnt) { + raceOneTurn(cars, currentRecord); + currentTryCnt++; + System.out.println(); + } + + int[] maxIndices = findMaxIndices(currentRecord); + + ArrayList winners = findWinners(cars, currentRecord); + + System.out.println("최종 우승자 : " + String.join(", ", winners)); + } + + public static void testValidInput(String[] cars) { + + for (int i = 0; i < cars.length; i++) { + if (cars[i].length() > 5) { + throw new IllegalArgumentException("입력이올바르지 않습니다"); + } + } + } + + static void raceOneTurn(String[] cars, int[] currentRecord) { + for (int i = 0; i < cars.length; i++) { + moveAndPrintCar(cars[i], currentRecord, i); + } } + + static void moveAndPrintCar(String carName, int[] currentRecord, int index) { + int randNum = camp.nextstep.edu.missionutils.Randoms.pickNumberInRange(0, 9); + if (randNum >= 4) { + currentRecord[index]++; + } + System.out.println(carName + " : " + "-".repeat(currentRecord[index])); + } + + public static int[] findMaxIndices(int[] arr) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + + // 1. 최댓값 찾기 + int maxValue = arr[0]; + for (int num : arr) { + maxValue = Math.max(maxValue, num); + } + + // 2. 최댓값의 개수 세기 + int count = 0; + for (int num : arr) { + if (num == maxValue) { + count++; + } + } + + // 3. 결과 배열 생성 및 최댓값의 인덱스 저장 + int[] maxIndices = new int[count]; + int index = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] == maxValue) { + maxIndices[index++] = i; + } + } + + return maxIndices; + } + + private static ArrayList findWinners(String[] cars, int[] currentRecord) { + int[] maxIndices = findMaxIndices(currentRecord); + ArrayList winners = new ArrayList<>(); + + for (int i = 0; i < maxIndices.length; i++) { + winners.add(cars[maxIndices[i]]); + } + + return winners; + } + + } diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..e56d2dea39 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -1,12 +1,21 @@ package racingcar; import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.ArrayList; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; 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 static org.assertj.core.api.Fail.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; @@ -31,6 +40,111 @@ class ApplicationTest extends NsTest { ); } + @Nested + @DisplayName("자동차 이름 유효성 검사 (testValidInput)") + class NameValidationTest { + + @Test + @DisplayName("각 자동차 이름이 5자 이하이면 예외가 발생하지 않는다") + void validCarNames_doNotThrow() { + String[] cars = {"pobi", "woni", "jun"}; + assertDoesNotThrow(() -> Application.testValidInput(cars)); + } + + @Test + @DisplayName("자동차 이름이 6자 이상이면 IllegalArgumentException 발생") + void tooLongName_throwsException() { + String[] cars = {"pobi", "superlong"}; // superlong 은 9글자 + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> Application.testValidInput(cars) + ); + assertEquals("입력이올바르지 않습니다", ex.getMessage()); + } + } + + @Nested + @DisplayName("우승자 인덱스 찾기 (findMaxIndices)") + class FindMaxIndicesTest { + + @Test + @DisplayName("여러 값 중 최댓값의 인덱스를 모두 반환한다") + void returnsAllMaxIndices() { + int[] record = {3, 5, 5, 2}; + int[] result = Application.findMaxIndices(record); + + // 기대: 최댓값 5는 index 1과 2 + assertArrayEquals(new int[]{1, 2}, result); + } + + @Test + @DisplayName("배열이 비어있거나 null이면 빈 배열 반환") + void emptyOrNullInput() { + assertArrayEquals(new int[]{}, Application.findMaxIndices(new int[]{})); + assertArrayEquals(new int[]{}, Application.findMaxIndices(null)); + } + + @Test + @DisplayName("모든 값이 동일하면 모든 인덱스를 반환") + void allSameValues() { + int[] record = {4, 4, 4}; + int[] result = Application.findMaxIndices(record); + + assertArrayEquals(new int[]{0, 1, 2}, result); + } + } + + @Nested + @DisplayName("우승자 찾기 (findWinners)") + class FindWinnersTest { + + @Test + @DisplayName("최댓값을 가진 자동차 이름만 반환한다") + void winnersByRecord() { + String[] cars = {"pobi", "woni", "jun"}; + int[] record = {4, 7, 7}; // woni와 jun이 공동 1등 + + ArrayList winners = callFindWinners(cars, record); + + assertEquals(2, winners.size()); + assertTrue(winners.contains("woni")); + assertTrue(winners.contains("jun")); + } + + @Test + @DisplayName("단독 우승자가 있을 경우 한 명만 반환한다") + void singleWinner() { + String[] cars = {"pobi", "woni", "jun"}; + int[] record = {1, 5, 3}; // woni 우승 + + ArrayList winners = callFindWinners(cars, record); + + assertEquals(1, winners.size()); + assertEquals("woni", winners.get(0)); + } + + // findWinners가 private 이라 직접 못 부르므로 + // 동일한 로직을 우회 호출 또는 간접 테스트(메인 로직)를 하기 어렵다. + // 여기서는 테스트 편의를 위해 같은 패키지 내에서 + // 리플렉션으로 private 메서드를 호출하는 헬퍼를 만든다. + private ArrayList callFindWinners(String[] cars, int[] currentRecord) { + try { + var method = Application.class.getDeclaredMethod( + "findWinners", String[].class, int[].class + ); + method.setAccessible(true); + @SuppressWarnings("unchecked") + ArrayList winners = + (ArrayList) method.invoke(null, cars, currentRecord); + return winners; + } catch (Exception e) { + fail("리플렉션 호출 실패: " + e.getMessage()); + return null; // unreachable + } + } + } + + @Override public void runMain() { Application.main(new String[]{});