HTML5 Tetris 게임 만들기 - (5) 다양한 모양 블럭 추가하기
08 Jan 2020 | javascript html5다양한 모양 블럭 추가하기
다양한 블럭 모양을 추가하기 위해서는 블럭도 배열 형태로 바꿔줘야 합니다.
기존 코드에 다음과 같은 항목들을 추가했습니다.
const BLOCK_SHAPE = {
    shape1 : [[0, 0], [1, 0], [0, 1], [1, 1]],
};
var curShape = BLOCK_SHAPE.shape1;
블럭의 중점은 (0, 0) 이며, 그 주위로 채워진 블럭의 정보를 JSON 형태 배열로 정의했습니다.
일단 조금 쉽게 접근하기 위해서 블럭은 2x2 사이즈의 정사각형으로 정했고, 기본 틀이 만들어지면 다양한 모양의 블럭을 더 추가하면 됩니다.
function drawFallingBlock() {
    ctx.fillStyle = 'orange';
    for(var i=0; i < curShape.length; i++) {
        var canvasX = (x + curShape[i][0]) * cellWidth;
        var canvasY = (y + curShape[i][1]) * cellHeight;
        ctx.fillRect(canvasX, canvasY, cellWidth, cellHeight);
    }
}
기존의 drawBlock() 메소드 이름을 조금 더 명확하게 하기 위해서 drawFallingBlock()으로 수정했습니다.
function canBlockMoveTo(toX, toY) {
    for(var i=0; i < curShape.length; i++) {
        var shapeX = toX + curShape[i][0];
        var shapeY = toY + curShape[i][1];
        if(shapeX < 0) return false;
        if(shapeX >= COLS) return false;
        if(shapeY < 0) return false;
        if(shapeY >= ROWS) return false;
        
        if(grid[shapeY][shapeX] == 1) return false;
    }
    return true;
}
기존에 아래 방향으로 떨어질 수 있는지를 판단하던 canBlockMoveToDown() 메소드도 좀 더 범용적으로 쓸 수 있게 canBlockMoveTo(toX, toY) 형태로 수정했습니다.
따라서 이를 호출하던 부분들도 전부 수정했습니다.
function addKeyEventListener() {
    addEventListener('keydown', function(event) {
        switch(event.key) {
            case 'ArrowLeft':
                console.log("Left");
                if(canBlockMoveTo(x - 1, y)) {
                    x -= 1;
                }
                break;
            case 'ArrowRight':
                console.log("Right");
                if(canBlockMoveTo(x + 1, y)) {
                    x += 1;
                }
            break;
            case 'ArrowUp':
                console.log("Up");
                break;
            case 'ArrowDown':
                console.log("Down");
                break;
        }
    });
}
function handleBlockDown() {
    var curTime = (new Date()).getTime();
    var diff = curTime - lastBlockDownTime;
    if(diff > BLOCK_DROP_DELAY) {
        if(canBlockMoveTo(x, y + 1)) {
            y += 1;    
        }
        else {
            console.log("block down stopped(x: " + x, "y: " + y + ")");
            for(var i=0; i < curShape.length; i++) {
                var shapeX = x + curShape[i][0];
                var shapeY = y + curShape[i][1];
                grid[shapeY][shapeX] = 1;
            }
            
            initBlock();
        }
        lastBlockDownTime = curTime;
    }
}
블럭 모양 추가
이제 다양한 모양의 블럭들을 추가해봅니다.
const BLOCK_SHAPE = {
    shape1 : [[0, 0], [1, 0], [0, 1], [1, 1]],
    shape2 : [[0, -1], [0, 0], [1, 0], [0, 1]],
    shape3 : [[0, -1], [0, 0], [0, 1], [0, 2]],
    shape4 : [[0, -1], [0, 0], [0, 1], [1, 1]],
    shape5 : [[0, -1], [0, 0], [1, 0], [1, 1]],
};
var curShape = BLOCK_SHAPE.shape1;
그리고 랜덤하게 블럭이 변경되도록 initBlock() 메소드도 수정합니다.
function initBlock() {
    x = COLS / 2;
    y = 0;
    const keys = Object.keys(BLOCK_SHAPE);
    const randomIdx = Math.floor(Math.random() * keys.length);
    curShape = BLOCK_SHAPE[keys[randomIdx]];
}
전체 소스
var canvas;
var ctx;
var x, y;
const BLOCK_DROP_DELAY = 100;
var lastBlockDownTime = 0;
const FRAME = 60;
var drawingTimeDelay = 1000/FRAME;
const ROWS = 24;
const COLS = 12;
var grid;
var cellWidth;
var cellHeight;
const BLOCK_SHAPE = {
    shape1 : [[0, 0], [1, 0], [0, 1], [1, 1]],
    shape2 : [[0, -1], [0, 0], [1, 0], [0, 1]],
    shape3 : [[0, -1], [0, 0], [0, 1], [0, 2]],
    shape4 : [[0, -1], [0, 0], [0, 1], [1, 1]],
    shape5 : [[0, -1], [0, 0], [1, 0], [1, 1]],
};
var curShape = BLOCK_SHAPE.shape1;
//console.log("curShape size: " + curShape.length);
function init() {
    console.log("init()");
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');
    initGrid(COLS, ROWS);
    initBlock();    
    addKeyEventListener();
}
function initBlock() {
    x = COLS / 2;
    y = 0;
    const keys = Object.keys(BLOCK_SHAPE);
    const randomIdx = Math.floor(Math.random() * keys.length);
    curShape = BLOCK_SHAPE[keys[randomIdx]];
}
function initGrid(cols, rows) {
    console.log("initGrid(" + cols + ", " + rows + ")");
    cellWidth = canvas.width / COLS;
    cellHeight = canvas.height / ROWS;
    grid = new Array(rows);
    for(var i=0; i < rows; i++) {
        grid[i] = new Array(cols);
    }
    for(var r=0; r < rows; r++) {
        for(var c=0; c < cols; c++) {
            grid[r][c] = 0;
        }
    }
}
function start() {
   animate(-1);
}
function draw() {
    drawBackground();
    drawGridLine();
    drawBlocks();
    drawFallingBlock();
}
function drawBackground() {
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function drawGridLine() {
    ctx.strokeStyle = 'gray';
    for(var i=1; i < COLS; i++) {
        ctx.beginPath();
        ctx.moveTo(i * cellWidth, 0);
        ctx.lineTo(i * cellWidth, canvas.height);
        ctx.stroke();
    }
    for(var i=1; i < ROWS; i++) {
        ctx.beginPath();
        ctx.moveTo(0, i * cellHeight);
        ctx.lineTo(canvas.width, i * cellHeight);
        ctx.stroke();
    }
}
function drawBlocks() {
    for(var r=0; r < ROWS; r++) {
        for(var c=0; c < COLS; c++) {
            if(grid[r][c] == 1) {
                fillGrid(c, r);
            }
        }
    }
}
function fillGrid(x, y) {
    ctx.fillStyle = 'green';
    var canvasX = x * cellWidth;
    var canvasY = y * cellHeight;
    ctx.fillRect(canvasX, canvasY, cellWidth, cellHeight);
}
function drawFallingBlock() {
    ctx.fillStyle = 'orange';
    for(var i=0; i < curShape.length; i++) {
        var canvasX = (x + curShape[i][0]) * cellWidth;
        var canvasY = (y + curShape[i][1]) * cellHeight;
        ctx.fillRect(canvasX, canvasY, cellWidth, cellHeight);
    }
}
function addKeyEventListener() {
    addEventListener('keydown', function(event) {
        switch(event.key) {
            case 'ArrowLeft':
                console.log("Left");
                if(canBlockMoveTo(x - 1, y)) {
                    x -= 1;
                }
                break;
            case 'ArrowRight':
                console.log("Right");
                if(canBlockMoveTo(x + 1, y)) {
                    x += 1;
                }
            break;
            case 'ArrowUp':
                console.log("Up");
                break;
            case 'ArrowDown':
                console.log("Down");
                break;
        }
    });
}
function animate(lastTime) {
    var curTime = (new Date()).getTime();
    var diff = curTime - lastTime;
    
    if(diff > drawingTimeDelay) {
        draw();
        lastTime = curTime;
    }
    handleBlockDown();
    requestAnimationFrame(function() {
        animate(lastTime);
    });
}
function handleBlockDown() {
    var curTime = (new Date()).getTime();
    var diff = curTime - lastBlockDownTime;
    if(diff > BLOCK_DROP_DELAY) {
        if(canBlockMoveTo(x, y + 1)) {
            y += 1;    
        }
        else {
            console.log("block down stopped(x: " + x, "y: " + y + ")");
            for(var i=0; i < curShape.length; i++) {
                var shapeX = x + curShape[i][0];
                var shapeY = y + curShape[i][1];
                grid[shapeY][shapeX] = 1;
            }
            
            initBlock();
        }
        lastBlockDownTime = curTime;
    }
}
function canBlockMoveTo(toX, toY) {
    for(var i=0; i < curShape.length; i++) {
        var shapeX = toX + curShape[i][0];
        var shapeY = toY + curShape[i][1];
        if(shapeX < 0) return false;
        if(shapeX >= COLS) return false;
        if(shapeY < 0) return false;
        if(shapeY >= ROWS) return false;
        
        if(grid[shapeY][shapeX] == 1) return false;
    }
    return true;
}
  