JUINTINATION

우테코 백엔드 프리코스 체험해보기 2(자동차 경주 게임) 본문

StudyNote

우테코 백엔드 프리코스 체험해보기 2(자동차 경주 게임)

DEOKJAE KWON 2025. 1. 14. 07:54
반응형

카카오테크 부트캠프 풀스택 과정 2기에 최종 합격했지만 마냥 놀기에는 양심에 찔리고, 그렇다고 갑자기 혼자 프로젝트를 시작하자니 부담스러워서 지난 우테코 백엔드 프리코스 체험해보기(숫자 야구 게임)에 이어 자동차 경주 게임 미션에 도전했다.

미션 - 자동차 경주 게임

  • 미션은 기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항 세 가지로 구성되어 있다.
  • 세 개의 요구사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.

이번에는 최대한 모든 요구사항을 만족하도록 코드를 작성하고, 나름 Git의 커밋 단위도 맞춰봤다.

기능 요구사항

초간단 자동차 경주 게임을 구현한다.

  • 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
  • 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
  • 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
  • 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
  • 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
  • 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
  • 아래의 프로그래밍 실행 결과 예시와 동일하게 입력과 출력이 이루어져야 한다.

구현할 기능 목록

  • 각 자동차에 이름 부여
    • 자동차 이름은 쉼표(,)를 기준으로 구분
    • 이름은 5자 이하만 가능
  • 시도할 횟수 입력
    • 시도 횟수는 0보다 큰 숫자만 입력 가능
  • 자동차 전진 기능 추가
    • 0에서 9 사이의 랜덤 값을 구한 후, 값이 4 이상일 경우 전진
  • 게임 진행 결과 출력 기능 추가
    • 각 턴 별 자동차의 이동 상황을 출력
  • 우승자 판별 및 출력 기능 추가
    • 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분

추가된 요구사항

  • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
  • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.

프로그래밍 요구사항 - Car 객체

  • 다음 Car 객체를 활용해 구현해야 한다.
  • Car 기본 생성자를 추가할 수 없다.
  • name, position 변수의 접근 제어자인 private을 변경할 수 없다.
  • 가능하면 setPosition(int position) 메소드를 추가하지 않고 구현한다.
public class Car {
    private final String name;
    private int position = 0;

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

    // 추가 기능 구현
}

프로그래밍 요구사항 - Randoms, Console

  • JDK에서 기본 제공하는 Random, Scanner API 대신 camp.nextstep.edu.missionutils에서 제공하는 Randoms, Console API를 활용해 구현해야 한다.
    • Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange()를 활용한다.
    • 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.Console의 readLine()을 활용한다.
  • 프로그램 구현을 완료했을 때 src/test/java 디렉터리의 ApplicationTest에 있는 모든 테스트 케이스가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다.

이외에 입출력 요구사항, 과제 진행 요구사항 등은 우테코 깃허브에서 확인이 가능하다.


Application.java

package racingcar;

public class Application {
    public static void main(String[] args) {
        Game game = Game.getInstance();
        game.start();
    }
}

기능 요구사항을 보면 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후에 "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받기 때문에 try-catch문으로 감싸지 않았다.

Game.java

package racingcar;

import java.util.*;

import camp.nextstep.edu.missionutils.Console;

public class Game {

    private static final Game game = null;
    private Queue<String> winnersQueue;
    private List<Car> cars;
    private int tryCount;

    private Game() {
    }

    public static Game getInstance() {
        if (game == null) {
            return new Game();
        }
        return game;
    }

    public void inputCarNames() {
        System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
        cars = new ArrayList<>();
        try {
            String carNames = Console.readLine();
            StringTokenizer st = new StringTokenizer(carNames, ",");
            while (st.hasMoreTokens()) {
                String carName = st.nextToken();
                validateCarName(carName);
                cars.add(new Car(carName));
            }
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
            inputCarNames();
        }
    }

    public void validateCarName(String carName) {
        if (carName.length() > 5) {
            throw new IllegalArgumentException("[ERROR] 자동차 이름은 5자 이하만 가능합니다.");
        }
    }

    public void inputTryCount() {
        System.out.println("시도할 회수는 몇회인가요?");
        try {
            String tryCount = Console.readLine();
            validateTryCount(tryCount);
            this.tryCount = Integer.parseInt(tryCount);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
            inputTryCount();
        }
        System.out.println();
    }

    public void validateTryCount(String tryCount) {
        if (tryCount.chars().anyMatch(ch -> !Character.isDigit(ch))) {
            throw new IllegalArgumentException("[ERROR] 숫자가 아닌 값이 포함되어 있습니다.");
        }
        if (Integer.parseInt(tryCount) <= 0) {
            throw new IllegalArgumentException("[ERROR] 시도 횟수는 1 이상이어야 합니다.");
        }
    }

    public void moveCars() {
        System.out.println("실행 결과");
        for (Car car : cars) {
            car.move();
        }
    }

    public void printCars() {
        for (Car car : cars) {
            car.printPosition();
        }
        System.out.println();
    }

    public void identifyWinners() {
        int maxPosition = 0;
        for (Car car : cars) {
            maxPosition = Math.max(maxPosition, car.getPosition());
        }
        winnersQueue = new LinkedList<>();
        for (Car car : cars) {
            if (car.getPosition() == maxPosition) {
                winnersQueue.offer(car.getName());
            }
        }
    }

    public void printWinners() {
        StringBuilder sb = new StringBuilder();
        sb.append("최종 우승자 : ").append(winnersQueue.poll());
        while (!winnersQueue.isEmpty()) {
            sb.append(", ").append(winnersQueue.poll());
        }
        System.out.println(sb);
    }

    public void start() {
        inputCarNames();
        inputTryCount();
        while (tryCount-- > 0) {
            moveCars();
            printCars();
        }
        identifyWinners();
        printWinners();
    }

}

게임은 한 번 실행되며, 하나의 Application 코드 안에서 반복적으로 실행되기 때문에 하나 이상의 인스턴스가 필요하지 않기 때문에 싱글턴(Singleton) 패턴을 적용했다. 각각의 메서드에 대한 설명은 생략하도록 하겠다.

Car.java

package racingcar;

import camp.nextstep.edu.missionutils.Randoms;

public class Car {

    private final String name;
    private int position = 0;

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

    public String getName() {
        return name;
    }

    public int getPosition() {
        return position;
    }

    public void move() {
        int randomNumber = Randoms.pickNumberInRange(0, 9);
        if (randomNumber >= 4) {
            position++;
        }
    }

    public void printPosition() {
        StringBuilder sb = new StringBuilder();
        sb.append(name).append(" : ");
        for (int i = 0; i < position; i++) {
            sb.append("-");
        }
        System.out.println(sb);
    }

}

기존의 Car.java 코드에서 getter 메서드와 전진을 의미하는 move() 메서드, 현재 위치를 출력하는 printPosition() 메서드를 추가했다.


결론

오히려 지난 숫자 야구 게임보다 시간이 적게 걸렸다. 이래서 미리 경험해보는 것이 진짜 중요한 것 같다. 
사실 객체지향적인 설계를 정말 좋아하지만, 너무 과도한 오버 엔지니어링을 그리 좋아하지 않아서 간단한 프로그래밍 문제였기 때문에 최대한 간단하게 작성했다. 그 다음 문제에 도전할지는 확실하지 않지만.. 시간이 남는다면 꼭 마저 다 풀어보고 싶다.

728x90
Comments