데브리 님의 유튜브 강의를 통해 테트리스를 만들어보고 이에 대해 공부하였습니다.
앞으로 무엇을 해야할 지 막막해 하던 차에 유튜브 추천영상에 테트리스 만들기가 올라왔다. 테트리스는 한번 만들어보고 싶었기도 했고 재밌을 것 같아서 해보게 되었다. 그냥 무작정 따라하기 보다는 먼저 내가 해보고 강의와 비교해보는 방식으로 진행했다.(직접 구현하지 못한게 많아 사실상 클론코딩과 다를바 없긴했다,,ㅎㅎ)
기본적인 테트리스 게임의 기능만 구현하였다.
게임을 시작했을 때와 블록이 다 내려왔을 때, 새로운 블록을 랜덤으로 생성한다. 생성된 블록은 일정한 속도로 내려온다.
한 줄이 블록으로 모두 차게 되면, 해당 라인을 제거하고, 최상단에 새로운 라인을 추가한다.
라인을 제거하면 점수를 추가한다.
블록을 이동시키다 게임판을 넘어서게 되면 직전의 블록을 다시 불러와 새로 랜더링을 해줬는데, 이 과정에서 재귀함수를 사용하였다.
재귀함수를 사용할 때 maximum call stack size exceeded
에러가 발생할 수 있으므로, 항상 call stack을 염두해야 한다.
해당 에러가 발생할 시 setTimeout()를 활용해 재귀함수 호출하게 하여 task queue로 작업을 넣어두는 방식으로 call stack이 넘치지 않게 할 수 있다.
renderBlocks(){
...
setTimeout(() => {
renderBlocks();
}, 0);
// task queue로 작업을 미뤄두기 위해 setTimeout을 사용한 것이므로 0으로 둬도 무방하다.
}
- 재귀함수 말고 다른 방법을 찾아보기
- setTimeout()을 굳이 안써도 되는지 확인해보기(seizedBlock()이나 게임오버 코드가 있으므로)
classList에 name을 추가, 삭제하면서 블록들을 구분할 수 있었고, 여러 기능들을 좀 더 편하게 구현할 수 있었다.
class를 추가하고 삭제하면서 element들을 보여주거나 보여주지 않을 수 있다.
테트리스 블록이 생성된 후 아래로 떨어지는 것을 어떻게 구현해야할 지 도저히 감이 오질 않았는데, 강의에서는 setInterval()
을 사용해 간단하게 해결하였다.
downInterval = setInterval(() => {
moveBlock("top", 1);
}, duration);
앞으로 주기적으로 실행해야할 함수가 있을 때, 자동으로 동작해야하는 기능을 구현해야 할 때, setInterval()
을 먼저 생각해봐야겠다.
forEach를 쓰게 되면 반복문을 중단할 수 없기 때문에, 상황에 따라 some()
이나 every()
를 사용한다면 좀 더 효율적인 코드를 짤 수 있을 것이다.
some : callbackFn이 배열 요소 중에 하나라도 truthy한 값을 반환하면 즉시 순회 중단, true를 반환. 모든 요소에 대해 참인 값을 반환하지 않는 다면 false를 반환.
every : callbackFn이 모든 배열 요소에 대해 참인 값(truthy한 값)을 반환하면 true를 반환. falsy한 값을 반환하면 즉시 순회를 중단하고 false 반환.
childNodes는 유사 배열 객체인 nodeList를 반환하는 것으로 알고 있는데 강의에서 childNodes.forEach()를 바로 사용하였다.
이러한 점이 의아하여 찾아보니, nodeList는 Array는 아니지만 forEach 메서드를 사용할 수 있다는 것을 알게 되었다. (일부 브라우저는 지원하지 않는다고 한다)
보통 상위 div의 클래스에 container를 주로 쓰는 것으로 알고 있었는데 강의에서는 게임판을 담당하는 div의 클래스에 container대신 wrapper를 사용했다. 찾아보니,
<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 }
const tempMovingItem = movingItem
가끔 얕은 복사를 고려하지 않고 코딩을 할 때가 있다. 항상 주의하도록 명심해야겠다.
게임판을 생성하는 다른 방법은 없을지 고민해보기
블록이 게임판을 넘어가는 것을 막기 위한 다른 방법 찾아보기
이벤트를 핸들링할 때 switch말고 다른 방법 찾아보기
새 블록을 만들 때 key값만 있어도 되는데 Object.keys()대신 Object.entries를 사용한 이유 알아보기
코드 깔끔하게 정리하기 위한 방법 찾아보기
id를 사용하지 않고 class만 사용한 이유에 대해 찾아보기 + id와 class 언제 사용하면 좋을지 찾아보기
forEach vs for
css 속성들에 대해 정리하기
nodeList의 메서드 알아두기