본문 바로가기
개발관련/이것저것

우테코 프리코스 풀어보기 - 레이싱카

by yjoo_ 2023. 10. 27.

우테코 프리코스 문제가 업데이트 되었다.

 

마침 시험이라 학교 갔다가 출근도 하지 않았고, 약속 시간도 좀 남은터라 잠깐 풀어보았다.

 

이번에는 객체지향 답게 각자의 클래스와 메서드를 좀 더 깔끔하게 분리했다.

 

https://github.com/Jym-lab/java-racingcar-6/tree/yjoo

 

GitHub - Jym-lab/java-racingcar-6: Yummy2

Yummy2. Contribute to Jym-lab/java-racingcar-6 development by creating an account on GitHub.

github.com

 

만약 제가 작성한 내용이 문제가 된다면 얼마든지 삭제할 용의가 있으니 연락 부탁드립니다.


문제 요구사항 파악

 

이번에는 간단한 자동차 경주 게임을 구현하는 것이다.

 

자동차 이름과 시도할 라운드 횟수를 입력하면, 각 자동차들이 랜덤 값을 굴린다.

 

값이 4 이상이 나오면 각 자동차들은 한칸 전진한다.

 

매 라운드마다 자동차들이 전진하고 가장 멀리 간 자동차가 승리한다. (공동 우승이 가능하다)

 

그리 어려울 것이 없는 문제다. 다만 이번엔 추가 요구사항들이 추가 되었다.

 

1. for, if, while의 중첩을 최대한 지양할 것.

 

2. 삼항 연산자 사용 금지

 

3. 테스트 코드 작성해보기

 

이런 것을 연습하는 것은 꽤나 좋은 습관이다.

 

깔끔한 코드를 작성할 수 있도록 유도하고, 클래스와 메서드가 각자 하나의 책임만 질 수 있도록 설계하게 된다.

 

나도 42서울에서 이러한 것들을 연습했었다. 당시엔 화도나고 짜증도 많이 났지만, 지금의 코딩 스타일을 갖게해주었다.


풀이 과정

이번 문제를 풀이하기 전 미리 작성한 로직 구성도.

각 클래스에 메서드들이 깔끔하게 설계 될 수 있도록 목표를 정하고 로직을 구성했다.

 

Car 클래스 (자동차)

class Car {
    private String name;
    private int position;

    public Car(String name){
        this.name = name;
        position = 0;
    }
    public void move(int randomNumber){
        if (randomNumber > 3){
            position++;
        }
    }
    public String getName(){
        return name;
    }

    public int getPosition() {
        return position;
    }
}

클래스를 공부할 때 가장 많은 예시로 나오는 자동차.

 

기능 구현 목록에 작성했던 대로 자동차 객체는 자기 자신의 위치와 이름을 갖고있다.

 

각 자동차는 이동할 때 본인의 랜덤 값을 가지고 각자 이동하기 때문에 move 메서드를 Car가 가져야한다.

 

생성자에선 입력 받은 자동차 이름과 포지션을 초기화해준다.

 

Race 클래스 (경기 관리)

class Race{
    private List<Car> cars;

    public Race(String[] carNames) {
        cars = new ArrayList<>();
        for (String name : carNames){
            cars.add(new Car(name));
        }
    }

    public void startRace(){
        for (Car car : cars){
            car.move(Randoms.pickNumberInRange(0, 9));
        }
    }

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

Race 메서드는 경기를 관리한다.

 

사용자로부터 입력받은 자동차들을 경기장에 배치하고 (Race 생성자)

 

매 라운드 마다 각 자동차에게 랜덤 값을 부여한다. (startRace)

 

Input 클래스 (사용자 입력)

class Input {
    public String[] getCarNames(){
        System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
        String[] carNames = Console.readLine().split(",");
        for (int i = 0; i < carNames.length; i++){
            carNames[i] = carNames[i].strip();
        }
        ExceptionChecker.checkCarNames(carNames);
        return carNames;
    }
    public int getRound() {
        System.out.println("시도할 회수는 몇회인가요?");
        String round = Console.readLine();
        int roundNum = ExceptionChecker.checkRound(round);
        return roundNum;
    }
}

자동차들 이름을 입력받는 메서드 (getCarNames)에선 입력받은 인자를 콤마 단위로 구분한다.

 

그리고 앞 뒤로 공백을 제거해주고 ExceptionChecker로 넘겨 입력 값을 검사한다.

 

문제 없이 진행되었다면 자동차 이름 List를 return한다.

 

Result 클래스 (경기 경과 출력 및 판정)

class Result {
    public static void printRoundResult(List<Car> cars){
        for (Car car : cars){
            System.out.println(car.getName() + " : " + "-".repeat(car.getPosition()));
        }
        System.out.println();
    }
    public static List<Car> winnerDeter(List<Car> cars){
        int max = -1;
        List<Car> winner = new ArrayList<>();
        for (Car car : cars){
            if (car.getPosition() > max){
                max = car.getPosition();
            }
        }
        for (Car car : cars){
            if (car.getPosition() == max){
                winner.add(car);
            }
        }
        return winner;
    }
    public static void printWinner(List<Car> cars){
        System.out.print("최종 우승자 : ");
        for (int i = 0; i < cars.size(); i++){
            Car car = cars.get(i);
            if (i == cars.size() - 1){
                System.out.println(car.getName());
            } else {
                System.out.print(car.getName() + ", ");
            }
        }
    }
}

 

매 라운드 자동차들의 전진 값을 출력하는 printRoundResult()

이건 그냥 각 자동차 객체의 position을 가져와서 출력한다.

 

모든 라운드가 종료되고, 결과를 판정하는 winnerDeter.

가장 멀리 전진한 자동차의 position값을 저장하고, 각 배열을 돌면서 같은 position에 존재하는 자동차가 있는지 검사한다.

우승한 자동차 리스트를 return한다.

 

마지막으로 결과를 출력하는 printWinner

winnerDeter에서 return 받은 우승한 자동차 리스트를 받아서 출력한다.

만약 자동차 개수가 여러개라면 콤마를 찍어줘야 하기 때문에 if문으로 제어해주었다.

ExceptionChecker 클래스 (에러 체커)

이 어플리케이션에서 날 수 있는 오류는 사용자 입력 파트기 때문에 Input 클래스에서만 호출된다.

static으로 선언해 간단하게 호출했다.

 

class ExceptionChecker {
    public static void checkCarNames(String[] carNames){
        for (String name : carNames){
            if (name.length() > 5){
                throw new IllegalArgumentException("The car name is longer than 5 characters.");
            }
        }
    }

    public static int checkRound(String round){
        try{
            int number = Integer.parseInt(round);
            if (number < 0){
                throw new IllegalArgumentException("Negative numbers cannot be entered");
            }
            return number;
        } catch (NumberFormatException e){
            throw new IllegalArgumentException("The input round value is not a number!");
        }
    }
}

예외처리는 다음과 같이 처리해주면 된다.

 

1. 입력한 car이름이 5글자가 넘어가면 에러를 던진다

 

2. 입력한 round값이 int로 캐스팅이 불가하거나, 변환된 숫자 값이 음수라면 에러를 던진다.

 

main 메서드 (게임 시작)


public class Application {
    public static void main(String[] args) {
        Input input = new Input();
        String[] carNames = input.getCarNames();
        int round = input.getRound();

        Race race = new Race(carNames);
        for (int i = 0; i < round; i++){
            race.startRace();
            Result.printRoundResult(race.getCars());
        }
        List<Car> winner = Result.winnerDeter(race.getCars());
        Result.printWinner(winner);
    }
}

지금까지 만든 클래스와 메서드를 모두 사용하여 게임을 진행한다.

 

구현이 끝났다.

후기

음... 입력받은 자동차의 앞 뒤 공백을 왜 제거하라고 안하는지 모르겠다.

 

이거 꽤 중요한 에러일텐데... 공백을 앞뒤로 삽입하고 자동차 이름을 넣으면 자동차 이름이 4개면 터질거다.

 

설마 처리한다 해도 출력에서 깨질 수도 있고... 근데 뭐 막을 사람은 다 막겠지~~

 

테스트 코드를 그냥 각 메서드가 정상 작동하는지 검사하는걸 간단하게 넣어봤다.

 

솔직히 요구사항이 너무 간단하게 적혀있어서 어느 정도 수준을 원하는지도 모르겠다.

 

보통 이런 경우엔 "너가 할 수 있는만큼 최대한 많이" 라는 뜻이긴 한데 뭐... 난 부캠 지원자도 아니니까...

 

아무튼 끝!