素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

HTML5のcanvasでテトリス

canvas thisに置いた。
JavaScriptの部分は下記の通りで、テトリスの画面の二次元配列とブロックの二次元配列を保存して、画面の上にブロックを重ねるように描画するというごく普通の実装。
画面の端を壁にしておくことで、はみだしチェックなどを省くことができる。番兵と呼ばれるテクニックで、よく使われる。
だいたいの部分の作成は1時間くらいで終わって、ブロックが落ちてくる+ブロックを動かせる/回転させられるようにするところまでで1時間30分。
あとは、描画まわりのバグ取りとブロックに色をつけるところ、次のブロックを左上に表示、ブロックの回転の調整(最初はブロックの左上を軸に回転していたが中央に軸をもってくるように+回転済みのブロックを保持しておく)を、リファクタリングをしつつやった。
全体で3時間くらいかかった。
どこかの動画で1時間でテトリスを作っていたのを見たので、自分でも作ってみたがぜんぜん駄目だった。

function Tetris(options) {
    this.width = options.width;
    this.height = options.height;
    this.speed = options.speed;
    this.canvas = options.canvas;
    this.colors = options.colors || this.colors;
    this.blocks = options.blocks || this.blocks;
    this.table = [];
    this.block = null;
    this.nextBlock = null;
    this.x = 0;
    this.y = 0;
    this.id = 0;
    this.rotate = 0;
    this.beginTime = 0;
}

Tetris.prototype = {
    colors: ["white", "lightgray", "red", "yellow", "pink", "lightgreen", "blue", "orange", "lightblue"],
    initTable: function () {
        var x, y;
        for (y = 0; y < this.height; y++) {
            this.table[y] = [];
            for (x = 0; x < this.width; x++) {
                this.table[y][x] = 0;
            }
        }
        for (y = 0; y < this.height; y++) {
            this.table[y][0] = 1;
            this.table[y][this.width - 1] = 1;
        }
        for (x = 0; x < this.width; x++) {
            this.table[0][x] = 1;
            this.table[this.height - 1][x] = 1;
        }
    },
    blocks: [
        [
            [
                [2, 2, 2, 2],
                [0, 0, 0, 0],
                [0, 0, 0, 0],
                [0, 0, 0, 0]
            ],
            [
                [0, 0, 0, 2],
                [0, 0, 0, 2],
                [0, 0, 0, 2],
                [0, 0, 0, 2]
            ],
            [
                [0, 0, 0, 0],
                [0, 0, 0, 0],
                [2, 2, 2, 2],
                [0, 0, 0, 0]
            ],
            [
                [2, 0, 0, 0],
                [2, 0, 0, 0],
                [2, 0, 0, 0],
                [2, 0, 0, 0]
            ], ],

        [
            [
                [3, 3],
                [3, 3]
            ]
        ],

        [
            [
                [0, 4, 4],
                [4, 4, 0],
                [0, 0, 0]
            ],
            [
                [0, 4, 0],
                [0, 4, 4],
                [0, 0, 4]
            ],
            [
                [0, 0, 0],
                [0, 4, 4],
                [4, 4, 0]
            ],
            [
                [4, 0, 0],
                [4, 4, 0],
                [0, 4, 0]
            ]
        ],


        [
            [
                [5, 5, 0],
                [0, 5, 5],
                [0, 0, 0]
            ],
            [
                [0, 0, 5],
                [0, 5, 5],
                [0, 5, 0]
            ],
            [
                [0, 0, 0],
                [5, 5, 0],
                [0, 5, 5]
            ],
            [
                [0, 5, 0],
                [5, 5, 0],
                [5, 0, 0]
            ]
        ],


        [
            [
                [6, 0, 0],
                [6, 6, 6],
                [0, 0, 0]
            ],
            [
                [0, 6, 6],
                [0, 6, 0],
                [0, 6, 0]
            ],
            [
                [0, 0, 0],
                [6, 6, 6],
                [0, 0, 6]
            ],
            [
                [0, 6, 0],
                [0, 6, 0],
                [6, 6, 0]
            ]
        ],


        [
            [
                [0, 0, 7],
                [7, 7, 7],
                [0, 0, 0]
            ],
            [
                [0, 7, 0],
                [0, 7, 0],
                [0, 7, 7]
            ],
            [
                [0, 0, 0],
                [7, 7, 7],
                [7, 0, 0]
            ],
            [
                [7, 7, 0],
                [0, 7, 0],
                [0, 7, 0]
            ]
        ],


        [
            [
                [0, 8, 0],
                [8, 8, 8],
                [0, 0, 0]
            ],
            [
                [0, 8, 0],
                [0, 8, 8],
                [0, 8, 0]
            ],
            [
                [0, 0, 0],
                [8, 8, 8],
                [0, 8, 0]
            ],
            [
                [0, 8, 0],
                [8, 8, 0],
                [0, 8, 0]
            ]
        ]
    ],

    createNewBlock: function () {
        var block = this.blocks[Math.floor(Math.random() * this.blocks.length)];
        block = block.concat();
        return block;
    },
    setNewBlock: function () {
        var block = this.nextBlock;
        var x = Math.floor((this.width - block[0].length) / 2),
            y = 1;
        var rotate = 0;
        if (this.canPutIn(block[rotate], x, y)) {
            this.nextBlock = this.createNewBlock();
            this.block = block;
            this.x = x
            this.y = y;
            this.rotate = rotate;
            this.updateView();
        } else {
            this.gameOver();
        }
    },
    initTimer: function () {
        var self = this;
        this.id = setInterval(function () {
            if (!self.isFixed()) {
                self.moveBlockBy(0, 1);
            }
            if (self.isFixed()) {
                self.enter();
                self.setNewBlock();
            }
        },
        this.speed);
    },
    clearTimer: function () {
        clearInterval(this.id);
    },
    canPutIn: function (block, x, y) {
        var ax, ay;
        for (ay = 0; ay < block.length; ay++) {
            for (ax = 0; ax < block[ay].length; ax++) {
                if (block[ay][ax] != 0 && this.table[ay + y][ax + x] != 0) {
                    return false;
                }
            }
        }
        return true;
    },
    enter: function () {
        var table = this.table;
        var block = this.block[this.rotate];
        var x, y;
        for (y = 0; y < block.length; y++) {
            for (x = 0; x < block[y].length; x++) {
                if (block[y][x] != 0) {
                    table[this.y + y][this.x + x] = block[y][x];
                }
            }
        }
        this.removeCompleted();
    },
    isFixed: function () {
        var block = this.block[this.rotate];
        var table = this.table;
        var x, y;
        for (y = 0; y < block.length; y++) {
            for (x = 0; x < block[y].length; x++) {
                if (block[y][x] != 0 && table[y + this.y + 1][x + this.x] != 0) {
                    return true;
                }
            }
        }
        return false;
    },
    removeCompleted: function () {
        var x, y;
        var empty_line = [1];
        for (x = 1; x < this.width - 1; x++) {
            empty_line[x] = 0;
        }
        empty_line[this.width - 1] = 1;
        for (y = 1; y < this.height - 1; y++) {
            if (this.table[y].indexOf(0) == -1) {
                this.table.splice(y, 1);
                this.table.splice(1, 0, empty_line.concat());
            }
        }

    },
    moveBlockBy: function (x, y) {
        if (this.canPutIn(this.block[this.rotate], this.x + x, this.y + y)) {
            this.x += x;
            this.y += y;
            this.updateView();
        }
    },
    hardDrop: function () {
        while (this.canPutIn(this.block[this.rotate], this.x, this.y + 1)) {
            this.moveBlockBy(0, 1);
        }
    },
    leftRotateBlock: function () {
        var block = this.block[this.rotate];
        var rotate = (this.rotate == 0 ? this.block.length : this.rotate) - 1;
        if (this.canPutIn(this.block[rotate], this.x, this.y)) {
            this.rotate = rotate;
            this.updateView();
        }
    },
    rightRotateBlock: function () {
        var block = this.block[this.rotate];
        var rotate = (this.rotate + 1) % this.block.length;
        if (this.canPutIn(this.block[rotate], this.x, this.y)) {
            this.rotate = rotate;
            this.updateView();
        }
    },
    updateView: function () {
        var canvas = this.canvas;
        var context = canvas.getContext("2d");
        var block = this.block[this.rotate];
        var w_scale = canvas.width / this.width;
        var h_scale = canvas.height / this.height;
        var x, y;
        context.clearRect(0, 0, canvas.width, canvas.height);
        for (y = 0; y < this.height; y++) {
            for (x = 0; x < this.width; x++) {
                context.fillStyle = this.colors[this.table[y][x]];
                context.fillRect(x * w_scale, y * h_scale, w_scale, h_scale);
            }
        }
        for (y = 0; y < block.length; y++) {
            for (x = 0; x < block[y].length; x++) {
                if (block[y][x] != 0) {
                    context.fillStyle = this.colors[block[y][x]];
                    context.fillRect((x + this.x) * w_scale, (y + this.y) * h_scale, w_scale, h_scale);
                }
            }
        }
        block = this.nextBlock[0];
        for (y = 0; y < block.length; y++) {
          for (x = 0; x < block[y].length; x++) {
            if (block[y][x] != 0) {
              context.fillStyle = this.colors[block[y][x]];
              break;
            }
          }
        }
        context.fillRect(0, 0, w_scale, h_scale);
    },
    gameOver: function () {
        this.clearTimer();
        this.clearHandle()
        var canvas = this.canvas;
        var context = canvas.getContext("2d");
        context.fillStyle = "rgba(255,0,0, 0.3)";
        context.fillRect(0, 0, canvas.width, canvas.height);;
    },
    handleEvent: function (event) {
        var ignore_default = true;
        switch (true) {
        case event.keyCode == 37:
            // left
            this.moveBlockBy(-1, 0);
            break;
        case event.keyCode == 38:
            // up
            this.hardDrop();
            break;
        case event.keyCode == 39:
            // right
            this.moveBlockBy(1, 0);
            break;
        case event.keyCode == 40:
            // bottom
            this.moveBlockBy(0, 1);
            break;
        case String.fromCharCode(event.which).toLowerCase() == "x":
            // x 
            this.leftRotateBlock();
            break;
        case String.fromCharCode(event.which).toLowerCase() == "z":
            // z
            this.rightRotateBlock();
            break;
        default:
            ignore_default = false;
            break;

        }
        if (ignore_default) {
            event.preventDefault();
        }
    },
    handle: function () {
        var type = navigator.userAgent.indexOf("WebKit") >= 0 ? "keydown" : "keypress";
        document.addEventListener(type, this, false);
    },
    clearHandle: function () {
        var type = navigator.userAgent.indexOf("WebKit") >= 0 ? "keydown" : "keypress";
        document.removeEventListener(type, this, false);
    },
    run: function () {
        this.initTable();
        this.nextBlock = this.createNewBlock();
        this.beginTime = Date.now();
        this.setNewBlock();
        this.initTimer();
        this.handle();
        this.updateView();
    }
};

document.addEventListener("DOMContentLoaded", function () {
    var opts = [{
        width: 12,
        height: 22,
        speed: 1000,
        canvas: document.getElementById("view")
    },
    {
        width: 12,
        height: 22,
        speed: 10,
        blocks: [
            [
                [[1]]
            ]
        ],
        canvas: document.getElementById("view")
    }];
    var i = 0;
    var start = document.getElementById("start");
    var checkbox = document.getElementById("checkbox");
    var tetris = null;
    start.addEventListener("click", function (e) {
        if (tetris) {
            tetris.gameOver();
        }
        tetris = new Tetris(opts[checkbox.checked - 0]);
        tetris.run();
    },
    false);
},
false);