HTML とは

HTML とは、誤解を恐れずに言ってしまうとウェブページ(ホームページ)を作るために使われている言語です。 例えば、以下のようなものが HTML です。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <title>タイトル</title>
  </head>
  <body>

    <h1>見出し</h1>
    <p>
      本文本文本文本文本文本文本文本文本文本文本文本文本文本文
      本文本文<a href="hoge.html">ハイパーリンク</a>本文
      本文本文本文本文本文本文本文本文本文本文本文本文本文本文
    </p>
  </body>

</html>

サンプル

イメージ湧きましたか?

では、何故 HTML というものが必要なのでしょうか。 言い方を変えると「シンプルテキストとの違いは?」なんなのでしょうか。 それは、二つあります。

ハイパーリンクとは

ハイパーリンクとはこういったテキストのこと、つまり単語をポチっと押して別の文章に飛ぶ仕組みです。

ハイパーテキスト以前の文章では、関連する事柄を知りたいときに、 その文章に付属する参考文献の章などを調べなければいけませんでした。 それがあたりまえだったのです。 ハイパーリンクはとてつもなく画期的な仕組みだったのです。

また、文章中にハイパーリンクを含んだ文章を、ハイパーテキストといいます。 HyperText Markup Language というのは、元々そういう意味だったんですね。

以下に、ハイパーリンクを張っている HTML の例を示します。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd"><html lang="ja"><head><title>タイトル</title></head><body>

<p>
    <a href="programming.html">プログラミング</a>ってどんなことだと思いますか?
    一番想像しやすい例をあげると、「ゲームを作る作業」は
    <a href="programming.html">プログラミング</a>ですよね。
    そんなイメージありませんか?
    僕も、最初に「<a href="programming.html">プログラミング</a>ってどんなこと?」で想像したのは
    「ゲームとか<a href="excel.html">エクセル</a>みたいなのを作ること!」でした。
</p>

</body></html>

サンプル

簡単ですね!

文章を構造化するとは

文章を構造化するとは、段落や見出しなどの「テキストの文章内における役割」を文章中に持たせることです。

たとえば、以下のような文章があったとします。

** 今日買ったもの **

今日買ったものは、以下の通りです。

- りんご
- みかん
- にんじん

もし、この文章を見たのが人間ならば、どこが見出しで、どこが段落で、どこが箇条書きかなどの文章の構造が分かると思います。 しかし、それは機械からは分かりません。

インターネット上に置いた文章は、人間だけが読むものではありません。 検索エンジンなど、様々なプログラムがインターネット上に置いた文章を読む可能性があります。

では、この文章を HTML を使って構造化してみましょう。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <title>今日買ったもの</title>
  </head>
  <body>
    <h1>今日買ったもの</h1>
    <p>
      今日買ったものは、以下の通りです。
    </p>
    <ul>
        <li>りんご</li>
        <li>みかん</li>
        <li>にんじん</li>
    </ul>
  </body>
</html>

サンプル

簡単ですね!

HTML の様々な仕様

HTML の仕様には、以下のように様々なものがあります。

「どの仕様が正しい」ということはないですが、最近では HTML 4.01 や XHTML 1.0 や XHTML 1.1 が使われることが多いです。 なお、この講座では以降 HTML5 という策定中の仕様を使います。

JavaScript とは

JavaScript とは、「HTML に動きを付けるためのプログラミング言語」です。 今では Opera, Firefox, Safari, IE で JavaScript (または、 JavaScript 互換)の言語を使うことができます。

JavaScript は、 script 要素の中(<script type="text/javascript"> と </script>の間)に記述します。 特に head 要素の中に書く必要はありませんが、慣例的に head 要素の中に書かれることが多いです。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>

        <script type="text/javascript">
            alert("Hello, world!");
        </script>

    </head>
    <body>
        <p>Hello, world!</p>
    </body>
</html>

サンプル

動きましたか? alert( ... ) の中に書いた値が表示されたんですね! なんとなく分かった感じになってください!

JavaScript で HTML を書き換えてみよう(1)

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">

            function main() {
                var elm = document.getElementById("target");
                elm.innerHTML = "Hello, <strong>JavaScript!</strong>";
            }

        </script>
    </head>

    <body onload="main()">
        <p id="target">Hello, world!</p>
    </body>
</html>

サンプル

JavaScript で HTML を書き換えてみよう(2)

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">

            function main() {
                var elm = document.getElementById('target');
                elm.innerHTML = 'Hello, <strong>JavaScript!</strong>';
            }

        </script>
    </head>

    <body>
        <p id="target">Hello, world!</p>
        <input type="button" value="click" onclick="main()" />
    </body>
</html>

サンプル

足し算をやってみよう!

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">

            function main() {
                var elmTarget = document.getElementById('target');
                var elmValue0 = document.getElementById('value0');
                var elmValue1 = document.getElementById('value1');

                elmTarget.innerHTML = parseInt(elmValue0.value) +
                                            parseInt(elmValue1.value);
            }

        </script>
    </head>

    <body>
        <p id="target">Hello, world!</p>
        <input id="value0" type="text" value="100" />
        +
        <input id="value1" type="text" value="200" />
        <input type="button" value="click" onclick="main()" />
    </body>
</html>

サンプル

四角を書いてみよう

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">

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

                ctx.fillStyle = 'rgb(255, 0, 0)';
                ctx.fillRect(0, 0, 20, 20);
            }

        </script>
    </head>

    <body>
        <canvas id="target" style="border: 5px solid gray" width="200" height="400"></canvas>
        <input type="button" value="click" onclick="main()" />
    </body>
</html>

サンプル

四角を書いたり消したりしてみよう!

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">
            var ctx;

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');
                ctx.fillStyle = 'rgb(255, 0, 0)';
            }

            function paint() {
                ctx.fillRect(10, 10, 20, 20);
            }

            function clean() {
                ctx.clearRect(0, 0, 200, 400);
            }
        </script>
    </head>

    <body onload="load()">
        <canvas id="target" style="border: 5px solid gray" width="200" height="400"></canvas>
        <input type="button" value="paint"  onclick="paint()" />
        <input type="button" value="clear" onclick="clean()" />
    </body>
</html>

サンプル

配列をブロックに変換してみよう

<!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]
            ];

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');
                ctx.fillStyle = 'rgb(255, 0, 0)';
            }

            function paint() {
                for (var y = 0; y < block.length; y ++) {
                    for (var x = 0; x < block[y].length; x ++) {
                        if (block[y][x]) {
                            ctx.fillRect(x * 20, y * 20, 20, 20);
                        }
                    }
                }
            }

            function clean() {
                ctx.clearRect(0, 0, 200, 400);
            }
        </script>
    </head>

    <body onload="load()">
        <canvas id="target" style="border: 5px solid gray" width="200" height="400"></canvas>
        <input type="button" value="paint"  onclick="paint()" />
        <input type="button" value="clear" onclick="clean()" />
    </body>
</html>

サンプル

ブロックを動かしてみよう

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

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');
                ctx.fillStyle = 'rgb(255, 0, 0)';
            }

            function paint() {
                ctx.clearRect(0, 0, 200, 400);
                for (var y = 0; y < block.length; y ++) {
                    for (var x = 0; x < block[y].length; x ++) {
                        if (block[y][x]) {
                            ctx.fillRect((x + posx) * 20, (y + posy) * 20, 20, 20);
                        }
                    }
                }
                posy = posy + 1;
            }
        </script>
    </head>

    <body onload="load()">
        <canvas id="target" style="border: 5px solid gray" width="200" height="400"></canvas>
        <input type="button" value="paint"  onclick="paint()" />
    </body>
</html>

サンプル

ブロックを自動で動かしてみよう

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

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');
                ctx.fillStyle = 'rgb(255, 0, 0)';

                setInterval(paint, 200);
            }

            function paint() {
                ctx.clearRect(0, 0, 200, 400);
                for (var y = 0; y < block.length; y ++) {
                    for (var x = 0; x < block[y].length; x ++) {
                        if (block[y][x]) {
                            ctx.fillRect((x + posx) * 20, (y + posy) * 20, 20, 20);
                        }
                    }
                }
                posy = posy + 1;
            }
        </script>
    </head>

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

サンプル

配列の描画を関数にする

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

            function load() {
                var elmTarget = document.getElementById('target');
                ctx = elmTarget.getContext('2d');
                ctx.fillStyle = 'rgb(255, 0, 0)';

                setInterval(paint, 200);
            }

            function paintMatrix(matrix, offsetx, offsety) {
                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 paint() {
                ctx.clearRect(0, 0, 200, 400);
                paintMatrix(block, posx, posy);
                posy = posy + 1;
            }
        </script>
    </head>

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

サンプル

全体のエリアの配列を作る

<!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 paint() {
                ctx.clearRect(0, 0, 200, 400);
                paintMatrix(map, 0, 0, 'rgb(128, 128, 128)');
                paintMatrix(block, posx, posy, 'rgb(255, 0, 0)');
                posy = posy + 1;
            }
        </script>
    </head>

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

サンプル

ブロックがエリアに溜まるようにする

<!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(map, 0, 0, 'rgb(128, 128, 128)');
                paintMatrix(block, posx, posy, 'rgb(255, 0, 0)');

                if (check(map, block, posx, posy + 1)) {
                    posy = posy + 1;
                }
                else {
                    mergeMatrix(map, block, posx, posy);
                    posx = 0; posy = 0;
                }
            }
        </script>
    </head>

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

サンプル

左右下に動かす

<!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 key(keyCode) {
                switch (keyCode) {
                    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>

サンプル

回転させる

<!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>

サンプル

揃ったら消す

<!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>

サンプル

ランダムなブロックを出す

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">
            var ctx;
            var blocks = [
                [
                    [1,1],
                    [0,1],
                    [0,1]
                ],
                [
                    [1,1],
                    [1,0],
                    [1,0]
                ],
                [
                    [1,1],
                    [1,1]
                ],
                [
                    [1,0],
                    [1,1],
                    [1,0]
                ],
                [
                    [1,0],
                    [1,1],
                    [0,1]
                ],
                [
                    [0,1],
                    [1,1],
                    [1,0]
                ],
                [
                    [1],
                    [1],
                    [1],
                    [1]
                ]
            ];

            var block = blocks[Math.floor(Math.random() * blocks.length)];
            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;
                    block = blocks[Math.floor(Math.random() * blocks.length)];
                }
            }

            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>

サンプル

よりディープな JavaScript の世界へ

テトリスがさっさと完成しちゃったっていう人に送る、よりディープな JavaScript の世界を紹介したいと思います。

完成したテトリスのコードを、より JavaScript 的に書き換えてみましょう!

関数の名前は、関数という値が入った変数名

function hoge() {
    alert(1);
}

hoge();

サンプル

この関数宣言は、実際には以下のようなことが行われています。

var hoge = function () {
    alert(1);
};

hoge();

実は、この書き方こそが本質的な JavaScript の関数の作りかたなのです。 今までの書き方は、これの簡易記法に過ぎません。

試しにさっきのコードを書き換えてみましょう!

サンプル

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">
            var ctx;
            var blocks = [
                [
                    [1,1],
                    [0,1],
                    [0,1]
                ],
                [
                    [1,1],
                    [1,0],
                    [1,0]
                ],
                [
                    [1,1],
                    [1,1]
                ],
                [
                    [1,0],
                    [1,1],
                    [1,0]
                ],
                [
                    [1,0],
                    [1,1],
                    [0,1]
                ],
                [
                    [0,1],
                    [1,1],
                    [1,0]
                ],
                [
                    [1],
                    [1],
                    [1],
                    [1]
                ]
            ];

            var block = blocks[Math.floor(Math.random() * blocks.length)];
            var posx = 0, posy = 0;
            var map, mapWidth = 10, mapHeight = 20;

            var load = function() {
                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);
            };

            var paintMatrix = function(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);
                        }
                    }
                }
            };

            var check = function(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;
            };

            var mergeMatrix = function(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]++;
                        }
                    }
                }
            };

            var clearRows = function(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);
                    }
                }
            };

            var paint = function() {
                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;
                    block = blocks[Math.floor(Math.random() * blocks.length)];
                }
            };

            var rotate = function(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;
            };

            var key = function(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>

サンプル

関数は、関数に渡せる

前の項で、関数は変数に代入可能な値というのが分かりました。

変数に代入可能な値は、関数にも渡すことができます。

実は、今までの例でも関数に関数を渡している箇所があります。 そうです。 paint 関数を定期的に呼び出すところですね。

以下のような例は setInterval という関数に hoge という関数を渡しているのですね。

var hoge = function() {
    alert(1);
};

// 3 秒に一回 hoge を呼び出す
setInterval(hoge, 3000);

サンプル

もちろん、変数に一回代入しなくても、直接関数を渡すことができます。

setInterval(function() {
    alert(1);
}, 3000);

サンプル

では、テトリスのコードも直接関数を渡す形式に書き換えてみましょう! (paint 関数の中身を直接 setInterval にぶち込みます)

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">
            var ctx;
            var blocks = [
                [
                    [1,1],
                    [0,1],
                    [0,1]
                ],
                [
                    [1,1],
                    [1,0],
                    [1,0]
                ],
                [
                    [1,1],
                    [1,1]
                ],
                [
                    [1,0],
                    [1,1],
                    [1,0]
                ],
                [
                    [1,0],
                    [1,1],
                    [0,1]
                ],
                [
                    [0,1],
                    [1,1],
                    [1,0]
                ],
                [
                    [1],
                    [1],
                    [1],
                    [1]
                ]
            ];

            var block = blocks[Math.floor(Math.random() * blocks.length)];
            var posx = 0, posy = 0;
            var map, mapWidth = 10, mapHeight = 20;

            var load = function() {
                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(function() {
                    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;
                        block = blocks[Math.floor(Math.random() * blocks.length)];
                    }
                }, 200);
            };

            var paintMatrix = function(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);
                        }
                    }
                }
            };

            var check = function(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;
            };

            var mergeMatrix = function(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]++;
                        }
                    }
                }
            };

            var clearRows = function(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);
                    }
                }
            };

            var rotate = function(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;
            };

            var key = function(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>

サンプル

オブジェクトを作ってみよう

今までに出てきた ctx などのような値の中にさらに値(関数も含む)を持つ値のことを オブジェクトと言います。

さっそく、オブジェクトを作ってみましょう。

var a = {
    hoge: 1,
    fuga: 2
};

alert(a.hoge);
alert(a.fuga);

サンプル

ここでの hoge や fuga のことをプロパティといいます。

プロパティには、値を代入することもできます。

var a = {
    hoge: 1,
    fuga: 2
};

a.hoge = 3;

alert(a.hoge);

サンプル

存在しないプロパティに値を代入することもできます。

var a = {
    hoge: 1,
    fuga: 2
};

a.piyo = 3;

alert(a.piyo);

サンプル

関数をプロパティに持たせることもできます。 このときに this という特殊な変数なようなものを使うと、そのオブジェクト自体を関数内で扱うことが出来ます。

var a = {
    hoge: 1,
    fuga: 2,
    alertHoge: function() {
        alert(this.hoge);
    }
};

a.alertHoge();

サンプル

このように、値に対する様々な操作はオブジェクトにプロパティに入れた関数で行うと、 プログラム全体の見通しがよくなります。

同じ特性を持ったオブジェクトを作る

プログラムをやっていると、同じ特性を持ったオブジェクトを作りたいという時があります。 そのようなときは new という仕組みを使います。

var Dog = function(name) {
    this.name = name;
    this.type = '犬';
};

var pochi = new Dog('ポチ');
var hachi = new Dog('ハチ');

alert(pochi.name);
alert(pochi.type);

alert(hachi.name);
alert(hachi.type);

サンプル

ちょっと変な感じがするかもしれませんが、関数には二つの使い方があるのです。

  • 処理をまとめる(普通の関数として使う)
  • 同じ特性を持ったオブジェクトを生成する

この 2 番目の使い方をする関数のことをコンストラクタといいます。

このように関数と new を使ってオブジェクトを作った場合は、全員に共通のプロパティを持たせることができます。

以下の例をみてみましょう。

var Dog = function(name) {
    this.name = name;
};

Dog.prototype.type = '犬';
Dog.prototype.bow = function() {
    alert(this.name + '「わんわん」');
};

var pochi = new Dog('ポチ');
var hachi = new Dog('ハチ');

pochi.bow();
hachi.bow();

サンプル

つまり pochi オブジェクトや hachi オブジェクトは Dog.prototype オブジェクトが持っている全てのプロパティにアクセスすることができます。

このとき、 pochi オブジェクトや hachi オブジェクトは、 Dog.prototype オブジェクトを継承したオブジェクトといいます。

配列が継承しているオブジェクト

このように、オブジェクトはオブジェクトを継承することで、共通のプロパティを作ることができます。

実は、配列も Array.prototype というオブジェクトを継承しています。

以下の例を見てみましょう。

Array.prototype.first = function() {
    return this[0];
};

var array = ['hoge', 'fuga', 'piyo'];

alert(array.first());
alert([10, 20, 30].first());

サンプル

Array.prototype のプロパティに関数を追加して、すべての配列に操作が加わったことが分かります。

では、さっきのテトリスの例も Array.prototype を拡張してみましょう。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">

            Array.prototype.each = function(fn) {
                var result = [];
                for (var i = 0; i < this.length; i ++) {
                    result[i] = fn(this[i], i);
                }
                return result;
            };

            var ctx;
            var blocks = [
                [
                    [1,1],
                    [0,1],
                    [0,1]
                ],
                [
                    [1,1],
                    [1,0],
                    [1,0]
                ],
                [
                    [1,1],
                    [1,1]
                ],
                [
                    [1,0],
                    [1,1],
                    [1,0]
                ],
                [
                    [1,0],
                    [1,1],
                    [0,1]
                ],
                [
                    [0,1],
                    [1,1],
                    [1,0]
                ],
                [
                    [1],
                    [1],
                    [1],
                    [1]
                ]
            ];

            var block = blocks[Math.floor(Math.random() * blocks.length)];
            var posx = 0, posy = 0;
            var mapWidth = 10, mapHeight = 20;

            var map = new Array(mapHeight).each(function() {
                return new Array(mapWidth).each(function() { return 0; });
            });

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

                setInterval(function() {
                    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;
                        block = blocks[Math.floor(Math.random() * blocks.length)];
                    }
                }, 200);
            };

            var paintMatrix = function(matrix, offsetx, offsety, color) {
                ctx.fillStyle = color;
                
                matrix.each(function(row, y) {
                    row.each(function(val, x) {
                        if (val) {
                            ctx.fillRect((x + offsetx) * 20, (y + offsety) * 20, 20, 20);
                        }
                    });
                });
            };

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

                var ok = true;
                block.each(function(row, y) {
                    row.each(function(val, x) {
                        if (val && map[y + offsety][x + offsetx]) {
                            ok = false;
                        }
                    });
                });
                return ok;
            };

            var mergeMatrix = function(map, block, offsetx, offsety) {
                map.each(function(row, y) {
                    row.each(function(val, x) {
                        if (block[y - offsety] && block[y - offsety][x - offsetx]) {
                            row[x]++;
                        }
                    });
                });
            };

            var clearRows = function(map) {
                map.each(function(row, y) {
                    var full = true;
                    row.each(function(val, x) {
                        if (!val) {
                            full = false;
                        }
                    });
                    if (full) {
                        map.splice(y, 1);
                        map.unshift(new Array(mapWidth).each(function() { return 0 }));
                    }
                });
            };

            var rotate = function(block) {
                return new Array(block[0].length).each(function(_, y) {
                    return new Array(block.length).each(function(_, x) {
                        return block[block.length - x - 1][y];
                    });
                });
            };

            var key = function(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>

サンプル

オブジェクト指向

今までに作ってきた、テトリスのプログラムを見ると 2 重配列の操作関数が沢山出てきます。

  • paintMatrix
  • check
  • mergeMatrix
  • clearRows
  • rotate

あるデータに対する操作関数が沢山ある場合は、 そのデータと操作関数をオブジェクトのプロパティにすると全体の見通しが良くなります。

このように、データと操作をオブジェクトというものにまとめて、 整理しながらプログラム書くことをオブジェクト指向と言います。

では、 Matrix というコンストラクタを作って、二重配列への操作関数をまとめてみましょう。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script type="text/javascript">

            Array.prototype.each = function(fn) {
                var result = [];
                for (var i = 0; i < this.length; i ++) {
                    result[i] = fn(this[i], i);
                }
                return result;
            };

            var Matrix = function(matrix) {
                this.matrix = matrix;
                this.height = matrix.length;
                this.width  = matrix[0].length;
            };

            Matrix.prototype.each = function(fn) {
                return this.matrix.each(fn);
            };

            Matrix.prototype.get = function(x, y) {
                return (this.matrix[y] && this.matrix[y][x]) || 0;
            };

            Matrix.prototype.paint = function(offsetx, offsety, color) {
                ctx.fillStyle = color;
                this.matrix.each(function(row, y) {
                    row.each(function(val, x) {
                        if (val) {
                            ctx.fillRect((x + offsetx) * 20, (y + offsety) * 20, 20, 20);
                        }
                    });
                });
            };

            Matrix.prototype.check = function(block, offsetx, offsety) {
                if (offsetx < 0 || offsety < 0 ||
                    this.height < offsety + block.height ||
                    this.width  < offsetx + block.width) {
                    return false;
                }

                var matrix = this.matrix;
                var ok = true;
                block.each(function(row, y) {
                    row.each(function(val, x) {
                        if (val && matrix[y + offsety][x + offsetx]) {
                            ok = false;
                        }
                    });
                });
                return ok;
            };

            Matrix.prototype.merge = function(block, offsetx, offsety) {
                this.matrix.each(function(row, y) {
                    row.each(function(val, x) {
                        row[x] += block.get(x - offsetx, y - offsety);
                    });
                });
            };

            Matrix.prototype.clearRows = function() {
                var matrix = this.matrix;
                var width  = this.width;
                matrix.each(function(row, y) {
                    var full = true;
                    row.each(function(val, x) {
                        if (!val) {
                            full = false;
                        }
                    });
                    if (full) {
                        matrix.splice(y, 1);
                        matrix.unshift(new Array(width).each(function() { return 0 }));
                    }
                });
            };

            Matrix.prototype.rotate = function() {
                var matrix = this.matrix;
                return new Matrix(new Array(matrix[0].length).each(function(_, y) {
                    return new Array(matrix.length).each(function(_, x) {
                        return matrix[matrix.length - x - 1][y];
                    });
                }));
            };



            var ctx;
            var blocks = [
                new Matrix([
                    [1,1],
                    [0,1],
                    [0,1]
                ]),
                new Matrix([
                    [1,1],
                    [1,0],
                    [1,0]
                ]),
                new Matrix([
                    [1,1],
                    [1,1]
                ]),
                new Matrix([
                    [1,0],
                    [1,1],
                    [1,0]
                ]),
                new Matrix([
                    [1,0],
                    [1,1],
                    [0,1]
                ]),
                new Matrix([
                    [0,1],
                    [1,1],
                    [1,0]
                ]),
                new Matrix([
                    [1],
                    [1],
                    [1],
                    [1]
                ])
            ];

            var block = blocks[Math.floor(Math.random() * blocks.length)];
            var posx = 0, posy = 0;

            var map = new Matrix(new Array(20).each(function() {
                return new Array(10).each(function() { return 0; });
            }));

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

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

                    if (map.check(block, posx, posy + 1)) {
                        posy = posy + 1;
                    }
                    else {
                        map.merge(block, posx, posy);
                        map.clearRows();
                        posx = 0; posy = 0;
                        block = blocks[Math.floor(Math.random() * blocks.length)];
                    }
                }, 200);
            };

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

サンプル

ずいぶん、見通しが良くなりましたね!

Internet Explorer で動かすために

実は、ここまでのテトリスは Internet Explorer では動きません。 Internet Explorerでは、デフォルトでは canvas 要素が使えないのです。

でも、がっかりする必要はありません。 excanvas.js という JavaScript ファイルを html から読み込んでやれば、 Internet Explorer でも canvas を使うことができるようになります。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Sample</title>
        <script src="http://svn.coderepos.org/share/docs/amachang/20080813-procamp2008/excanvas.js" type="text/javascript"></script>
        <script type="text/javascript">

            Array.prototype.each = function(fn) {
                var result = [];
                for (var i = 0; i < this.length; i ++) {
                    result[i] = fn(this[i], i);
                }
                return result;
            };

            var Matrix = function(matrix) {
                this.matrix = matrix;
                this.height = matrix.length;
                this.width  = matrix[0].length;
            };

            Matrix.prototype.each = function(fn) {
                return this.matrix.each(fn);
            };

            Matrix.prototype.get = function(x, y) {
                return (this.matrix[y] && this.matrix[y][x]) || 0;
            };

            Matrix.prototype.paint = function(offsetx, offsety, color) {
                ctx.fillStyle = color;
                this.matrix.each(function(row, y) {
                    row.each(function(val, x) {
                        if (val) {
                            ctx.fillRect((x + offsetx) * 20, (y + offsety) * 20, 20, 20);
                        }
                    });
                });
            };

            Matrix.prototype.check = function(block, offsetx, offsety) {
                if (offsetx < 0 || offsety < 0 ||
                    this.height < offsety + block.height ||
                    this.width  < offsetx + block.width) {
                    return false;
                }

                var matrix = this.matrix;
                var ok = true;
                block.each(function(row, y) {
                    row.each(function(val, x) {
                        if (val && matrix[y + offsety][x + offsetx]) {
                            ok = false;
                        }
                    });
                });
                return ok;
            };

            Matrix.prototype.merge = function(block, offsetx, offsety) {
                this.matrix.each(function(row, y) {
                    row.each(function(val, x) {
                        row[x] += block.get(x - offsetx, y - offsety);
                    });
                });
            };

            Matrix.prototype.clearRows = function() {
                var matrix = this.matrix;
                var width  = this.width;
                matrix.each(function(row, y) {
                    var full = true;
                    row.each(function(val, x) {
                        if (!val) {
                            full = false;
                        }
                    });
                    if (full) {
                        matrix.splice(y, 1);
                        matrix.unshift(new Array(width).each(function() { return 0 }));
                    }
                });
            };

            Matrix.prototype.rotate = function() {
                var matrix = this.matrix;
                return new Matrix(new Array(matrix[0].length).each(function(_, y) {
                    return new Array(matrix.length).each(function(_, x) {
                        return matrix[matrix.length - x - 1][y];
                    });
                }));
            };



            var ctx;
            var blocks = [
                new Matrix([
                    [1,1],
                    [0,1],
                    [0,1]
                ]),
                new Matrix([
                    [1,1],
                    [1,0],
                    [1,0]
                ]),
                new Matrix([
                    [1,1],
                    [1,1]
                ]),
                new Matrix([
                    [1,0],
                    [1,1],
                    [1,0]
                ]),
                new Matrix([
                    [1,0],
                    [1,1],
                    [0,1]
                ]),
                new Matrix([
                    [0,1],
                    [1,1],
                    [1,0]
                ]),
                new Matrix([
                    [1],
                    [1],
                    [1],
                    [1]
                ])
            ];

            var block = blocks[Math.floor(Math.random() * blocks.length)];
            var posx = 0, posy = 0;

            var map = new Matrix(new Array(20).each(function() {
                return new Array(10).each(function() { return 0; });
            }));

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

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

                    if (map.check(block, posx, posy + 1)) {
                        posy = posy + 1;
                    }
                    else {
                        map.merge(block, posx, posy);
                        map.clearRows();
                        posx = 0; posy = 0;
                        block = blocks[Math.floor(Math.random() * blocks.length)];
                    }
                }, 200);
            };

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

        </script>
    </head>

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

サンプル

やった!これで動くようになりました!