Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b296b8a
1단계- 움직이는 자동차 구현
kimsky247-coder Sep 11, 2025
f5ba9a5
2단계- 우승 자동차 구하기 구현
kimsky247-coder Sep 11, 2025
9050ac3
README 추가
kimsky247-coder Sep 11, 2025
3b46072
클래스 멤버 선언 순서 수정, Random 객체 삭제, 매직넘버 수정
kimsky247-coder Sep 13, 2025
3093bd2
MoveCar 클래스 추가
kimsky247-coder Sep 13, 2025
dcea9ff
maxPosition 수정
kimsky247-coder Sep 13, 2025
544e29c
main 추가
kimsky247-coder Sep 14, 2025
04b7121
개행 수정
kimsky247-coder Sep 14, 2025
678b584
간격 수정
kimsky247-coder Sep 14, 2025
0a00e38
테스트 코드 작성
kimsky247-coder Sep 14, 2025
652b6e9
오타 수정
kimsky247-coder Sep 15, 2025
7c1319d
RaceTest 가독성 및 구조 개선
kimsky247-coder Sep 15, 2025
81068ce
main과 Controller 역할 분리
kimsky247-coder Sep 15, 2025
9a36142
RacingGameController 추가
kimsky247-coder Sep 15, 2025
846907e
MVC 패턴 적용
kimsky247-coder Sep 18, 2025
41dfc93
단위 테스트 코드 추가
kimsky247-coder Sep 18, 2025
519416b
패키지 변경
kimsky247-coder Sep 18, 2025
8393534
README 수정
kimsky247-coder Sep 18, 2025
79877da
개행 및 출력 수정
kimsky247-coder Sep 18, 2025
877fc91
정적 팩토리 적용
kimsky247-coder Sep 19, 2025
4042972
일급 컬렉션 적용
kimsky247-coder Sep 19, 2025
705dab7
클래스 이름 변경
kimsky247-coder Sep 19, 2025
02d127e
코드 개선
kimsky247-coder Sep 19, 2025
6794256
테스트 코드 경고 제거
kimsky247-coder Sep 19, 2025
7126155
Random 상속 구조를 NumberGenerator 인터페이스 기반으로 변경
kimsky247-coder Sep 19, 2025
da974f7
README 업데이트
kimsky247-coder Sep 20, 2025
e09c747
유효성 검사 로직 fromName 메서드로 이동
kimsky247-coder Sep 20, 2025
52842d5
자동차 추가 로직 분리
kimsky247-coder Sep 20, 2025
266f64b
가독성 개선
kimsky247-coder Sep 20, 2025
c867636
리팩터링
kimsky247-coder Sep 21, 2025
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#자동차 경주 미션

##기능

- 자동차 이름 입력: 쉼표(,)로 구분된, 1자 이상 5자 이하의 자동차 이름을 입력받는다.
- 시도 횟수 입력: 경주를 진행할 총 라운드 수를 입력받는다.
- 전진 또는 정지: 각 자동차는 매 라운드마다 0~9 사이의 무작위 값을 생성하여, 4 이상일 경우 전진한다.
- 라운드별 결과 출력: 매 라운드가 끝날 때마다 모든 자동차의 현재 위치를 출력한다.
- 최종 우승자 발표: 경주가 끝난 후, 가장 멀리 전진한 자동차를 최종 우승자로 발표한다. (공동 우승 가능)

##조건

- MVC 패턴을 적용하여 역할을 분리한다.
- 모든 로직에 단위 테스트를 구현한다.
- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다.
- 3항 연산자를 쓰지 않는다.
- else 예약어를 쓰지 않는다.
- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.

##프로젝트 구조

- model
* Car.java: 자동차 객체 (이름, 위치 상태 및 유효성 검사)
* Cars.java: 자동차 목록을 관리하는 일급 컬렉션
* MoveCarStrategy.java: NumberGenerator에 따라 자동차 이동 여부를 결정하는 전략.
* Race.java: 경주 상태 및 로직 관리
* NumberGenerator: 숫자 생성을 위한 인터페이스
* RandomNumberGenerator:NumberGenerator의 구현체
- controller
* RacingGameController.java: 게임 전체 흐름 관리
- view
* InputView.java: 사용자 입력 담당
* OutputView.java: 결과 출력 담당
11 changes: 11 additions & 0 deletions src/main/java/RaceMain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import controller.RacingGameController;
import model.strategy.NumberGenerator;
import model.strategy.RandomNumberGenerator;

public class RaceMain {
public static void main(String[] args) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자바 미션에 대한 내용은 아니지만, 백엔드 준비하신다면 나중에 시간 나실 때 T메모리에 대해서 찾아보시면 좋아요
(필수 아님, 개발 관련 내용보다는 CS에 가깝습니다)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T메모리라는 개념은 처음 들어보네요. 좋은 키워드 알려주셔서 감사합니다! 이번 기회에 꼭 찾아서 학습해보겠습니다.

NumberGenerator numberGenerator = new RandomNumberGenerator();
RacingGameController controller = new RacingGameController(numberGenerator);
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 코드와 다르게 이제 RacingGameController 안에 NumberGenerator를 넣게 되는데요. 이러면 어떤 장점을 가지게 된다고 생각하시나요?!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 코드에서는 컨트롤러가 직접 Random을 생성했습니다. 이 방법은 컨트롤러와 Random이 결합되어 컨트롤러의 전체 흐름을 테스트 할 때, 실행할 때 마다 결과가 달라져 테스트가 어려웠습니다.
현재 코드는 랜덤 숫자를 반환하는 RandomNumberGenerator대신, PredictableNumberGenerator를 주입할 수 있어 전체 흐름을 예측하고 검증할 수 있어 테스트가 용이해졌다고 생각합니다.

controller.run();
}
}
51 changes: 51 additions & 0 deletions src/main/java/controller/RacingGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package controller;

import model.domain.Car;
import model.domain.Cars;
import model.strategy.MoveCarStrategy;
import model.strategy.NumberGenerator;
import model.domain.Race;
import view.InputView;
import view.OutputView;

import java.util.List;

public class RacingGameController {

private final NumberGenerator numberGenerator;

public RacingGameController(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}

public void run() {
Cars cars = setupCars();
Race race = new Race(cars, new MoveCarStrategy(numberGenerator));

int rounds = InputView.readRounds();
runRace(race, rounds);
OutputView.printWinners(race.getWinners());

InputView.close();
}

private Cars setupCars() {
String[] carNames = InputView.readCarNames();
return Cars.fromNames(List.of(carNames));
}

private void runRace(Race race, int rounds) {
OutputView.printRaceStartMessage();
for (int i = 0; i < rounds; i++) {
race.runRound();
printCurrentPositions(race.getCars()); }
}

private void printCurrentPositions(List<Car> cars) {
for (Car car : cars) {
OutputView.printSingleCarStatus(car.getCarName(), car.getPosition());
}
OutputView.printNewLine();
}

}
40 changes: 40 additions & 0 deletions src/main/java/model/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package model.domain;

public class Car {

private static final int MOVE_DISTANCE = 1;
private static final int MAX_NAME_LENGTH = 5;

private final String carName;
private int position;

private Car(String name) {
this.carName = name;
this.position = 0;
}

public static Car fromName(String name) {
String trimmedName = name.trim();
validateName(trimmedName);
return new Car(trimmedName);
}

private static void validateName(String name) {
if (name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException("자동차 이름은 5자 이하만 가능합니다.");
}
}

public void moveForward() {
position += MOVE_DISTANCE;
}

public String getCarName() {
return carName;
}

public int getPosition() {
return position;
}

}
49 changes: 49 additions & 0 deletions src/main/java/model/domain/Cars.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package model.domain;

import model.strategy.MoveCarStrategy;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Cars {

private final List<Car> cars;

public Cars(List<Car> cars) {
this.cars = cars;
}

public static Cars fromNames(List<String> carNames) {
List<Car> carList = carNames.stream()
.map(Car::fromName)
.collect(Collectors.toList());
return new Cars(carList);
}

public void moveEachCar(MoveCarStrategy moveCarStrategy) {
for (Car car : cars) {
moveCarStrategy.tryMove(car);
}
}

public List<String> findWinners() {
int maxPosition = getMaxPosition();
return cars.stream()
.filter(car -> car.getPosition() == maxPosition)
.map(Car::getCarName)
.collect(Collectors.toList());
}

private int getMaxPosition() {
return cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElse(0);
}

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

}
29 changes: 29 additions & 0 deletions src/main/java/model/domain/Race.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package model.domain;

import model.strategy.MoveCarStrategy;

import java.util.List;

public class Race {

private Cars cars;
private MoveCarStrategy moveCarStrategy;

public Race(Cars cars, MoveCarStrategy moveCarStrategy) {
this.cars = cars;
this.moveCarStrategy = moveCarStrategy;
}

public void runRound() {
cars.moveEachCar(moveCarStrategy);
}

public List<String> getWinners() {
return cars.findWinners();
}

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

}
27 changes: 27 additions & 0 deletions src/main/java/model/strategy/MoveCarStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package model.strategy;

import model.domain.Car;

public class MoveCarStrategy {

private static final int MOVE_THRESHOLD = 4;

private final NumberGenerator numberGenerator;

public MoveCarStrategy(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}

public void tryMove(Car car) {
int randomNumber = numberGenerator.generate();

if (shouldMove(randomNumber)) {
car.moveForward();
}
}

private boolean shouldMove(int randomNumber) {
return randomNumber >= MOVE_THRESHOLD;
}

}
5 changes: 5 additions & 0 deletions src/main/java/model/strategy/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package model.strategy;

public interface NumberGenerator {
int generate();
}
14 changes: 14 additions & 0 deletions src/main/java/model/strategy/RandomNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package model.strategy;

import java.util.Random;

public class RandomNumberGenerator implements NumberGenerator {

private static final Random random = new Random();
private static final int RANDOM_NUMBER_BOUND = 10;

@Override
public int generate() {
return random.nextInt(RANDOM_NUMBER_BOUND);
}
}
24 changes: 24 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package view;

import java.util.Scanner;

public class InputView {

private static Scanner scanner = new Scanner(System.in);

public static String[] readCarNames() {
System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).");
return scanner.nextLine().split(",");
}

public static int readRounds() {
System.out.println("시도할 회수는 몇회인가요?");
int rounds = scanner.nextInt();
scanner.nextLine();
return rounds;
}

public static void close() {
scanner.close();
}
}
31 changes: 31 additions & 0 deletions src/main/java/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package view;

import model.domain.Car;

import java.util.List;

public class OutputView {

public static void printSingleCarStatus(String name, int position) {
System.out.print(name + " : ");
printPosition(position);
System.out.println();
}

public static void printNewLine() {
System.out.println();
}

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

private static void printPosition(int position) {
System.out.print("-".repeat(position));
}

public static void printWinners(List<String> winners) {
System.out.println(String.join(", ", winners) + "가 최종 우승했습니다.");
}

}
41 changes: 41 additions & 0 deletions src/test/java/model/domain/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package model.domain;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@DisplayName("model.domain.Car 클래스 테스트")
@SuppressWarnings("NonAsciiCharacters")
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
public class CarTest {

@Test
void 자동차_생성_시_이름과_초기_위치가_올바르게_설정된다() {
String name = "car";

Car car = Car.fromName(name);

assertEquals(name, car.getCarName());
assertEquals(0, car.getPosition());
}

@Test
void moveForward_호출_시_위치가_1_증가한다() {
Car car = Car.fromName("car");

car.moveForward();

assertEquals(1, car.getPosition());
}

@Test
void 자동차_이름이_5자를_초과하면_예외가_발생한다() {
String carName = "testCar";

assertThrows(IllegalArgumentException.class, () -> Car.fromName(carName));
}
}
Loading