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
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,37 @@
# java-racingcar-precourse
# 카테캠 미니과제 2 - 자동차 경주

## 개요
- 카카오 테크 캠퍼스 2차 미니과제
- 간단한 자동차 경주 게임을 구현
- 사용자가 입력한 자동차 이름과 경주 횟수에 따라 자동차들이 랜덤하게 전진하며, 가장 멀리 이동한 자동차가 우승

## 기능 목록

1. **자동차 경주 게임 초기 설정**
- [X] 자동차 이름 입력 및 검증
- [X] 경주 횟수 입력 및 검증

2. **자동차 객체 생성**
- [X] 각 이름을 바탕으로 자동차 객체 생성
- [X] 자동차 객체는 이름과 위치를 가짐

3. **경주 로직 구현**
- [X] 무작위 값을 통해 자동차의 전진 여부 결정
- [X] 각 회차 결과 출력

4. **우승자 결정 및 출력**
- [X] 가장 멀리 이동한 자동차 결정
- [X] 여러 우승자가 있을 경우 쉼표로 구분하여 출력

5. **입력 오류 처리**
- [X] 잘못된 입력에 대한 예외 처리 및 재입력 요청

6. **단위 테스트 작성**
- [X] 주요 로직에 대한 단위 테스트 구현

## 요구 사항
- JDK 17
- Google Java Style Guide 준수
- indent depth 2 이내
- 함수 길이 15라인 이하
- else, switch/case 사용 금지
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.CarRaceController;

public class Application {
public static void main(String[] args) {
CarRaceController controller = new CarRaceController();
controller.startRace();
}
}
32 changes: 32 additions & 0 deletions src/main/java/controller/CarRaceController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package controller;

import java.util.List;
import java.util.stream.Collectors;
import model.Car;
import model.Race;
import view.InputView;
import view.OutputView;

public class CarRaceController {
public void startRace() {
try {
List<String> carNames = InputView.getCarNames();
int raceCount = InputView.getRaceCount();

Race race = new Race(carNames);
race.run(raceCount);

List<Car> winners = race.getWinners();
printWinners(winners);
} catch (IllegalArgumentException e) {
OutputView.printMessage(e.getMessage());
}
}

private void printWinners(List<Car> winners) {
String winnerNames = winners.stream()
.map(Car::getName)
.collect(Collectors.joining(", "));
OutputView.printMessage("최종 우승자 : " + winnerNames);
}
}
26 changes: 26 additions & 0 deletions src/main/java/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package model;

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

public Car(String name) {
if (name.length() > 5 || name.isEmpty()) {
throw new IllegalArgumentException("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.");
}
this.name = name;
this.position = 0; // 생성자에서 위치를 초기화
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}

public void move() {
this.position++;
}
}
53 changes: 53 additions & 0 deletions src/main/java/model/Race.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package model;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import view.OutputView; // 추가

public class Race {
private final List<Car> cars;
private static final int FORWARD_THRESHOLD = 4;
private static final int RANDOM_BOUND = 10;
private static final Random RANDOM = new Random();

public Race(List<String> carNames) {
this.cars = carNames.stream()
.map(Car::new)
.collect(Collectors.toList());
}

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

public void run(int raceCount) {
for (int i = 0; i < raceCount; i++) {
moveCars();
OutputView.printRaceStatus(cars); // 현재 상태 출력
}
}

private void moveCars() {
for (Car car : cars) {
moveCar(car);
}
}

private void moveCar(Car car) {
if (RANDOM.nextInt(RANDOM_BOUND) >= FORWARD_THRESHOLD) {
car.move();
}
}

public List<Car> getWinners() {
int maxPosition = cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);

return cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.collect(Collectors.toList());
}
}
72 changes: 72 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package view;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class InputView {
private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

public static List<String> getCarNames() {
while (true) {
try {
OutputView.printMessage("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String input = br.readLine();
List<String> carNames = parseCarNames(input);
validateCarNames(carNames);
return carNames;
} catch (IOException e) {
handleInputError("[ERROR] 입력을 읽는 중 오류가 발생했습니다. 다시 시도해주세요.");
} catch (IllegalArgumentException e) {
handleInputError(e.getMessage());
}
}
}

public static int getRaceCount() {
while (true) {
try {
OutputView.printMessage("시도할 횟수는 몇회인가요?");
String input = br.readLine();
return validateRaceCount(input);
} catch (IOException e) {
handleInputError("[ERROR] 입력을 읽는 중 오류가 발생했습니다. 다시 시도해주세요.");
} catch (IllegalArgumentException e) {
handleInputError(e.getMessage());
}
}
}

private static void handleInputError(String message) {
OutputView.printMessage(message);
}

private static List<String> parseCarNames(String input) {
return Arrays.stream(input.split(","))
.map(String::trim)
.collect(Collectors.toList());
}

private static void validateCarNames(List<String> carNames) {
for (String name : carNames) {
if (name.length() > 5 || name.isEmpty()) {
throw new IllegalArgumentException("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.");
}
}
}

private static int validateRaceCount(String input) {
try {
int raceCount = Integer.parseInt(input);
if (raceCount <= 0) {
throw new IllegalArgumentException("[ERROR] 경주 횟수는 1 이상이어야 합니다.");
}
return raceCount;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 유효한 숫자를 입력하세요.");
}
}
}
19 changes: 19 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package view;

import model.Car;
import java.util.List;

public class OutputView {
public static void printMessage(String message) {
System.out.println(message);
}

public static void printRaceStatus(List<Car> cars) {
for (Car car : cars) {
String status = car.getName() + " : "
+ "-".repeat(Math.max(0, car.getPosition()));
printMessage(status);
}
printMessage(""); // 빈 줄로 회차 구분
}
}
57 changes: 57 additions & 0 deletions src/test/java/model/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package model;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class CarTest {
@Test
public void carNameShouldBeFiveCharsOrLess() {
IllegalArgumentException thrown = assertThrows(
IllegalArgumentException.class,
() -> new Car("abcdef")
);
assertEquals("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.", thrown.getMessage());
}

@Test
public void carNameShouldNotBeEmpty() {
IllegalArgumentException thrown = assertThrows(
IllegalArgumentException.class,
() -> new Car("")
);
assertEquals("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.", thrown.getMessage());
}

@Test
public void carNameShouldBeSetCorrectly() {
Car car = new Car("pobi");
assertEquals("pobi", car.getName());
}

@Test
public void carShouldStartAtPositionZero() {
Car car = new Car("test");
assertEquals(0, car.getPosition());
}

@Test
public void carShouldMove() {
Car car = new Car("test");
car.move();
assertEquals(1, car.getPosition());
}

@Test
public void carShouldMoveMultipleTimes() {
Car car = new Car("test");
car.move();
car.move();
assertEquals(2, car.getPosition());
}

@Test
public void carNameShouldAllowSpecialCharacters() {
Car car = new Car("p@bi");
assertEquals("p@bi", car.getName());
}
}
Loading