Skip to content

[최보현] 자동차 경주 미션 제출 #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
04e2c45
docs: 1단계 요구사항 작성
boya-go May 8, 2025
884845c
feat: car 객체 정보 클래스 생성
boya-go May 8, 2025
72129c3
feat: 랜덤값 생성기 구현 및 car 객체 정보에 움직임 메서드 추가
boya-go May 8, 2025
34fcac4
refactor: 테스트를 위해 랜덤숫자 생성기능과 움직이는 기능 분리
boya-go May 8, 2025
fcaa1d5
test: 움직이는 자동차 테스트 작성
boya-go May 8, 2025
0c97bd5
docs: 1단계 요구사항 결과 및 2단계 요구사항 작성
boya-go May 8, 2025
933f644
refactor: 불필요하게 Random 객체 생성되는 것을 방지하기 위해 static final로 변경해서 선언
boya-go May 9, 2025
3a8ff9b
feat: 게임 실행해 우승자 반환하는 기능 작성, 기존 Car 클래스에 있던 랜덤숫자생성기를 인터페이스로 만듦
boya-go May 9, 2025
bbb7480
test: 우승 자동차 구하기 테스트 작성
boya-go May 9, 2025
8ebd4ee
docs: 2단계 요구사항 결과 및 3단계 요구사항 작성
boya-go May 9, 2025
27b8519
refactor: mvc패턴으로 수정 및 게임 실행 구현
boya-go May 9, 2025
1dd8c56
refactor: 전반적인 코드 리팩토링 및 테스트 케이스 추가
boya-go May 10, 2025
f21a690
docs: 3단계 요구사항 결과 및 4단계 요구사항, 결과 작성
boya-go May 10, 2025
51cd513
refactor: RacingGameController 구조 변경
boya-go May 14, 2025
960b262
test: 테스트에만 사용하는 FixedNumberGeneraotor 구현체 테스트 패키지로 이동, @Nested 사용해 논…
boya-go May 15, 2025
f5fe592
refactor: 변수에 값 할당 이전에 검증하도록 수정
boya-go May 15, 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
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 자동차 경주 - 초간단 애플리케이션

## [1] 움직이는 자동차

- 자동차에 관한 요구사항
- [x] 자동차는 이름을 가지고 있다.
- [x] 자동차는 움직일 수 있다.
- [x] 0에서 9 사이의 random 값을 구해 그 값이 **4 이상이면 전진**하고, **3 이하 값이면 멈춘다**.

---

## [2] 우승 자동차 구하기

- 기능 요구사항
- [x] n대의 자동차 참여 가능
- [x] 주어진 횟수동안 n대의 자동차는 전진 또는 멈출 수 있다.
- [x] 이동 조건은 앞의 [움직이는 자동차]와 동일
- [x] 경주 게임 완료 후 우승자를 구할 수 있다.
- [x] 우승자는 한 명 이상일 수 있다.

---

## [3] 게임 실행

- 기능 요구사항
- [x] 자동차 및 게임 방법은 앞의 [움직이는 자동차], [우승 자동차 구하기]와 동일
- [x] 사용자로부터 `이름`, `게임횟수` 를 입력받는다.
- [x] 자동차 이름은 쉼표(,) 기준으로 구분하며, 이름은 5자 이하만 가능하다.
- [x] 메인 메서드를 추가하여 실행 가능하게 만든다.

---

## [4] 리팩터링

- 요구사항
- [x] 단위 테스트를 구현한다.

---

- 코드 작성 요구사항
- [x] 자동차가 움직이는 기능이 의도대로 되는지 테스트한다.
- [x] 자바 코드 컨벤션 `Java Style Guide` 원칙으로 프로그래밍한다.
- [x] 3항 연산자를 쓰지 않는다.
- [x] else 예약어를 쓰지 않는다.
- [x] switch/case도 사용하지 않는다.
- [x] 함수(또는 메서드) 길이가 15라인을 넘어가지 않도록 구현한다.
- [x] 함수(또는 메서드)가 한 가지 일만 하도록 한다.
10 changes: 10 additions & 0 deletions src/main/java/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import controller.RacingGameController;

public class App {
public static void main(String[] args) {
RacingGameController racingGameController = new RacingGameController();

racingGameController.startRacingGame();

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

import domain.Car;
import domain.CarNameParser;
import domain.CarRaceGame;
import java.util.List;
import view.inputView;
import view.outputView;

public class RacingGameController {

public void startRacingGame() {
String carNames = inputView.enterCarNames();
List<Car> cars = CarNameParser.parseCarName(carNames);
final CarRaceGame carRaceGame = new CarRaceGame(cars);

int round = inputView.enterRoundNumber();

outputView.printGameResultTitle();

printGameRounds(carRaceGame, round, cars);

printWinners(carRaceGame);
}

private void printGameRounds(CarRaceGame carRaceGame, int round, List<Car> cars) {
carRaceGame.validateRoundNumber(round);

for (int i = 0; i < round; i++) {

carRaceGame.playOneRound();
System.out.println();
outputView.printRoundResult(cars);
}
}


private void printWinners(CarRaceGame carRaceGame) {

List<String> winnersName = carRaceGame.getWinnerNames();

outputView.printGameWinners(winnersName);

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

import domain.generator.NumberGenerator;

public class Car {

private final String name;
private int position;
private final NumberGenerator generator;
private static final int MOVE_THRESHOLD = 4;

public Car(String name, NumberGenerator generator) {
validateName(name);
this.name = name.trim();
this.position = 0;
this.generator = generator;
}

public String getName() {
return name;
}

public int getPosition() {
return position;
}

private void validateName(String name) {

if (name == null || name.isBlank()) {
throw new IllegalArgumentException("이름은 빈 값이 될 수 없습니다.");
}
if (name.length() > 5) {
throw new IllegalArgumentException("이름은 5자 이하만 가능합니다.");
}
}

public void movePosition() {
if (generator.generate() >= MOVE_THRESHOLD ) {
position++;
}
}
}
35 changes: 35 additions & 0 deletions src/main/java/domain/CarNameParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package domain;

import domain.generator.RandomNumberGenerator;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class CarNameParser {

public static final String CAR_NAME_DELIMITER = ",";

public static List<Car> parseCarName(String carNames) {

List<String> carNameList = Arrays.stream(carNames.split(CAR_NAME_DELIMITER))
.map(String::trim)
.collect(Collectors.toList());

validateDuplicateCarName(carNameList);

return carNameList.stream()
.map(name -> new Car(name.trim(), new RandomNumberGenerator()))
.collect(Collectors.toList());
}

private static void validateDuplicateCarName(List<String> carNameList) {

Set<String> uniqueCarNames = new HashSet<>(carNameList);

if (uniqueCarNames.size() != carNameList.size()) {
throw new IllegalArgumentException("중복된 자동차 이름은 입력할 수 없습니다.");
}
}
}
57 changes: 57 additions & 0 deletions src/main/java/domain/CarRaceGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package domain;

import java.util.List;

public class CarRaceGame {

private final List<Car> cars;

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

public void validateRoundNumber(int gameRound) {
if (gameRound < 1 ) {
throw new IllegalArgumentException("라운드는 한 번 이상 진행되어야 합니다.");
}
}

public void playOneRound() {
for (Car car : cars) {
car.movePosition();
}
}

private int getMaxDistance() {
int maxDistance = cars.stream()
.mapToInt(Car::getPosition)
.max()
.orElseThrow();

return maxDistance;
}

private List<String> getWinners(int maxDistance) {
return cars.stream()
.filter(car -> car.getPosition() == maxDistance)
.map(Car::getName)
.toList();
}

public void playRacingGame(int gameRound) {
validateRoundNumber(gameRound);

for (int i=0; i < gameRound;i++) {
playOneRound();
}
}

public List<String> getWinnerNames() {
List<String> winners = getWinners(getMaxDistance());

if (winners == null) {
throw new IllegalStateException("게임이 실행되지 않았습니다.");
}
return winners;
}
}
5 changes: 5 additions & 0 deletions src/main/java/domain/generator/NumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package domain.generator;

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

import java.util.Random;

public class RandomNumberGenerator implements NumberGenerator {

private static final Random RANDOM = new Random();

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

import java.util.Scanner;

public class inputView {

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

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

public static int enterRoundNumber() {
System.out.println("시도할 회수는 몇회인가요?");
return scanner.nextInt();
}
}
26 changes: 26 additions & 0 deletions src/main/java/view/outputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package view;

import domain.Car;

import java.util.List;

public class outputView {

private static final String CAR_POSITION_EXPRESSION = "-";
private static final String WINNER_NAMES_DELIMITER = ",";

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

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

public static void printGameWinners(List<String> winners) {
System.out.println(String.join(WINNER_NAMES_DELIMITER, winners) + "가 최종 우승했습니다.");
}
}
59 changes: 59 additions & 0 deletions src/test/java/domain/CarNameParserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package domain;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class CarNameParserTest {

@Nested
@DisplayName("입력된 문자열은 구분자로 나누어 차의 이름이 된다.")
class ParseCarNameTest {

@Test
@DisplayName("공백없이 들어왔을 경우")
public void parseCarName_문자열을입력하면_차이름리스트가생성된다() {

// given
String carNames = "차1,차2,차3";

// when
List<Car> cars = CarNameParser.parseCarName(carNames);

// then
assertThat(cars).extracting(Car::getName).containsExactly("차1", "차2", "차3");

}

@Test
@DisplayName("공백 포함해 들어왔을 경우")
public void parseCarName_구분자기준으로공백이포함된경우_공백제거후차이름리스트가생성된다() {

// given
String carNames = "차1 , 차2,차3 ";

// when
List<Car> cars = CarNameParser.parseCarName(carNames);

// then
assertThat(cars).extracting(Car::getName).containsExactly("차1", "차2", "차3");

}
}

@Test
@DisplayName("입력된 차들의 이름은 중복이 없어야 한다.")
public void parseCarName_중복된차이름_예외처리() {
// given
CarNameParser carNameParser = new CarNameParser();
String carNames = "차1,차1,차3";

assertThatThrownBy(() -> carNameParser.parseCarName(carNames))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("중복된 자동차 이름은 입력할 수 없습니다.");
}
}
Loading