[2022.05.06] 자바스크립트로 타이핑게임 만들기

REASON·2022년 5월 6일
0

STUDY

목록 보기
37/127
post-thumbnail

타이핑게임 따라 만들기

유튜버 데브리님의 영상을 보고 따라 만들어봤다.
숙련된 개발자분들은 그냥 바로 뚝딱 코드를 짜시는구나 또 한번 감탄했던 것 같다.
데브리님 영상을 보면 영상 후반부가 빠르고 복잡하게 흘러가서 어렵긴 했지만 API사용하는 부분이나 함수나 변수같은거 어떻게 활용하는지 정말 도움이 많이 되는것 같다.
처음에는 이해하면서 잘 따라갔는데 후반부로 갈수록 function 추가하고 수정하는 내용이 나오는데
자바스크립트 어린이 입장에서는 1번방 들어갔다가 10번방 들어갔다가 5번방 들어가서 이거하시고요 이런 느낌으로 흘러가서 머릿속이 복잡해졌다..ㅜㅜ
분명 어려운 내용은 아닌데 여기갔다 저기갔다 해서 어렵게 느껴졌던 것 같다.
그래서 후반부에는 거의 따라적기식 코딩이 되었기 때문에 이런식으로 구현하는구나~정도만 대충 익히고 직접 안보고 따라 만들어보기로 했다. (막히면 다시 영상 돌려서 정답을 확인하지 않고 구글링으로 찾아보는 걸로!)

완성한 게임 프리뷰

게임 방법

게임 시작 버튼을 누르면 게임이 시작된다.
남은 시간 내에 해당 단어를 타이핑하면 점수가 1점 증가한다.

남은 시간 내에 타이핑을 성공하지 못하면 게임이 종료되며 획득 점수가 초기화되고
게임을 다시 시작할 수 있는 버튼이 활성화된다.


스스로 만들어보기

앞서 프리뷰에 올린 움짤은 데브리님의 영상을 보면서 만든 결과물이다.
이제부터는 직접 똑같은 게임을 만들어보기로 했다.

자바스크립트 공부 목적이기 때문에 스타일링은 크게 건들지 않고 구조나 레이아웃 배치만 비슷하게 구성하였다.

const input = document.querySelector('.input');
const scoreDisplay = document.querySelector('.score');
const button = document.querySelector('.btn');

let word = document.querySelector('.word');
let score = 0;

input.addEventListener('input', () => {
  if (word.innerText.toLowerCase() === input.value.toLowerCase()) {
    score += 1;
    scoreDisplay.innerText = score;
    input.value = '';
  }
});

먼저, 사용자가 input박스에 입력한 값을 출력해보는 간단한 테스트를 마친후 다음 코드를 작성하였다.
1. input 박스에 input 이벤트리스너 달기
2. 사용자가 입력한 input의 text와 실제 문제의 text가 같은지 비교 (모두 소문자로 변경)
3. 비교한 값이 참이면 변수 score에 1점을 추가한 후, 렌더링된 화면의 score가 변경(+1점)되도록 작성, input 박스의 값 지우기

word를 let으로 선언한 것은 현재는 HTML에서 정적으로 작성된 텍스트로 테스트중이기 때문에 임시로 넣은 것이고 나중에 word는 API를 통해 배열로 추가해줄 것이기 때문이다.

const input = document.querySelector('.input');
const scoreDisplay = document.querySelector('.score');
const timeDisplay = document.querySelector('.time');
const button = document.querySelector('.btn');

let word = document.querySelector('.word');
let score = 0;
let isPlaying = false;
const GAME_TIME = 9;
let currentTime;

먼저, 변수를 조금 더 추가해주었다.
게임시작 버튼을 누르면 타이머가 9초부터 1초씩 감소할 것이고
문제를 맞추면 스코어 점수가 1점 증가하며(이건 아까 만들었고) 타이머가 다시 9초부터 시작하도록 만들 것이다.
const GAME_TIME 변수는 변경되지 않고 계속 사용될 것이기 때문에 변수명을 대문자로 작명하였다.

앞서 작성한 코드는 일치하는지 계속 확인해야 하므로 자주 사용될 것이기 때문에 함수에 담아 사용하기로 했다.


init();

function init() {
  button.classList.remove('loading');

  // 게임시작 버튼을 누르면
  button.addEventListener('click', () => {
    currentTime = GAME_TIME;
    isPlaying = true;
    remainingTime();
    isPlayingGame();
  });
}

// 게임중에 따라 버튼 텍스트&스타일 변경 (수정추가 필요)
function isPlayingGame() {
  if (isPlaying) {
    input.focus();

    if (button.classList.contains('loading')) {
      button.classList.remove('loading');
      button.innerText = '게임시작';
    } else {
      button.classList.add('loading');
      button.innerText = '게임중';
    }
  }
}

// 남은 시간 타이머 (0보다 크면 1씩 감소한다.)
function remainingTime() {
  setInterval(() => {
    wordMatch(); // 정답이 맞는지 계속 확인

    timeDisplay.innerText = currentTime;
    timeDisplay.innerText > 0 ? currentTime-- : false;
  }, 1000);
}

// 입력값과 문제가 동일한지 확인
function wordMatch() {
  input.addEventListener('input', () => {
    if (word.innerText.toLowerCase() === input.value.toLowerCase()) {
      score += 1;
      scoreDisplay.innerText = score;
      input.value = '';
      currentTime = GAME_TIME;
    }
  });
}

아직 손볼 코드가 있긴 하지만..!
일단 이렇게까지 작성하면 다음과 같은 결과물은 확인할 수 있다.

이제 남은 시간 내에 성공하지 못했을 때 스코어가 0으로 초기화되는 것과 게임이 종료되면 게임 시작 버튼이 다시 활성화되도록 코드를 추가할 것이다. 또, 게임 시작 버튼을 여러번 누르지 못하는 코드도 추가해야할 것 같다. (인터벌이 중첩된다..!) 게임이 끝났을 때 텍스트를 작성해도 스코어가 오르는 등 몇가지 버그가 있어서 해결하고자 한다.

init();

function init() {
  button.classList.remove('loading');

  // 게임시작 버튼을 누르면
  button.addEventListener('click', () => {
    if (isPlaying) {
      return;
    }
    currentTime = GAME_TIME;
    isPlaying = true;
    remainingTime();
    isPlayingGame();
  });
}

// 게임중에 따라 버튼 텍스트&스타일 변경 (수정추가 필요)
function isPlayingGame() {
  if (isPlaying) {
    input.focus();

    if (button.classList.contains('loading')) {
      button.classList.remove('loading');
      button.innerText = '게임시작';
    } else {
      button.classList.add('loading');
      button.innerText = '게임중';
    }
  } else {
    button.classList.remove('loading');
    button.innerText = '게임시작';
  }
}

// 남은 시간 타이머 (0보다 크면 1씩 감소한다.)
function remainingTime() {
  if (isPlaying) {
    interval = setInterval(() => {
      wordMatch(); // 정답이 맞는지 계속 확인

      timeDisplay.innerText = currentTime;
      if (timeDisplay.innerText == 0) {
        score = 0;
        scoreDisplay.innerText = score;
        isPlaying = false;
        clearInterval(interval);
        isPlayingGame();
      }
      timeDisplay.innerText > 0 ? currentTime-- : (isPlaying = false);
    }, 1000);
  } else {
    isPlaying = false;
  }
}

// 입력값과 문제가 동일한지 확인
function wordMatch() {
  input.addEventListener('input', () => {
    if (isPlaying) {
      if (word.innerText.toLowerCase() === input.value.toLowerCase()) {
        score += 1;
        scoreDisplay.innerText = score;
        input.value = '';
        currentTime = GAME_TIME;
      }
    }
  });
}

추가 및 수정한 코드는 위와 같다.
몇개 중복 코드도 있고 좀 지저분하게 됐지만..
코드 리팩토링은 나중에 해보는거로 하고 일단 기능 구현에 중점을 뒀다.

하나씩 살펴보면,

function init() {
  button.classList.remove('loading');

  // 게임시작 버튼을 누르면
  button.addEventListener('click', () => {
    if (isPlaying) {
      return;
    }
    currentTime = GAME_TIME;
    isPlaying = true;
    remainingTime();
    isPlayingGame();
  });
}

게임 시작 버튼을 여러번 눌렀을 때 setInterval이 중첩되지 않고록 isPlaying이 true일 때는 return시켜서 다른 코드가 실행되지 않도록 작성했다.

// 입력값과 문제가 동일한지 확인
function wordMatch() {
  input.addEventListener('input', () => {
    if (isPlaying) {
      if (word.innerText.toLowerCase() === input.value.toLowerCase()) {
        score += 1;
        scoreDisplay.innerText = score;
        input.value = '';
        currentTime = GAME_TIME;
      }
    }
  });
}

게임시작을 누르지 않더라도 인풋 값과 문제가 동일하면 스코어가 증가하는 문제도 isPlaying이 참일때만 스코어가 증가하도록 if문을 추가했다.

테스트를 위해 10초는 너무 길어서 임의로 시간을 3초 정도로 단축해서 테스트했다.

게임 시작중이 아닐때 작성해도 스코어가 올라가지 않는다.
그런데 게임 시작 버튼을 눌렀을 때 미리 작성된 input값도 지우는 코드가 필요할 것 같다.

테스트해보니 미리 작성된 input값을 지워버려야 될 것 같아서
isPlayingGame() 함수 안에 isPlaying일 때 아래 값을 추가했다.

input.value = '';

이제 랜덤한 단어를 보여주는 코드를 작성하고자 한다.
랜덤 글자 API를 사용해서 추가해보고자 한다.

데브리님 영상에서는 라이브러리를 사용했던 것으로 기억하지만 자바스크립트 fetch를 사용해보기로 했다.

let words = [];

fetch('https://random-word-api.herokuapp.com/word?number=100').then((res) => {
  res.json().then((data) => {
    data.forEach((word) => {
      words.push(word);
    });
  });
});

words 배열에 forEach 반복문을 사용해서 받아온 단어를 하나씩 push시켰다.
이제 이 배열에 있는 단어를 랜덤하게 문제로 뿌려주는 코드를 추가할 것이다.


function showWord() {
  wordDisplay.innerText = words[Math.floor(Math.random() * words.length)];
}

먼저, 랜덤으로 워드를 보여주는 코드를 작성했다.
이 함수를 실행하는 것은 스코어가 1추가된 이후에 실행할 수 있도록 wordMatch()함수 안에서 호출하도록 작성했다.


// 입력값과 문제가 동일한지 확인
function wordMatch() {
  input.addEventListener('input', () => {
    if (isPlaying) {
      if (wordDisplay.innerText.toLowerCase() === input.value.toLowerCase()) {
        score += 1;
        scoreDisplay.innerText = score;
        input.value = '';
        currentTime = GAME_TIME;
        showWord();
      }
    }
  });
}

이렇게하면 맞출때 마다 랜덤한 단어가 나타난다!
구글링은 fecth사용하는 방법이 기억이 잘 안나서 그거랑 math함수 문서 조금 참고하는 정도였고 나머지는 스스로 작성해서 코드가 중구난방일지도 모른다..ㅜ 그나마 어제 데브리님 영상을 본 기억이 있어서 덜 걸린것 같고.. 원래 같으면 더 오래 잡고 있거나 해결 못하는 문제도 있었을 것 같은데 ( 초보특 : 아는 내용인데 어떻게 활용하는지 모름ㅋㅋ return false 이런거..영상 보면서 오!! 이렇게 쓰는거구나!! 했었다.)
엉성하긴해도 완성되니 뿌듯뿌듯ㅎㅎ
나중에 까먹을 쯤에 다시 만들어보는 것도 좋은 공부가 될 것 같다.

이제 데브리님이 짰던 코드랑 내가 짠 코드랑 비교하면서 현타시간과 함께..ㅠㅠ 이부분은 이렇게 짜는게 더 낫구나하고 공부해야겠다!

글자를 받아오는데 시간이 걸리다보니 게임시작 버튼을 눌렀을 때 바로 word를 변경하고 싶은 경우, 처음 word가 undefined가 떠버려서 미리 몇개 만들어 놓고 받아온 word들을 push를 시키거나 100개가 다 로딩되면 게임을 시작할 수 있게끔 코드를 수정하는 것도 좋을듯 하다.


참고 자료
데브리 - 바닐라 자바스크립트 타자게임 만들기!
Random word API
MDN fetch

profile
블로그 티스토리로 이전했어요! ⇒ https://reasonz.tistory.com/

0개의 댓글