[TIL] Carrot Game

dk.han·2022년 10월 7일
0
post-thumbnail

2022 . 10. 07.Fri

Carrot Game

드림코딩의 브라우저 101 강의 section 중 하나인 Carrot game을 이틀간에 걸쳐 완성했고, 오늘 솔루션 영상을 봤다.
솔루션 영상을 보면서 내가 작성한 코드와 엘리가 작성한 코드를 비교해 보고, 리팩토링 과정을 거쳤다. 이 과정에서 내가 고쳐야 할 부분과 느낀점들에 대해 정리하고자 한다.

1. 함수단위로 코드를 작성하자.

리팩토링을 하기 전 작성했던 코드를 보면, class를 추가하고, 당근과 벌레를 추가하고, counter를 수정하는 등 여러가지 동작들이 함께 작성되어 있다. 이런 코드는 기능이 잘 작동한다고 해도 가독성이 좋지 않아 코드가 무엇을 의미하는지 직관적으로 알기 힘들다. 또한 프로젝트의 규모가 커질 경우 유지, 보수에도 어려움이 있어 지양해야하는 코드 작성이다.

const startBtn = document.querySelector('.startBtn');
const counter = document.querySelector('.counter');

startBtn.addEventListener('click', (e) => {
  if (startBtn.classList.contains('active') === false) {
    startBtn.classList.add('active');
    startBtn.innerHTML = `<i class="fa-solid fa-stop"></i>`;
    addItem('carrot__img', CARROT_COUNT, '/carrot/img/carrot.png');
    addItem('bug__img', BUG_COUNT, '/carrot/img/bug.png');
    startTimer();
    const carrotEl = document.querySelectorAll('.carrot__img');
    counter.textContent = carrotEl.length;
    console.log(carrotEl);
  } else {
    modalStop();
    startBtn.classList.remove('active');
  }
});

때문에, 함수 단위로 코드를 작성하고, 하나의 함수에는 한가지 동작을 수행하도록 작성하는 것을 지향해야 한다.
위의 코드를 함수 단위로 작성하게 되면, 가독성이 좋아져 코드를 직관적으로 이해할 수 있으며, 함수마다 어떤 동작을 수행하는지 알 수 있어, 코드 수정 시 더욱 편하게 할 수 있다. 처음부터 적응이 쉽지 않겠지만 항상 함수 단위로 작성해야 한다는 것을 기억하도록 하자.

const startBtn = document.querySelector('.startBtn');

let started = false;

startBtn.addEventListener('click', (e) => {
  if (started) {
    stopGame();
  } else {
    startGame();
  }
});

function startGame() {
  started = true;
  initGame(); // counter, bug, carrot 생성
  showTimerAndScore(); // timer, counter 다시 보이게
  startTimer(); // timer 시작
  startBtn.innerHTML = `<i class="fa-solid fa-stop"></i>`;
}

function stopGame() {
  started = false;
  stopTimer(); // timer stop
  hideStartBtn(); // startBtn 숨기기
  showModaleAndText('Replay ❓');
}

2. Timer 만들기.

내가 만든 Timer는 stop 버튼을 누르면 1초가 지나가고 멈추는 현상이 발생했다.

  • 10초가 지난 후 1초가 더 지나고 나서 Modal이 뜨는 현상
    if (reaminingTimeSec <= 0) 이 조건문에서 return을 해주지 않으면 reaminingTimeSec=0이 된 후에도 조건문을 실행하고 나서 그 아래 코드를 실행하게 된다. 때문에 -1:-1이 뜨게 되는것.
    조건문에서 return을 써 줘야 아래 코드가 실행되지 않고 종료가 된다.
let timer = undefined;

function startTimer() {
  let reaminingTimeSec = GAME_DURATION_SEC;
  updateTimerText(reaminingTimeSec);
  timer = setInterval(() => {
    if (reaminingTimeSec <= 0) {
      clearInterval(timer);
      finishGame(false);
      return; // 이 부분을 놓치지 말자.
    }
    updateTimerText(--reaminingTimeSec);
    // if (!modal.classList.contains('hidden')) {
    //   clearInterval(timer);
    //   seconde = 10;
    // }
  }, 1000);
}
  • 스탑 버튼을 눌렀을 때 1초가 지난 후에 Modal이 뜨는 현상
    clearInterval(timer)를 setInterval() 함수 안에서 처리했기 때문이다. 그럴 수밖에 없던 이유는 startTimer 함수 안에 timer 변수를 선언했기 때문. 이렇게 되면 startTimer 함수 밖에서는 clearInterval(timer)를 사용할 수 없기 때문에, 1초가 지나서야 동작이 수행됐던 것이다.
    때문에 timer를 전역 변수로 선언해주어 문제를 해결할 수 있었다.

3. Inline CSS 적용

당근과 벌레 이미지에 마우스를 올리면 이미지가 커지는 transform: scale(1.1)을 적용하고 싶었으나 적용되지 않았다. 이유는 이미지를 랜덤하게 배치할 때 transform: translate() 속성을 사용해서 배치했기 때문! item.style.transform 이런 식으로 스타일을 주면 inline CSS로 적용되기 때문에 최우선으로 적용이 된다. 때문에 CSS 파일에서 아무리 transform을 줘도 적용이 되지 않았던 것. 그래서 left, top 속성을 이용해서 배치를 하고 transform을 적용하였다.

  for (let i = 0; i < count; i++) {
    const item = document.createElement('img');
    item.setAttribute('class', className);
    item.setAttribute('src', imgPath);
    const x = Math.random() * (bottomRect.width - CARROT_SIZE);
    const y = Math.random() * (bottomRect.height - CARROT_SIZE);
    item.style.left = `${x}px`;
    item.style.top = `${y}px`;
    // item.style.transform = `translate(${x}px, ${y}px)`
    bottomContainer.appendChild(item);
  }

느낀점


기능은 모두 구현해서 기분은 좋았으나, 솔루션 영상을 보고난 뒤에는 내 코드가 너무 쓰레기 같아서 조금 실망했다. 하지만 다음부터는 제대로 짜면 되는거 아닌가!!? 라는 마인드를 가져본다.
아마 1번의 리팩토링 과정을 더 거칠텐데 그때도 제대로 배우고 포스팅하도록 하겠다.

Reference

프론트엔드 필수 브라우저 101

0개의 댓글