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
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,44 @@
# java-racingcar-precourse



## Constraints
- **JDK 21** 기준, 엔트리포인트: `racingcar.Application.main`.
- **mission-utils** 라이브러리의 `Console`, `Randoms` API만 사용 가능.
- Indent 깊이 **최대 2단계**, 삼항 연산자 금지. -> 메소드를 최대한 쪼개면 뎁스 낮출 수 있음
- [Java Style Guide](https://github.com/woowacourse/woowacourse-docs/blob/main/styleguide/java) 준수.
- `build.gradle` 수정 금지, 외부 라이브러리 추가 금지.

## Architecture
- **Application**
- 프로그램의 시작점. 입력 → 게임 실행 → 출력 순서로 흐름 제어.
- **InputParser**
- `parseNames(String)` → `List<String>` (빈 문자열, 5자 초과 이름 검증)
- `parseRounds(String)` → `int` (양의 정수 검증)
- **Car**
- `name`, `position`, `move()` 보유.
- **MoveRules** (Functional Interface)
- `boolean shouldMove()` 정의.
- **RandomMoveRule**: `Randoms.pickNumberInRange(0,9) >= 4` 일 때 true 반환.
- **RacingGame**
- `play(List<Car> cars, int rounds, MoveRules rule)`
- 주어진 라운드 수만큼 반복하며 position 업데이트.
- **ResultView**
- `printRound(List<Car>)` : `"name : ---"` 형태 출력.
- `printWinners(List<String>)` : `"최종 우승자 : pobi, jun"` 출력.
- **WinnerFinder**
- `findWinners(List<Car>)` → `List<String>` (최대 position 기준 복수 우승자 처리)

> 목적: 입력, 게임 로직, 규칙, 출력 단계를 완전히 분리하여 **작은 메서드** 중심으로 설계.

## Feautres (커밋 단위)
이름 입력/검증
시도 횟수 입력/검증
이동 로직 (≥4 시 전진)
n라운드 실행 & 위치 업데이트
라운드 단위 출력
우승자 계산/출력
입력 예외 처리 (IllegalArgumentException)
단일 책임화 및 indent ≤ 2 유지 (리팩토링)
테스트 (JUnit5 + AssertJ)
g
126 changes: 125 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,131 @@
package racingcar;

import camp.nextstep.edu.missionutils.Console;
import camp.nextstep.edu.missionutils.Randoms;

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

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
List<String> names = readNames();
int rounds = readRounds();

RacingGame game = new RacingGame(names, rounds);

printExecutionHeader();
runRounds(game.getCars(), rounds);
// printFinalResult(game.getCars());
printWinners(game.getCars());
}

// 사용자 입력 - 이름, 시도 횟수
private static List<String> readNames() {
System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
String namesLine = Console.readLine();
return InputParser.parseNames(namesLine);
}

private static int readRounds() {
System.out.println("시도할 횟수는 몇 회인가요?");
String roundsLine = Console.readLine();
return InputParser.parseRounds(roundsLine);
}

// 게임 과정 조회
private static void printExecutionHeader() {
System.out.println();
System.out.println("실행 결과");
}

private static void runRounds(List<Car> cars, int rounds) {
for (int r = 1; r <= rounds; r = r + 1) {
playOneRound(cars);
printRound(cars);
System.out.println();
}
}

private static void playOneRound(List<Car> cars) {
for (Car car : cars) {
boolean shouldMove = drawAndDecide();
if (shouldMove) {
car.move();
}
}
}

private static boolean drawAndDecide() {
int value = Randoms.pickNumberInRange(0, 9);
if (value >= 4) {
return true;
}
return false;
}

private static void printRound(List<Car> cars) {
for (Car car : cars) {
System.out.print(car.getName());
System.out.print(" : ");
printDashes(car.getPosition());
System.out.println();
}
}

private static void printDashes(int count) {
for (int i = 0; i < count; i = i + 1) {
System.out.print("-");
}
}

private static void printFinalResult(List<Car> cars) {
System.out.println("==== GAME RESULT ====");
for (Car car : cars) {
System.out.print(car.getName());
System.out.print(" : ");
printDashes(car.getPosition());
System.out.println();
}
System.out.println("=====================");
}

private static void printWinners(List<Car> cars) {
int max = findMaxPosition(cars);
List<String> winnerNames = collectWinners(cars, max);
System.out.println("최종 우승자 : " + joinByCommaAndSpace(winnerNames));
}

// ---------- helpers ----------
private static int findMaxPosition(List<Car> cars) {
int max = 0;
for (Car car : cars) {
if (car.getPosition() > max) {
max = car.getPosition();
}
}
return max;
}

private static List<String> collectWinners(List<Car> cars, int max) {
List<String> winners = new ArrayList<>();
for (Car car : cars) {
if (car.getPosition() == max) {
winners.add(car.getName());
}
}
return winners;
}


private static String joinByCommaAndSpace(List<String> names) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.size(); i = i + 1) {
sb.append(names.get(i));
if (i < names.size() - 1) {
sb.append(", ");
}
}
return sb.toString();
}
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar;

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

Car(String name) {
if (name == null) {
throw new IllegalArgumentException("자동차 이름이 null입니다.");
}
String trimmed = name.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("자동차 이름이 비어 있습니다.");
}
this.name = trimmed;
}

String getName() {
return name;
}

int getPosition() {
return position;
}

void move() {
position = position + 1;
}
}
75 changes: 75 additions & 0 deletions src/main/java/racingcar/InputParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package racingcar;

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

final class InputParser {
private static final int MAX_NAME_LENGTH = 5;

private InputParser() {
// utility class
}

/** 이름 입력 처리 */
static List<String> parseNames(String line) {
if (line == null) {
throw new IllegalArgumentException("이름 입력이 null입니다.");
}

String trimmed = line.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("이름을 한 개 이상 입력해야 합니다.");
}

String[] parts = trimmed.split(",");
List<String> names = new ArrayList<>();

for (String raw : parts) {
String name = "";
if (raw != null) {
name = raw.trim();
}

if (name.isEmpty()) {
throw new IllegalArgumentException("빈 이름은 허용되지 않습니다.");
}
if (name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException("이름은 5자 이하여야 합니다: " + name);
}
names.add(name);
}
return names;
}

/** 라운드 입력 처리 */
static int parseRounds(String line) {
if (line == null) {
throw new IllegalArgumentException("라운드 입력이 null입니다.");
}

String trimmed = line.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("라운드는 1 이상의 정수여야 합니다.");
}

// 숫자만 허용
for (int i = 0; i < trimmed.length(); i = i + 1) {
char ch = trimmed.charAt(i);
if (!Character.isDigit(ch)) {
throw new IllegalArgumentException("라운드는 숫자만 입력해야 합니다: " + trimmed);
}
}

int rounds;
try {
rounds = Integer.parseInt(trimmed);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("라운드 파싱에 실패했습니다: " + trimmed);
}

if (rounds <= 0) {
throw new IllegalArgumentException("라운드는 1 이상의 정수여야 합니다.");
}
return rounds;
}
}
6 changes: 6 additions & 0 deletions src/main/java/racingcar/MoveRules.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package racingcar;

@FunctionalInterface
interface MoveRules {
boolean shouldMove();
}
33 changes: 33 additions & 0 deletions src/main/java/racingcar/RacingGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package racingcar;

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

final class RacingGame {
private final List<Car> cars = new ArrayList<>();
private final int rounds;

RacingGame(List<String> names, int rounds) {
if (names == null || names.isEmpty()) {
throw new IllegalArgumentException("자동차 이름 목록이 비어 있습니다.");
}
if (rounds <= 0) {
throw new IllegalArgumentException("라운드 수는 1 이상의 정수여야 합니다.");
}

for (String n : names) {
cars.add(new Car(n));
}
this.rounds = rounds; //테스트용 보관
}

/** 자동차 조회 get메소드 */
List<Car> getCars() {
return new ArrayList<>(cars);
}

/** round 조회 */
int getRounds() {
return rounds;
}
}
15 changes: 15 additions & 0 deletions src/main/java/racingcar/RandomMoveRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package racingcar;

import camp.nextstep.edu.missionutils.Randoms;

final class RandomMoveRule implements MoveRules {

@Override
public boolean shouldMove() {
int value = Randoms.pickNumberInRange(0, 9);
if (value >= 4) {
return true;
}
return false;
}
}