react

리액트 테트리스

진블로그 2023. 5. 27. 18:21
다른 명언

728x90
반응형

 

function tetris() {
    const tetrisView = document.querySelector(".tetris__play .view ul");
    const tetScore = document.querySelector(".tetris__score");

    const line_rows = 20; //가로
    const line_cols = 12; //세로

    const movingItem = {
        //블록의 정보 변수
        type: "Tmino",
        direction: 0,
        top: 0,
        left: 6,
    };

    const blocks = {
        Tmino: [
            [
                [2, 1],
                [0, 1],
                [1, 0],
                [1, 1],
            ],
            [
                [1, 2],
                [0, 1],
                [1, 0],
                [1, 1],
            ],
            [
                [1, 2],
                [0, 1],
                [2, 1],
                [1, 1],
            ],
            [
                [2, 1],
                [1, 2],
                [1, 0],
                [1, 1],
            ],
        ],
        Imino: [
            [
                [0, 0],
                [0, 1],
                [0, 2],
                [0, 3],
            ],
            [
                [0, 0],
                [1, 0],
                [2, 0],
                [3, 0],
            ],
            [
                [0, 0],
                [0, 1],
                [0, 2],
                [0, 3],
            ],
            [
                [0, 0],
                [1, 0],
                [2, 0],
                [3, 0],
            ],
        ],
        Omino: [
            [
                [0, 0],
                [0, 1],
                [1, 0],
                [1, 1],
            ],
            [
                [0, 0],
                [0, 1],
                [1, 0],
                [1, 1],
            ],
            [
                [0, 0],
                [0, 1],
                [1, 0],
                [1, 1],
            ],
            [
                [0, 0],
                [0, 1],
                [1, 0],
                [1, 1],
            ],
        ],
        Zmino: [
            [
                [0, 0],
                [1, 0],
                [1, 1],
                [2, 1],
            ],
            [
                [1, 0],
                [0, 1],
                [1, 1],
                [0, 2],
            ],
            [
                [0, 0],
                [1, 0],
                [1, 1],
                [2, 1],
            ],
            [
                [1, 0],
                [0, 1],
                [1, 1],
                [0, 2],
            ],
        ],
        Smino: [
            [
                [1, 0],
                [2, 0],
                [0, 1],
                [1, 1],
            ],
            [
                [0, 0],
                [0, 1],
                [1, 1],
                [1, 2],
            ],
            [
                [1, 0],
                [2, 0],
                [0, 1],
                [1, 1],
            ],
            [
                [0, 0],
                [0, 1],
                [1, 1],
                [1, 2],
            ],
        ],
        Jmino: [
            [
                [0, 2],
                [1, 0],
                [1, 1],
                [1, 2],
            ],
            [
                [0, 0],
                [0, 1],
                [1, 1],
                [2, 1],
            ],
            [
                [0, 0],
                [1, 0],
                [0, 1],
                [0, 2],
            ],
            [
                [0, 0],
                [1, 0],
                [2, 0],
                [2, 1],
            ],
        ],
        Lmino: [
            [
                [0, 0],
                [0, 1],
                [0, 2],
                [1, 2],
            ],
            [
                [0, 0],
                [1, 0],
                [2, 0],
                [0, 1],
            ],
            [
                [0, 0],
                [1, 0],
                [1, 1],
                [1, 2],
            ],
            [
                [0, 1],
                [1, 1],
                [2, 0],
                [2, 1],
            ],
        ],
    };

    let score = 0;
    let duration = 500;
    let tetrisLine = 0;
    let downInterval;
    let tempMovingItem;
    let gameover = false;

    //라인 만들기
    function newLine() {
        const li = document.createElement("li");
        const ul = document.createElement("ul");

        for (let j = 0; j < line_cols; j++) {
            const subLi = document.createElement("li");
            ul.prepend(subLi);
        }

        li.prepend(ul);
        tetrisView.prepend(li);
    }

    //시작하기
    function init() {
        gameover = false;
        score = 0;
        duration = 500;
        tetrisLine = 0;
        tetrisView.innerHTML = "";
        tempMovingItem = { ...movingItem };

        for (let i = 0; i < line_rows; i++) {
            newLine(); //라인만들기
        }

        generateNewBlock();
        console.log(score);
    }

    //블록 만들기
    function renderBlocks(moveType = "") {
        if (gameover) return;
        // console.log(movingItem.type);
        // console.log(movingItem.direction);
        // console.log(movingItem.top);
        // console.log(movingItem.left);
        // const type = movingItem.type;
        // const direction = movingItem.direction;
        // const top = movingItem.top;
        // const left = movingItem.left;
        const { type, direction, top, left } = tempMovingItem;
        // console.log(type, direction, top, left);

        const movingBlocks = document.querySelectorAll(".moving");
        movingBlocks.forEach((moving) => {
            moving.classList.remove(type, "moving");
        });

        //블록 모양잡기 foreach 대신 some 사용 some은 중간에 멈출 수 있음.
        blocks[type][direction].some((block) => {
            const x = block[0] + left;
            const y = block[1] + top;

            const target = tetrisView.childNodes[y]
                ? tetrisView.childNodes[y].childNodes[0].childNodes[x]
                : null;
            const isAvailable = checkEmpty(target);

            if (isAvailable) {
                target.classList.add(type, "moving");
            } else {
                tempMovingItem = { ...movingItem };
                setTimeout(() => {
                    renderBlocks();
                    if (moveType === "top") {
                        seizeBlock();
                    }
                }, 0);
                return true;
            }
        });
        movingItem.left = left;
        movingItem.top = top;
        movingItem.direction = direction;
    }

    //블록 감지하기
    function seizeBlock() {
        const movingBlocks = document.querySelectorAll(".moving");
        movingBlocks.forEach((moving) => {
            moving.classList.remove("moving");
            moving.classList.add("seized");
        });

        checkMatch();
    }

    //한줄 제거
    function checkMatch() {
        const childNodes = tetrisView.childNodes;

        childNodes.forEach((child) => {
            let matched = true;
            child.children[0].childNodes.forEach((li) => {
                if (!li.classList.contains("seized")) {
                    matched = false;
                }
            });
            if (matched) {
                child.remove();
                newLine();
                score += 10;
                tetScore.innerText = score + "점";
                tetrisLine++;
            }
        });

        generateNewBlock();
    }

    //새로운 블록 만든기
    function generateNewBlock() {
        if (gameover) {
            TsoundBg.pause();
            setTimeout(() => {
                document.querySelector(".tetris__gameover").style.display =
                    "block";
            }, 2000);
            document.querySelector(".tetris__gameover span").innerHTML =
                tetrisScore;
            return;
        }
        clearInterval(downInterval);
        downInterval = setInterval(() => {
            moveBlock("top", 1);
        }, duration);

        const blockArray = Object.entries(blocks);
        const randomIndex = Math.floor(Math.random() * blockArray.length);
        movingItem.type = blockArray[randomIndex][0];

        movingItem.top = 0;
        movingItem.left = 4;
        movingItem.direction = 0;
        tempMovingItem = { ...movingItem };

        renderBlocks();
    }

    //빈칸 감지
    function checkEmpty(target) {
        if (!target || target.classList.contains("seized")) {
            return false;
        }
        return true;
    }

    //블록 움직이기
    function moveBlock(moveType, amount) {
        tempMovingItem[moveType] += amount;
        renderBlocks(moveType);
    }

    //모양 변경하기
    function chageDirection() {
        const direction = tempMovingItem.direction;
        direction === 3
            ? (tempMovingItem.direction = 0)
            : (tempMovingItem.direction += 1);
        renderBlocks();
    }

    //스페이스바 누르기
    function dropBlock() {
        clearInterval(downInterval);
        downInterval = setInterval(() => {
            moveBlock("top", 1);
        }, 10);
    }

    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 32:
                dropBlock();
                break;
            case 38:
                chageDirection();
                break;
            default:
                break;
        }
    });

    window.addEventListener("DOMContentLoaded", () => {
        init();
    });
}
export default tetris;
 
 

함수는 DOM에서 필요한 요소를 선택합니다. 게임 보기(tetrisView)와 점수 표시(tetScore) 등이 여기에 해당합니다.

게임의 차원과 속성을 정의합니다. 이에는 행과 열의 수(line_rows 및 line_cols), 현재 이동 중인 블록(movingItem), 사용 가능한 블록 유형과 그 모양(blocks), 그리고 여러 게임 관련 변수가 포함됩니다.

newLine 함수는 게임 보기에 새로운 줄(행)을 생성하는 역할을 합니다.

init 함수는 게임을 초기화합니다. 변수를 재설정하고 게임 보기를 지우며 새로운 줄을 생성하고 새로운 블록을 생성합니다.

renderBlocks 함수는 블록을 화면에 렌더링하는 역할을 합니다. 현재 이동 중인 블록(tempMovingItem)의 정보를 기반으로 블록을 위치시키고 CSS 클래스를 추가하여 화면에 표시합니다. 또한, 블록이 이동 가능한지 확인하고 이동이 불가능한 경우에는 seizeBlock 함수를 호출하여 블록을 고정시킵니다.

seizeBlock 함수는 이동 중인 블록을 고정시킵니다. 이동 중인 블록의 CSS 클래스를 변경하여 이동 중인 상태(moving)에서 고정된 상태(seized)로 변경합니다.

checkMatch 함수는 라인이 완전히 채워진 경우 해당 라인을 제거하고 점수를 증가시킵니다. 이 함수는 게임 보기를 탐색하여 각 라인이 고정된 블록으로 채워져 있는지 확인하고 제거합니다.

generateNewBlock 함수는 새로운 블록을 생성합니다. 일정 시간 간격으로 블록을 아래로 이동시키기 위해 downInterval을 설정하고, 랜덤하게 블록 유형을 선택하여 이동 중인 블록(movingItem)을 설정합니다. 이동 중인 블록의 초기 위치와 방향을 설정하고 renderBlocks 함수를 호출하여 블록을 화면에 렌더링합니다.

checkEmpty 함수는 주어진 위치에 빈 공간이 있는지 확인합니다. 해당 위치가 seized 클래스를 포함하지 않는 경우에만 빈 공간으로 간주됩니다.

moveBlock 함수는 블록을 이동시킵니다. 이동 방향(moveType)과 이동 거리(amount)를 매개변수로 받아 현재 이동 중인 블록(tempMovingItem)의 위치를 업데이트하고 renderBlocks 함수를 호출하여 블록을 화면에 렌더링합니다.

changeDirection 함수는 현재 이동 중인 블록의 방향을 변경합니다. 방향은 0부터 3까지 순환하며, tempMovingItem의 방향을 변경한 후 renderBlocks 함수를 호출하여 블록을 화면에 다시 렌더링합니다.

dropBlock 함수는 블록을 최하단까지 빠르게 이동시킵니다. downInterval을 초기화하고 일정 시간 간격으로 블록을 아래로 이동시킵니다.

keydown 이벤트 리스너를 통해 사용자의 키 입력에 따라 블록을 이동하고 방향을 변경합니다. 이동 키(ArrowLeft, ArrowRight)는 moveBlock 함수를 호출하여 좌우로 블록을 이동시키고, 방향 키(ArrowUp)는 changeDirection 함수를 호출하여 블록의 방향을 변경합니다. 또한, 빠른 이동 키(ArrowDown)는 dropBlock 함수를 호출하여 블록을 최하단까지 빠르게 이동시킵니다.

728x90