読者です 読者をやめる 読者になる 読者になる

生涯未熟

プログラミングをちょこちょこと。

JavaScriptでテトリスっぽいもの#5

今回はブロックの回転操作から始めます。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">
            var ctx;
            var block = [
                [1,1],
                [0,1],
                [0,1]
            ];
            var posx = 0, posy = 0;
            var map, mapWidth = 10, mapHeight = 20;

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');

                map = [];
                for (var y = 0; y < mapHeight; y++) {
                    map[y] = [];
                    for (var x = 0; x < mapWidth; x++) {
                        map[y][x] = 0;
                    }
                }
                setInterval(paint, 200);
            }

            function paintMatrix(matrix, offsetx, offsety, color) {
                ctx.fillStyle = color;
                for (var y = 0; y < matrix.length; y ++) {
                    for (var x = 0; x < matrix[y].length; x ++) {
                        if (matrix[y][x]) {
                            ctx.fillRect((x + offsetx) * 20, (y + offsety) * 20, 20, 20);
                        }
                    }
                }
            }

            function check(map, block, offsetx, offsety) {
                if (offsetx < 0 || offsety < 0 ||
                    mapHeight < offsety + block.length ||
                    mapWidth < offsetx + block[0].length) {
                    return false;;
                }
                for (var y = 0; y < block.length; y ++) {
                    for (var x = 0; x < block[y].length; x ++) {
                        if (block[y][x] && map[y + offsety][x + offsetx]) { 
                            return false;
                        }
                    }
                }
                return true;
            }

            function mergeMatrix(map, block, offsetx, offsety) {
                for (var y = 0; y < mapHeight; y ++) {
                    for (var x = 0; x < mapWidth; x ++) {
                        if (block[y - offsety] && block[y - offsety][x - offsetx]) {
                            map[y][x]++;
                        }
                    }
                }
            }

            function paint() {
                ctx.clearRect(0, 0, 200, 400);
                paintMatrix(block, posx, posy, 'rgb(255, 0, 0)');
                paintMatrix(map, 0, 0, 'rgb(128, 128, 128)');

                if (check(map, block, posx, posy + 1)) {
                    posy = posy + 1;
                }
                else {
                    mergeMatrix(map, block, posx, posy);
                    posx = 0; posy = 0;
                }
            }

            function rotate(block) {
                var rotated = [];
                for (var x = 0; x < block[0].length; x ++) {
                    rotated[x] = [];
                    for (var y = 0; y < block.length; y ++) {
                        rotated[x][block.length - y - 1] = block[y][x];
                    }
                }
                return rotated;
            }

            function key(keyCode) {
                switch (keyCode) {
                    case 38:
                        if (!check(map, rotate(block), posx, posy)) {
                            return;
                        }
                        block = rotate(block);
                        break;
                    case 39:
                        if (!check(map, block, posx + 1, posy)) {
                            return;
                        }
                        posx = posx + 1;
                        break;
                    case 37:
                        if (!check(map, block, posx - 1, posy)) {
                            return;
                        }
                        posx = posx - 1;
                        break;
                    case 40:
                        var y = posy;
                        while (check(map, block, posx, y)) { y++; }
                        posy = y - 1;
                        break;
                    default:
                        return;
                }
                ctx.clearRect(0, 0, 200, 400);
                paintMatrix(block, posx, posy, 'rgb(255, 0, 0)');
                paintMatrix(map, 0, 0, 'rgb(128, 128, 128)');
            }

        </script>
    </head>

    <body onload="load()" onkeydown="key(event.keyCode)">
        <canvas id="target" style="border: 5px solid gray" width="200" height="400"></canvas>
    </body>
</html>

見所は関数rotatekeyのcase38というところでしょうか。
まずはrotateから見ていきます。


関数rotateはブロックを回転させるための関数です。
rotatedという2次元配列を用意し、そこに2重for文を使って本来のブロック位置とは90度違う配置をrotatedに流し込みます。
そして、それを関数keyの中でblock = rotate(block);という風にすれば配列blockに反映されます。

回転させている2重for文がなかなか分かりにくいと思いますので実際に配列を見ながら説明します。

test = [
 [1 , 0 , 0],
 [1 , 0 , 0],
 [1 , 1 , 1]
]

今、仮にこのような2次元配列がありそれを回転させることを考えましょう。
まず、簡単にxとyを逆にしてみた場合を考えてみます。

test = [
 [1 , 1 , 1],
 [0 , 0 , 1],
 [0 , 0 , 1]
]

となることは分かりますね?
そこで初期状態から90度を回転させることを考えるとxの位置が反対であるのでこれを直すようにします。
どのようにするかというと、xの位置を指定している部分を配列の最大値からblockを入れるようにします。
それを示しているのが下記の部分ですね。

rotated[x][block.length - y - 1] = block[y][x];

こうすることによりxの挿入位置が逆になり配列が以下のようになります。

test = [
 [1 , 1 , 1],
 [1 , 0 , 0],
 [1 , 0 , 0]
]

これで90度づつ回転させられるわけです。

あとは関数keyでkeycode38の↑を押したときにまずはif文で回転出来るかどうかcheck関数を通し、回転出来るようであればrotateで回転させます。



それでは次はブロックが1列に揃った時に消えるプログラムです。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">
            var ctx;
            var block = [
                [1,1],
                [0,1],
                [0,1]
            ];
            var posx = 0, posy = 0;
            var map, mapWidth = 10, mapHeight = 20;

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');

                map = [];
                for (var y = 0; y < mapHeight; y++) {
                    map[y] = [];
                    for (var x = 0; x < mapWidth; x++) {
                        map[y][x] = 0;
                    }
                }
                setInterval(paint, 200);
            }

            function paintMatrix(matrix, offsetx, offsety, color) {
                ctx.fillStyle = color;
                for (var y = 0; y < matrix.length; y ++) {
                    for (var x = 0; x < matrix[y].length; x ++) {
                        if (matrix[y][x]) {
                            ctx.fillRect((x + offsetx) * 20, (y + offsety) * 20, 20, 20);
                        }
                    }
                }
            }

            function check(map, block, offsetx, offsety) {
                if (offsetx < 0 || offsety < 0 ||
                    mapHeight < offsety + block.length ||
                    mapWidth < offsetx + block[0].length) {
                    return false;;
                }
                for (var y = 0; y < block.length; y ++) {
                    for (var x = 0; x < block[y].length; x ++) {
                        if (block[y][x] && map[y + offsety][x + offsetx]) { 
                            return false;
                        }
                    }
                }
                return true;
            }

            function mergeMatrix(map, block, offsetx, offsety) {
                for (var y = 0; y < mapHeight; y ++) {
                    for (var x = 0; x < mapWidth; x ++) {
                        if (block[y - offsety] && block[y - offsety][x - offsetx]) {
                            map[y][x]++;
                        }
                    }
                }
            }

            function clearRows(map) {
                for (var y = 0; y < mapHeight; y ++) {
                    var full = true;
                    for (var x = 0; x < mapWidth; x ++) {
                        if (!map[y][x]) {
                            full = false;
                        }
                    }
                    if (full) {
                        map.splice(y, 1);
                        var newRow = [];
                        for (var i = 0; i < mapWidth; i ++) {
                            newRow[i] = 0;
                        }
                        map.unshift(newRow);
                    }
                }
            }

            function paint() {
                ctx.clearRect(0, 0, 200, 400);
                paintMatrix(block, posx, posy, 'rgb(255, 0, 0)');
                paintMatrix(map, 0, 0, 'rgb(128, 128, 128)');

                if (check(map, block, posx, posy + 1)) {
                    posy = posy + 1;
                }
                else {
                    mergeMatrix(map, block, posx, posy);
                    clearRows(map);
                    posx = 0; posy = 0;
                }
            }

            function rotate(block) {
                var rotated = [];
                for (var x = 0; x < block[0].length; x ++) {
                    rotated[x] = [];
                    for (var y = 0; y < block.length; y ++) {
                        rotated[x][block.length - y - 1] = block[y][x];
                    }
                }
                return rotated;
            }

            function key(keyCode) {
                switch (keyCode) {
                    case 38:
                        if (!check(map, rotate(block), posx, posy)) {
                            return;
                        }
                        block = rotate(block);
                        break;
                    case 39:
                        if (!check(map, block, posx + 1, posy)) {
                            return;
                        }
                        posx = posx + 1;
                        break;
                    case 37:
                        if (!check(map, block, posx - 1, posy)) {
                            return;
                        }
                        posx = posx - 1;
                        break;
                    case 40:
                        var y = posy;
                        while (check(map, block, posx, y)) { y++; }
                        posy = y - 1;
                        break;
                    default:
                        return;
                }
                ctx.clearRect(0, 0, 200, 400);
                paintMatrix(block, posx, posy, 'rgb(255, 0, 0)');
                paintMatrix(map, 0, 0, 'rgb(128, 128, 128)');
            }

        </script>
    </head>

    <body onload="load()" onkeydown="key(event.keyCode)">
        <canvas id="target" style="border: 5px solid gray" width="200" height="400"></canvas>
    </body>
</html>

関数clearRowsが追加されています。
この関数はまず一つ一つの列ごとを確認するためにfullという変数を初期値trueで設定しておき、もし1列の中に1つでも0があればfullをfalseにすることで1列にブロックが溜まったかどうかを判断しています。
もしfullがtrueであるならばspliceメソッドを使ってブロックが溜まった1列を削除しています。
spliceメソッドは、第1引数で指定した場所から第2引数分削除し、もし第3引数を指定すればその指定された値に置換されます。

そして削除された分はunshiftメソッドを用いて配列の先頭に新しく作った全ての配列が0のnewRowを挿入しています。
unshiftメソッドは指定されたオブジェクトの先頭に引数を挿入するという命令です。
check関数もくぐり抜け、mergeMatricsもくぐり抜けてから関数clearRowsの判定に入ります。


これでだいぶテトリスらしくなってきました。
今回はここまで。
次回は、最終回ということでランダムなブロックを生成します。

JavaScriptでテトリスっぽいもの#6