[JS] Vanilla javascript 로 테트리스 만들기

황현근·2023년 8월 17일
0

JS

목록 보기
1/1
post-thumbnail

🧸 Intro

Vanilla javascript 로 할만한 간단한 프로젝트가 뭐가 있을까 고민을 하다가 유튜브 강의 중에 바닐라 자바스크립트로 테트리스 만들기가 있어서 한 번 따라 만들어 봤다.

https://www.youtube.com/watch?v=1lNy2mhvLFk


🎲 Function

기본적인 테트리스 게임의 기능만 구현했다.
(시간이 된다면 CSS 및 다른 기능들을 추가할 예정이다)

블록 생성 및 제거

  • 게임을 시작했을 때와 블록이 다 내려왔을 때, 새로운 블록을 랜덤으로 생성한다. 생성된 블록은 일정한 속도로 내려온다.
    (점수가 높아질수록 속도 추가예정 ..)

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

  • 라인을 제거하면 점수를 추가한다.
    (한 번에 많이 제거하면 점수 ++ 예정)

주요 코드

function prependNewLine(){
    const li = document.createElement("li");
    const ul = document.createElement("ul");
    for (let j = 0; j < GAME_COLS; j++) {
        const matrix = document.createElement("li");
        ul.prepend(matrix);
    }
    li.prepend(ul);
    playground.prepend(li);
}

function renderBlocks(moveType=""){
    //변수에 바로 접근할 수 있도록 선언.
    const { type, direction, top, left } = tmpMovingItem;
    const movingBlocks = document.querySelectorAll(".moving");
    movingBlocks.forEach((moving) =>{
        moving.classList.remove(type, "moving");
    })
    // forEach 대신 some 사용 > 원하는 시점에 멈출 수 있게
    BLOCKS[type][direction].some(block=>{
        const x = block[0] + left;
        const y = block[1] + top;
        const target = playground.childNodes[y] ? playground.childNodes[y].childNodes[0].childNodes[x] : null;
        const isAvailable = checkEmpty(target);
        if (isAvailable) {
            target.classList.add(type, "moving");
        } else {
            tmpMovingItem = { ... movingItem }
            if(moveType === 'retry'){
                clearInterval(downInterval);
                showGameOverText();
            }
            //콜스택 에러방지
            setTimeout(()=>{
                renderBlocks('retry');
                if(moveType === "top"){
                    seizeBlock();
                }
            }, 0)
            return true;
        }
    })
    movingItem.left = left;
    movingItem.top = top;
    movingItem.direction = direction;
}

블록의 위치

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

주요 코드

document.addEventListener("keydown", (e)=>{
    switch (e.keyCode){
        case 39:
            moveBlock("left", 1);
            break;
        case 37:
            moveBlock("left", -1);
            break;
        case 40:
            moveBlock("top", 1);
            break;
        case 38:
            chageDirection();
            break;
        case 32:
            dropBlock();
            break;
        default :
            break;
    }
})

게임 시작 및 종료

  • 새로운 블록이 생성될 수 없을 때 게임 종료 메세지를 띄운다.
  • 게임 종료 시 다시 시작 버튼을 누르면 게임을 시작할 수 있다.
  • 재시작시 점수와 게임 화면을 리셋한다.
    주요 코드
function init(){
    // 값만 따오는 것이기 때문에 tmpMovingItem 은 변경 x >> 값을 복사
    tmpMovingItem = { ...movingItem };
    for (let i = 0; i < GAME_ROWS; i++) {
        prependNewLine();
    }
    generateNewBlock();
}

GitHub [전체 코드]

https://github.com/hyeongeunee/ToyPJ_TETRIS


💡 만들면서 배운 것


🔎 반복문 사용하기

반복문을 사용할 때 상황에 따라 some()이나 every()를 사용해보자 !

forEach를 쓰게 되면 반복문을 중단할 수 없다.

some()
성능을 위해 조건을 만족하는 값이 발견되면 그 즉시 순회가 중단된다 (Return True)

사용 예시

var testArr = [1, 2, 3, 4, 5]

testArr.some(el => {
    if(el === 2) { return true }
    console.log(el)
})


// 출력
> 1

--------------
var testArr = [1, 2, 3, 4, 5]

testArr.some(el => {
    if(el === 2) { return false }
    console.log(el)
})

// 출력
> 1
> 3
> 4
> 5

every()
모든 원소가 조건을 만족하면 true, 하나라도 만족하지 않으면 false를 반환

사용 예시

var testArr = [1, 2, 3, 4, 5]

testArr.every(el => el > 3); // false
testArr.every(el => el >= 1); // true

🔎 Wrapper의 개념

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은 이전의 상태를 불러오기 위해 사용하는 객체이므로 spread 연산자를 활용하여 프로퍼티를 깊은 복사했다.

const tempMovingItem = { ...movingItem }
이와 같이 객체를 복사하면 얕은 복사가 되기 때문에 tempMovingItem의 값을 변경하게 되면 원본인 movingItem도 같이 변하게 된다.


🤔 얕은 복사 (Shllow Copy)

얕은 복사란 객체를 복사할 때 기존 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말한다.

객체 안에 객체가 있을 경우 한 개의 객체라도 기존 변수의 객체를 참조하고 있다면 이를 얕은 복사라고 한다.

사용 예시

const obj = { a: 1 };
const copyObj = obj;

copyObj.a = 2;

console.log(obj.a); // 2
console.log(obj === copyObj); // true

🤔 깊은 복사 (Deep Copy)

깊은 복사된 객체는 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 말한다.

사용 예시

const a = 1;
const b = a;

b = 2;

console.log(a); // 1
console.log(b); // 2
console.log(a === b); // false

0개의 댓글