ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 우아한 테크코스 - 3기 2주 차 프리코스 후기
    생각과 일상/혼자의 생각 2020. 12. 18. 01:44

    2주 차 미션은 자동차 경주 게임이었다.

    2주 차부터는 추가 요구 사항으로 else를 사용하지 않는 것, 객체를 활용해 구현해야 한다는 게 주어졌다. 

     

     

     

     

     

     

    1. 경주할 자동차 목록을 입력받는다.

    package racingcar;
    
    import java.util.Scanner;
    
    public class InputView {
        private static Scanner input = new Scanner(System.in);
    
        private static String ASK_CAR_NAME_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
        private static String ASK_NUMBER_OF_ATTEMPTS = "시도할 회수는 몇회인가요?";
    
        public static String askCarName() {
            System.out.println(ASK_CAR_NAME_MESSAGE);
            return input.nextLine();
        }
    
        public static String askNumberOfAttempts() {
            System.out.println(ASK_NUMBER_OF_ATTEMPTS);
            return input.nextLine();
        }
    }

    package view;
    
    import java.util.Scanner;
    
    public class InputView {
        private static final String GET_CAR_NAME_LIST = "경주할 자동차 이름을  입력하세요.(이름은 쉼표(,) 기준으로 구분)";
        private static final String GET_RACING_TRIAL_NUMBER = "시도할 회수는 몇회인가요?";
    
        private static Scanner scanner = new Scanner(System.in);
    
        public static String getCarNameList() {
            System.out.println(GET_CAR_NAME_LIST);
            String input = scanner.nextLine();
            return input;
        }
    
        public static String getGetRacingTrialNumber() {
            System.out.println(GET_RACING_TRIAL_NUMBER);
            String input = scanner.nextLine();
            return input;
        }
    }
    
    오히려 전에 작성했던 코드가 한 줄이 더 짧네.. 2주 차 미션에서는 두 번만 입력을 받기 때문에 Inputview 부분은 구현하는 데 큰 어려움은 없었다. 입력 받은 문자열을 예외처리 하는 경우가 많아 예외처리를 담당하는 클래스를 따로 만들어 관리했다.

     

     

    2. 입력받은 문자열 예외 처리 (자동차 이름, 회수)

    package racingcar;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class CheckValidation {
        private static final String ERROR_MESSAGE = "[ERROR] : ";
    
        public static boolean carName(String userInput) {
            List<String> carList = stringToList(userInput);
            return checkValidList(carList);
        }
    
        public static List<String> stringToList(String userInput) {
            return Arrays.asList(userInput.split(","));
        }
    
        public static boolean checkValidList(List<String> carList) {
            return (checkNotnull(carList) && checkNotSame(carList) &&
                    checkNameLength(carList) && checkContainBlank(carList));
        }
    
        public static boolean checkNotSame(List<String> carList) {
            String checkName = carList.get(0);
            for (int i = 1; i < carList.size(); i++) {
                if (carList.get(i).equals(checkName)) {
                    throw new IllegalArgumentException(ERROR_MESSAGE + "중복된 이름이 존재합니다.");
                }
            }
            return true;
        }
    
        public static boolean checkNotnull(List<String> carList) {
            if (carList.size() <= 1 || carList == null) {
                throw new IllegalArgumentException(ERROR_MESSAGE + "두 대 이상의 자동차 이름을 입력해주세요.");
            }
            return true;
        }
    
        public static boolean checkNameLength(List<String> carList) {
            for (String carName : carList) {
                if (carName.length() > 5) {
                    throw new IllegalArgumentException(ERROR_MESSAGE + "자동차 이름은 5글자를 넘을 수 없습니다.");
                }
            }
            return true;
        }
    
        public static boolean checkContainBlank(List<String> carList) {
            for (String carName : carList) {
                if(carName.contains(" ")) {
                    throw new IllegalArgumentException(ERROR_MESSAGE + "이름엔 공백이 들어갈 수 없습니다.");
                }
            }
            return true;
        }
    
        public static boolean numberOfAttepmts(String attemptsInput) {
            return isNotnull(attemptsInput) && isNumber(attemptsInput);
        }
    
        public static boolean isNotnull(String input) {
            return input != null;
        }
    
        public static boolean isNumber(String input) {
            try {
                int parsedNum = Integer.parseInt(input);
                if (parsedNum <= 0) {
                    throw new IllegalArgumentException(ERROR_MESSAGE + "올바른 숫자가 아닙니다.");
                }
            } catch (NumberFormatException error) {
                throw new IllegalArgumentException(ERROR_MESSAGE + "숫자만 입력 가능합니다.");
            }
            return true;
        }
    }

    package racingcar;
    
    import view.InputView;
    import view.OutputView;
    
    import java.util.Arrays;
    
    public class ValidChecker {
        private final String NOT_VALID_LIST_LENGTH = "두 대 이상의 자동차 이름을 입력해주세요.";
        private final String NOT_VALID_NAME_LENGTH = "자동차 이름은 5글자 이하만 가능합니다.";
        private final String NAME_NULL_ERROR = "자동차 이름은 공백일 수 없습니다.";
        private final String DUPLICATED_NAME_ERROR = "중복된 이름이 존재합니다.";
        private final String HAVE_TO_OVER_ZERO_ERROR = "시도 회수는 1보다 이상만 입력 가능합니다.";
        private final String NOT_VALID_NUMBER_ERROR = "시도 횟수는 숫자여야 합니다.";
    
        private final int MIN_CAR_LIST = 1;
        private final int MAX_NAME_LENGTH = 5;
        private final String NULL = "";
        private final String SPACE = " ";
        private final String SEPARATOR = ",";
    
        public boolean isValidCar(String userInput) {
            String[] carList = userInput.split(SEPARATOR);
            return isValidListLength(carList) && isValidNameLength(carList)
                    && hasNotContainSpace(carList) && streamDuplicateCheck(carList);
        }
    
        private boolean isValidNameLength(String[] carList) {
            for (String carName : carList) {
                if (carName.length() > MAX_NAME_LENGTH) {
                    OutputView.printError(NOT_VALID_NAME_LENGTH);
                    return false;
                }
            }
            return true;
        }
    
        private boolean hasNotContainSpace(String[] carList) {
            for (String carName : carList) {
                if (carName.equals(NULL) || carName.contains(SPACE)) {
                    OutputView.printError(NAME_NULL_ERROR);
                    return false;
                }
            }
            return true;
        }
    
        private boolean streamDuplicateCheck(String[] carList) {
            if (Arrays.stream(carList).anyMatch(car -> car.equals(car))) {
                OutputView.printError(DUPLICATED_NAME_ERROR);
                return false;
            }
            return true;
        }
    
        private boolean isValidListLength(String[] carList) {
            if(carList.length <= MIN_CAR_LIST) {
                OutputView.printError(NOT_VALID_LIST_LENGTH);
                return false;
            }
            return true;
        }
    
        public boolean isValidNum(String userInput) {
            try {
                int parseNum = Integer.parseInt(userInput);
                if (parseNum <= 0) {
                    OutputView.printError(HAVE_TO_OVER_ZERO_ERROR);
                    return false;
                }
            } catch (NumberFormatException e) {
                OutputView.printError(NOT_VALID_NUMBER_ERROR);
                return false;
            }
            return true;
        }
    }
    
    전에 작성한 코드는 상수가 거의 없다. 그리고 static 함수를 사용했는데 다시 코드를 짤 땐 유효성판별하는 인스턴스를 불러와서 인스턴스에게 일을 시키도록 했다. 그리고 잘못된 입력이 들어왔을 경우 오류 메세지를 출력하고 재입력을 받으려고 했는데 5시간을 잡아놓고 구현을 하다보니 시간이 모자라서 그냥 IllergalArgumentException 오류를 던지고 프로그램이 종료되도록 만들었다. 공백처리와 null처리를 같은 메소드로 묶어 처리해 메서드 하나를 줄일 수 있었고, 나머지는 거의 똑같다. 
    이름 중복체크하는 부분을 stream을 사용해서 같은 이름이 2개 이상들어오면 바로 false를 반환하고 싶어 stream().anyMatch를 사용했다. 스트림으로 쭉 지나가면서 같은 이름이 발견되면 바로 false를 반환!

     

     

    3. 각 자동차가 난수 생성 후 4이상일 경우만 전진

    package racingcar;
    
    import utils.RandomUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Car {
        private final String name;
        private int position = 0;
        private String positionSign = "";
        private static List<Car> carList = new ArrayList<>();
        private static int MOVE_LIMIT_NUM = 4;
        private static int MIN_RANDOM_NUM = 0;
        private static int MAX_RANDOM_NUM = 9;
    
        public Car(String name, int position) {
            this.name = name;
            this.position = position;
        }
    
        public static List<Car> makeCarList(String userInput) {
            List<String> userList = CheckValidation.stringToList(userInput);
            for (String car : userList) {
                carList.add(new Car(car, 0));
            }
            return carList;
        }
    
        public static void setPosition(Car car) {
            moveOrStop(car);
        }
    
        public static void moveOrStop(Car car) {
            if (randomNumGenerator() >= MOVE_LIMIT_NUM) {
                car.move();
            }
        }
    
        public static int randomNumGenerator() {
            return RandomUtils.nextInt(MIN_RANDOM_NUM,MAX_RANDOM_NUM);
        }
    
        public void move() {
            position += 1;
        }
    
        public static void displayPosition(List<Car> cars) {
            for (Car car : cars) {
                car.changePositionsign(car);
                System.out.println(car.name + " : " + car.positionSign);
            }
            System.out.println();
        }
    
        public String changePositionsign(Car car) {
            for (int i = 0; i < car.position; i++) {
                positionSign += "-";
            }
            return positionSign;
        }
    
        public static List<String> getWinner(List<Car> carList) {
            int maxPosition = getMaxPosition(carList);
            List<String> winnerList = new ArrayList<>();
            for (Car car : carList) {
                if (car.position == maxPosition) {
                    winnerList.add(car.name);
                }
            }
            return winnerList;
        }
    
        public static int getMaxPosition(List<Car> carList) {
            int max = 0;
    
            for (Car car : carList) {
                if (car.position >= max) {
                    max = car.position;
                }
            }
            return max;
        }
    }

    package racingcar;
    
    import utils.RandomUtils;
    import view.OutputView;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Car {
        private static int MIN_NUM = 0;
        private static int MAX_NUM = 9;
        private static int MOVABLE_NUM = 4;
        public static List<Car> carList = new ArrayList<>();
    
        private final String name;
        private int position = 0;
        public static int maxPosition = 0;
    
    
        public Car(String name, int position) {
            this.name = name;
            this.position = position;
        }
    
        public String getName() {
            return name;
        }
    
        public int getPosition() {
            return position;
        }
    
        public void addCar(Car car) {
            carList.add(car);
        }
    
        private void move() {
            position++;
        }
    
        public static void canMove(Car car) {
            int randomNum = RandomUtils.nextInt(MIN_NUM, MAX_NUM);
            if (randomNum >= MOVABLE_NUM) {
                car.move();
            }
        }
    
        public static void getWinner() {
            getMaxPosition();
            makeWinnerList();
            OutputView.printWinner(makeWinnerList());
        }
    
        private static int getMaxPosition() {
            for (Car car : carList) {
                if (car.position > maxPosition) {
                    maxPosition = car.position;
                }
            }
            return maxPosition;
        }
    
        private static List<String> makeWinnerList() {
            List<String> winnerList = new ArrayList<>();
            for (Car car : carList) {
                if (car.position == maxPosition) {
                    winnerList.add(car.getName());
                }
            }
            return winnerList;
        }
    }
    
    비지니스 로직과 UI로직을 분리해 출력되는 부분은 OutputView 클래스에서 처리하려고 노력했다. 15라인을 넘으면 안되는 제한도 최대한 지켰고, 메서드 이름을 정할 때도 좀더 알아볼 수 있도록 정하려고 했다. 원래 제출한 코드에선 난수 생성과 판단을 구별했지만, 다시 썼을 땐 그냥 움직일 수 있는지 없는지를 기준으로 메서드를 구현했는데 한 가지 일만하도록 최대한 작게 구현하는 것에 있어선 전에 작성한 코드가 더 제한 사항에 맞게 작성한 것 같다.

     

    4. 입력 받은 횟수만큼 반복 실행하며 위치 출력

    ackage racingcar;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import racingcar.InputView;
    
    public class RacingGame {
        private String userInput;
        private String attemptsInput;
        private List<Car> cars;
        private int attemptsNum;
        private static String WINNER_MESSAGE = "최종 우승자: ";
    
        public void readyGame() {
            userInput = InputView.askCarName();
            if (CheckValidation.carName(userInput)) {
                cars = Car.makeCarList(userInput);
            }
    
            attemptsInput = InputView.askNumberOfAttempts();
            if (CheckValidation.numberOfAttepmts(attemptsInput)) {
                attemptsNum = Integer.parseInt(attemptsInput);
            }
        }
    
        public void playGame() {
            for (int i = 0; i < attemptsNum; i++){
                setCarPosition(cars);
                Car.displayPosition(cars);
            }
        }
    
        public static void setCarPosition(List<Car> cars) {
            for (Car car : cars) {
                Car.setPosition(car);
            }
        }
    
        public void displayWinner() {
            List<String> winnerList = Car.getWinner(cars);
            String[] winnerArr = winnerList.toArray(new String[winnerList.size()]);
    
            String winner = String.join(", ", winnerArr);
            System.out.print(WINNER_MESSAGE + winner);
        }
    }

    package racingcar;
    
    import view.InputView;
    import view.OutputView;
    
    import java.util.List;
    
    public class RacingGame {
        private final String SEPARATOR = ",";
        private final String ESCAPE_GAME = "[ERROR] 잘못된 입력으로 프로그램을 종료합니다.";
    
        private ValidChecker validChecker = new ValidChecker();
        private static List<Car> carList = Car.carList;
    
        public void start() {
            String[] cars = askCarName();
            int trialNum = askTrialNumber();
            makeCarList(cars);
            printDistance(carList, trialNum);
            Car.getWinner();
        }
    
        public void printDistance(List<Car> carList, int trialNum) {
            OutputView.printGameResult();
            for (int i = 0; i < trialNum; i++) {
                for (Car car : carList) {
                    Car.canMove(car);
                    OutputView.printPlayerAndPosition(car);
                }
                System.out.println();
            }
        }
    
        private void makeCarList(String[] cars) {
            for (String name : cars) {
                Car car = new Car(name, 0);
                car.addCar(car);
            }
        }
    
        private String[] askCarName() {
            String inputCars = InputView.getCarNameList();
            boolean result = validChecker.isValidCar(inputCars);
            if (!result) {
                throw new IllegalArgumentException(ESCAPE_GAME);
            }
            return inputCars.split(SEPARATOR);
        }
    
        private int askTrialNumber() {
            String inputTrialNum = InputView.getGetRacingTrialNumber();
            boolean result = validChecker.isValidNum(inputTrialNum);
            if (!result) {
                throw new IllegalArgumentException(ESCAPE_GAME);
            }
            return Integer.parseInt(inputTrialNum);
        }
    }
    
    package view;
    
    import racingcar.Car;
    
    import java.util.List;
    
    public class OutputView {
        private static final String GAME_RESULT_MESSAGE = "\n실행 결과";
        private static final String COLON = " : ";
        private static final String DISTANCE = "-";
        private static final String ERROR = "[ERROR] ";
        private static final String WINNER_IS = "최종 우승자: ";
        private static final String AND = ", ";
    
        public static void printGameResult() {
            System.out.println(GAME_RESULT_MESSAGE);
        }
    
        public static void printPlayer(String carName) {
            System.out.printf(carName + COLON);
        }
    
        public static void printPosition(int distance) {
            for (int i = 0; i < distance; i++) {
                System.out.printf(DISTANCE);
            }
            System.out.println();
        }
    
        public static void printError(String message) {
            System.out.println(ERROR + message);
        }
    
        public static void printWinner(List<String> winnerList) {
            System.out.println(WINNER_IS + String.join(AND, winnerList));
        }
    
        public static void printPlayerAndPosition(Car car) {
            OutputView.printPlayer(car.getName());
            OutputView.printPosition(car.getPosition());
        }
    }
    
    출력을 담당하는 클래스와 게임로직을 담당하는 클래스로 나누다 보니 코드가 더 길어진 것도 같다. 다만 UI로직과 비지니스 로직을 분리하는 것을 의도했기 때문에 개인적으로 전에 작성한 코드보단 다시 작성한 코드가 더 맘에 든다. 

     

     

    5. 우승자 가리기

    package racingcar;
    import java.util.Scanner;
    public class Application {
        public static void main(String[] args) {
            final Scanner scanner = new Scanner(System.in);
            // TODO 구현 진행
            RacingGame racingGame = new RacingGame();
    
            racingGame.readyGame();
            racingGame.playGame();
            racingGame.displayWinner();
        }
    }

    package racingcar;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.Scanner;
    
    public class Application {
        public static void main(String[] args) {
            final Scanner scanner = new Scanner(System.in);
            // TODO 구현 진행
            RacingGame racingGame = new RacingGame();
            racingGame.start();
        }
    }
    

     

     

     

    2주차 때 어려웠던 부분은 객체를 분리해 객체별로 메소드를 나누는 것이었다. 다시 한 번 해봐도 객체가 해야 하는 부분들을 정확하게 나눴다는 생각이 들지 않았다. 전체 피드백으로 받았던 객체에 메세지를 보내는 부분이 잘 지켜지지 않는다.. 개념이 부족한 탓이겠지... 기초가 부족한 채 해결해 나가려다보니 생기는 문제라 생각한다. 2주 차를 진행하는 동안은 비지니스 로직과 UI로직을 나누는 정도만 겨우 해낸 거 같다. 미션을 진행하면서 좌절감도 많이 느꼈지만, 이렇게 프로그램 짜보면서 피드백도 받고, 스스로도 계속 점검할 수 있는 이런 기회가 주어졌음에 정말 감사했다. 

Designed by Tistory.