Skip to content
Open
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# java-racingcar-precourse
## 기능 요구 사항 체크리스트
- [ ] 자동차 이름 목록 입력받기
- [ ] 시도할 횟수 입력받기
- [ ] 자동차(Car)가 이동 조건을 만족하면 전진(위치 +1)
- [ ] 이동 조건: 0~9 난수 중 4 이상이면 전진 (Randoms.pickNumberInRange 사용)
- [ ] 입력된 횟수만큼 라운드를 반복하며 모든 자동차 이동
- [ ] 매 라운드마다 `이름 : -----` 형태로 현황 출력
- [ ] 최종 최대 위치를 가진 자동차(들) 이름을 쉼표로 연결해 출력
- [ ] 잘못된 입력 시 IllegalArgumentException 발생 후 애플리케이션 종료
23 changes: 22 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;
import racingcar.domain.Race;
import racingcar.view.IOView;

import java.util.List;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
try {
List<String> names = IOView.readCarNames();
int attempts = IOView.readAttemptCount();

Race race = new Race(names);
IOView.printStart();

for (int i = 0; i < attempts; i++) {
race.playOneRound();
IOView.printRound(race.getCars());
}

IOView.printWinners(race.findWinners());
} finally {
Console.close();
}
}
}
25 changes: 25 additions & 0 deletions src/main/java/racingcar/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package racingcar.domain;

import racingcar.util.Validators;

public class Car {
private final String name;
private int position = 0;

public Car(String name) {
Validators.validateName(name);
this.name = name.trim();
}

public void forward() {
position += 1;
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}
}
50 changes: 50 additions & 0 deletions src/main/java/racingcar/domain/Race.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package racingcar.domain;

import camp.nextstep.edu.missionutils.Randoms;
import racingcar.util.Validators;

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

public class Race {
private final List<Car> cars;

public Race(List<String> names) {
Validators.validateNamesList(names);
List<Car> list = new ArrayList<>();
for (String n : names) {
list.add(new Car(n));
}
this.cars = Collections.unmodifiableList(list);
}

public void playOneRound() {
for (Car c : cars) {
int n = Randoms.pickNumberInRange(0, 9);
if (n >= 4) {
c.forward();
}
}
}

public List<Car> getCars() {
return cars;
}

public List<String> findWinners() {
int max = 0;
for (Car c : cars) {
if (c.getPosition() > max) {
max = c.getPosition();
}
}
List<String> winners = new ArrayList<>();
for (Car c : cars) {
if (c.getPosition() == max) {
winners.add(c.getName());
}
}
return winners;
}
}
48 changes: 48 additions & 0 deletions src/main/java/racingcar/util/Validators.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package racingcar.util;

import java.util.List;

public class Validators {

public static void validateName(String name) {
if (name == null) {
throw new IllegalArgumentException("자동차 이름은 null일 수 없습니다.");
}
String t = name.trim();
if (t.isEmpty()) {
throw new IllegalArgumentException("자동차 이름은 비어 있을 수 없습니다.");
}
if (t.length() > 5) {
throw new IllegalArgumentException("자동차 이름은 5자 이하여야 합니다: " + t);
}
}

public static void validateNamesList(List<String> names) {
if (names == null || names.isEmpty()) {
throw new IllegalArgumentException("자동차 이름 목록이 비어 있습니다.");
}
for (String n : names) {
validateName(n);
}
}

public static int parsePositiveInt(String s) {
if (s == null) {
throw new IllegalArgumentException("시도 횟수 입력이 비어 있습니다.");
}
String t = s.trim();
if (t.isEmpty()) {
throw new IllegalArgumentException("시도 횟수 입력이 비어 있습니다.");
}
int v;
try {
v = Integer.parseInt(t);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("시도 횟수는 정수여야 합니다.");
}
if (v < 1) {
throw new IllegalArgumentException("시도 횟수는 1 이상이어야 합니다.");
}
return v;
}
}
53 changes: 53 additions & 0 deletions src/main/java/racingcar/view/IOView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package racingcar.view;

import camp.nextstep.edu.missionutils.Console;
import racingcar.domain.Car;
import racingcar.util.Validators;

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

public class IOView {

public static List<String> readCarNames() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String line = Console.readLine();
if (line == null) {
throw new IllegalArgumentException("입력이 비어 있습니다.");
}
String[] parts = line.split(",");
List<String> names = new ArrayList<>();
for (String p : parts) {
names.add(p.trim());
}
Validators.validateNamesList(names);
return names;
}

public static int readAttemptCount() {
System.out.println("시도할 횟수는 몇 회인가요?");
String line = Console.readLine();
return Validators.parsePositiveInt(line);
}

public static void printStart() {
System.out.println();
System.out.println("실행 결과");
}

public static void printRound(List<Car> cars) {
for (Car c : cars) {
System.out.println(c.getName() + " : " + "-".repeat(c.getPosition()));
}
System.out.println();
}

public static void printWinners(List<String> winners) {
StringJoiner sj = new StringJoiner(", ");
for (String w : winners) {
sj.add(w);
}
System.out.println("최종 우승자 : " + sj);
}
}
37 changes: 37 additions & 0 deletions src/test/java/racingcar/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package racingcar;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import racingcar.domain.Car;
import racingcar.util.Validators;
import static org.assertj.core.api.Assertions.*;

class CarTest {

@Test
@DisplayName("자동차 이름 규칙과 forward 동작을 검증한다")
void car_name_and_forward() {
Car car = new Car("pobi");
assertThat(car.getPosition()).isZero();

car.forward();
assertThat(car.getPosition()).isEqualTo(1);

assertThatThrownBy(() -> new Car("abcdef"))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> new Car(" "))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
@DisplayName("시도 횟수 입력은 1 이상 정수만 허용된다")
void validate_attempt_input() {
assertThat(Validators.parsePositiveInt("3")).isEqualTo(3);
assertThatThrownBy(() -> Validators.parsePositiveInt("0"))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> Validators.parsePositiveInt("-1"))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> Validators.parsePositiveInt("abc"))
.isInstanceOf(IllegalArgumentException.class);
}
}
33 changes: 33 additions & 0 deletions src/test/java/racingcar/RaceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package racingcar;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import racingcar.domain.Car;
import racingcar.domain.Race;

import java.util.List;

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

class RaceTest {

@Test
@DisplayName("Race 생성 시 이름 검증 및 초기 상태 확인")
void race_initial_state() {
List<String> names = List.of("pobi", "woni", "jun");
Race race = new Race(names);

for (Car c : race.getCars()) {
assertThat(c.getPosition()).isZero();
}

assertThat(race.findWinners()).containsExactlyInAnyOrderElementsOf(names);
}

@Test
@DisplayName("잘못된 이름이 포함되면 Race 생성 시 예외 발생")
void invalid_name_throws_exception() {
assertThatThrownBy(() -> new Race(List.of("abcdef", "pobi")))
.isInstanceOf(IllegalArgumentException.class);
}
}