본문 바로가기
Programming/JavaScript

'숫자 야구게임 만들기' 토이 프로젝트(1) - [Javascript 입문 _8]

by Muko 2020. 4. 12.

이번 시간에는 흔히들 수업시간에 공부하기 싫어서, 지하철이나 버스타면서 친구와 장난칠 때, 군대에서 시간 보내기 용으로 많이하던 숫자 야구게임을 만들어보겠습니다. 숫자 야구 게임이 무엇인지 모르는 분들을 위해 설명하면, 문제 출제자가 4자리 수의 숫자를 생각하고 다른 한 사람이 그 숫자를 10번의 기회 안에서 맞춰야 합니다. 다만 아무런 힌트가 없다면 10번 안에 문제 출제자가 생각한 4자리 숫자를 맞추는 것이 불가능하므로, 출제자는 답을 맞추려는 시도 때마다 힌트를 넘겨주어야 합니다. 게임 이름이 숫자 야구게임인 이유는 힌트를 주는 형식 때문입니다. 정확하게 숫자와 자리수까지 일치할 경우 스트라이크(S), 숫자는 존재하는데 위치가 다를 경우 볼(B)을 넘겨줍니다. 예를 들어볼게요.

출제자: 5329

참가자: 5143 // 1S 1B (5는 동일, 3은 다른 자리수)
참가자: 2793 // 3B (2, 3, 9가 정답과 다른 자리수)
참가자: 5392 // 2S 2B (5, 3은 동일, 2, 9는 다른 자리수)
참가자: 5329 // 정답!

이런식으로 게임이 진행됩니다.

우리는 이런 게임을 진행할 수 있도록 구현해보려고 합니다. 여기서 주의하셔야 할 점은 문제 출제자가 생각한 답과, 참가자가 제시하는 숫자 모두 중복되는 숫자가 없어야 한다는 전제 조건이 유지되어야 한다는 것입니다. 우리가 이 게임을 구현할 때 출제자는 컴퓨터가, 참가자는 우리가 되는 방식으로 구현해야한다면 컴퓨터가 제시하는 숫자가 중복이 되면 안되므로 우리가 신경써서 코딩을 해야합니다.

그러면 어떻게 숫자가 중복되지 않게 만들 수 있을까요? 방법은 여러가지가 있을 수 있습니다. 자바스크립트에서는 숫자를 랜덤으로 생성할 수 있는데요, 이 0에서 9까지 랜덤값을 생성하면서 4자리 수 숫자를 차곡차곡 만들어 나가는 방법이 있을 수 있습니다. 이 때 이미 나온 숫자는 제외시키고 다시 랜덤 값을 불러오는 방식으로 4자리 수를 만드는 것이죠.

여기서 이미 나온 숫자를 제외한다라는 방법에는 코딩을 할 때 '지금까지 나온 숫자를 파악' 혹은 '중복되지 않은 수를 반환'할 수 있는 수단이 필요하다고 볼 수 있습니다. 간단하게 두 가지 방법으로 구현할 수 있습니다.

첫 번째는 10칸 짜리 배열을 생성한 뒤 모두 false로 초기화를 시켜줍니다. 그 후에 랜덤값을 하나씩 뽑으면서 그 값을 배열의 index로 사용해서 해당 배열 위치의 값을 true로 바꿔줍니다. 만약 해당 위치에 이미 true 값을 가지고 있다면, 그 랜덤 값은 중복되는 값이라는 것을 알 수 있겠죠? 코드로 살펴보면 더욱 이해가 잘 되실 겁니다.

const flags = Array(10).fill(false);

let question = '';
while (question.length < 4) {
  const random = parseInt(Math.random()*10);

  if (!flags[random]) {
    question += random;
    flags[random] = true;
  }
}
console.log(question);

처음 줄 부터 살펴보시겠습니다. flags라는 상수에 Array(10).fill(false)라는 값으로 초기화했습니다. 여기서 Array(10)의 의미는 길이가 10인 배열을 생성하겠다는 의미이고, .fill(값)은 생성된 배열 공간안에 모든 인덱스에 값(여기서는 false)를 넣겠다는 의미입니다. 그래서 첫 번째 코드를 실행하면 const flags = [false, false, false, false, false, false, false, false, false, false];가 되는 것이죠.

세 번째 줄에 let question = '';이라는 코드가 있는데, 이는 question이라는 변수에 빈 문자열 ''을 사용하겠다는 의미입니다. 우리는 이 변수 안에다가 출제자가 생각한 4자리 숫자를 저장하게 됩니다.

그 바로 다음에 while문이 있는데요, 이는 '반복문'이라는 기능을 동작시키는 자바스크립트 문법 중 하나 입니다. 반복문에 대해서는 Javascript 입문 - 반복문 편 포스팅을 참고해주세요. 만약 포스팅을 보시기 어려운 상황이라면, 여기서 while이라는 키워드 뒤, 괄호 안에 적은 조건을 만족한다면 계속해서 중괄호 안에 있는 코드가 실행된다고 이해하시면 됩니다. 그래서 이 코드에서는 question.length가 4보다 작으면 줄괄호 안에있는 코드가 반복해서 동작합니다. 바로 위에서 question을 빈 문자열로 초기화했으니 당연히 length값은 0을 가지게 되고, while문의 조건에 맞아떨어지게 되서 동작하게 되는 원리입니다.

이제 while문 안의 코드를 살펴보겠습니다. const random = parseInt(Math.random()*10);이라는 코드가 이해하기가 처음에는 참 어렵습니다. 이렇게 이해가 되지 않을 때, 그리고 여러가지가 복합적으로 작성된 것으로 보이는 코드는 분해해서 하나씩 살펴보면 이해하기 편합니다. 그러니까 뜯어보죠. 우선 const random 이라는 상수 안에다가 우측 값으로 초기화 한다는 것은 쉽게 파악이 가능합니다. 그다음에 parseInt(값)을 통해서 정수형으로 값을 변환해서 초기화 하고자 하는 의도도 파악할 수 있습니다. 마지막으로 Math.random()은 0이상 1미만의 소수값을 반환하는 함수입니다. 정확하게는 0이상 1미만의 부동소수점 의사 난수를 반환하는 함수라고 하는데요, 그냥 소수점 랜덤값을 반환해주는 함수라고 생각하시면 됩니다. 그런데 우리가 원하는 랜덤 값은 0에서 9까지의 정수 값이므로 이 소수점 값에 10을 곱해줍니다. 그러면 랜덤의 범위가 0이상 10미만이 되죠? 이 범위의 값을 정수형으로 바꿔주게 되면 최종적으로 우리가 원하는 값인 0~9의 정수 랜던값을 얻을 수 있게 됩니다.

자, 이제 랜덤 값을 얻었으니 위에 설명한 4자리 숫자를 만들어 봅시다. 지금 생성한 값을 index로 여기고 flags 배열안에 접근했을 때 가지고 있는 값이 false일 경우, 이 코드에서 아직 한 번도 해당 숫자를 사용하지 않았음을 의미합니다. 예를 들어 random값이 4일 경우, flags[4]가 true인가 false인가를 통해 동작이 나뉘게 됩니다. 우리가 원하듯이 false일 경우에는 출제자 문제를 저장할 question 변수에 더해주고, 해당 flag는 true로 채워줍니다. 여기서 question는 문자열이고 random은 숫자이므로 어떻게 더하냐는 의문이 들 수 있는데, 이 의문이 생긴 분들은 Javascript 입문 - 불리언과 연산자 포스팅에서 문자열 연산자를 참고해주세요!



두 번째는 배열에 미리 0부터 9까지 수를 넣어놓은 상태에서 인덱스값을 랜덤으로 하나씩 뽑아내는 방식입니다. 사실 뽑아낸다라고 표현했지만 함수명으로 보았을 때는 조각낸다라고 보시면 이해하기 편합니다. splice라는 메소드를 사용할건데요, 예시를 살펴보시겠습니다.

const numbers = [...Array(10).keys()]; // [0,1,2,3,4,5,6,7,8,9]

let question = '';
while (question.length < 4) {
  const curIndex = parseInt(Math.random()*numbers.length);
  question += numbers.splice(curIndex, 1)[0];
}
console.log(question);

이번에는 numbers라는 상수에 0부터 9까지의 값을 가지는 배열로 초기화했습니다. 그 다음에 동일하게 반복문(while)을 작성하고, 중괄호 안의 코드를 바꿨습니다. 똑같이 랜덤값을 생성한 뒤에 numbers.splice(curIndex, 1)[0]이 부분이 위에 설명한 방법을 구현한 코드입니다. splicesplice(시작 위치, 길이) 형식으로 사용되며, splice에 해당되는 부분이 기존 배열에서 분리되어 배열로 반환이 되는 메소드입니다. 또한 기존 배열은 분리된 부분이 반영되어 사라집니다. 이와 비슷한 메소드로 slice가 있는데, 이는 splice와 다르게 기존 배열을 건드리지 않는다는 차이점이 있습니다.

그래서 이렇게 splice(curIndex, 1)을 하게되면 numbers에서 curIndex(랜덤으로 정한 값)위치로 부터 1개를 분리시키는 동작을 진행합니다. 만약 curIndex가 4라면 numbers에서 인덱스 4의 위치에 해당하는 값인 '4'가 분리되어 question에는 4라는 값이 더해지고, numbers는 [0,1,2,3,5,6,7,8,9]가 남게됩니다.

이렇게 구현하면 자연스레 중복되는 숫자가 생길 수가 없으므로 출제자의 문제가 조건에 적합함을 항상 유지할 수 있게 됩니다.



자, 이제 4자리 수를 생성하는 함수도 만들었으니 실제로 동작하는 모습을 만들어 봅시다. 저번 프로젝트처럼 이번에도 HTML, CSS 제공하겠습니다 :) 프로젝트 생성부터 파일 생성, 실행 방법에 대해서 잘 모르시는 분들은 자바스크립트 입문 - 첫 번째 프로젝트 '클리커게임' 포스팅을 참고해주세요!

1. index.html

<!DOCTYPE html>
<html lang="ko">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link rel="stylesheet" href="./index.css">
 <script src="./baseball.js"></script>
 <title>Baseball Game</title>
</head>
<body>
 <div class="container">
     <div class="title">
         test
     </div>
     <div class="col">
         <div class="answerBox notSolved">
             ?
         </div>
     </div>
     <div class="col">
         <div class="questionBox">

         </div>
     </div>
     <form class="questionForm" method="GET" onsubmit="askQuestion(event)">
         <input type="number" name="question">
         <input type="submit" value="제출">
     </form>
 </div>
</body>
</html>

 

2. index.css

body {
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.container {
    width: 500px;
    height: 350px;
}

.title {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 2.5rem;
    font-weight: bold;
}

.col {
    float: left;
    display: flex;
    width: 50%;
    height: 100%;
    justify-content: center;
    align-items: center;
}

.answerBox {
    width: 150px;
    height: 50px;
    font-size: 2.1rem;
    display: flex;
    justify-content: center;
    align-items: center;
}

.notSolved {
    border: 2px solid coral;
}

.failed {
    background-color: coral;
    color: white;
}

.solved {
    border: 2px solid green;
}

.questionForm {
    float: right;
    width: 250px;
    display: flex;
    justify-content: center;
    align-items: center;
}

 

3. baseball.js

const askQuestion = (event) => {
    event.preventDefault();
    
    const question = event.target[0].value;
    const questionBox = document.querySelector('.questionBox');
    questionBox.innerHTML += `<div>${question}</div>`

    event.target[0].value = '';
}

이 코드를 다 작성한 뒤에 실행시키면 다음과 같은 결과가 나옵니다.

입력 칸에 숫자를 입력하면 위의 빈 공간에 지금까지 입력한 답에 대해서 출력이 되도록 구현되어 있습니다. 그런데 문제는 숫자 입력의 자리 수가 4자리가 아니어도 제출이 된다는 점, 그리고 숫자 중복 체크가 되지 않는다는 점, 그리고 정답인지 아닌지 체크하는 부분, 힌트 제공 함수, 정답 시도 횟수가 10번 이내인지 체크하는 함수가 구현되어 있지 않습니다. 즉, 이 프로젝트를 완성시키기 위해서는 5가지 함수를 구현해야 합니다.

  • 제출 숫자는 4자리 체크
  • 숫자 중복 체크
  • 정답인지 아닌지 체크
  • 힌트 제공 함수
  • 정답 시도 횟수 10번 이내인지 체크

이에 대해서는 다음 포스팅에서 이어서 작성하도록 하겠습니다.

댓글4