vanilla js로 테트리스 만들기

형동킴·2022년 7월 14일
0

tetris

목록 보기
1/3
post-thumbnail

게임종료

데브리 님의 유튜브 강의를 통해 테트리스를 만들어보고 이에 대해 공부하였습니다.

앞으로 무엇을 해야할 지 막막해 하던 차에 유튜브 추천영상에 테트리스 만들기가 올라왔다. 테트리스는 한번 만들어보고 싶었기도 했고 재밌을 것 같아서 해보게 되었다. 그냥 무작정 따라하기 보다는 먼저 내가 해보고 강의와 비교해보는 방식으로 진행했다.(직접 구현하지 못한게 많아 사실상 클론코딩과 다를바 없긴했다,,ㅎㅎ)



구현한 기능

기본적인 테트리스 게임의 기능만 구현하였다.

블록 생성 및 제거

  1. 게임을 시작했을 때와 블록이 다 내려왔을 때, 새로운 블록을 랜덤으로 생성한다. 생성된 블록은 일정한 속도로 내려온다.

  2. 한 줄이 블록으로 모두 차게 되면, 해당 라인을 제거하고, 최상단에 새로운 라인을 추가한다.

  3. 라인을 제거하면 점수를 추가한다.

블록의 위치

  1. 좌,우 방향키를 눌렀을 때 블록 위치를 좌,우로 옮긴다.
  2. 아래 방향키를 눌렀을 때 블록을 아래로 내린다.
  3. 위 방향키를 눌렀을 때 블록의 모양을 바꾼다.
  4. 블록을 이동시킬 때 격자를 넘어가지 못하게 한다.
  5. 블록이 더이상 내려가지 못하면 새로운 블록을 생성한다.

게임 시작 및 종료

  1. 새로운 블록이 생성될 수 없을 때 게임 종료 메세지를 띄운다.
  2. 게임 종료 시 다시 시작 버튼을 누르면 게임을 시작할 수 있다.
  3. 재시작시 점수와 게임 화면을 리셋한다.

테트리스 js 코드



새롭게 배운 것들

❗ 재귀함수 사용시 주의사항

블록을 이동시키다 게임판을 넘어서게 되면 직전의 블록을 다시 불러와 새로 랜더링을 해줬는데, 이 과정에서 재귀함수를 사용하였다.

  • 재귀함수를 사용할 때 maximum call stack size exceeded에러가 발생할 수 있으므로, 항상 call stack을 염두해야 한다.

  • 해당 에러가 발생할 시 setTimeout()를 활용해 재귀함수 호출하게 하여 task queue로 작업을 넣어두는 방식으로 call stack이 넘치지 않게 할 수 있다.


renderBlocks(){
  ...
  setTimeout(() => {

      renderBlocks();

  }, 0); 
  // task queue로 작업을 미뤄두기 위해 setTimeout을 사용한 것이므로 0으로 둬도 무방하다.
}
  • 재귀함수 말고 다른 방법을 찾아보기
  • setTimeout()을 굳이 안써도 되는지 확인해보기(seizedBlock()이나 게임오버 코드가 있으므로)

✅ class속성을 잘 활용하자

  • classList에 name을 추가, 삭제하면서 블록들을 구분할 수 있었고, 여러 기능들을 좀 더 편하게 구현할 수 있었다.

  • class를 추가하고 삭제하면서 element들을 보여주거나 보여주지 않을 수 있다.

✅ setInterval()을 사용하면 자동으로 함수를 실행할 수 있다.

테트리스 블록이 생성된 후 아래로 떨어지는 것을 어떻게 구현해야할 지 도저히 감이 오질 않았는데, 강의에서는 setInterval()을 사용해 간단하게 해결하였다.


 downInterval = setInterval(() => {
    moveBlock("top", 1);
  }, duration);

앞으로 주기적으로 실행해야할 함수가 있을 때, 자동으로 동작해야하는 기능을 구현해야 할 때, setInterval()을 먼저 생각해봐야겠다.

✅ 반복문쓸 때 forEach와 더불어 some(), every()도 고려하자

forEach를 쓰게 되면 반복문을 중단할 수 없기 때문에, 상황에 따라 some()이나 every()를 사용한다면 좀 더 효율적인 코드를 짤 수 있을 것이다.

some : callbackFn이 배열 요소 중에 하나라도 truthy한 값을 반환하면 즉시 순회 중단, true를 반환. 모든 요소에 대해 참인 값을 반환하지 않는 다면 false를 반환.

every : callbackFn이 모든 배열 요소에 대해 참인 값(truthy한 값)을 반환하면 true를 반환. falsy한 값을 반환하면 즉시 순회를 중단하고 false 반환.

✅ childNodes는 forEach()을 쓸 수 있다.

  • childNodes는 유사 배열 객체인 nodeList를 반환하는 것으로 알고 있는데 강의에서 childNodes.forEach()를 바로 사용하였다.

  • 이러한 점이 의아하여 찾아보니, nodeList는 Array는 아니지만 forEach 메서드를 사용할 수 있다는 것을 알게 되었다. (일부 브라우저는 지원하지 않는다고 한다)

✅ wrapper는 단일 요소를 감싸는 경우 사용!

보통 상위 div의 클래스에 container를 주로 쓰는 것으로 알고 있었는데 강의에서는 게임판을 담당하는 div의 클래스에 container대신 wrapper를 사용했다. 찾아보니,

  • 단일 요소를 감싸는 div인 경우 wrapper를,
  • 여러 요소를 감싸는 경우 container를 사용한다고 한다.
  • 명시된 규칙은 아니고 관용적으로 이렇게 사용한다고 한다.
<ul class="items-container">
    <li class="item-wrapper">
        <div class="item">...</div>
    </li>
    <li class="item-wrapper">
        <div class="item">...</div>
    </li>
    <li class="item-wrapper">
        <div class="item">...</div>
    </li>
</ul>

✅ 객체를 복사할 때 얕은 복사를 조심하자!

  • movingItem은 오류 발생 시 이전의 상태를 불러오기 위해 사용하는 객체이므로 펼침 연산자를 활용하여 프로퍼티들을 일일이 복사(깊은 복사)해줬다.
 
const tempMovingItem = { ...movingItem }
  • 아래와 같이 객체를 복사하면 주소값만 복사(얕은 복사)되기 때문에 tempMovingItem의 값을 변경하게 되면 원본인 movingItem도 같이 변하게 된다.
const tempMovingItem = movingItem

가끔 얕은 복사를 고려하지 않고 코딩을 할 때가 있다. 항상 주의하도록 명심해야겠다.



➕ 추가해보고 싶은 기능

  1. 시간이 지날수록 내려오는 속도 빠르게 하기
  2. 스페이스 누를 때 한번에 떨어지게 만들기
  3. 다음 블록을 게임판 옆에서 보여주기



❓ 추가로 공부할 것들

리팩토링 어떻게 할 지 고민해보기

  1. 게임판을 생성하는 다른 방법은 없을지 고민해보기

  2. 블록이 게임판을 넘어가는 것을 막기 위한 다른 방법 찾아보기

  3. 이벤트를 핸들링할 때 switch말고 다른 방법 찾아보기

  4. 새 블록을 만들 때 key값만 있어도 되는데 Object.keys()대신 Object.entries를 사용한 이유 알아보기

  5. 코드 깔끔하게 정리하기 위한 방법 찾아보기

그 외

  1. id를 사용하지 않고 class만 사용한 이유에 대해 찾아보기 + id와 class 언제 사용하면 좋을지 찾아보기

  2. forEach vs for

  3. css 속성들에 대해 정리하기

  4. nodeList의 메서드 알아두기

profile
결과보다 성장을!

0개의 댓글