[우아한테크코스 2022] 프리코스 2주차 풀어보기

재오·2023년 8월 5일
4
post-thumbnail

2주차

2주차의 주제는 숫자 야구 게임을 구현하는 것이다. 1주차와 마찬가지로 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 을 지켜서 문제를 풀면 된다. 하지만 2주차 문제는 1주차와는 다른 점이 여러 개 있었다.

indent depth 제한

기존에는 들여쓰기에 제한이 없었지만 이번 주차부터는 3을 넘기지 않도록 제한 사항이 생겼다. 들여쓰기를 2까지만 허용하는 것이다. 한마디로 while 문 안에 if문 하나가 있다면 이것의 indent depth가 2인 것이다.

함수를 최대한 많이 쪼개기

추가된 요구사항으로 하나의 함수에는 최소한의 일만 할 수 있도록 설계해야 한다고 적혀있다. 클래스화를 하던 함수를 하던 최소한의 역할을 할 수 있도록 함수를 잘 쪼갤 예정이다. 여기서 변수나 함수명 또한 직관적으로 이해가 잘가게끔 코드 컨벤션에 의해 작성할 예정이다.

Jest 활용

정말 많이 당황했던 것이 바로 이 Jest 활용이었다. 실제로 2일 정도를 Jest 이해하는데 쓰였을 정도였다. Jest는 짧게 말해서 코드의 테스트케이스를 내가 직접 구현하는 것이다. 기존 프로그래머스나 우테코 1주차 온보딩 미션에서는 모든 테스트케이스가 주어져 있었지만 이를 실제로 내가 직접 구현해야 한다. 예시 코드가 몇개 쓰여져있었지만 어떻게 이것을 실제 코드로 활용할 것인지에 대한 이해도가 전혀 없었기 때문에 시간이 오래 소요되긴 하였다.

MissionUtils 활용하기

MissionUtils 라이브러리에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다. 하지만 사용법이 아래 링크에 자세하게 나오기 때문에 사용하는 데 있어서 큰 무리는 없었다.
MissionUtils 활용하기

🔑 설계

코드를 짤 때 클래스를 이용하거나 함수를 이용한 적이 프로젝트를 할 때 말고는 없었다. 코딩테스트나 과제테스트 문제를 풀어볼 때 하나의 함수 안에 모든 기능을 집어넣는, 한마디로 가독성 없는 복잡한 코드를 주로 작성하였다. 그게 문제인 것을 알았더라도 '일단은 작동이 되게끔 하고 수정하는 방향으로 하자'라는 마인드가 머리 속에 지배적이었던 것 같다. 이번 2주차 과제 미션도 그렇고 더 나은 코드를 쓰는 개발자가 되기 위해서는 함수를 최대한 많이 활용하고 싶었다. 그래서 로직을 짜는데 더 많은 시간할당을 하였다.

크게 프로그램 기능 흐름, 기능 목록, 구현 방향성 에 대해 작성하였다.

🛹 프로그램 기능 흐름

  1. 컴퓨터가 1부터 9까지 서로 다른 수로 이루어진 3자리의 숫자를 생성한다.
  2. 사용자에게 1부터 9까지 서로 다른 수로 이루어진 3자리의 숫자를 입력받는다.
  3. 컴퓨터가 생성한 숫자와 사용자가 입력한 숫자를 비교한다.
  4. 정답이 아니라면 스트라이크, , 낫싱으로 이루어진 힌트를 제공하고 게임을 계속 진행한다.
  5. 정답이라면 "3개의 숫자를 모두 맞히셨습니다! 게임 종료"를 출력하고 게임을 종료한다.
  6. 게임이 종료된 후 "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."를 출력하고 사용자의 입력에 맞게 재시작 여부를 결정한다.

🪜 기능 목록

  1. 컴퓨터가 랜덤한 수를 생성하는 기능
  2. 사용자에게 숫자를 입력받는 기능
  3. 사용자의 입력 숫자가 유효한 숫자인지 판단하는 기능 (진행 중)
  4. 사용자의 입력 숫자가 유효한 숫자인지 판단하는 기능 (종료 후)
  5. 컴퓨터의 숫자와 사용자가 입력한 숫자를 비교해 힌트를 계산하는 기능
  6. 게임을 지속적으로 이어나갈 수 있는 기능
  7. 게임 종료 후 재시작 여부를 결정하는 기능
  8. 이전 게임에서 사용되었던 변수를 모두 리셋하는 기능

🔧 전반적인 구현 방향성

  1. Computer 클래스를 통해 컴퓨터가 생성하는 랜덤 숫자를 관리하기.
  2. BaseballGame 클래스를 통해 게임 진행을 관리한다.
  3. BaseballGame 클래스 내부에 Computer 클래스를 생성하여 생성된 랜덤 숫자와 사용자 입력을 비교하여 게임을 진행한다.
  4. App의 play 메서드를 통해 게임을 진행한다.

🛠️ 세부적인 구현 방향성

  1. 컴퓨터가 랜덤한 수를 생성하는 기능

    • 빈 배열을 선언

    • MissionUtils.Random.pickNumberInRange(1,9)를 통해 랜덤한 숫자 생성

    • 생성한 숫자가 배열에 존재하면 다시 생성하고, 존재하지 않으면 배열에 넣기

    • 배열의 길이가 3이라면 join메서드를 사용하여 문자열 형태로 변환

  1. 사용자에게 숫자를 입력받는 기능

    • MissionUtils.Console.readLine을 통해 숫자를 입력하여 게임 실행
    • handleInputDuringGame 메서드
      • 게임을 지속적으로 진행할 것인지에 대해 사용자 입력을 다루는 메서드
      • 입력: 사용자 입력값
      • 출력: X
      • getHint를 통해 반환된 hint가 3스트라이크와 일치하는지 판별
      • 일치한다면 recommendRestart 메서드 호출
      • 불일치하다면 getUserInput 메서드 호출
    • handleInputAfterGame 메서드
      • 게임 진행 후 재시작 여부에 대해 다루는 메서드
      • 입력: 사용자 입력값
      • 출력: X
      • 입력값이 '1', '2'에 해당하지 않으면 에러 발생
      • 입력값이 '1'이라면 restartGame 메서드 호출
      • 입력값이 '2'라면 MissionUtils.Console.close 호출하여 게임 종료
  1. 사용자의 입력 숫자가 유효한지 판단하는 기능 (게임 진행 중)
    • getIsInputValueValid 모듈 추가
    • 입력값이 세글자인 지를 확인하는 함수 추가
    • 입력값에 중복된 값이 없는지 확인하는 함수 추가
    • 입력값이 숫자가 아닌 값이 들어있는 지 확인하는 함수 추가
    • 이 세가지가 모두 true인 경우 true 반환
  1. 사용자의 입력 숫자가 유효한지 판단하는 기능 (게임 종료 후)
    • '1'과 '2'만 들어있는 배열 선언
    • 입력값이 배열에 있는 값이 아니라면 에러 선언
  1. 컴퓨터의 숫자와 사용자의 숫자를 비교해 힌트를 계산하는 기능
    • getHint 모듈 추가
      • 입력값: 컴퓨터 숫자, 사용자 입력 숫자
      • 출력값: 힌트 문자열
      • strikeCount, ballCount 변수를 선언
      • countStrike 함수와 countBall 함수로 스트라이크, 볼 개수를 계산해 strikeCount, ballCount 변수에 대입한다.
      • strikeCount, ballCount 변수를 convertToHintString 함수의 인자로 전달한다.
      • 반환된 힌트 문자열을 리턴한다.
    • convertStringToArray 함수 추가
      • 입력값: 문자열
      • 출력값: 배열
      • String의 split 메서드를 사용하여 배열로 분리한다.
    • countStrike 함수 추가
      • 입력값: 컴퓨터 숫자, 사용자 입력 숫자
      • 출력값: 스트라이크 개수
      • count 변수를 0으로 초기화하여 선언한다.
      • 컴퓨터 숫자와 사용자의 숫자를 convertStringToArray 함수를 사용하여 배열로 변환한다.
      • 반복문을 사용하여 각 배열의 값을 비교한다.
      • 서로 같은 수라면 count 변수에 1을 더해준다.
      • count를 리턴한다.
    • countBall 함수 추가
      • 입력값: 컴퓨터 숫자, 사용자의 입력 숫자, 스트라이크 개수
      • 출력값: 볼 개수
      • filter 메서드를 이용하여 컴퓨터의 숫자 중 사용자 입력 숫자와 일치하는 숫자만을 남긴 배열을 만든다.
      • 앞에서 만든 배열의 크기에서 스트라이크 개수를 뺀 후 그 값을 리턴한다.
    • convertCountToHintString 함수 추가
      • 입력값: 볼 개수, 스트라이크 개수
      • 출력값: 힌트 문자열
      • 빈 문자열 변수 hint를 선언한다.
      • 볼 개수가 0 이상이라면 ${ballCount}볼을 힌트 문자열에 추가한다.
      • 스트라이크 개수가 0 이상이라면 ${strikeCount}스트라이크를 힌트 문자열에 추가한다.
      • 만약 두 값이 모두 0이라면 낫싱을 힌트 문자열에 추가한다.
      • hint를 리턴한다.
  1. 게임을 지속적으로 이어나갈 수 있게 하는 기능 (반복)
    • MissionUtils.Console.readLine을 통해 사용자의 입력값을 받아온다.
    • 입력값을 handleInputDuringGame에 전달한다.
    • 입력값과 ComputercorrectNumbergetHint의 인자로 전달해 반환 값을 hint에 대입한다.
    • hint를 출력한다.
    • hint3스트라이크가 불일치하면 getUserInput()함수 다시 호출
  1. 게임 종료 이후 재시작 여부를 결정하는 기능
    • BaseballGame 클래스에 recommendRestart 메서드 추가
    • 3개의 숫자를 모두 맞히셨습니다! 게임 종료 문구를 출력한다.
    • MissionUtils.Console.readLine을 통해 사용자에게 값을 입력받는다.
    • 1이 입력된 경우 restartGame() 호출
    • 2가 입력된 경우 MissionUtils.Console.close()를 호출해 게임 종료
  1. 이전 게임에서 사용된 변수들의 값을 리셋하는 기능
    • Computer 클래스의 setNewCorrectNumber 메서드 호출하여 랜덤 숫자 교체

📐 Jest 이용

Jest에 관한 내용을 구글에서 찾게 된다면 상당히 다양한 문법이 있다. 하지만 여기서 기억해야 하는 명령어는 describe, test, expect, toEqual 정도이다.

아래의 예시 코드를 통해 Jest가 어떻게 동작하는지 간단하게 확인해보자.

const { getHint } = require("../src/Hint");

describe("힌트 산출 테스트", () => {
  test('볼과 스트라이크가 함께 있을 때는 "~볼 ~스트라이크"가 출력된다.', () => {
    const correctNumber = ["123", "456", "789", "159", "753"];
    const inputNumber = ["321", "461", "981", "519", "715"];
    const answer = [
      "2볼 1스트라이크",
      "1볼 1스트라이크",
      "1볼 1스트라이크",
      "2볼 1스트라이크",
      "1볼 1스트라이크",
    ];

    for (let i = 0; i < 5; i++) {
      expect(getHint(correctNumber[i], inputNumber[i])).toEqual(answer[i]);
    }
  });

  test('블, 스트라이크 모두 없을 때는 "낫싱"이 출력된다', () => {
    const correctNumber = ["123", "456", "789", "159", "753"];
    const inputNumber = ["456", "789", "123", "743", "149"];
    const answer = "낫싱";

    for (let i = 0; i < 5; i++) {
      expect(getHint(correctNumber[i], inputNumber[i])).toEqual(answer);
    }
  });
});

우선 getHint라는 함수를 받아와서 import 해준다. 그리고 describe로 여러 개의 test를 하나로 묶는다. 위 예제 코드에서는 두개의 test 코드를 하나로 묶었다. describe와 test 마찬가지로 첫번째 인자로는 해당 test의 설명과 두번째 인자로는 실제 실행되는 함수가 들어간다.

그리고 expect 에는 입력되는 값이 그리고 뒤에 toEqual에는 기대값이 들어간다. 배열이나 객체일 때는 toEqual을 사용하지만 원시값인 경우에는 보통 toBe를 사용한다.

profile
블로그 이전했습니다

0개의 댓글