Skip to content
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# java-racingcar-precourse

# 기능 목록

1. 자동차경주 뷰
- input, output(차수별, 우승자)

2. 자동차 움직임 뷰
- 자동차가 움직이면 그걸 출력하는 뷰

3. 자동차경주 컨트롤러
- 입력값 처리 및 자동차 모델에게 전달 및 생성
- 자동차 경주 게임 진행

4. 자동차경주 서비스
- 예외처리 후 모델에게 전달
- 우승자 판별
- 1회차 경기 기능

5. 자동차 모델(클래스)
- 이름, 좌표 관련 입출력
- 좌표 이동 후 뷰에 통보하는 observer
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.gradle.jvmargs=-Dfile.encoding=UTF-8
19 changes: 18 additions & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
package racingcar;

import racingcar.controller.RacingGameController;
import racingcar.service.RacingGameService;
import racingcar.view.RacingGameView;

import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
new Application().run();
}

public void run(){
RacingGameService racingGameService = new RacingGameService();
RacingGameView racingGameView = new RacingGameView();
RacingGameController racingGameController = new RacingGameController(racingGameView, racingGameService);

racingGameController.makeCar();
racingGameController.runGame();
racingGameController.result();
}
}
42 changes: 42 additions & 0 deletions src/main/java/racingcar/controller/RacingGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package racingcar.controller;

import racingcar.service.RacingGameService;
import racingcar.util.ErrorMessage;
import racingcar.util.Validator;
import racingcar.view.RacingGameView;

public class RacingGameController {

private RacingGameView racingGameView;
private RacingGameService racingGameService;

public RacingGameController(RacingGameView racingGameView, RacingGameService racingGameService){
this.racingGameService = racingGameService;
this.racingGameView = racingGameView;
}

public void makeCar(){
String[] carNames = racingGameView.readName().split(",");
for(String name : carNames) racingGameService.addCar(name);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

for도 좋지만 forEach로 써도 가독성이 높을 것 같아요

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

오 그러게요 왜 그렇게 안했지 수정하겠습니다~~

racingGameService.addObserver(racingGameView);
}

public void runGame(){
String input = racingGameView.readRepeat();
if(!Validator.isNumber(input)) throw new IllegalArgumentException(ErrorMessage.WRONG_REPEAT_INPUT.getMessage());

int repetitionNumber = Integer.parseInt(input);
if(repetitionNumber<1)
racingGameView.printEndGuide();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

코드 포멧이 미션에서 제시된 거랑 다른 것 같아요! 전반적으로 코드 포멧을 맞추면 가독성이 높아질 것 같아요

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

오 그런가요 넴~~~

for (int i=0;i<repetitionNumber;i++) {
racingGameService.playOneGame();
racingGameView.lineBreak();
}
}

public void result(){
String winners = racingGameService.checkWinner();
racingGameView.printFinalWinner(winners);

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

import racingcar.util.ErrorMessage;

public class Car {
private String name;
private int position;
private CarObserver observer;

public Car(String name){
setName(name);
this.position = 0;
}

public String getName() {
return name;
}

public void setName(String name) {
if(name.length()>5) throw new IllegalArgumentException(ErrorMessage.TOO_LONG.getMessage());
this.name = name;
}

public int getPosition() {
return position;
}

public void addPosition(int dice) {
if(dice>3) this.position++;
observer.onCarMove(name, position);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우라는 전진 조건을 자동차의 책임으로 본 이유가 있나요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

저걸 일종의 validation이라구 봐서 자동차 모델 안에 집어넣어두 되지 않을까 생각했습니다~~ 저 메세드는 내부에서 검사를 하지만 받은 파라미터가 뭘 뜻하는진 모르니까용 흠 먼가 사용이 부적절한가??


public void setObserver(CarObserver observer) {
this.observer = observer;
}
}
5 changes: 5 additions & 0 deletions src/main/java/racingcar/model/CarObserver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package racingcar.model;

public interface CarObserver {
void onCarMove(String carName, int position);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

모델을 확장해서 뷰로 구현하는 구조라고 이해했는데(CarObserver가 model 패키지 내 인터페이스이고, 인터페이스를 구현한 것이 view 이기에), 모델이 뷰와 강한 의존관계를 갖게 되기 때문에 이러한 구조가 MVC 패턴에 적절한지에 대해서는 다시 생각해볼 부분이 있을 것 같아요! 옵저버를 컨트롤러와 연결시켜 컨트롤러가 다시 뷰에게 전달하는 방향이 더 좋지 않을까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

저도 컨트롤러 매개가 더 낫지 않나 생각하고 있었는데 제 블로그 정리글을 보면 타 개발글들이 옵저버를 바로 뷰랑 연결하드라구요 일단 걔네들이 그러라구 하니 그러던 중...왜 이래야하는지는 추가 조사가 필요할 듯 싶습니다

41 changes: 41 additions & 0 deletions src/main/java/racingcar/service/RacingGameService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package racingcar.service;

import racingcar.model.Car;
import racingcar.model.CarObserver;

import java.net.CacheRequest;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static camp.nextstep.edu.missionutils.Randoms.pickNumberInRange;

public class RacingGameService {
private List<Car> cars = new ArrayList<>();

public void addCar(String carName){
Car car = new Car(carName);
cars.add(car);
}

public void addObserver(CarObserver carObserver){
cars.forEach(car->car.setObserver(carObserver));
}

public void playOneGame(){
for(Car car : cars) car.addPosition(pickNumberInRange(0,9));
}

public String checkWinner(){
Optional<Integer> maxValue = cars.stream().map(Car::getPosition).max(Integer::compareTo);

String maxPositionCars = cars.stream()
.filter(n -> maxValue.isPresent() && n.getPosition()==maxValue.get())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isPresent()라는 메서드도 있는지 처음 알았어요
Optional, 즉 Null 처리를 위한 메서드가 있었네요

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

저도 점찍고 스크롤하다가 보여서 알게 되었읍니다..

.map(Car::getName)
.collect(Collectors.joining(", "));

return maxPositionCars;
}
}
15 changes: 15 additions & 0 deletions src/main/java/racingcar/util/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package racingcar.util;

public enum ErrorMessage {
TOO_LONG("이름 5자 초과"),
WRONG_REPEAT_INPUT("입력 반복 횟수는 1 이상의 숫자여야합니다.");


private String message;

ErrorMessage(String message){this.message = message;}

public String getMessage() {
return "Error: "+message;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

각 에러 메시지에 "Error: "를 미리 붙이지 않고, 왜 getMessage()에서 추가하도록 짠건지 궁금해요ㅕ!!
에러 메시지 문자열을 미리 완성하면 코드가 더 간결해질 것 같은데, 현재 방식이 더 나은 이유가 있을까요?!! 배우고 싶어요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

에러는 메세지 출력 때 공통으로 나오는거니 enum에 넣기보단 view로 떠넘기는게 더 낫지 않을까 큰 생각없이 만들었습니다만 좀 더 효율적이려면 enum을 조작해서 에러부분을 스태틱으로 하던지 하면 될 것 같습니다! 크게 고민 안하고 한거라 거창한 이유는 없었습니다..ㅋㅋㅋ

17 changes: 17 additions & 0 deletions src/main/java/racingcar/util/SystemMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package racingcar.util;

public enum SystemMessage {
START_NAME_GUIDE("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"),
START_REPEAT_GUIDE("시도할 회수는 몇 회인가요?"),
RESULT_GUIDE("실행 결과"),
RESULT_WINNER("최종 우승자 : ");


private String message;

SystemMessage(String message){this.message = message;}

public String getMessage() {
return this.message;
}
}
10 changes: 10 additions & 0 deletions src/main/java/racingcar/util/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package racingcar.util;

import java.util.regex.Pattern;

public class Validator {
public static boolean isNumber(String arg){
if(arg == null || !arg.matches("\\d+")) return false;
return true;
}
}
37 changes: 37 additions & 0 deletions src/main/java/racingcar/view/RacingGameView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package racingcar.view;

import camp.nextstep.edu.missionutils.Console;
import racingcar.model.CarObserver;
import racingcar.util.SystemMessage;

public class RacingGameView implements CarObserver {

public String readName(){
System.out.println(SystemMessage.START_NAME_GUIDE.getMessage());
return Console.readLine();
}

public String readRepeat(){
System.out.println(SystemMessage.START_REPEAT_GUIDE.getMessage());
return Console.readLine();
}

public void lineBreak(){
System.out.println("");
}

public void printEndGuide(){
System.out.println(SystemMessage.RESULT_GUIDE.getMessage());
}

public void printFinalWinner(String winner){
System.out.println(SystemMessage.RESULT_WINNER.getMessage() + winner);
}

@Override
public void onCarMove(String carName, int position){
//뷰가 모델과 분리되어야해서 인스턴스 말고 각각 보내는게 좋다 함.
String message = carName + " : " + "-".repeat(position);
System.out.println(message);
}
}