본문 바로가기
JavaScript

[JavaScript] 간단한 테트리스 게임 만들기 (3) - 일시정지

by teamnova 2024. 9. 14.
728x90

 

안녕하세요 테트리스 마지막 게시글입니다 

테트리스 게임 이전편은 아래 링크에서 확인하실 수 있습니다 

 

[JavaScript] 간단한 테트리스 게임 만들기

오늘은 자바 스크립트로 간단한 테트리스 게임을 만들어보도록 하겠습니다. 먼저 HTML  코드입니다 (자바 스크립트 전체 코드는 게시글 하단에서 확인하실 수 있습니다)  테트리스 도형이 쌓

stickode.tistory.com

 

 

[JavaScript] 간단한 테트리스 게임 만들기 (2) - 레벨업 하기

안녕하세요 오늘은 지난번에 만들었던 테트리스 게임에 레벨업 기능을 추가시켜보도록 하겠습니다. 기존 게임에서는 1초 간격으로 블럭이 하단으로 떨어지도록 구현하였습니다. 전체 코드는

stickode.tistory.com

 

 

오늘은 일시정지 기능을 추가해보도록 하겠습니다 

기존 게임은 중간에 멈추지 않고 계속 플레이할 수 있었는데요 

키보드의 p 글자를 눌러서 게임을 멈추고 다시 누르면 이어서 플레이할 수 있도록 구현해보았습니다 

 

먼저 html 코드 변경된 부분입니다. 

status 라는 id 를 가진 div 를 추가해 "게임 중..." 임을 표시하도록 했습니다 

       /* 현재 진행 상태div에 대한 style 속성값 */
        #status {
            position: absolute;
            top: 10px; /* 상단에 위치 */
            left: 50%;
            transform: translateX(-50%); /* 수평 중앙 정렬 */
            font-size: 15px;
            color: white;
            font-family: Arial, sans-serif;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
            display: none;  /* 초기에는 숨겨짐 */
        }
    </style>
</head>
<body>
    <canvas id="tetris" width="300" height="600"></canvas>
    <div id="info">
        <div id="level">Level: 0</div>
        <div id="speed">Speed: 1000ms</div>
    </div>
    <!-- 현재 게임 진행 상태 div -->
    <div id="status">게임 중...</div> <!-- 상태 텍스트 추가 -->
    
    
    <script src="tetris2.js"></script>
</body>
</html>

 

 

 

자바 스크립트 변경된 부분입니다 

방향키에 따라 블록 위치를 조정하는 함수에 p 키에 대한 조건을 추가해줍니다 

     let paused = false;
document.addEventListener('keydown', event => {
    if (event.keyCode === 37) { // 왼쪽 화살표
        playerMove(-1);
    } else if (event.keyCode === 39) { // 오른쪽 화살표
        playerMove(1);
    } else if (event.keyCode === 40) { // 아래 화살표
        playerDrop();
    } else if (event.keyCode === 81) { // Q 키 (회전 왼쪽)
        player.matrix = rotate(player.matrix, -1);
    } else if (event.keyCode === 87) { // W 키 (회전 오른쪽)
        player.matrix = rotate(player.matrix, 1);
    } else if (event.keyCode === 80) { // P 키 (일시 정지/재시작)
        togglePause();
    }
});


function togglePause() {
    paused = !paused;
    updateLevelAndSpeed();
}

 

 

일시 정지 상태에 따라 UI 를 업데이트 해주는 코드 입니다 


   // 일시 정지 상태에 따른 UI 업데이트
    const status = document.getElementById('status');
    if (paused) {
        status.textContent = '일시 정지';
        status.style.display = 'block';
    } else {
        status.textContent = '게임 중...';
        status.style.display = 'block';
        setTimeout(() => {
            status.style.display = 'none';
        }, 1000);  // "게임 중..." 표시를 1초간 유지
    }
}

 

 

 

전체 코드 입니다 

 

tetris.html 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tetris Game2</title>
    <style>
        canvas {
            background-color: #000;
            display: block;
            margin: 0 auto;
            border: 2px solid;
        }
        #info {
            text-align: center;
            color: rgb(0, 0, 0);
            font-family: Arial, sans-serif;
            margin-top: 10px;
        }
        #level, #speed {
            font-size: 18px;
            margin: 5px 0;
        }

        /* 현재 진행 상태 */
        #status {
            position: absolute;
            top: 10px; /* 상단에 위치 */
            left: 50%;
            transform: translateX(-50%); /* 수평 중앙 정렬 */
            font-size: 15px;
            color: white;
            font-family: Arial, sans-serif;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
            display: none;  /* 초기에는 숨겨짐 */
        }
    </style>
</head>
<body>
    <canvas id="tetris" width="300" height="600"></canvas>
    <div id="info">
        <div id="level">Level: 0</div>
        <div id="speed">Speed: 1000ms</div>
    </div>
    <!-- 현재 게임 진행 상태 -->
    <div id="status">게임 중...</div> <!-- 상태 텍스트 추가 -->
    
    
    <script src="tetris2.js"></script>
</body>
</html>

 

 

tetris2.js

const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');

const grid = 20;  // 테트리스 그리드 크기
const cols = canvas.width / grid;
const rows = canvas.height / grid;

let board = Array.from({ length: rows }, () => Array(cols).fill(0));

const colors = [
    null,
    'cyan',
    'blue',
    'orange',
    'yellow',
    'green',
    'purple',
    'red'
];

const tetrominoes = [
    [
        [1, 1, 1, 1],  // I
    ],
    [
        [0, 2, 0],
        [2, 2, 2],     // T
    ],
    [
        [3, 3],
        [3, 3],        // O
    ],
    [
        [0, 4, 4],
        [4, 4, 0],     // S
    ],
    [
        [5, 5, 0],
        [0, 5, 5],     // Z
    ],
    [
        [6, 0, 0],
        [6, 6, 6],     // J
    ],
    [
        [0, 0, 7],
        [7, 7, 7],     // L
    ]
];

function createPiece(type) {
    return tetrominoes[type];
}

function drawMatrix(matrix, offset) {
    matrix.forEach((row, y) => {
        row.forEach((value, x) => {
            if (value !== 0) {
                context.fillStyle = colors[value];
                context.fillRect((x + offset.x) * grid, (y + offset.y) * grid, grid, grid);
            }
        });
    });
}

function collide(board, player) {
    const [m, o] = [player.matrix, player.pos];
    for (let y = 0; y < m.length; y++) {
        for (let x = 0; x < m[y].length; x++) {
            if (m[y][x] !== 0 &&
               (board[y + o.y] &&
                board[y + o.y][x + o.x]) !== 0) {
                return true;
            }
        }
    }
    return false;
}

function merge(board, player) {
    player.matrix.forEach((row, y) => {
        row.forEach((value, x) => {
            if (value !== 0) {
                board[y + player.pos.y][x + player.pos.x] = value;
            }
        });
    });
    clearLines();
}

function clearLines() {
    outer: for (let y = board.length - 1; y >= 0; y--) {
        for (let x = 0; x < board[y].length; x++) {
            if (board[y][x] === 0) {
                continue outer;
            }
        }
        const row = board.splice(y, 1)[0].fill(0);
        board.unshift(row);
        linesCleared += 1;
    }
}

let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
let paused = false;

function update(time = 0) {
    if (!paused) {
        const deltaTime = time - lastTime;
        lastTime = time;

        dropCounter += deltaTime;
        if (dropCounter > dropInterval) {
            playerDrop();
        }

        draw();
    }
    requestAnimationFrame(update);
}

function playerDrop() {
    player.pos.y++;
    if (collide(board, player)) {
        player.pos.y--;
        merge(board, player);
        playerReset();
    }
    dropCounter = 0;
}

function playerMove(dir) {
    player.pos.x += dir;
    if (collide(board, player)) {
        player.pos.x -= dir;
    }
}

function playerReset() {
    player.matrix = createPiece(Math.floor(Math.random() * tetrominoes.length));
    player.pos.y = 0;
    player.pos.x = Math.floor((cols - player.matrix[0].length) / 2);

    if (collide(board, player)) {
        board.forEach(row => row.fill(0));
    }
}

function draw() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    drawMatrix(board, { x: 0, y: 0 });
    drawMatrix(player.matrix, player.pos);

    // 일시 정지 상태에 따른 UI 업데이트
    const status = document.getElementById('status');
    if (paused) {
        status.textContent = '일시 정지';
        status.style.display = 'block';
    } else {
        status.textContent = '게임 중...';
        status.style.display = 'block';
        setTimeout(() => {
            status.style.display = 'none';
        }, 1000);  // "게임 중..." 표시를 1초간 유지
    }
}

function rotate(matrix, dir) {
    for (let y = 0; y < matrix.length; y++) {
        for (let x = 0; x < y; x++) {
            [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
        }
    }
    if (dir > 0) {
        matrix.forEach(row => row.reverse());
    } else {
        matrix.reverse();
    }
    return matrix;
}

const player = {
    pos: { x: 0, y: 0 },
    matrix: createPiece(Math.floor(Math.random() * tetrominoes.length))
};

document.addEventListener('keydown', event => {
    if (event.keyCode === 37) { // 왼쪽 화살표
        playerMove(-1);
    } else if (event.keyCode === 39) { // 오른쪽 화살표
        playerMove(1);
    } else if (event.keyCode === 40) { // 아래 화살표
        playerDrop();
    } else if (event.keyCode === 81) { // Q 키 (회전 왼쪽)
        player.matrix = rotate(player.matrix, -1);
    } else if (event.keyCode === 87) { // W 키 (회전 오른쪽)
        player.matrix = rotate(player.matrix, 1);
    } else if (event.keyCode === 80) { // P 키 (일시 정지/재시작)
        togglePause();
    }
});

function togglePause() {
    paused = !paused;
    updateLevelAndSpeed();
}

update();

// 1분마다 레벨을 증가시키고, 속도를 올리는 타이머
setInterval(() => {
    if (!paused) { // 게임이 일시 정지 상태가 아닐 때만
        increaseLevel();
    }
}, 60000);

let level = 0;
let linesCleared = 0;

function increaseLevel() {
    level += 1;
    dropInterval *= 0.8;  // 속도 증가 비율을 20%로 조정
    updateLevelAndSpeed();  // 레벨과 속도 UI 업데이트
}

function updateLevelAndSpeed() {
    document.getElementById('level').textContent = `Level: ${level}`;
    document.getElementById('speed').textContent = `Speed: ${Math.round(dropInterval)}ms`;
}

// 게임 시작 시 UI 초기화
updateLevelAndSpeed();

 

 

 

 

시연 영상입니다