diff --git a/README.md b/README.md index 491aece1..57c37284 100644 --- a/README.md +++ b/README.md @@ -1 +1,50 @@ -# java-racingcar-precourse \ No newline at end of file + +# java-racingcar-precourse +
+ +## 구현할 기능 목록 + +### 1. 자동차 이름 입력 메세지를 출력하고, 데이터를 입력받는 기능 +### 2. 자동차 이름 입력값을 검증하는 기능 +- 이름이 1개만 입력되었을 경우 에러 발생 +- 0글자 또는 6글자 이상의 이름이 입력되었을 경우 에러 발생 +- 중복된 이름이 입력되었을 경우 에러 발생 +### 3. 시도 횟수 입력 메세지를 출력하고, 데이터를 입력받는 기능 +### 4. 시도 횟수 입력값을 검증하는 기능 +- 입력받은 값이 1이상의 자연수가 아닐 경우 에러 발생 +### 5. 0~9 사이의 무작위 정수 생성 후, 4 이상일경우 자동차를 전진시키는 기능 +### 6. 모든 자동차의 5번 기능을 일괄적으로 실행 후 결과를 반환하는 기능 +### 7. 6번의 실행 결과를 화면에 출력하는 기능 +### 8. 우승자를 계산하는 기능 +### 9. 우승자를 출력하는 기능 +
+ +## 모듈별 클래스의 기능 정리 + +### model +1. Car + - 이름, 전진 횟수 저장 + - 0~9 사이의 랜덤 숫자 생성 후 4이상일경우 전진 횟수를 증가시키는 기능 +2. Game + - Car 객체 생성 + - 모든 Car 객체의 전진 기능 호출 후 전진 결과 반환 + - 우승자 반환 + +### view +1. InputView + - 자동차 이름 입력 메세지 출력, 입력받은 데이터 반환 + - 시도 횟수 입력 메세지 출력, 입력받은 데이터 반환 +2. MoveView + - 실행 결과 메세지 출력 + - 각 자동차의 이름과 전진 횟수 출력 +3. WinnerView + - 우승자 출력 + +### controller +1. Controller + - view에서 받은 입력을 검증하고, model에서 데이터를 받아 다시 view를 호출하며 게임을 진행하는 기능 + +### util +1. InputValidator + - 자동차 이름 입력값 검증 + - 시도 횟수 입력값 검증 \ No newline at end of file diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..68c9b862 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,8 @@ +import controller.Controller; + +public class Application { + + public static void main(String[] args) { + Controller.playGame(); + } +} diff --git a/src/main/java/controller/Controller.java b/src/main/java/controller/Controller.java new file mode 100644 index 00000000..a78f5e18 --- /dev/null +++ b/src/main/java/controller/Controller.java @@ -0,0 +1,61 @@ +package controller; + +import java.util.List; +import java.util.Map; +import model.Game; +import utils.InputValidator; +import view.InputView; +import view.MoveView; +import view.WinnerView; + +public class Controller { + + public static void playGame() { + List names = getNames(); + Game game = new Game(names); + + int attemptNum = getAttemptNum(); + + MoveView.printResultMessage(); + for (int i = 0; i < attemptNum; i++) { + Map positions = game.play(); + MoveView.printResult(positions); + } + + List winners = game.getWinners(); + WinnerView.printWinners(winners); + } + + private static List getNames() { + InputView.printCarNamesMesage(); + List names = null; + while (names == null) { + try { + names = List.of(InputView.getInput().split(",")); + InputValidator.validateNames(names); + } + catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + names = null; + } + } + return names; + } + + private static int getAttemptNum() { + InputView.printAttemptNumMessage(); + String attemptNumInput = null; + + while (attemptNumInput == null) { + try { + attemptNumInput = InputView.getInput(); + InputValidator.validateAttemptNum(attemptNumInput); + } + catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + attemptNumInput = null; + } + } + return Integer.parseInt(attemptNumInput); + } +} diff --git a/src/main/java/model/Car.java b/src/main/java/model/Car.java new file mode 100644 index 00000000..346ae3c6 --- /dev/null +++ b/src/main/java/model/Car.java @@ -0,0 +1,25 @@ +package model; + +public class Car { + private final String name; + private int position = 0; + + public Car(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + public void tryMove() { + int randNum = (int)(Math.random() * 10); + if (randNum >= 4) { + position++; + } + } +} diff --git a/src/main/java/model/Game.java b/src/main/java/model/Game.java new file mode 100644 index 00000000..b6e182af --- /dev/null +++ b/src/main/java/model/Game.java @@ -0,0 +1,50 @@ +package model; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +public class Game { + private List cars; + + public Game(List names) { + this.cars = new ArrayList<>(); + + for (String name : names) { + cars.add(new Car(name)); + } + } + + private Map getPositions() { + Map positions = new LinkedHashMap<>(); + for (Car car : cars) { + positions.put(car.getName(), car.getPosition()); + } + return positions; + } + + public Map play() { + for (Car car : cars) { + car.tryMove(); + } + return getPositions(); + } + + public List getWinners() { + Map positions = getPositions(); + List winners = new ArrayList<>(); + + int max_position = positions.values().stream() + .max(Integer::compareTo) + .orElseThrow(NoSuchElementException::new); + + for (String name : positions.keySet()) { + if (positions.get(name) == max_position) { + winners.add(name); + } + } + return winners; + } +} diff --git a/src/main/java/utils/InputValidator.java b/src/main/java/utils/InputValidator.java new file mode 100644 index 00000000..f88966f7 --- /dev/null +++ b/src/main/java/utils/InputValidator.java @@ -0,0 +1,35 @@ +package utils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class InputValidator { + public static void validateNames(List input) { + if (input.size() < 2) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 2개 이상 입력해야 합니다."); + } + + for (String name : input) { + if (name.isEmpty() || name.length() > 5) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 1~5글자여야 합니다."); + } + } + + Set names = new HashSet<>(input); + if (names.size() != input.size()) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 중복될 수 없습니다."); + } + } + + public static void validateAttemptNum(String input) { + try { + int num = Integer.parseInt(input); + if (num < 1) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 0 이하가 될 수 없습니다."); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 1 이상의 자연수로 입력해야 합니다."); + } + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..227a8c27 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,19 @@ +package view; + +import java.util.Scanner; + +public class InputView { + private static final Scanner scanner = new Scanner(System.in); + + public static void printCarNamesMesage() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + } + + public static void printAttemptNumMessage() { + System.out.println("시도할 회수는 몇회인가요?"); + } + + public static String getInput() { + return scanner.nextLine(); + } +} diff --git a/src/main/java/view/MoveView.java b/src/main/java/view/MoveView.java new file mode 100644 index 00000000..fa0a4def --- /dev/null +++ b/src/main/java/view/MoveView.java @@ -0,0 +1,17 @@ +package view; + +import java.util.Map; + +public class MoveView { + + public static void printResultMessage() { + System.out.println("\n실행 결과"); + } + + public static void printResult(Map positions) { + for (Map.Entry entry : positions.entrySet()) { + System.out.println(entry.getKey() + " : " + "-".repeat(entry.getValue())); + } + System.out.println(); + } +} diff --git a/src/main/java/view/WinnerView.java b/src/main/java/view/WinnerView.java new file mode 100644 index 00000000..768f5ed3 --- /dev/null +++ b/src/main/java/view/WinnerView.java @@ -0,0 +1,10 @@ +package view; + +import java.util.List; + +public class WinnerView { + public static void printWinners(List winners) { + System.out.println("최종 우승자 : " + String.join(", ", winners)); + } +} + diff --git a/src/test/java/utils/InputValidatorTest.java b/src/test/java/utils/InputValidatorTest.java new file mode 100644 index 00000000..150f3f98 --- /dev/null +++ b/src/test/java/utils/InputValidatorTest.java @@ -0,0 +1,65 @@ +package utils; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class InputValidatorTest { + + @Test + @DisplayName("이름이 2개 미만 입력되었을 경우 에러 발생") + void validateNames_NamesCountsLessThanTwo_ThrowsException() { + List names = List.of("name1"); + + Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("0글자의 이름이 입력되었을 경우 에러 발생") + void validateNames_EmptyName_ThrowsException() { + List names = List.of("name1", "name2", ""); + + Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("6글자 이상의 이름이 입력되었을 경우 에러 발생") + void validateNames_NameLengthMoreThan6_ThrowsException() { + List names = List.of("name1", "name2", "name345"); + + Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("중복되는 이름이 입력되었을 경우 에러 발생") + void validateNames_DuplicatedNames_ThrowsException() { + List names = List.of("name1", "name2", "name2"); + + Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("숫자가 아닌 값을 입력하면 에러 발생") + void validateAttemptNum_NotANumber_ThrowsException() { + String input = "a123"; + + Assertions.assertThatThrownBy(() -> InputValidator.validateAttemptNum(input)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("입력받은 값이 0 이하의 정수일 경우 에러 발생") + void validateAttemptNum_NumberLessThanOne_ThrowsException() { + String input = "-1"; + + Assertions.assertThatThrownBy(() -> InputValidator.validateAttemptNum(input)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file