[HTML/CSS/Javascript] Tetris 구현하기

김선우·2021년 12월 30일

HTML/CSS/Javascript

목록 보기
6/9
post-thumbnail

추억이 담긴 게임 '테트리스(TETRIS)'

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>테트리스</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div class="wrapper">
        <div class="game-text">
            <span>!게임종료!</span>
            <button>다시시작</button>
        </div>
        <div class="score">0</div>
        <div class="playground">
            <ul></ul>
        </div>
    </div>
    <script src="js/tetris.js" type="module"></script>
</body>
</html>

CSS

* {
    margin: 0;
    padding: 0;
}

ul {
    list-style: none;
}

body {
    height: 100%;
    overflow: hidden;
}

.game-text {
    display: none;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    position: fixed;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
    left: 0;
    top: 0;
    color: #fff;
    font-size: 50px;

}

.game-text > button {
    padding: 0.5rem 1rem;
    color: #fff;
    background: salmon;
    border: none;
    cursor: pointer;
}

.score {
    text-align: center;
    font-size: 36px;
    margin-bottom: 2rem;
}

.playground > ul {
    border: 1px solid #333;
    width: 250px;
    margin: 0 auto;
}

.playground > ul > li {
    width: 100%;
    height: 25px;
}

.playground > ul > li > ul {
    display: flex;
}

.playground > ul > li > ul > li {
    width: 25px;
    height: 25px;
    outline: 1px solid #ccc;
    
}

.tree {
    background: #67c23a
}
.bar {
    background: salmon;
}
.zee {
    background: #e6a23c;
}
.elLeft {
    background: #8e44ad;
}
.elRight {
    background: #16a085
}
.squre {
    background: #505050;
}  

JS

import BLOCKS from "./blocks.js"

// DOM
const playground = document.querySelector(".playground > ul");
const gameText = document.querySelector(".game-text");
const scoreDisplay = document.querySelector(".scoreDisplay");
const restartButton = document.querySelector(".game-text > button");

// Setting
const GAME_ROMS = 20;
const GAME_COLS = 10;     

// Variables
let score = 0;
let duration = 500;
let downInterval;
let tempMovingItem;



const movingItem = {
    type: "",
    direction: 0,
    top: 0,
    left: 0,
};


init()

// functions
function init() {
    tempMovingItem  = { ...movingItem};
    for(let i=0; i < GAME_ROMS ; i++) {
        prependNewLine()
    }
    generateNewBlock();
}

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 } = tempMovingItem;
    const movingBlocks = document.querySelectorAll(".moving");
    movingBlocks.forEach(moving => {
        moving.classList.remove(type, "moving");
        console.log(moving)
    })
    BLOCKS[type][direction].some(block=>{
        const x = block[0] + left;
        const y = block[1] + top;
        
        console.log(playground.childNodes[y])

        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 {
            tempMovingItem = { ...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;  
}

function seizeBlock() {
    const movingBlocks = document.querySelectorAll(".moving");
    movingBlocks.forEach(moving => {
        moving.classList.remove("moving");
        moving.classList.add("seized");
    })
    checkMatch()
}

function checkMatch() {

    const childNodes = playground.childNodes;
    childNodes.forEach(child => {
        let matched = true;
        child.children[0].childNodes.forEach(li => {
            if(!li.classList.contains("seized")) {
                matched = false;
            }
        })
        if(matched) {
            child.remove();
            prependNewLine();
            score ++;
            scoreDisplay.innerText = score;

        }
    })
    generateNewBlock()

}

function generateNewBlock() {

    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 = 3;
    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 changeDirection() {
    const direction = tempMovingItem.direction;
    direction === 3 ? tempMovingItem.direction = 0 :  tempMovingItem.direction += 1;
    renderBlocks();
}

function dropBlock() {
    clearInterval(downInterval);
    downInterval = setInterval(() => {
        moveBlock("top",1)
    }, 10)
}

function showGameoverText() {
    gameText.style.display = "flex"
}

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

restartButton.addEventListener("click", () => {
    playground.innerHTML = '';
    gameText.style.display = "none";
    init();
})
 

#comment

  1. 아직 약간의 오류들이 존재한다. 이 정도를 혼자 힘으로 구현할 실력이 된다면 1차적인 목표는 달성하지 않을까
  2. 갈 길이 멀다는 사실을 체감 중이다.
  3. 꾸준히 코딩 공부한지 3주째이다. 근무를 제외하고 하루도 빠지지 않았다. 요즘엔 연등2시간에 개인정비시간 1시간 더 투자하고 있다. 배움에 대한 즐거움을 수능 이후로 다시끔 느낀다.
  4. 더 잘하고 싶다. javascript를 집중적으로 파 볼 생각이다. 그리고 node.js 등을 건들여보겠다.
profile
꿈꾸는중

1개의 댓글

comment-user-thumbnail
2024년 9월 15일

James had always been skeptical about online casinos, viewing them as little more than flashy distractions. However, a https://tortugacasino-france.com conversation with a colleague about a new blackjack platform piqued his curiosity. Intrigued by the site’s innovative approach and positive reviews, James decided to give it a try. He started with a modest deposit, not expecting much but hoping for a bit of fun.

답글 달기