본문 바로가기
활동/이노베이션 캠프

[과제] 야구게임 (JavaScript) - Chapter 1

by gardenii 2023. 6. 19.

과제 안내

💡Goal : 숫자야구 프로그램 만들기

  • 4인1팀에서 페어로 2인1팀으로 과제를 진행합니다. (5명 팀일 경우 2인1팀/3인1팀)
  • 간단한 숫자야구 프로그램을 Java/JS로 만들어봅니다.
  • 조건문, 반복문을 활용하여 해결합니다.

💡To do : 과제 조건

  • 컴퓨터는 0과 9 사이의 서로 다른 숫자 3개를 무작위로 뽑습니다. (ex) 123, 759
  • 사용자는 컴퓨터가 뽑은 숫자를 맞추기 위해 시도합니다.
  • 컴퓨터는 사용자가 입력한 세자리 숫자에 대해서, 아래의 규칙대로 스트라이크(S)와 볼(B)를 알려줍니다.
    • 숫자의 값과 위치가 모두 일치하면 S
    • 숫자의 값은 일치하지만 위치가 틀렸으면 B
  • 기회는 무제한이며, 몇번의 시도 후에 맞췄는지 기록됩니다.
  • 숫자 3개를 모두 맞춘 경우, 게임을 종료합니다.

💡진행 방식

컴퓨터가 숫자를 생성하였습니다. 답을 맞춰보세요!
1번째 시도 : 134
0B0S
2번째 시도 : 238
1B1S
3번째 시도 : 820
2B1S
4번째 시도 : 028
3B
5번째 시도 : 280
3S
4번만에 맞히셨습니다. 
게임을 종료합니다.

풀이 과정

1. 구현 과정
힌트에서 제공해주셨던 순서로 구현을 대략 진행했었다. 완전히 같게 하지는 않았고 코드 진행 상황에 따라 조금씩 뒤바뀌었던 것 같다.
(힌트: 랜덤 숫자 만들기 -> 한자리 숫자에 대해 볼, 스트라이크 판단 하는 부분 구현하기 -> 볼, 스트라이크 표현 부분 구현하기 -> 게임 종료 부분 구현하기)
 
2. 랜덤 숫자 만들기
- 랜덤 숫자 만드는 부분부터 꽤나 애를 먹었는데, Math.random 메서드를 이용해서 랜덤 숫자를 받는 것은 됐지만 이 숫자를 겹치지 않게 넣어서 정답 배열을 만드는것이 어려웠다. 이 과정에서 includes() 메서드를 처음 알게 되었는데, arr.includes()는 배열에 특정 요소가 있는지 탐색하여 있으면 true, 없으면 false를 반환해주는 메서드이다. 이번 코드에서는 정답 배열의 길이가 3이 넘지 않을 때 까지 랜덤 넘버를 새로 생성해서, 만약 정답 배열에 랜덤 넘버가 들어있지 않으면 (!includes) 정답 배열에 랜덤 넘버를 push 해 줌으로써 정답 배열을 생성하는 방식으로 문제를 해결할 수 있었다. 

function getRandomNumbers() {
  let numbers = [];
  for (let i = 0; numbers.length < 3; i++) {
    const randomNumber = Math.floor(Math.random() * 10);
    if (!numbers.includes(randomNumber)) numbers.push(randomNumber);
  }
  return numbers;
}

 
3. 입력값 반복해서 받기
- 다른 언어와 달리 내가 느끼기에 자바스크립트나 노드는 유독 입력값 받는 게 조금 어려운 것 같다. readline 방법을 구글로 찾아 입력값을 받고 출력하는 것 까지는 구현했지만, 사실 어떻게 동작하는지 이해하지는 못하고 우선 사용만 했다. 이것도 좀 더 공부해 볼 필요가 있다. 종료조건이 될 때 까지 반복해서 입력을 받아야 했는데 이 부분은 도저히 구현을 못 해서 다른 팀원 분 도움을 조금 받아 재귀적으로 실행해주는 방법을 알게 되었다. 조건을 만족하지 않으면 입력 함수 내부에서 다시 함수를 호출해서 계속 입력받게끔 해 주었다.
 
4. 스트라이크 수, 볼 수 판별하기
- 입력값과 정답 배열의 값을 비교하여 스트라이크 수, 볼 수를 판별하는 부분은 비교적 쉽게 풀었지만 후에 코드 리뷰를 통해 좀 더 나은 방식으로 바꾸었다.
- 처음 짰던 코드는 이중 for문을 이용한 아래 방식이였는데, 직관적이라 이해는 쉽지만 이중이라 시간도 오래 걸리고 좀 더 효율적인 방법이 있을거라는 생각이 들었다.

for (let i = 0; i < ranArr.length; i++) {
      for (let j = 0; j < inputArr.length; j++) {
        if (ranArr[i] === inputArr[j] && i === j) strike++;
        else if (ranArr[i] === inputArr[j]) ball++; 
    }
}

- 제출일에 다른 분 코드를 리뷰하며 더 나은 방법을 발견하여 수정해주었다.
- 아래는 includes()를 이용한 방식인데, 우선 입력받은 값을 split()을 통해 자리별로 나누어 문자 배열을 생성해주고, map()을 통해 배열 요소를 하나씩 돌면서 Number로 형변환 한 값을 반환받아 다시 배열로 만들어주어 입력값 숫자 배열을 완성해 주었다. 그 후 스트라이크와 볼을 각각 0으로 초기화하고 for문을 통해 정답 배열과 입력 배열 요소를 하나씩 돌면서 값이 같으면 strike++, 정답 배열이 inputArr요소를 포함하면 (includes) ball++를 해 주어 수를 판별하도록 했다.

for (let i = 0; i < 3; i++) {
      if (ranArr[i] === inputArr[i]) strike++;
      else if (ranArr.includes(inputArr[i])) ball++;
    }

 
5. 입력 시도 횟수 출력하기
- 그 후에는 입력 예시와 같이 입력 시도 횟수를 출력하는 것에 애를 먹었는데, 시도 횟수 변수를 만들어주어 입력 함수에 정답 배열과 함께 인자로 전달하여 시도가 추가될 때 마다 ++ 해주는 방식으로 해결할 수 있었다. 그 뒤 readline의 입력 문자...?값으로 시도 횟수를 리터럴로 넣어주어 'n번째 시도: ' 가 시도마다 뜨도록 해주었다. 이 부분도 좀 더 공부 필요...
 
6. 게임 종료하기
- 스트라이크 값이 3이 될 경우 'n번만에 맞히셨습니다!'를 출력하고 rl.close()를 호출하여 입력을 종료한 뒤, 호출된 close()내부에서 게임을 종료한다는 문구를 출력해주도록 했다.
 
+ 7. 입력값 자릿수 3자리로 제한해주기
- 이 부분은 문제 조건에는 없었지만, 다른 분이 주신 코드에 있어서 참고하여 우리 팀에서도 추가로 구현해 본 기능이다.
-  생성된 입력값 배열의 길이가 3이 아니면 '3자리 숫자만 입력하세요!' 를 출력한 뒤 해당 시도 횟수를 유지한 상태에서 다시 입력을 받아야 했는데, 어떻게 시도를 유지할 지 고민하다가 이 if문 내에서 다시 재귀로 입력함수를 실행하면 되겠다 생각했고, 그렇게 했지만 콘솔에서 이상하게 출력되었다.

원했던 값 / 이상한 값

- 오른쪽에서는 곧바로 다시 입력을 받지 않고, 1번째 시도라는 문구와 함께 위에 입력한 값에 대한 볼/스트라이크 판별이 출력된 후에야 다시 입력을 받아오는 것을 확인할 수 있었다.
- 문제가 무엇인지 분석은겨우 할 수 있었지만 한참을 고민해도 해결방법을 못 찾다가, 다른 팀원 분 코드에 있었던 return문을 무심코 넣어서 돌려봤는데 해결이 된 것이였다. 왜 해결이 되는지에 대해서도 한참을 생각하고 ChatGPT에도 물어본 끝에 확실하지는 않아도 아래와 같은 답변을 통해 어느 정도 결론은 얻을 수 있었다. 
- 재귀 함수에 대해 좀 더 알아보고 공부해야 완전히 이해할 수 있을 것 같다.

열심히 물어본 흔적들...
ChatGPT의 답변! 제일 이해가 잘 되는 것으로 캡쳐했다.

 


최종 코드

const readline = require("readline");

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

function getRandomNumbers() {
  let numbers = [];
  for (let i = 0; numbers.length < 3; i++) {
    const randomNumber = Math.floor(Math.random() * 10);
    if (!numbers.includes(randomNumber)) numbers.push(randomNumber);
  }
  return numbers;
}

function baseballGame(ranArr, attemps) {
  rl.question(`${attemps}번째 시도 : `, (line) => {
    let inputArr = line.split("");
    inputArr = inputArr.map((e) => {
      return Number(e);
    });

    if (inputArr.length !== 3) {
      console.log("3자리 숫자만 입력하세요!");
      baseballGame(ranArr, attemps);
      return;
    }

    let strike = 0;
    let ball = 0;

    for (let i = 0; i < 3; i++) {
      if (ranArr[i] === inputArr[i]) strike++;
      else if (ranArr.includes(inputArr[i])) ball++;
    }

    console.log(ball + "B" + strike + "S");

    if (strike === 3) {
      console.log(attemps + "번만에 맞히셨습니다!");
      rl.close();
    } else {
      attemps++;
      baseballGame(ranArr, attemps);
    }
  });
}

rl.on("close", () => {
  console.log("게임을 종료합니다.");
});

function gameStart() {
  let ranArr = getRandomNumbers();
  let attemps = 1;

  console.log("컴퓨터가 숫자를 생성하였습니다. 답을 맞혀보세요!");
  baseballGame(ranArr, attemps);
}

gameStart();

https://github.com/jwc406/R2_baseball 
좀 더 나은 풀이나 틀린 사항이 있다면 알려주세요 :)