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