Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# java-racingcar-precourse
# 자동차 경주
## 학습 목표
- 여러 역할을 수행하는 큰 함수를 단일 역할을 수행하는 작은 함수로 분리한다.
- 테스트 도구를 사용하는 방법을 배우고 프로그램이 제대로 작동하는지 테스트한다.
- 1주 차 공통 피드백(디스코드 참고)을 최대한 반영한다.

## 기능 요구 사항
초간단 자동차 경주 게임을 구현한다.

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

## 프로그래밍 요구 사항 1
- JDK 21 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 Application의 main()이다.
- build.gradle 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 System.exit()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Java Style Guide를 원칙으로 한다.

## 프로그래밍 요구 사항 2
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.

## 기능 목록
- [x] 사용자에게 자동차 이름 입력 받기
- [x] null이라면? 공백이라면? (예외: 한글자 이상 이름 입력)
- [x] 입력 받은 이름을 `,` 기준으로 파싱
- [x] 입력 받은 이름 예외 검사
- [x] 5자 이상이라면? (예외: 5자 이하로만 이름 부여 가능)
- [x] 문자가 아닌 숫자나 `,`이외의 기호라면? (예외: 영문자만 입력 가능)
- [x] 빈 토큰이 있다면? (예외: 빈 이름은 허용되지 않음)
- [x] 사용자에게 이동 횟수 입력 받기
- [x] null이라면? 공백이라면? (예외: 잘못된 숫자 입력)
- [x] 입력 받은 횟수 숫자로 변환
- [x] 숫자가 아닌 값을 입력했다면? (예외: 숫자만 입력 가능)
- [x] 입력 받은 횟수 예외 검사
- [x] 입력된 숫자가 0이라면? (예외: 1 이상의 숫자를 입력)
- [x] 자동차 수만큼 0-9 숫자 랜덤 돌리기
- [x] 랜덤 결과로 +1 or 0 저장
- [x] +1 or 0 만큼 전진 or 유지
- [x] 사용자에게 과정 출력
- [x] 최종 우승자 출력
13 changes: 12 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package racingcar;

import java.util.List;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
String plyeNames = NameInputView.nameInput();
List<String> names = NameParser.split(plyeNames);
names = NameValidator.validate(names);

String playCount = CountInputView.countInput();
int count = CountParser.parse(playCount);
count = CountValidator.countValidate(count);

RacingGame.run(names, count);

}
}
19 changes: 19 additions & 0 deletions src/main/java/racingcar/CountInputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;

public class CountInputView {

private CountInputView() {}

public static String countInput() {
System.out.println("시도할 횟수는 몇 회 인가요?");
String countInput = Console.readLine();

if (countInput == null || countInput.isBlank()) {
throw new IllegalArgumentException("숫자를 입력해야 합니다.");
}

return countInput;
}
}
14 changes: 14 additions & 0 deletions src/main/java/racingcar/CountParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package racingcar;

public class CountParser {

private CountParser() {}

public static int parse(String countInput) {
try {
return Integer.parseInt(countInput);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("시도 횟수는 숫자만 입력 가능합니다.");
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/racingcar/CountValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package racingcar;

public class CountValidator {

private CountValidator() {}

public static int countValidate(int countInput) {
if (countInput < 1) {
throw new IllegalArgumentException("1 이상의 시도 횟수를 입력해야 합니다.");
}

return countInput;
}
}
27 changes: 27 additions & 0 deletions src/main/java/racingcar/MoveAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package racingcar;

import java.util.List;
import java.util.Map;

public class MoveAction {

private MoveAction() {}

public static void printAction(List<String> names, Map<String, Integer>positions, List<Integer> moves) {

for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
int move = moves.get(i);

if (move == 1) {
positions.put(name, positions.get(name) + 1);
}
}

for (String name : names) {
int position = positions.get(name);
System.out.println(name + " : " + "-".repeat(position));
}
System.out.println();
}
}
23 changes: 23 additions & 0 deletions src/main/java/racingcar/MoveResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racingcar;

import java.util.ArrayList;
import java.util.List;

public class MoveResult {

private MoveResult() {}

public static List<Integer> generateMoveResults(List<Integer> results) {
List<Integer> moves = new ArrayList<>();

for (Integer result : results) {
if (result < 4) {
moves.add(0);
continue;
}
moves.add(1);
}

return moves;
}
}
19 changes: 19 additions & 0 deletions src/main/java/racingcar/NameInputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;

public class NameInputView {

private NameInputView() {}

public static String nameInput() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String name = Console.readLine();

if (name == null || name.isBlank()) {
throw new IllegalArgumentException("한 글자 이상 이름을 입력해야 합니다.");
}

return name;
}
}
15 changes: 15 additions & 0 deletions src/main/java/racingcar/NameParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package racingcar;

import java.util.List;
import java.util.Arrays;

public class NameParser {

private NameParser() {}

public static List<String> split(String name) {
return Arrays.stream(name.split(","))
.map(String::trim)
.toList();
}
}
32 changes: 32 additions & 0 deletions src/main/java/racingcar/NameValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package racingcar;

import java.util.List;

public class NameValidator {

private NameValidator() {}

public static List<String> validate(List<String> names) {
if (names.isEmpty()) {
throw new IllegalArgumentException("한 글자 이상 이름을 입력해야 합니다.");
}

for (String name : names) {
if (name.length() > 5) {
throw new IllegalArgumentException("5자 이하로만 이름 부여가 가능합니다.");
}

if (!name.matches("[a-zA-Z]+")) {
throw new IllegalArgumentException("영문자만 입력 가능합니다.");
}

if (name.isBlank()) {
throw new IllegalArgumentException("빈 이름은 허용되지 않습니다.");
}
}

return names;
}


}
48 changes: 48 additions & 0 deletions src/main/java/racingcar/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package racingcar;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class RacingGame {

public static void run(List<String> names, int count) {
Map<String, Integer> positions = initPositions(names);
System.out.println("\n실행 결과");

for (int i = 0; i < count; i++) {
playRound(names, positions);
}

int max = 0;
for (Integer distance : positions.values()) {
if (distance > max) {
max = distance;
}
}

List<String> winners = new ArrayList<>();
for (String name : positions.keySet()) {
if (positions.get(name) == max) {
winners.add(name);
}
}

System.out.println("최종 우승자 : " + String.join(", " , winners));
}

private static Map<String, Integer> initPositions(List<String> names) {
Map<String, Integer> positions = new LinkedHashMap<>();
for (String name : names) {
positions.put(name, 0);
}
return positions;
}

private static void playRound(List<String> names, Map<String, Integer> positions) {
List<Integer> randomNumbers = RandomGenerator.numbersForOneRound(1, names);
List<Integer> moves = MoveResult.generateMoveResults(randomNumbers);
MoveAction.printAction(names, positions, moves);
}
}
21 changes: 21 additions & 0 deletions src/main/java/racingcar/RandomGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package racingcar;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.ArrayList;
import java.util.List;

public class RandomGenerator {

private RandomGenerator() {}

public static List<Integer> numbersForOneRound(int countInput, List<String> names) {
List<Integer> results = new ArrayList<>(names.size());

for (int i = 0; i < names.size(); i++) {
int number = Randoms.pickNumberInRange(0, 9);
results.add(number);
}

return results;
}
}
23 changes: 23 additions & 0 deletions src/test/java/racingcar/NameValidatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package racingcar;

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

class NameValidatorTest {

@Test
void emptyList_throws() {
assertThatThrownBy(() -> NameValidator.validate(List.of()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("한 글자 이상");
}

@Test
void blankElement_throws() {
assertThatThrownBy(() -> NameValidator.validate(List.of("pobi", " ")))
.isInstanceOf(IllegalArgumentException.class);
}
}
17 changes: 17 additions & 0 deletions src/test/java/racingcar/TryCountValidatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package racingcar;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

class TryCountValidatorTest {

@Test
void nonPositive_throws() {
assertThatThrownBy(() -> CountValidator.countValidate(0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("1 이상");
}

}