본문 바로가기

javascript/JS웹게임

반복문 사용하기-숫자야구 게임

숫자야구 게임이다. 순서도를 먼저 그려보면, 

이렇게 된다. 컴퓨터를 상대로 숫자를 맞추면 사용자가 승리하도록 순서도를 작성했다. 프로그램이 처음 실행되면 컴퓨터는 무작위로 4개의 값을 중복 없이 뽑고, 사용자는 답을 제출하는 이벤트를 발생시켜 정답이 맞는지 확인받는다. 10번 안에 검사 결과가 홈런이 되면 사용자가 승리한다. 

<html>
<head>
  <meta charset="utf-8">
  <title>숫자야구</title>
</head>
<body>
<form id="form">
  <input type="text" id="input">
  <button>확인</button>
</form>
<div id="logs"></div>
<script>
  const $input = document.querySelector('#input');
  const $form = document.querySelector('#form');
  const $logs = document.querySelector('#logs');
</script>
</body>
</html>

다음 코드를 통해 간단한 시작화면을 만들어보자. 

정답을 입력하고 제출하는 input 태그와 button 태그, 그리고 div 태그를 통해 기입한 정답들을 기록해주는 공간을 만들었다. 

 

무작위 숫자 뽑기

숫자 야구 게임을 시작한 후 숫자 네 개를 뽑는데, 이는 사실 생각보다 복잡하다. 

1. 네 개의 숫자를 저장할 자리를 마련한다. 

2. 무작위로 숫자를 하나씩 뽑아 저장한다. 

3. 2번을 4번 반복한다. 단, 4개의 숫자는 중복 될 수 없다. 

 

Math.random()을 통해 무작위 숫자를 만들 수 있다. 이는 0 이상, 1 미만의 실수를 생성하는데 , 계산을 통해 원하는 값으로 조정할 수 있다.

우리가 원하는 값은 1부터 9이므로 다음 과정을 통해 이를 구할 수 있다.  

 

Math.floor(Math.random() *9 + 1)을 단순히 네 번 호출한다고 되는 것이 아니다. 1부터 9까지의 숫자를 모아두고 뽑을 때 마다 뽑힌 숫자를 지워가며 네 번 뽑으면 된다. 

<script>
  const numbers = [];
  for (let n = 1; n <= 9; n += 1) {
    numbers.push(n);
  }
</script>

 

다음 코드를 통해 numbers 배열 안에 1부터 9까지의 숫자를 담아 놓을 수 있다. 다음으로 생각해야할 것은 이 9개의 숫자들 중에서 한 개씩 네 번 뽑아가면 된다. answer[]이라는 배열을 생성해 이 곳에 4개의 숫자를 담을 것이다. 

<script>
const answer=[] ;
for(let n = 0; n<4; n+=1){
  const index = Math.floor(Math.random()*(number.length-n)) //0~8의 정수
  answer.push(numbers[index]);
  numbers.splice(index,1);
}
</script>

이를 통해 answer[]에 컴퓨터가 처음 정답으로 저장할 4개의 숫자를 완성할 수 있다. 무작위로 뽑아내는 0부터 8까지의 정수를 index로 numbers의 9개의 자연수에 접근한다는 점과 하나를 뽑을 때 마다 numbers 배열은 요솟값 하나를 잃기 때문에 number.length에 n값을 빼주면서 배열이 줄어드는 것을 반영하는 것에 주목하자.

 

form 태그

앞선 코드를 보면 input과 button 태그를 form 태그가 감싸고 있다. 이럴 때, form에 이벤트를 달아 줄 수 있다. 

$form.addEventListener('submit', (event) => {
	 event.preventDefault();
});

form 태그의 submit 이벤트는 form 태그 안에 있는 button 태그의 click 이벤트를 통해 발생된다. submit이 발생되면 페이지를가 새로고침하는 기본 동작을 수행한다.  이는 변수를 선언하고 여기에 저장한 값을 모두 초기화 시키기 때문에 기본 동작을 막아야 한다. event를 매개변수로 event.preventDefault()라는 함수를 통해 기본 동작을 수행하는 것을 취소할 수 있다. 

또한 form 태그 내부에서 event.target을 배열처럼 활용하여 form 내부 태그에 접근할 수 있다. 

 

//////////////////////////////////////////////////////////////////////숫자야구게임-4 -> 07:16

 

입력값 검증하기

const tries = [];
function checkInput(input) {}
$form.addEventListener('submit', (event) => {
  event.preventDefault();
  const value = $input.value;
  $input.value = '';
  const valid = checkInput(value);
});

입력값을 검증하는 코드는 다음과 같다. 입력한 값을 $input.value로 가져온다. 이때 숫자가 아닌 문자열이 저장된다는 것에 주의해야한다. 이 value 값을 checkInput 함수로 검증한다. 이 함수를 통해 다음을 검증한다. 

1. 4글자인가

2,중복된 숫자는 없는가

3.이미 시도했던 값은 아닌가

10번의 카운트를 소모하지 않기 위해 검증 함수에서 미리 걸러내는 것이다.

function checkInput(input){
	if(input.length !==4) {
    	return alert('경고');
    }
    if(new Set(input).size !== 4){
    	return alert('경고');
    }
    if(tries.includes(input){
    	return alert('경고');
    }
    return true;
}

이 함수는 if문에 걸리게 되면 alert를, 모두 통과하면 true를 반환한다. alert 함수는 undefined를 반환하기 때문에 결국 valid는 true 또는 false를 반환하여 뒤에 if문에 넣어 검사의 실패 여부에 따라 입력값과 정답을 비교할지 말지를 판단하게 된다. 

 

입력값과 정답 비교

순서도에서 답이 형식에 맞는지 검사한다에서 '예'로 분기한 후의 내용이다. 

 

  if (!valid) 
    return;
  if (answer.join('') === value) {
    $logs.textContent = '홈런!';
    return;
  }
    if (tries.length >= 9 ) {
      const message = document.createTextNode(`패배! 정답은 ${answer.join('')}`);
      $logs.appendChild(message);
    return;
    }

 

valid 값을 저장한 후의 내용이다. if문이 중첩되어 있어 앞선 내용에서 처럼 중첩문을 풀어낸 코드이다. 

정답이 맞다면 div 태그에 '홈런!'이라는 문자열을 출력하고 그렇지 않으면 다음 if문을 통해 몇번째 시도인지를 검사한다. 

 

여기서 마지막(10번째) 시도라면 패배했다는 메시지를 그렇지 않으면 다시 검사에 들어간다. 

검사의 내용은 다음과 같다.

let strike = 0;
let ball = 0;
for(let i=0; i < answer.length; i++){
  const index = value.indexOf(answer[i]);
  if(index>-1){
    if(index === i)
      strike += 1;
    else 
      ball += 1;
  }
}
 $logs.append(`${value}: ${strike} 스트라이크 ${ball} 볼`, document.createElement('br'));
  tries.push(value);

 

forEach문

for문 외에 배열에서도 반복문 역할을 하는 메서드들이 있다. 그 중 대표적인 것이 forEach인데 몇 볼 몇 스트라이크를 판단하는 부분을 다음과 같이 forEach메서드로 바꿀 수 있다. 

answer.forEach((number, aIndex) => {
  const index = value.indexOf(String(number));
  if (index > -1) { // 일치하는 숫자 발견
    if (index === aIndex) { // 자릿수도 같음
      strike += 1;
    } else { // 숫자만 같음
      ball += 1;
    }
  }
});

forEach()메서드는 주어진 함수를 배열 요소 각각에 대해 실행한다.

 

반복문보다 배열 자체의 메서드가 더욱 강력할 때가 많다. 

const numbers = [];
for (let n = 1; n <= 9; n += 1) {
  numbers.push(n);
}

위와 같은 반복문을 배열의 메서드만으로 같은 결과를 얻을 수 있다. 

const numbers = Array(9).fill().map((v, i) => i + 1);

Array(num)는 길이가 num인 배열을 만든다. fill()은 배열의 요소로 indefined를 채워 넣고, map은 요소들을 일대일로 짝지어서 다른 값으로 변환하는 메서드이다.