Repository navigation
[그리디] 이규형 자동차 경주 미션 3, 4단계 제출합니다. #219
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
Changes from all commits
6a79ac5
5abde42
59d96cc
24cc64d
f498e9e
a44bfc2
b604a08
650178a
d540163
9e0da6a
7881dc5
3155ecf
c5e9ce9
ee8ed1d
b4297b3
2b164c6
ef757a0
f55f832
935b269
e5085e0
c271a58
74af726
6663b91
a4ea09f
7e668f3
32b64c4
f5d1430
205e40f
44c05e3
6b3d73c
53fee69
c93f9c9
21840e4
74c4ad6
b752cd4
5cbd68d
be705aa
53feef7
4259c2d
3acb21a
9d2d30a
95a4239
6e02d15
06d2295
bd625fd
53f1ed2
61bbf08
193209a
6999405
e92c5d4
58c1404
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,37 @@ | ||
| # 자동차 경주 | ||
| # 자동차 경주 게임 (Car Racing Game) | ||
|
|
||
| ## 설명 | ||
| - 자동차 전진(4이상) 또는 정지(3이하) | ||
| - 자동차 리스트 저장으로 n대의 자동차 경주 가능 | ||
| - 우승자 찾기 가능 | ||
| - 랜덤 숫자 생성 가능 | ||
| ## 프로젝트 개요 | ||
|
|
||
| ## 패키지 구조 | ||
| 콘솔에서 실행되는 자동차 경주 게임입니다. | ||
| 자동차 이름을 입력받아 여러 대의 자동차 객체를 생성하고, 입력한 횟수만큼 라운드를 진행하며 각 자동차가 무작위 값에 따라 전진하거나 멈춥니다. 모든 라운드가 끝나면 가장 멀리 이동한 자동차(들)이 우승자로 | ||
| 선정됩니다. | ||
|
|
||
| ## 게임 규칙 | ||
|
|
||
| - 자동차 이름은 쉼표(,)로 구분해서 여러 대를 한 번에 입력받습니다. | ||
| - 자동차 이름은 5자를 초과할 수 없습니다. | ||
| - 0~9 사이의 랜덤 값을 구해, 4 이상이면 전진하고 3 이하면 멈춥니다. | ||
| - 사용자가 입력한 횟수만큼 라운드를 반복하며, 매 라운드마다 각 자동차의 위치를 출력합니다. | ||
| - 가장 멀리 이동한 자동차가 우승자이며, 공동 우승자가 나올 수 있습니다. | ||
|
|
||
| ## 패키지 / 클래스 구조 | ||
|
|
||
| ``` | ||
| Application.java # 입력/출력을 조립하는 실행 진입점(main 메서드) | ||
|
|
||
| domain | ||
| ├── Car.java # 이름과 위치를 가지며, 4 이상이면 전진 | ||
| ├── Cars.java # n대의 Car를 리스트로 관리, 이름 배열로 Cars를 생성하는 정적 팩토리(createCars) 포함 | ||
| ├── RandomNumber.java # 랜덤 값 생성 인터페이스 | ||
| ├── DefaultRandomNumber.java # RandomNumber의 실제 구현체 (0~9 사이 랜덤 값) | ||
| └── RacingGame.java # 한 라운드씩 경주를 진행 | ||
|
|
||
| view | ||
| ├── InputView.java # 자동차 이름/시도 횟수 입력받기 | ||
| └── ResultView.java # 라운드별 진행 상황, 최종 우승자 출력 | ||
|
|
||
| test/domain | ||
| ├── CarTest.java # 전진/정지, 이름 검증(null/blank/5자 초과) 테스트 | ||
| ├── CarsTest.java # 이동, 우승자 판별, 이름 배열로 생성하는 로직 테스트 | ||
| └── FixedRandomNumber.java # 테스트용 고정값 RandomNumber 구현체 | ||
| ``` | ||
| racingcar | ||
| ├── Car.java # 이름과 위치를 가지며, 4 이상이면 전진 | ||
| ├── Cars.java # n대의 Car를 리스트로 관리 | ||
| ├── RandomNumber.java # 0~9 사이 랜덤 값 생성 | ||
| ├── CarMove.java # 주어진 라운드 수만큼 자동차들을 이동 | ||
| └── FindCarWinner.java # 가장 높은 위치의 자동차를 우승으로 판별 | ||
|
|
||
| test/racingcar | ||
| ├── CarMovingTest.java # 전진 또는 정지 테스트 | ||
| ├── CarsTest.java # 자동차 목록 저장 테스트 | ||
| └── FindCarWinnerTest.java # 단독/공동 우승자 테스트 | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import controller.RacingController; | ||
|
|
||
| public class Application { | ||
|
|
||
| public static void main(String[] args) { | ||
| new RacingController().run(); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package controller; | ||
|
|
||
| import static domain.Cars.createCars; | ||
| import static view.InputView.readCarName; | ||
| import static view.InputView.readTryCount; | ||
| import static view.ResultView.printFinalWinners; | ||
| import static view.ResultView.printRound; | ||
|
|
||
| import domain.Cars; | ||
| import domain.DefaultRandomNumber; | ||
| import domain.RacingGame; | ||
|
|
||
| public class RacingController { | ||
|
|
||
| public void run() { | ||
| //자동차 이름 입력받기 | ||
| String[] carNameArray = readCarName(); | ||
| //자동차 생성시키기 | ||
| Cars carsList = createCars(carNameArray); | ||
| //시도할 횟수 받기 | ||
| int tryCount = readTryCount(); | ||
| System.out.println(); | ||
| //시도 횟수만큼 경주시키기 (단, 경주할떄마다 값 출력하기) | ||
| RacingGame racingGame = new RacingGame(carsList, new DefaultRandomNumber(), tryCount); | ||
| raceCars(racingGame, carsList); | ||
| //최종 우승자 출력하기 | ||
| printFinalWinners(racingGame.findWinners()); | ||
| } | ||
|
|
||
| //시도 횟수만큼 경주시키기 (단, 경주할떄마다 값 출력하기) | ||
| private void raceCars(RacingGame racingGame, Cars carsList) { | ||
| for (int i = 0; i < racingGame.getTryCount(); i++) { | ||
| racingGame.carRaceOnce(); | ||
| //경주할때마다 값 출력하기 | ||
| printRound(carsList); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public class Cars { | ||
|
|
||
| private final List<Car> cars; | ||
|
|
||
| public Cars(List<Car> cars) { | ||
| validateNoDuplicateNames(cars); //중복이름 예외검증 | ||
| this.cars = new ArrayList<>(cars); | ||
| } | ||
|
|
||
| public List<Car> getCars() { | ||
| return Collections.unmodifiableList(cars); //외부에서 수정 못하게 막는거 | ||
| } | ||
|
|
||
| public int size() { | ||
| return cars.size(); | ||
| } | ||
|
|
||
| public void move(RandomNumber randomNumber) { | ||
| for (int i = 0; i < cars.size(); i++) { | ||
| int number = randomNumber.getRandomNumber(); | ||
| cars.get(i).moveIfPossible(number); | ||
| } | ||
| } | ||
|
|
||
| //자동차 생성시키기(위치가 여기가 맞는것같음) | ||
| public static Cars createCars(String[] carNameArray) { | ||
| List<Car> cars = new ArrayList<>(); | ||
| for (String name : carNameArray) { | ||
| cars.add(new Car(name.trim(), 0)); | ||
| } | ||
| return new Cars(cars); | ||
| } | ||
|
|
||
| private void validateNoDuplicateNames(List<Car> cars) { | ||
| Set<String> duplicateNames = new HashSet<>(); //[학습] Set은 중복을 허용하지 않는 자료구조 | ||
| for (Car car : cars) { | ||
| if (!duplicateNames.add(car.getCarName())) { //false면 중복이니까 | ||
| throw new IllegalArgumentException("자동차 이름은 중복될 수 없습니다."); | ||
| //중복 검증을 하여, 동일한 이름의 자동차가 존재하면 예외를 발생시킵니다. | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //계산은 여기서 | ||
| public int getMaxPosition() { | ||
| int maxPosition = 0; | ||
| for (Car car : cars) { | ||
| maxPosition = Math.max(maxPosition, car.getCarPosition()); | ||
| } | ||
| return maxPosition; | ||
| } | ||
|
|
||
| // 특정 위치에 있는 자동차들을 반환 | ||
| public List<Car> carsAt(int position) { // "이 위치에 있는 차들 줘" | ||
| List<Car> result = new ArrayList<>(); | ||
| for (Car car : cars) { | ||
| result.addAll(carAt(car, position)); | ||
| } | ||
| return result; | ||
| } | ||
| //add로 하면 값을 car로 받아야하는데 이때 우승자가 없는경우 반환값을 정하기 어려워 addAll 사용 | ||
|
|
||
| private List<Car> carAt(Car car, int position) { | ||
| if (car.samePosition(position)) { | ||
| return List.of(car); | ||
| } | ||
| return List.of(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class RacingGame { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 하나는 역할을 주는 겁니다. 지금 다른 하나는 없애는 겁니다. 하는 일 없이 전달만 하는 층이라면 오히려 읽는 사람이 한 단계 더 따라가야 하니까요. 어느 쪽이든 괜찮습니다. 규형님이 고르신 방향과 그 이유가 궁금하네요.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 들어보니 말씀하신부분이 맞는 것 같습니다. 그래서 RacingGame에 역할을 주기로 하였습니다.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 시도횟수도 RacingGame이 알만한 부분이라서 그렇게 코드수정하였습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
다만 "가장 멀리 간 위치가 어디냐"는 자동차들만 답할 수 있는 질문 아닐까요? 그건 그렇게 나누면 우승 규칙이 바뀌었을 때 어디를 고칠지도 분명해집니다. "3칸 이상 간 차는 모두 완주"로 바뀐다면
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 듣고보니 맞는 말씀입니다. 반영완료하였습니다. |
||
| private final Cars cars; | ||
| private final RandomNumber randomNumber; | ||
| private final int tryCount; | ||
|
|
||
| public RacingGame(Cars cars, RandomNumber randomNumber, int tryCount) { | ||
| this.cars = cars; | ||
| this.randomNumber = randomNumber; | ||
| this.tryCount = tryCount; | ||
| } | ||
|
|
||
| public void carRaceOnce() { | ||
| cars.move(randomNumber); | ||
| } | ||
|
|
||
| public int getTryCount() { | ||
| return tryCount; | ||
| } | ||
|
|
||
| public List<Car> findWinners() { | ||
| int maxPosition = cars.getMaxPosition(); | ||
| return cars.carsAt(maxPosition); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package domain; | ||
|
|
||
| public interface RandomNumber { | ||
| int getRandomNumber(); | ||
| } |
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package view; | ||
|
|
||
| import java.util.Scanner; | ||
|
|
||
| public class InputView { | ||
| private static Scanner scanner = new Scanner(System.in); | ||
|
|
||
| //자동차 이름 입력받기 | ||
| //static 쓰면 클래스 전체에서 공유하는거라서, 객체 생성 안하고 바로 쓸수있음 | ||
| public static String[] readCarName() { | ||
| System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); | ||
| String carNames = scanner.nextLine(); | ||
| //쉼표기준으로 잘라서 넣게끔 하는거구나 split() | ||
| return carNames.split(","); | ||
| } | ||
|
|
||
| //자동차 생성시키기는 Cars로 옮겼음 | ||
|
|
||
| //시도할 횟수 받기 | ||
| public static int readTryCount() { | ||
| System.out.println("시도할 횟수는 몇회인가요?"); | ||
| return scanner.nextInt(); | ||
| //nextInt()는 콘솔 입력에서 정수 하나(다음 토큰)를 읽어서 int로 반환하는 메서드임. | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package view; | ||
|
|
||
| import domain.Car; | ||
| import domain.Cars; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class ResultView { | ||
|
|
||
| //라운드간 우승자 출력하기 | ||
| public static void printRound(Cars carsList) { | ||
| for (Car car : carsList.getCars()) { | ||
| System.out.println(car.getCarName() + " : " + "-".repeat(car.getCarPosition())); | ||
| } | ||
| System.out.println(); | ||
| } | ||
| //repeat()는 문자열 반복 메서드임. car.getCarPosition()만큼 "-"를 반복해서 출력하는거. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 주석에 학습하신 내용을 정리해두셨네요. 찾아보고 기록하는 습관은 좋습니다. 다만 주석의 용도를 한번 생각해보시면 좋겠어요. 보통 주석은 코드만으로는 알 수 없는 것을 적을 때 값어치가 있다고 봅니다. 왜 이렇게 했는지, 왜 이 방법을 쓰지 않았는지 같은 거요.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀해주신 부분에 공감합니다. 자바를 처음 배우다 보니 새로 알게 된 메서드(repeat(), String.join() 등)를 바로 기록해두고 싶어서 주석으로 남겼는데, 돌아보니 코드를 읽는 분께 필요한 정보가 아니라 제 학습 메모였던 것 같습니다. 다만 코드를 보면서 바로 복습할 수 있다는 점 때문에 당분간은 학습 메모 주석을 유지하고 싶습니다.(주석에 //[학습] 붙여서 구분 시키겠습니다) 대신 "왜 이렇게 했는지, 왜 이 방법을 쓰지 않았는지"를 적은 주석을 추가하는 습관을 길러보도록 하겠습니다, |
||
|
|
||
| //최종 우승자 출력하기 | ||
| public static void printFinalWinners(List<Car> winners) { | ||
| List<String> names = new ArrayList<>(); | ||
| for (Car car : winners) { | ||
| names.add(car.getCarName()); // Car에서 이름만 뽑아서 새 리스트에 담음 | ||
| } | ||
| System.out.println(String.join(", ", names) + "가 최종 우승했습니다."); | ||
| } | ||
| //String.join(", ", ["neo", "brown"]) → "neo, brown" 이렇게 합쳐주는거임. | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 걸 정적 팩토리 메서드라고 하는데, 이름 배열에서
Cars를 만드는 책임을Cars자신이 가진 것 좋습니다.시간이 남으시면 생성자와 어떤 차이가 있는지 한번 찾아보세요. 이름을 붙일 수 있다는 것 말고도 몇 가지가 더 있습니다.
생성자의 특징에 대해 알면 조금 좋을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네 알겠습니다! 정적 팩토리 메서드와 생성자 차이 확인후 코멘트 추가로 남기겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 추가적으로 알려주시면 좋을 거 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
차이점에 대해서 말씀드리겠습니다.
첫 번째, 이름을 붙일 수 있습니다. 생성자는 클래스 이름만 쓸 수 있지만, 정적팩토리메서드는 createCars처럼 무엇을 만드는지 드러낼 수 있습니다.
두 번째, 정적팩토리 메소드에서는 new 처럼 새 객체를 계속해서 선언하지 않아도 됩니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
둘 다 맞습니다. 특히 두 번째는
Integer.valueOf()나Boolean.valueOf()처럼 미리 만들어둔 객체를 재사용할 수 있죠.찾아보시느라 고생하셨습니다. 시간 되시면
『이펙티브 자바』 아이템 1을 한번 보세요. 말씀해주신 두 가지 말고도 세 가지가 더 나옵니다. 전부 외우실 필요는 없고 이런 게 있구나 정도로 훑어두시면, 나중에 실제로 그런 상황을 만났을 때 알아보실 거예요.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네 알겠습니다!