Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
# java-racingcar-precourse

# java-racingcar-precourse
<br>

## 구현할 기능 목록

### 1. 자동차 이름 입력 메세지를 출력하고, 데이터를 입력받는 기능
### 2. 자동차 이름 입력값을 검증하는 기능
- 이름이 1개만 입력되었을 경우 에러 발생
- 0글자 또는 6글자 이상의 이름이 입력되었을 경우 에러 발생
- 중복된 이름이 입력되었을 경우 에러 발생
### 3. 시도 횟수 입력 메세지를 출력하고, 데이터를 입력받는 기능
### 4. 시도 횟수 입력값을 검증하는 기능
- 입력받은 값이 1이상의 자연수가 아닐 경우 에러 발생
### 5. 0~9 사이의 무작위 정수 생성 후, 4 이상일경우 자동차를 전진시키는 기능
### 6. 모든 자동차의 5번 기능을 일괄적으로 실행 후 결과를 반환하는 기능
### 7. 6번의 실행 결과를 화면에 출력하는 기능
### 8. 우승자를 계산하는 기능
### 9. 우승자를 출력하는 기능
<br>

## 모듈별 클래스의 기능 정리

### 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
- 자동차 이름 입력값 검증
- 시도 횟수 입력값 검증
8 changes: 8 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import controller.Controller;

public class Application {

public static void main(String[] args) {
Controller.playGame();
}
}
61 changes: 61 additions & 0 deletions src/main/java/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -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<String> names = getNames();
Game game = new Game(names);

int attemptNum = getAttemptNum();

MoveView.printResultMessage();
for (int i = 0; i < attemptNum; i++) {
Map<String, Integer> positions = game.play();
MoveView.printResult(positions);
}

List<String> winners = game.getWinners();
WinnerView.printWinners(winners);
}

private static List<String> getNames() {
InputView.printCarNamesMesage();
List<String> 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);
}
}
25 changes: 25 additions & 0 deletions src/main/java/model/Car.java
Original file line number Diff line number Diff line change
@@ -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++;
}
}
}
50 changes: 50 additions & 0 deletions src/main/java/model/Game.java
Original file line number Diff line number Diff line change
@@ -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<Car> cars;

public Game(List<String> names) {
this.cars = new ArrayList<>();

for (String name : names) {
cars.add(new Car(name));
}
}

private Map<String, Integer> getPositions() {
Map<String, Integer> positions = new LinkedHashMap<>();
for (Car car : cars) {
positions.put(car.getName(), car.getPosition());
}
return positions;
}

public Map<String, Integer> play() {
for (Car car : cars) {
car.tryMove();
}
return getPositions();
}

public List<String> getWinners() {
Map<String, Integer> positions = getPositions();
List<String> 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;
}
}
35 changes: 35 additions & 0 deletions src/main/java/utils/InputValidator.java
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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 이상의 자연수로 입력해야 합니다.");
}
}
}
19 changes: 19 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
17 changes: 17 additions & 0 deletions src/main/java/view/MoveView.java
Original file line number Diff line number Diff line change
@@ -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<String, Integer> positions) {
for (Map.Entry<String, Integer> entry : positions.entrySet()) {
System.out.println(entry.getKey() + " : " + "-".repeat(entry.getValue()));
}
System.out.println();
}
}
10 changes: 10 additions & 0 deletions src/main/java/view/WinnerView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package view;

import java.util.List;

public class WinnerView {
public static void printWinners(List<String> winners) {
System.out.println("최종 우승자 : " + String.join(", ", winners));
}
}

65 changes: 65 additions & 0 deletions src/test/java/utils/InputValidatorTest.java
Original file line number Diff line number Diff line change
@@ -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<String> names = List.of("name1");

Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("0글자의 이름이 입력되었을 경우 에러 발생")
void validateNames_EmptyName_ThrowsException() {
List<String> names = List.of("name1", "name2", "");

Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("6글자 이상의 이름이 입력되었을 경우 에러 발생")
void validateNames_NameLengthMoreThan6_ThrowsException() {
List<String> names = List.of("name1", "name2", "name345");

Assertions.assertThatThrownBy(() -> InputValidator.validateNames(names))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("중복되는 이름이 입력되었을 경우 에러 발생")
void validateNames_DuplicatedNames_ThrowsException() {
List<String> 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);
}
}