Skip to content
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

[자동차 경주 게임] 효오 미션 제출합니다. #40

Merged
merged 45 commits into from
May 15, 2019
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8d9603f
숫자 뽑아내기 test case 만들기
kimjungmin-developer May 8, 2019
741d9e2
문자 뽑아내기 test case 만들기
kimjungmin-developer May 8, 2019
36eace4
사칙 연산 적용 test code
kimjungmin-developer May 8, 2019
71fe0fb
전체 계산하기 test code
kimjungmin-developer May 8, 2019
355fa4a
예외처리:숫자가 아닐경우에 대한 예외처리.
kimjungmin-developer May 8, 2019
dd953db
java convention에 맞추기
kimjungmin-developer May 8, 2019
a5f3c03
리팩토링 완료
kimjungmin-developer May 8, 2019
92f7d2c
최종 리팩토링
kimjungmin-developer May 8, 2019
7e795e8
자동차 이름 입력 및 예외 처리
kimjungmin-developer May 8, 2019
04d5e48
자동차 게임의 횟수를 입력받고 예외처리를 한다.
kimjungmin-developer May 8, 2019
9bf8ef5
자동차 이름 리팩토링
kimjungmin-developer May 8, 2019
4f49cfb
테스트코드 수정.
kimjungmin-developer May 9, 2019
00b3923
자동차 이름 등록하기 및 테스트
kimjungmin-developer May 9, 2019
ee55775
자동차 이동거리 계산 및 출력과 테스트 코드 작성.
kimjungmin-developer May 9, 2019
e9009e2
우승자 최대 거리 알아내기 및 테스트 코드 작성
kimjungmin-developer May 9, 2019
de9cf4b
자동차 최종 거리 확인하는 메소드 테스트 코드 생성
kimjungmin-developer May 9, 2019
eac0a11
자동차 최종 거리 테스트 코드 생성
kimjungmin-developer May 9, 2019
40c500d
최종우승자 구하는 method 테스트코드 작성.
kimjungmin-developer May 9, 2019
12f9851
최종수응자를 출력한다.
kimjungmin-developer May 9, 2019
e71ab75
버그 수정.
kimjungmin-developer May 9, 2019
32c5e64
리팩토링
kimjungmin-developer May 9, 2019
f15e7a2
MVC 패턴 적용.
kimjungmin-developer May 10, 2019
d50b0b1
Cars 객체가 제대로 생성되었는지 확인하는 테스트 코드 작성
kimjungmin-developer May 10, 2019
3529fd4
최종 이동 거리 및 우승자에 대한 테스트 코드 수정
kimjungmin-developer May 10, 2019
60c443b
MVC 코드 컨벤션에 따른 수정 및 OutputView 생성
kimjungmin-developer May 10, 2019
67ca843
자동차 움직이는 함수 테스트 코드 작성.
kimjungmin-developer May 10, 2019
a4e18bb
자동차상태확인하는 테스트코드 작성.
kimjungmin-developer May 10, 2019
a7ce293
refactor
kimjungmin-developer May 10, 2019
800c083
reademe 수정.
kimjungmin-developer May 10, 2019
7fb5694
리팩토링 : 코드 컨벤션 및 불필요한 공백 제거.
hyojaekim May 11, 2019
25f9040
enum을 사용하여 계산기 구현하기 및 테스트 코드 수정
hyojaekim May 12, 2019
361f2bc
for each 적용 및 상수 활용
hyojaekim May 12, 2019
49fb2f5
Car와 Cars의 역할 명확 및 테스트 코드 수정
hyojaekim May 12, 2019
bc91ce4
List Position 없이 구현 및 테스트 코드 작성
hyojaekim May 13, 2019
d8a7233
InputView 처리 내용 분리하기
hyojaekim May 13, 2019
5a29bd0
OutputView 처리 내용 분리하기
hyojaekim May 13, 2019
ad2f38b
Winner 로직 분리 및 컨벤션
hyojaekim May 13, 2019
dddc55d
[수정] 랜덤 숫자 생성 리팩토링
hyojaekim May 15, 2019
7ee3a0e
[수정] test 메소드명, 코드 수정
hyojaekim May 15, 2019
7f316c3
[수정] null 값을 리턴하는 메소드 리팩토링
hyojaekim May 15, 2019
d548d21
[수정] 메인함수 역할 지정 및 play 리팩토링
hyojaekim May 15, 2019
0d5e7c9
[수정] toString 적용 및 리팩토링
hyojaekim May 15, 2019
324af58
[수정] Car 생성자 리팩토링
hyojaekim May 15, 2019
b2e0f3d
[수정] for each 적용 및 방식 변경
hyojaekim May 15, 2019
d9f6ad0
[수정] 우승자들 구하는 로직 변경 및 리팩토링
hyojaekim May 15, 2019
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# 1번째 미션 - 문자열 계산기
## 기능 구현 목록
1. [입력] 유저에게 인풋을 받는다.
2. [처리] 유저의 인풋을 숫자와 기호로 나눈다.
* [예외처리] 유저의 인풋이 숫자가 아닌 경우를 확인한다.
* [예외처리] 유저의 인풋이 기호가 아닌 경우를 확인한다.
* [예외처리] 유저의 인풋이 Zero-Division인지 확인한다.
3. [결과] 해당식의 결과를 출력한다.

#2번째 미션 - java-racingcar
## 기능 구현 목록
1. [입력] 유저에게 자동차의 이름을 입력받는다.
* [예외처리] 이름의 공백이 경우를 확인한다.
* [예외처리] 중복된 이름이 있는 경우
* [예외처리] 이름이의 길이가 다섯자 이하인지 확인한다.
2. [입력] 유저에게 시도학 횟수를 입력받는다.
* [예외처리] 0이하의 숫자인지 확인한다.
3. [계산/출력] 자동차가 간 거리를 계산한다.
4. [결과출력] 우승자를 출력한다.

# java-racingcar
자동차 경주 게임 미션 저장소

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
public class Calculator {
int addition(int i, int j) {
return i+j;
}
int subtraction(int i, int j) {
return i-j;
}
}
15 changes: 15 additions & 0 deletions src/main/java/calculator/Calculate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package calculator;

import exceptionsCalculator.CalculatorException;

enum Calculate {
RESULT();

public int getCalculateResult(int firstNumber, int secondNumber, String symbol) {
if (symbol.equals("+")) return firstNumber + secondNumber;
if (symbol.equals("-")) return firstNumber - secondNumber;
if (symbol.equals("*")) return firstNumber * secondNumber;
if (symbol.equals("/")) return CalculatorException.divisionException(firstNumber, secondNumber);
return CalculatorException.applyCalculationException();
}
}

Choose a reason for hiding this comment

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

앞으로 남은 미션을 진행하면서 enum이 굉장히 자주 유용하게 사용될것입니다!
따라서 이번 미션을 통해서 enum에 대해 완전히 숙지 하지 못하였더라도 꼭 공부하면 좋을 것 같습니다!!

Copy link
Author

Choose a reason for hiding this comment

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

다른분이 슬랙에 올려주신 내용을 확인했는데 enum 말고 다른 내용도 이해가 필요할 것 같아서 시간 남을 때마다 모르는 부분을 체크하고 공부하려고 합니다 ㅠ

47 changes: 47 additions & 0 deletions src/main/java/calculator/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package calculator;

import exceptionsCalculator.CalculatorException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Calculator {

public static void main(String[] args) {
doCalculate();
}

public static void doCalculate() {
String userInput = CalculatorException.readAndReceiveInput();
List<String> numbers = new ArrayList<>(Arrays.asList(userInput.split(" ")));
List<Integer> numberList = extractNumbers(numbers);
List<String> symbolList = extractSymbols(numbers);
System.out.println(calculate(numberList, symbolList));
}

public static List<Integer> extractNumbers(List<String> numbers) {
List<Integer> listOfNumbers = new ArrayList<>();
for (int i = 0; i < numbers.size(); i += 2) {
int number = CalculatorException.extractNumbersException(numbers.get(i));
listOfNumbers.add(number);
}
return listOfNumbers;
}

public static List<String> extractSymbols(List<String> symbols) {
List<String> listOfSymbols = new ArrayList<>();
for (int i = 1; i < symbols.size(); i += 2) {
listOfSymbols.add(symbols.get(i));
jihan805 marked this conversation as resolved.
Show resolved Hide resolved
}
return listOfSymbols;
}

public static int calculate(List<Integer> listOfNumbers, List<String> listOfSymbols) {
int result = listOfNumbers.get(0);
for (int i = 0; i < listOfSymbols.size(); i++) {
result = Calculate.RESULT.getCalculateResult(result, listOfNumbers.get(i + 1), listOfSymbols.get(i));
}
return result;
}
}
39 changes: 39 additions & 0 deletions src/main/java/exceptionsCalculator/CalculatorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package exceptionsCalculator;

import calculator.Calculator;

import java.util.Scanner;

public class CalculatorException {

public static String readAndReceiveInput() {
System.out.println("숫자를 입력해 주세요!");
Scanner reader = new Scanner(System.in);
return reader.nextLine();
}

public static int divisionException(int result, int number){
if (number == 0) {
System.out.println("0이 분모에 있습니다.");
Calculator.doCalculate();
}
return result / number;
}

public static int extractNumbersException(String stringNumber) {
try{
return Integer.parseInt(stringNumber);
}catch (Exception e){
System.out.println("잘못된 입력값입니다! 숫자가 아닙니다!");
Calculator.doCalculate();
}
return -1;
}

public static int applyCalculationException(){
System.out.println("잘못된 입력값입니다. 기호가 아닙니다!");
Calculator.doCalculate();
return -1;
}

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

import java.util.*;

public class Car {
private static final int MAX_NAME_SIZE = 5;
private static final int POSSIBLE_MOVE_CAR = 4;
private static final int MAX_RANDOM_NUMBER = 10;
private static final int MIN_RANDOM_NUMBER = 0;

private final String name;
jihan805 marked this conversation as resolved.
Show resolved Hide resolved
private int position = 0;

Car(String name) {
if (isWhiteSpaceOnly(name)) {
throw new IllegalArgumentException();
}
if (isOverLimit(name)) {
throw new IllegalArgumentException();
}
this.name = name;
}

Car(String name, int position) {
this.name = name;

Choose a reason for hiding this comment

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

더불어 Car(String name) 생성자에서 position에 대한 초기화도 함께 진행하는 것이 어떨까요
해당 생성자는 테스트를 위해 만드신걸로 보이는데, 비록 테스트를 위해 만들어졌다고 하더라도 위 의 생성자에서 하는 is~에 대한 확인은 해야한다고 생각합니다!

Copy link
Author

Choose a reason for hiding this comment

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

잘 반영했는지 모르겠지만 Car(String name) 생성자에서 position을 초기화 하도록 다른 생성자로 포지션을 0을 넘겨주어 리팩토링하였습니다.

this.position = position;
}

public static boolean isWhiteSpaceOnly(String name) {
return name.isEmpty();
}

public static boolean isOverLimit(String name) {
return name.length() > MAX_NAME_SIZE;
}

public Car moveCar(int randomNumber) {
if (randomNumber >= POSSIBLE_MOVE_CAR) {
position++;
}
return this;
}

public static int randomNumberGenerator() {
return (int) (Math.random() * MAX_RANDOM_NUMBER) + MIN_RANDOM_NUMBER;
}

Choose a reason for hiding this comment

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

(Math.random() * MAX_RANDOM_NUMBER) 자체로 0~9 사이의 랜덤 값을 생성해 주기 때문에 MIN_RANDOM_NUMBER(0) 는 더하지 않아도 될것 같아요!
추가로 랜덤 넘버 생성의 경우, Car 객체 보다는 Util 혹은, Car 외부에서 담당하는것이 더 좋을 것 같습니다!
자동차는 단순히 자신이 움직일 수 있는가?에 대해서만 확인 후 움직이거나 움직이지 않는 역할로 충분하지 않을까요?

Copy link
Author

Choose a reason for hiding this comment

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

넵 피드백 주신 내용 반영했고
Util에서 따로 만들어주니 더 깔끔해지는 효과가 있었습니다!


public int findMax(int max) {
if (position > max) {
max = position;
}
return max;
}

public String sameMaxPositionCarName(int maxPosition) {
if (this.position == maxPosition) {
return this.name;
}
return null;
}

Choose a reason for hiding this comment

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

null을 리턴하는 것은 굉장히 위험합니다!
sameMaxPositionCarName를 Winner 관련 클래스에서 사용하고 있는데, 아래에 피드백 남기겠습니다!
대안으로, isSamePosition과 같은 메서드를 통해 maxPosition인지 아닌지를 boolean 타입 등으로 확인하면 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

이번 미션을 통해서 null 값을 리턴하는 것이 위험하다고 깨달았고
메시지를 던져서 maxPosition인지 아닌지 확인하는 방식으로 리팩토링 하였습니다!

Choose a reason for hiding this comment

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

비교할 로직이 복잡하지 않은 경우, 위와 같이 단순하고 명료한 경우 return this.position == maxPosition 같이 한줄로 구현할 수 있어요!


public String getCarState() {
String carState = this.name + " : ";
for (int i = 0; i < this.position; i++) {
carState += "-";
}
return carState;
}

Choose a reason for hiding this comment

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

출력을 위한 string을 생성하는 코드로 보여지는데요, 관련하여 toString Override에 대해 찾아보고 적용하면 좋을것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

toString을 활용하니 더 효율적이고 앞으로도 유용하게 쓰일 것 같다고 느꼈습니다.

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return position == car.position &&
Objects.equals(name, car.name);
}

@Override
public int hashCode() {
return Objects.hash(name, position);
}
}
20 changes: 20 additions & 0 deletions src/main/java/racingcar/CarGameLauncher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package racingcar;

import racingcar.view.InputView;
import racingcar.view.InputViewException;
import racingcar.view.OutputView;

public class CarGameLauncher {
public static void main(String[] args) {
doCarGame();
}

public static void doCarGame() {
Cars cars = new Cars(InputView.askAndReceiveCarNames());
int totalTurns = InputViewException.askAndReceiveTotalTurns();
Play.printCarState(cars, totalTurns);
Winners winners = new Winners(cars);
OutputView.printWinners(winners);
System.exit(0);
}

Choose a reason for hiding this comment

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

Play 클래스의 역할과 CarGameLauncher 역할이 섞여 있는 것 같아요!
CarGameLauncher는 인풋을 받고, 경주를 실행하기 위해 필요한 인풋을 전달하고(Play 클래스로), 우승자를 구하고, 출력하기
Play는 전달 받은 인풋에 대해 validation을 확인 후 자동차를 생성/경주를 진행하기(하지만 출력도 함께 진행하고 있어요!)
가 각각 꼭 담당해야하는 역할이라고 생각해요

public static void main(String[] args) {
    //input for car names and round count

    Play play = new Play(carNames, tryCount);
    Winners winners = null;
    while(play.hasNextRound()) {
        winners = play.move();
        // print
    }
    // print
}

구현에 정답은 없지만, 힌트는 위에 첨부한 코드와 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

피드백 주신대로 play에서 자동차를 생성/ 경주를 진행하고
CarGameLauncher에서 단순히 인풋을 전달하고 출력만 하는 기능으로 최대한 나누어 보았습니다.

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 < totalTurns; i++) 이 부분도 Play가 담당하도록 구현해봐도 좋을 것 같아요! 👍

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

import java.util.*;

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

Cars(String names) {
names = names.replaceAll("\\s+", "");
List<String> carNames = new ArrayList<>(Arrays.asList(names.split(",")));
instantiateCar(carNames);
}
jihan805 marked this conversation as resolved.
Show resolved Hide resolved

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

public int getSize() {
return cars.size();
}

public String getCarState(int index) {
return cars.get(index).getCarState();
}

public void updateCarMovement(int index) {
cars.get(index).moveCar(Car.randomNumberGenerator());
}

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

public void instantiateCar(List<String> carNames) {
try {
isDuplicate(carNames);
addCarToCars(carNames);
} catch (Exception e) {
CarGameLauncher.doCarGame();
}
}

public void addCarToCars(List<String> carNames) {
for (String name : carNames) {
cars.add(new Car(name));
}
}

public static void isDuplicate(List<String> names) {
Set<String> nameSet = new HashSet<>(names);
if (names.size() != nameSet.size()) {
System.out.println("이름에 중복이 있습니다!");
throw new IllegalArgumentException();
}
}

jihan805 marked this conversation as resolved.
Show resolved Hide resolved
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cars cars1 = (Cars) o;
return Objects.equals(cars, cars1.cars);
}

@Override
public int hashCode() {
return Objects.hash(cars);
}
}
31 changes: 31 additions & 0 deletions src/main/java/racingcar/Play.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package racingcar;

import racingcar.view.OutputView;

import java.util.ArrayList;
import java.util.List;

public class Play {

public static void printCarState(Cars cars, int turn) {
OutputView.printResult();
for (int i = 0; i < turn; i++) {
moveCar(cars);
OutputView.printState(getCarStates(cars));
}
}

public static List<String> getCarStates(Cars cars) {
List<String> carStates = new ArrayList<>();
for (int i = 0, n = cars.getSize(); i < n; i++) {
carStates.add(cars.getCarState(i));
}
return carStates;
}

public static void moveCar(Cars cars) {
for (int i = 0, n = cars.getSize(); i < n; i++) {

Choose a reason for hiding this comment

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

index를 사용한 for loop 보다는 for each에 대해 공부해보면 좋을 것 같습니다!
또한 지금처럼 Cars 클래스에서 움직임을 구현하고 싶다면 updateCarMovement에 index를 넘겨 car.get(i).~ 보다는
위의 구조에서는
for (int i = 0; i < turn; i++) {
cars.moveCars();
}

로 변경하고 Cars 클래스에서 Cars가 가지고 있는 Car List를 순회하며 move 하는 것은 어떨까요?

다만, 해당 클래스의 구조는 제가 위에 드린 예시 코드 적용에 맞지 않을 수 있다고 생각됩니다!
따라서 제가 드린 피드백을 참고하여 고민해보면 좋을 것 같습니다!

Copy link
Author

Choose a reason for hiding this comment

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

전에 피드백 주신 play 부분을 고치고 나니 여기 부분도 쉽게 수정할 수 있었고 코드가 깔끔해지며 효율성이 더 높아진 효과가 있는 것 같습니다!

cars.updateCarMovement(i);
}
}
}
39 changes: 39 additions & 0 deletions src/main/java/racingcar/Winners.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package racingcar;

import java.util.*;

public class Winners {
private List<String> listOfWinners = new ArrayList<>();

Winners(Cars cars) {
List<Car> carsState = cars.getCars();
int maxPosition = decideMaxPosition(carsState);
decideWinners(carsState, maxPosition);
}

Choose a reason for hiding this comment

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

Winners 클래스는 listOfWinners를 String으로 가지고 있는 대신, List를 가지고 있는 것은 어떨까요!?
또한 Winners 생성자에서 우승자를 구하는 것이 아닌,
public String getWinners() {

}
와 같이 메서드로 분리하는 것은 어떨까요?
그럼 우승자 String을 구하는 로직이 조금 더 간단해질 것 같아요!
(구현하며 만약 Car의 name을 get 해야 하는 경우가 발생한다면 허용하겠습니다!)

Copy link
Author

Choose a reason for hiding this comment

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

피드백을 반영하여 수정했습니다!

public int decideMaxPosition(List<Car> cars) {
int max = 0;
for (Car car : cars) {
max = car.findMax(max);
}
return max;
}

Choose a reason for hiding this comment

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

max 포지션 분리 👍

public void decideWinners(List<Car> cars, int maxPosition) {
for (Car car : cars) {
addWinners(car, maxPosition);
}
}

public void addWinners(Car car, int maxPosition) {
String winnerName = car.sameMaxPositionCarName(maxPosition);
if (winnerName != null) {
listOfWinners.add(winnerName);
}
}

public String getWinners() {
return String.join(", ", listOfWinners);
}

}
Loading