우테코 6기 프리코스 (레이싱 게임) 2주차 회고

전승재·2023년 11월 3일

우테코 프리코스

목록 보기
1/2

우테코 프리코스 2주차 과제 깃허브

이번 2주차 과제에서는 지난 1주차에서 행했던 실수들을 보완하는 것을 목표로 삼고 진행했다.
다른 분들의 회고나 코드를 보면서 나름대로 분석했을 때, 내 코드가 많이 부족하다는 것을 알게되었다....

저번 1주차에서 제대로 지키지 못한 것은 모듈을 완벽히 분리하지 못한 점, 기능 단위로 커밋하지 않았던 점, 커밋 메시지 컨벤션을 지키지 않은 점이었던 것 같다. 따라서 이번에는 이 부분에 집중하여 구현을 진행해봤다.
처음에 기능 구현 목록을 정리할 때 게임의 진행 순서대로 기능을 정리하는 것이 읽기 편하다고 생각되어 진행 순서대로 작성했다. 사용자의 입력에서 발생할 수 있는 예외들을 모아두고 진행했지만 구현간에 더 생각난 예외가 있어 나중에 추가했다. 처음부터 발생하는 모든 예외를 예상하지 못해서 조금 아쉬웠던 것 같다.

지켜야할 규칙

  • 기능 단위로 커밋하기
  • 커밋 메시지 컨벤션
  • 클래스 분리 잘하기
  • 메서드의 이름이 길어지더라도 그 메서드가 하는 일, 출력하는 것을 잘 나타낼 수 있도록 정하기

기능 구현 목록

게임의 진행 순서대로 기능 목록을 정리하겠다.

사용자 입력 - 자동차 이름

사용자는 자동차의 이름을 입력해야한다.
이 때 쉼표(,)를 사용하여 자동차의 이름을 구분한다.
구분한 자동차의 이름들은 리스트에 저장된다.

이름 제약 조건 - 이름은 5자 이하만 가능하다, 알파벳으로만 이뤄져있다.
특수기호 및 숫자, 한글 불가.
ex) june,july,pobi

사용자 입력 - 몇 번의 시도를 할것인가?

사용자가 각 자동차들이 몇 번의 시도를 할지를 입력한다.
ex) 8

자동차의 전진 또는 멈춤 기능

자동차는 사용자가 입력한 시도횟수만큼 전진 또는 멈춤을 진행한다.

그 기준은 아래와 같다.
0부터 9 사이의 무작위 값을 구한다.
그 무작위 값이 4 이상일 경우에는 전진한다. 4보다 작은 값이 나온다면 멈춰있는다.

출력 - 자동차 전진 결과

한 번의 시도 이후에 각 자동차들이 얼마나 전진했는지를 나타내는 출력이다.
각 시도가 끝날 때마다 출력된다.
출력 예시)
pobi : --
woni : ----
jun : ---

사용자가 입력한 횟수만큼 모두 시도했을 경우

자동차 경주 게임이 완료된다.
가장 많이 전진한 자동차를 구하고 종료 결과를 출력한다.
만약 가장 많이 전진한 자동차가 여러 대라면 쉼표(,)를 통해 구분한다.
ex) 우승자가 단독일 경우
최종 우승자 : pobi
ex) 우승자가 여러명일 경우
최종 우승자 : pobi,july,jun

각각의 사용자 입력에서 잘못된 값을 입력했을 경우

사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
이번 게임에서 발생할 수 있는 잘못된 입력은 아래와 같다.

-[x] 사용자가 자동차의 이름을 6자 이상 입력했을 경우 ex) seungjae
-[x] 사용자가 자동차의 이름에 특수기호 및 숫자, 한글 입력했을 경우 ex) @,#,$,%,1,2,3,나

  • 사용자가 자동차의 이름을 중복으로 입력했을 경우 ex) aa,bb,cc,aa
  • 사용자가 몇 번의 이동을 할지 입력할 때 숫자가 아닌 값을 입력했을 경우 ex) hi
  • 사용자가 몇 번의 이동을 할지 입력할 때 음수값을 입력했을 경우 ex) -1, -3

Application.java - main 클래스

package racingcar;

import java.util.List;

public class Application {
    public static void main(String[] args) {
        Racing racing = new Racing();
        UserInput userInput = new UserInput();

        System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)");
        List<String> racingCarName = userInput.inputRacingCarName();
        System.out.println("시도할 회수는 몇회인가요?");
        int attemptNumber = userInput.inputAttemptNumber();

        racing.Start(attemptNumber, racingCarName);

    }
}

UserInput.java - 사용자의 입력을 받는 클래스

package racingcar;

import java.util.*;
import java.util.regex.Pattern;

import camp.nextstep.edu.missionutils.Console;

public class UserInput {
    public List<String> inputRacingCarName() {
        String input = Console.readLine();
        String inputArray[] = input.split(",");
        List<String> racingCarName = new ArrayList<>(Arrays.asList(inputArray));

        racingCarNameValidation(racingCarName);

        return racingCarName;
    }

    public int inputAttemptNumber() {
        String input = Console.readLine();

        try {
            int attemptNumber = Integer.parseInt(input);
            if(attemptNumber<=0){
                throw new IllegalArgumentException("0보다 큰 수를 입력해주세요");
            }
            return attemptNumber;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("숫자를 입력해야합니다.");
        }
    }

    private void racingCarNameValidation(List<String> racingCarName) {
        HashSet<String> hashSet = new HashSet<>(racingCarName);

        if (hashSet.size() != racingCarName.size()) {
            throw new IllegalArgumentException("이름을 중복으로 입력했습니다.");
        }

        for (String validationName : racingCarName) {
            if (validationName.length() > 5 || validationName.length() == 0 || !Pattern.matches("^[a-zA-Z]*$", validationName)) {
                throw new IllegalArgumentException("잘못된 입력입니다.");
            }
        }

    }
}

Racing.java - 경주의 시작과 끝, 우승자를 가려내는 클래스

package racingcar;

import camp.nextstep.edu.missionutils.Randoms;

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

public class Racing {
    private int GoOrStop() {
        int randomNumber = Randoms.pickNumberInRange(0, 9);
        if (randomNumber >= 4) {
            return 1;
        }
        return 0;
    }

    public void Start(int attemptNumber, List<String> racingCarName) {
        List<Integer> advancementResult = new ArrayList<>(Collections.nCopies(racingCarName.size(), 0));
        PrintResult printer = new PrintResult();

        System.out.println("\n실행 결과");
        for (int attempt = 0; attempt < attemptNumber; attempt++) {
            for (int i = 0; i < racingCarName.size(); i++) {
                advancementResult.set(i, advancementResult.get(i) + GoOrStop());
            }
            printer.PrintOneAttemptResult(advancementResult, racingCarName);
        }
        printer.PrintWinnerCarName(FindWinner(advancementResult), racingCarName);
    }

    private List<Integer> FindWinner(List<Integer> advancementResult) {
        List<Integer> winnerIndex = new ArrayList<>();

        int maxAdvancement = Collections.max(advancementResult);

        for (int i = 0; i < advancementResult.size(); i++) {
            if (advancementResult.get(i) == maxAdvancement) {
                winnerIndex.add(i);
            }
        }
        return winnerIndex;
    }
}

PrintResult - 결과를 출력하는 클래스

package racingcar;

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

public class PrintResult {
    public void PrintOneAttemptResult(List<Integer> advancementResult, List<String> carName) {
        for (int carIndex = 0; carIndex < carName.size(); carIndex++) {
            System.out.print(carName.get(carIndex) + " : ");
            for (int num = 0; num < advancementResult.get(carIndex); num++) {
                System.out.print("-");
            }
            System.out.println();
        }
        System.out.println();
    }

    public void PrintWinnerCarName(List<Integer> winnerIndex, List<String> carName) {
        List<String> winnerCarName = new ArrayList<>();
        for (Integer integer : winnerIndex) {
            winnerCarName.add(carName.get(integer));
        }
        System.out.println("최종 우승자 : " + String.join(",", winnerCarName));
    }
}

이제 기능을 정리한 문서를 토대로 구현을 진행했다.
가장 처음에 사용자의 입력을 받는 클래스를 생성해 자동차의 이름을 받는 메서드, 시도회수를 입력받는 메서드, 자동차의 이름을 검증하는 메서드를 구현했다.
각 메서드는 하나의 일만 할 수 있도록 구현하려 노력했다.
사용자가 입력한 결과는 Racing 클래스의 Start 메서드의 파라미터로 입력되면서 게임이 시작된다.
한 번의 시도회수가 끝난후에는 현재 결과를 출력해야 하기 때문에 출력 메서드를 호출했다.
이를 사용자가 입력한 회수만큼 반복하도록 하고, 게임이 끝난 후에 우승자를 찾는 메서드를 호출했다. 우승자 리스트를 반환받고 이를 출력하는 메서드를 호출하면서 애플리케이션을 종료했다.

사실 이 이후에 Test코드를 작성하고 있었는데, 사용자의 입력을 검증하는 Test코드는 모두 작성했지만 랜덤한 값을 가정하는 Test코드를 작성하는 법에 대해서 부족하여 작성하지 못했다...
이 부분에 대해서 학습이 더 필요하다고 느꼈다. 테스트 코드를 작성해본 경험이 거의 없어서 랜덤값에 대한 처리등에 마주치자 머리가 하얘졌던 것 같다.

또한 private 메서드 test코드를 작성하는 것이 안좋다는 것을 알았기에 어떤 메서드에 private 접근 제어자를 붙여야 하는지에 대해서 더 고민이 많아졌던 과제였다.

0개의 댓글