Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ec5f5e2
Update README.md
Seooooo24 Apr 3, 2025
0ebf462
Update README.md
Seooooo24 Apr 3, 2025
51f9116
docs: 구현할 기능 목록 정리
Seooooo24 Apr 4, 2025
1b7a20d
Merge remote-tracking branch 'origin/main'
Seooooo24 Apr 4, 2025
11f4eb1
feat: 입력 view 구현
Seooooo24 Apr 4, 2025
9aff35c
feat: view에 입력 안내 문구 추가
Seooooo24 Apr 4, 2025
cce482e
refactor: view 메서드명 변경
Seooooo24 Apr 4, 2025
3112912
feat: view에서 입력 받은 경주할 자동차 이름 검증 후 파싱
Seooooo24 Apr 4, 2025
8ceb05f
feat: 의존성 주입할 AppConfig 생성
Seooooo24 Apr 4, 2025
cf0a937
feat: 메인 메서드 작성
Seooooo24 Apr 4, 2025
edb68dc
feat: 에러메시지 관리 클래스 생성
Seooooo24 Apr 4, 2025
fc5513c
feat: 컨트롤러로부터 전달 받은 이름들로 RacingCar 객체 생성
Seooooo24 Apr 4, 2025
355510d
feat: RacingCar 객체들로 AllRacingCars 객체 생성
Seooooo24 Apr 4, 2025
7bfc90b
feat: 뷰로부터 입력 받은 시도 횟수 컨트롤러에서 검증 후 파싱
Seooooo24 Apr 4, 2025
2cd1a84
feat: AppConfig에서 RacingCarService 주입
Seooooo24 Apr 4, 2025
ffea020
rename: 도메인들 도메인 패키지로 이동
Seooooo24 Apr 4, 2025
a6bf57e
feat: AllRacingCars 객체와 시도 횟수로 FullGame 객체 생성
Seooooo24 Apr 4, 2025
fea1bfc
refactor: 서비스 레이어 제거
Seooooo24 Apr 5, 2025
1abc32c
refactor: 서비스 다시 생성
Seooooo24 Apr 5, 2025
ad8b7b4
feat: 게임 진행 및 게임 결과 기록 기능
Seooooo24 Apr 5, 2025
2aa8fba
refactor: 메서드명 및 책임 변경
Seooooo24 Apr 5, 2025
df376e5
feat: 출력을 위한 OutputView 생성
Seooooo24 Apr 5, 2025
5a04546
feat: 출력을 위한 DTO 생성
Seooooo24 Apr 5, 2025
39d1112
fix: 게임 기록 내부 로직 수정
Seooooo24 Apr 5, 2025
bc5bdde
refactor: RacingCar가 position 상태를 가지고 위치를 저장하도록 수정
Seooooo24 Apr 5, 2025
149a408
feat: 최종 우승자 출력 구현
Seooooo24 Apr 5, 2025
2ed5dbf
refactor: 매직넘버 처리
Seooooo24 Apr 5, 2025
9270897
refactor: 도메인 검증 로직 추가
Seooooo24 Apr 5, 2025
797f15a
refactor: 매직 넘버 처리, 중복 코드 변수로 처리
Seooooo24 Apr 5, 2025
6d4e7ea
refactor: 매직 넘버 처리
Seooooo24 Apr 5, 2025
74e241e
refactor: 최종 우승자 구하기 정적 팩토리 메소드 사용하여 리팩토링
Seooooo24 Apr 5, 2025
1abd8ac
refactor: 도메인 불변객체로 변경
Seooooo24 Apr 5, 2025
c553b49
refactor: 메서드 분리
Seooooo24 Apr 5, 2025
c290180
docs: 기능 정의 수정
Seooooo24 Apr 5, 2025
5d54e68
refactor: stream 적용해보기
Seooooo24 Apr 6, 2025
e98fdd7
refactor: Game 클래스 자원 명시 대신 의존 객체 주입받도록 수정, 클래스명 Round로 수정
Seooooo24 Apr 6, 2025
fd5f1b5
refactor: GameRecords 클래스 자원 명시 대신 의존 객체 주입받도록 수정
Seooooo24 Apr 6, 2025
9e66bdd
refactor: 도메인 객체 접근 생성자 수정
Seooooo24 Apr 6, 2025
d1b71f8
refactor: 도메인 정적 팩터리 메서드 사용하도록 수정
Seooooo24 Apr 6, 2025
4eecaca
refactor: final 클래스로 수정
Seooooo24 Apr 6, 2025
3d47f27
refactor: 서비스 레이어 삭제
Seooooo24 Apr 9, 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
50 changes: 50 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 구현할 기능 목록

## Model

### Domain
- RacingCar (하나의 레이싱 카)
- 차 이름 출력을 위한 getter
- 차 위치 이동 메서드
- AllRacingCars (모든 레이싱 카들)
- 최종 우승자 반환 메서드
- FullGame (전체 경기)
- Game (단일 경기)
- GameRecords (경기 기록)
- FinalWinners (최종 우승자)
- 최종 우승자 계산 메서드
- 최종 우승자 출력을 위한 getter

### Service
- RacingCarService (객체 생성과 메서드 호출 담당, 없애고 싶다.)

1. 컨트롤러로부터 전달 받은 자동차 이름으로 RacingCar 객체 생성
2. RacingCar 객체들로 AllRacingCars 객체 생성
2. AllRacingCars 객체와 컨트롤러로부터 전달 받은 시도 횟수로 FullGame 객체 생성
3. FullGame 객체 내에서 GameResult 생성
4. AllGameResults의 각 GameResult가 반환한 우승자들을 모아 FinalWinners 객체 생성
- 각 GameResult 객체 내에서 하이픈 개수 비교
6. GameResult에서 단일 경기 우승자 Winners 객체로 반환
7. 반환한 Winners 객체들을 WinnerCounts로 생성
8. WinnerCounts에서 최종 우승자 FinalWinners로 생성 및 반환


## View

### Input
1. 경주할 자동차 이름 입력 받기
2. 시도할 횟수 입력 받기

### Output
1. 각 차수별 실행 결과 출력 하기
2. 단독 우승자 안내 문구 출력 하기 or 공동 우승자 안내 문구 출력 하기

## Controller
1. 뷰에서 입력 받은 경주할 자동차 이름 파싱 및 검증
2. 서비스 객체에 자동차 이름 전달
3. 뷰에서 입력 받은 시도 횟수 파싱 및 검증
4. 서비스 객체에 시도 횟수 전달
5. 서비스 객체로부터 AllGameResults 객체 받아 와서 변환
6. 변환한 AllGameResults 뷰에게 전달
7. 서비스 객체로부터 FinalWinners 받아 와서 변환
8. 변환한 FinalWinners 뷰에게 전달
22 changes: 22 additions & 0 deletions src/main/java/racingcar/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package racingcar;

import racingcar.controller.Controller;
import racingcar.view.InputView;
import racingcar.view.OutputView;
import racingcar.view.TextInputView;
import racingcar.view.TextOutputView;

public final class AppConfig {

public Controller controller() {
return new Controller(inputView(), outputView());
}

private InputView inputView() {
return new TextInputView();
}

private OutputView outputView() {
return new TextOutputView();
}
}
6 changes: 5 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package racingcar;

import racingcar.controller.Controller;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
AppConfig appConfig = new AppConfig();
Controller controller = appConfig.controller();
controller.run();
}
}
11 changes: 11 additions & 0 deletions src/main/java/racingcar/common/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package racingcar.common;

public class ErrorMessage {
private static final String ERROR_MESSAGE = "[ERROR]";

public static final String ERROR_EMPTY_RACING_CAR_NAME = ERROR_MESSAGE + "입력된 자동차 이름이 없습니다.";
public static final String ERROR_SINGLE_RACING_CAR_NAME = ERROR_MESSAGE + "한 대의 자동차로는 경기할 수 없습니다.";
public static final String ERROR_EMPTY_GAME_COUNT = ERROR_MESSAGE + "입력된 경기 횟수가 없습니다.";
public static final String ERROR_GAME_COUNT_FORMAT = ERROR_MESSAGE + "경기 횟수는 정수여야 합니다.";
public static final String ERROR_TOO_SMALL_GAME_COUNT = ERROR_MESSAGE + "시도 횟수는 0보다 커야 합니다.";
}
79 changes: 79 additions & 0 deletions src/main/java/racingcar/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package racingcar.controller;

import static racingcar.common.ErrorMessage.ERROR_EMPTY_GAME_COUNT;
import static racingcar.common.ErrorMessage.ERROR_EMPTY_RACING_CAR_NAME;
import static racingcar.common.ErrorMessage.ERROR_GAME_COUNT_FORMAT;

import java.util.List;
import racingcar.model.domain.AllRacingCars;
import racingcar.model.domain.FullGame;
import racingcar.model.domain.GameRecords;
import racingcar.view.InputView;
import racingcar.view.OutputView;

public class Controller {

public static final String DELIMITER = ",";
public static final String NUMERIC_REGEX = "\\d+";

private final InputView inputView;
private final OutputView outputView;

public Controller(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void run() {
AllRacingCars allRacingCars = createAllRacingCars();

FullGame fullGame = createFullGame(allRacingCars);

getGameRecords(fullGame);

getFinalWinners(allRacingCars);
}

private void getFinalWinners(AllRacingCars allRacingCars) {
outputView.outputFinalWinners(allRacingCars.getFinalWinners().toDto());
}

private void getGameRecords(FullGame fullGame) {
GameRecords gameRecords = fullGame.playAllRounds();
outputView.outputGameRecords(gameRecords.toDto());
}

private FullGame createFullGame(AllRacingCars allRacingCars) {
String inputCount = inputView.inputGameCount();
validateInputCount(inputCount);
int gameCount = Integer.parseInt(inputCount);
return FullGame.of(allRacingCars, gameCount);
}

private AllRacingCars createAllRacingCars() {
String inputNames = inputView.inputRacingCarNames();
validateInputNames(inputNames);
List<String> carNames = splitCarNames(inputNames);
return AllRacingCars.from(carNames);
}

private void validateInputCount(String inputCount) {
if (inputCount.trim().isEmpty() || inputCount == null) {
throw new IllegalArgumentException(ERROR_EMPTY_GAME_COUNT);
}
if (!inputCount.matches(NUMERIC_REGEX)) {
throw new IllegalArgumentException(ERROR_GAME_COUNT_FORMAT);
}
}

private static List<String> splitCarNames(String racingCarNames) {
return List.of(racingCarNames.split(DELIMITER));
}

private static void validateInputNames(String inputNames) {
if (inputNames.trim().isEmpty() || inputNames == null) {
throw new IllegalArgumentException(ERROR_EMPTY_RACING_CAR_NAME);
}
}

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

import static racingcar.common.ErrorMessage.ERROR_SINGLE_RACING_CAR_NAME;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;


public final class AllRacingCars {

public static final int MIN_CAR_COUNT = 2;

private final List<RacingCar> cars;

private AllRacingCars(List<RacingCar> cars) {
validate(cars);
this.cars = cars;
}

public static AllRacingCars from(List<String> carNames) {
return new AllRacingCars(convertToRacingCar(carNames));
}

public Round playOneRound() {
return Round.from(moveAllRacingCars());
}

public FinalWinners getFinalWinners() {
return FinalWinners.of(cars, calculateMaxPosition());
}

private void validate(List<RacingCar> cars) {
if (cars.size() < MIN_CAR_COUNT) {
throw new IllegalArgumentException(ERROR_SINGLE_RACING_CAR_NAME);
}
}

private static List<RacingCar> convertToRacingCar(List<String> carNames) {
return carNames.stream().map(RacingCar::from).toList();
}

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

private Map<RacingCar, Integer> moveAllRacingCars() {
return cars.stream()
.map(car -> {
car.move();
return car;
})
.collect(Collectors.toMap(
Function.identity(),
RacingCar::getPosition
));
}
}
30 changes: 30 additions & 0 deletions src/main/java/racingcar/model/domain/FinalWinners.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package racingcar.model.domain;

import java.util.List;
import java.util.stream.Collectors;
import racingcar.model.dto.FinalWinnersDto;

public final class FinalWinners {

private final List<RacingCar> finalWinners;

private FinalWinners(final List<RacingCar> finalWinners) {
this.finalWinners = finalWinners;
}

public static FinalWinners of(List<RacingCar> cars, int maxPosition) {
List<RacingCar> result = calculateFinalWinners(cars, maxPosition);
return new FinalWinners(result);
}

private static List<RacingCar> calculateFinalWinners(List<RacingCar> cars, int maxPosition) {
return cars.stream()
.filter(car -> car.isSamePosition(maxPosition))
.collect(Collectors.toList());
}

public FinalWinnersDto toDto() {
return FinalWinnersDto.from(finalWinners.stream().map(RacingCar::getName).toList());
}

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

import java.util.HashMap;
import java.util.Map;
import racingcar.common.ErrorMessage;

public final class FullGame {

public static final int START_ROUND = 1;
public static final int MIN_GAME_COUNT = 0;

private final AllRacingCars allRacingCars;
private final int gameCount;

private FullGame(AllRacingCars allRacingCars, int gameCount) {
validate(gameCount);
this.allRacingCars = allRacingCars;
this.gameCount = gameCount;
}

public static FullGame of(AllRacingCars allRacingCars, int gameCount) {
return new FullGame(allRacingCars, gameCount);
}

public GameRecords playAllRounds() {
Map<Integer, Round> gameResult = new HashMap<>();
for (int roundCount = START_ROUND; roundCount <= gameCount; roundCount++) {
Round round = allRacingCars.playOneRound();
gameResult.put(roundCount, round);
}
return GameRecords.from(gameResult);
}

private void validate(int gameCount) {
if (gameCount <= MIN_GAME_COUNT) {
throw new IllegalArgumentException(ErrorMessage.ERROR_TOO_SMALL_GAME_COUNT);
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/racingcar/model/domain/GameRecords.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package racingcar.model.domain;

import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import racingcar.model.dto.GameResultDto;
import racingcar.model.dto.RoundResultDto;

public final class GameRecords {

private final Map<Integer, Round> gameRecords;

private GameRecords(Map<Integer, Round> gameRecords) {
this.gameRecords = gameRecords;
}

public static GameRecords from(Map<Integer, Round> gameRecords) {
return new GameRecords(gameRecords);
}

public GameResultDto toDto() {
Map<Integer, RoundResultDto> result = gameRecords.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
entry -> entry.getValue().toDto()
));
return GameResultDto.from(result);
}
}
39 changes: 39 additions & 0 deletions src/main/java/racingcar/model/domain/RacingCar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package racingcar.model.domain;

import camp.nextstep.edu.missionutils.Randoms;

final class RacingCar {

public static final int MIN_NUMBER = 0;
public static final int MAX_NUMBER = 9;
public static final int JUDGE_NUMBER = 4;

private final String name;
private int position = 0;

private RacingCar(String name) {
this.name = name;
}

public static RacingCar from(String name) {
return new RacingCar(name);
}

public void move() {
if (Randoms.pickNumberInRange(MIN_NUMBER, MAX_NUMBER) > JUDGE_NUMBER) {
position++;
}
}

public boolean isSamePosition(int otherPosition) {
return this.position == otherPosition;
}

public String getName() {
return this.name;
}

public Integer getPosition() {
return position;
}
}
Loading