HTML5 jQuery 사용하기

|

jQuery 사용하는 방법

자바스크립트를 사용하다보면, jQuery를 조금만 활용하면 아주 간편하게 구현이 되는 경우가 많아서 어쩔 수 없이 사용하는 경우가 많습니다.

jQuery 사용법은 w3schools과 같은 사이트에서 잘 설명되어 있습니다.

jQuery를 사용하는 방법은 크게 2가지 방법이 있습니다.

  • 직접 파일을 다운받아 사용하는 방법
  • CDN을 이용하는 방법

두 방법 모두 장단 점이 있습니다.


직접 다운받아 사용하는 방법

jquery.com에서 파일을 직접 받은 다음 다음 코드로 불러올 수 있습니다.

<head>
<script src="jquery-3.4.1.min.js"></script>
</head>


CDN을 이용하는 방법

CDN(Content Delivery Network)을 이용하면 파일을 다운로드 할 필요 없이 다음 호출 코드만으로 jQuery를 사용할 수 있어서 편리합니다.

<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>

또는

<head>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script>
</head>

HTML5 Canvas의 마우스 클릭 위치 얻기

|

Canvas의 마우스 클릭 위치 얻기

var canvas;
var ctx;

function init() {
    console.log("init()");
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');

    drawCanvasBorder();

    canvas.addEventListener("click", function(event) {
        var rect = canvas.getBoundingClientRect();
        var x = event.clientX - rect.left;
        var y = event.clientY - rect.top;

        console.log("(" + x + ", " + y + ") is clicked.");
    });
}

function drawCanvasBorder() {
    ctx.strokeStyle = 'black';

    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.stroke();
}

HTML5 Tetris 게임 만들기 - (8) 다 채워진 라인 삭제 및 전체 코드

|

다 채워진 라인 삭제 기능 구현

라인 삭제 기능은 2차 배열의 모든 칸이 1로 채워있는지 체크만 하면 되서 간편합니다.

function handleLineClear() {
    var r = ROWS - 1;
    while(r >= 0) {
        var count = 0;
        for(var c=0; c < COLS; c++) {
            count += grid[r][c];
        }

        if(count == COLS) {
            removeLine(r);
            continue;
        }

        r--;
    }
}

function removeLine(row) {
    for(var r = row; r >= 0; r--) {
        if(r == 0) {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = 0;
            }
        }
        else {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = grid[r - 1][c];
            }
        }
    }
}


전체 코드

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]].slice();
}

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, curShape)) {
                    x -= 1;
                }
                break;

            case 'ArrowRight':
                console.log("Right");
                if(canBlockMoveTo(x + 1, y, curShape)) {
                    x += 1;
                }
            break;

            case 'ArrowUp':
                console.log("Up");
                handleRotate();
                break;

            case 'ArrowDown':
                console.log("Down");
                dropBlock();
                break;
        }
    });
}

function animate(lastTime) {
    var curTime = (new Date()).getTime();
    var diff = curTime - lastTime;
    
    if(diff > drawingTimeDelay) {
        draw();

        lastTime = curTime;
    }

    handleBlockDown();
    handleLineClear();

    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, curShape)) {
            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, shape) {
    for(var i=0; i < shape.length; i++) {
        var shapeX = toX + shape[i][0];
        var shapeY = toY + shape[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;
}

function dropBlock() {
    for(var toY=ROWS-1; toY >= 0; toY--) {
        if(canBlockMoveTo(x, toY, curShape)) {
            y = toY;    
            return;
        }
    }
}

function handleRotate() {
    var nextShape = new Array(curShape.length);

    for(var i=0; i < curShape.length; i++) {
        nextShape[i] = new Array(2);        
        nextShape[i][0] = curShape[i][1];
        nextShape[i][1] = -curShape[i][0];
    }

    if(canBlockMoveTo(x, y, nextShape)) {
        curShape = nextShape;
    }
}

function handleLineClear() {
    var r = ROWS - 1;
    while(r >= 0) {
        var count = 0;
        for(var c=0; c < COLS; c++) {
            count += grid[r][c];
        }

        if(count == COLS) {
            removeLine(r);
            continue;
        }

        r--;
    }
}

function removeLine(row) {
    for(var r = row; r >= 0; r--) {
        if(r == 0) {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = 0;
            }
        }
        else {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = grid[r - 1][c];
            }
        }
    }
}

HTML5 Tetris 게임 만들기 - (7) 블럭 고속 낙하

|

블럭 고속 낙하 기능 구현

키보드 아래 버튼을 누르면 블럭이 바로 아래로 떨어지도록 해봅니다. 앞 부분의 코드들에 비해서 난이도가 쉽습니다.

기존에 canBlockMoveTo(toX, toY, shape) 메소드를 만들어 놓았으니깐 이를 활용해서 구현해봅니다.

function canBlockMoveTo(toX, toY, shape) {
    for(var i=0; i < shape.length; i++) {
        var shapeX = toX + shape[i][0];
        var shapeY = toY + shape[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;
}

function dropBlock() {
    for(var toY=ROWS-1; toY >= 0; toY--) {
        if(canBlockMoveTo(x, toY, curShape)) {
            y = toY;    
            return;
        }
    }
}

그 이후 키보드 아래 버튼을 눌렀을 때 dropBlock() 메소드가 호출되도록 하면 됩니다.

HTML5 Tetris 게임 만들기 - (6) 블럭 회전

|

블럭 회전 기능 구현

블럭 회전을 위해서 다음 함수들을 추가했습니다.

function handleRotate() {
    var nextShape = new Array(curShape.length);

    for(var i=0; i < curShape.length; i++) {
        var x = curShape[i][0];
        var y = curShape[i][1];

        nextShape[i] = new Array(2);        
        nextShape[i][0] = y;
        nextShape[i][1] = -x;
    }

    if(canRotate(nextShape)) {
        curShape = nextShape;
    }
}

function canRotate(nextShape) {
    for(var i=0; i < nextShape.length; i++) {
        var shapeX = x + nextShape[i][0];
        var shapeY = y + nextShape[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;
}

블럭을 회전시키기 전에 미리 가상의 블럭을 생성해서 회전한 다음, 회전이 가능한지 판단합니다. 만약 회전이 가능하면, 회전한 가상 블럭을 진짜 블럭으로 교체하며, 회전이 안되는 경우는 이를 무시하도록 되어 있습니다.


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;
}

function canRotate(nextShape) {
    for(var i=0; i < nextShape.length; i++) {
        var shapeX = x + nextShape[i][0];
        var shapeY = y + nextShape[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;
}

canRotate(nextShape) 코드를 자세히 보면 canBlockMoveTo(toX, toY) 메소드와 내용이 거의 흡사합니다. 회전 역시 이동의 일종으로 생각할 수 있기 때문에 두 메소드는 아래와 같이 하나의 메소드로 합칠 수 있습니다.

function canBlockMoveTo(toX, toY, shape) {
    for(var i=0; i < shape.length; i++) {
        var shapeX = toX + shape[i][0];
        var shapeY = toY + shape[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;
}


전체 코드

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]].slice();
}

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, curShape)) {
                    x -= 1;
                }
                break;

            case 'ArrowRight':
                console.log("Right");
                if(canBlockMoveTo(x + 1, y, curShape)) {
                    x += 1;
                }
            break;

            case 'ArrowUp':
                console.log("Up");
                handleRotate();
                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, curShape)) {
            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, shape) {
    for(var i=0; i < shape.length; i++) {
        var shapeX = toX + shape[i][0];
        var shapeY = toY + shape[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;
}

function handleRotate() {
    var nextShape = new Array(curShape.length);

    for(var i=0; i < curShape.length; i++) {
        nextShape[i] = new Array(2);        
        nextShape[i][0] = curShape[i][1];
        nextShape[i][1] = -curShape[i][0];
    }

    if(canBlockMoveTo(x, y, nextShape)) {
        curShape = nextShape;
    }
}