// ----------------------------------------------------------------------------
// tetris.js
// DOM Tetris
// Version 2.0
// Copyright (C) 2005-2007 Taku Watabe
// Time-stamp: <2007-09-09T00:10:07+09:00>
// ----------------------------------------------------------------------------
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// ----------------------------------------------------------------------------
// 意見・バグ報告は taku.eof@gmail.com まで。
// Subject に必ず「DOM Tetris」という文字列を含めてください。
// 存在しない場合は単にメールを無視します。
// ----------------------------------------------------------------------------
// Needed library:
//   * Prototype 1.5.0 Later
//   * Prototype+ 1.5 Later
// ----------------------------------------------------------------------------
// Execute checked UA:
//   * Mozilla Firefox 2.0.3 (Gecko/20070309 rv:1.8.3)
//   * Opera 9.10 (Build 8679)
//   * Microsoft Internet Explorer 7.0
//   * Microsoft Internet Explorer 6.0
// ----------------------------------------------------------------------------
// Objects:
//   木構造の親は継承元、子は継承先
//
//   Array (配列オブジェクト)
//     +- TetrisObject (基礎オブジェクト)
//          +- TetrisEvent (イベントコントローラ)
//          |    +- Tetris (テトリスゲーム)
//          +- TetrisBlock (ブロック)
//          +- TetrisDOMElement (XHTML 要素と W3C DOM インタフェイスによる描画)
//               +- TetrisElement (描画単位オブジェクト)
//                    +- TetrisTime (時間管理)
//                    +- TetrisScore (スコア管理)
//                    +- TetrisMessage (メッセージ管理)
//                    +- TetrisField (描画オブジェクト2次元配列)
//                         +- TetrisGameField (ゲーム領域)
//                         +- TetrisNextField (次ブロック領域)
// ----------------------------------------------------------------------------
// Todo:
//   * canvas 要素を用いた描画を行わせるモードを追加 (canvas mode)
//     TetrisField の継承を変えればいける？
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// TetrisObject
// 基礎オブジェクト
// 主に定数の管理を行う
// 継承：Array
// ----------------------------------------------------------------------------
function TetrisObject() {
    Array.apply(this);
} TetrisObject.inherit(Array);

// イベント
TetrisObject.prototype.EVENT = {
    KEYBIND : $H({
        // call:    呼出メソッド
        // repeat:  キーリピート有無
        // inteval: キーリピートのインターバル
        g     : { call : "switchDrawGhost", repeat : false, interval : 0 },
        r     : { call : "resetBlock", repeat : false, interval :   0 },
        e     : { call : "moveUp",     repeat : true,  interval : 100 },
        up    : { call : "moveUp",     repeat : true,  interval : 100 },
        s     : { call : "moveLeft",   repeat : true,  interval : 100 },
        left  : { call : "moveLeft",   repeat : true,  interval : 100 },
        f     : { call : "moveRight",  repeat : true,  interval : 100 },
        right : { call : "moveRight",  repeat : true,  interval : 100 },
        d     : { call : "moveDown",   repeat : true,  interval : 100 },
        down  : { call : "moveDown",   repeat : true,  interval : 100 },
        space : { call : "fallDown",   repeat : false, interval :   0 },
        j     : { call : "rollCCW",    repeat : false, interval :   0 },
        k     : { call : "rollCW",     repeat : false, interval :   0 },
        enter : { call : "start",      repeat : false, interval :   0 },
        p     : { call : "pause",      repeat : false, interval :   0 },
        h     : { call : "help",       repeat : false, interval :   0 },
        y     : { call : "yes",        repeat : false, interval :   0 },
        n     : { call : "no",         repeat : false, interval :   0 }
    })
};

// 描画要素
TetrisObject.prototype.ELEMENT = {
    GHOST_TYPE : "ghost" // ゴースト描画種類
};

// 時間管理
TetrisObject.prototype.TIME = {
    ID        : "tetris-time",
    PARENT_ID : "tetris",
    INTERVAL  : 10
};

// スコア管理
TetrisObject.prototype.SCORE = {
    ID        : "tetris-score",
    PARENT_ID : "tetris"
};

// メッセージ管理
TetrisObject.prototype.MESSAGE = {
    ID        : "tetris-message",
    PARENT_ID : "tetris"
};

// ゲーム領域
TetrisObject.prototype.GAME = {
    ID        : "tetris-game_field",
    PARENT_ID : "tetris",
    WIDTH     : 10,
    HEIGHT    : 20
};

// 次ブロック表示領域
TetrisObject.prototype.NEXT = {
    ID        : "tetris-next_field",
    PARENT_ID : "tetris",
    WIDTH     : 4,
    HEIGHT    : 4
};

// ブロック
TetrisObject.prototype.BLOCK = {
    WIDTH  : 4, // 最大幅
    HEIGHT : 4, // 最大長
    SHAPES : [  // 形状データ
        // [[{x:0,y:0},...],...]
        //   配列1次 ：回転情報 （単位 1/2 ラジアン）
        //   配列2次 ：ブロック情報
        //   連想配列：セル位置情報（x座標並びにy座標）
        { // I
            TYPE : "tetris-block-I",
            DATA : [[{x:1,y:0},{x:1,y:1},{x:1,y:2},{x:1,y:3}],
                    [{x:0,y:1},{x:1,y:1},{x:2,y:1},{x:3,y:1}],
                    [{x:2,y:0},{x:2,y:1},{x:2,y:2},{x:2,y:3}],
                    [{x:0,y:2},{x:1,y:2},{x:2,y:2},{x:3,y:2}]]
        },
        { // J
            TYPE : "tetris-block-J",
            DATA : [[{x:1,y:0},{x:1,y:1},{x:1,y:2},{x:0,y:2}],
                    [{x:2,y:1},{x:1,y:1},{x:0,y:1},{x:0,y:0}],
                    [{x:1,y:2},{x:1,y:1},{x:1,y:0},{x:2,y:0}],
                    [{x:0,y:1},{x:1,y:1},{x:2,y:1},{x:2,y:2}]]
        },
        { // L
            TYPE : "tetris-block-L",
            DATA : [[{x:1,y:0},{x:1,y:1},{x:1,y:2},{x:2,y:2}],
                    [{x:2,y:1},{x:1,y:1},{x:0,y:1},{x:0,y:2}],
                    [{x:1,y:2},{x:1,y:1},{x:1,y:0},{x:0,y:0}],
                    [{x:0,y:1},{x:1,y:1},{x:2,y:1},{x:2,y:0}]]
        },
        { // O
            TYPE : "tetris-block-O",
            DATA : [[{x:0,y:0},{x:1,y:0},{x:1,y:1},{x:0,y:1}]]
        },
        { // S
            TYPE : "tetris-block-S",
            DATA : [[{x:2,y:0},{x:1,y:0},{x:1,y:1},{x:0,y:1}],
                    [{x:2,y:2},{x:2,y:1},{x:1,y:1},{x:1,y:0}],
                    [{x:0,y:2},{x:1,y:2},{x:1,y:1},{x:2,y:1}],
                    [{x:0,y:0},{x:0,y:1},{x:1,y:1},{x:1,y:2}]]
        },
        { // T
            TYPE : "tetris-block-T",
            DATA : [[{x:0,y:1},{x:1,y:1},{x:2,y:1},{x:1,y:2}],
                    [{x:1,y:0},{x:1,y:1},{x:1,y:2},{x:0,y:1}],
                    [{x:2,y:1},{x:1,y:1},{x:0,y:1},{x:1,y:0}],
                    [{x:1,y:2},{x:1,y:1},{x:1,y:0},{x:2,y:1}]]
        },
        { // Z
            TYPE : "tetris-block-Z",
            DATA : [[{x:0,y:0},{x:1,y:0},{x:1,y:1},{x:2,y:1}],
                    [{x:2,y:0},{x:2,y:1},{x:1,y:1},{x:1,y:2}],
                    [{x:2,y:2},{x:1,y:2},{x:1,y:1},{x:0,y:1}],
                    [{x:0,y:2},{x:0,y:1},{x:1,y:1},{x:1,y:0}]]
        }
    ]
};


// ----------------------------------------------------------------------------
// TetrisEvent
// イベントコントローラ
// 継承：TetrisObject
// ----------------------------------------------------------------------------
function TetrisEvent() {
    TetrisObject.apply(this);
    this.executer = {}; // メソッド別インターバルイベントオブジェクト

    this.initExecuter();
} TetrisEvent.inherit(TetrisObject);

// executer 初期化
TetrisEvent.prototype.initExecuter = function() {
    // KEYBIND の key を連想配列のキーとする
    // キーひとつに対してひとつの TimerExecuter を生成する
    this.EVENT.KEYBIND.keys().each((function(key) { // キー抽出
        // 変数 key をクロージャで包まないと動作しない
        //   -> for-in ではダメ
        this.executer[key] = new TimerExecuter(    // 生成
            (function (event) {
                this[this.EVENT.KEYBIND[key].call](event); // メソッド起動
            }).bind(this),
            this.EVENT.KEYBIND[key].interval,   // インターバル
            this.EVENT.KEYBIND[key].repeat      // リピート有無
        );
    }).bind(this));
};

// 生成
TetrisEvent.prototype.create = function() {
    var observer = this.observer.bindAsEventListener(this);
    Event.observe(document, "keydown", observer, false);
    Event.observe(document, "keyup", observer, false);
};

// オブザーバ（イベント別動作へのクッション）
TetrisEvent.prototype.observer = function(event) {
    // 対象のイベント別メソッドがないなら何もしない
    if (this[event.type] === undefined) { return; }
    // 対象のイベント別メソッドを起動
    this[event.type](event);
};

// 対象のイベント別メソッド: keydown
TetrisEvent.prototype.keydown = function(event) {
    var key = Event.getKey(event); // キー
    // 対応インターバルイベントオブジェクトなし
    if (this.executer[key] === undefined) { return; }
    this.executer[key].start(event);
};

// 対象のイベント別メソッド: keyup
TetrisEvent.prototype.keyup = function(event) {
    var key = Event.getKey(event); // キー
    // 対応インターバルイベントオブジェクトなし
    if (this.executer[key] === undefined) { return; }
    this.executer[key].stop();
};


// ----------------------------------------------------------------------------
// TetrisDOMElement
// XHTML 要素と W3C DOM インタフェイスによる描画
// 継承：TetrisObject
// ----------------------------------------------------------------------------
function TetrisDOMElement(id, parentId) {
    TetrisObject.apply(this);
    this.isDraw   = false;                            // 描画フラグ
    this.isBlock  = false;                            // ブロックフラグ
    this.isGhost  = false;                            // ゴーストフラグ
    this.id       = id;                               // 描画要素 ID
    this.parentId = null;                             // 親要素 ID
    this.types    = ["tetris-element"];               // 描画種類
    this.obj      = $(document.createElement("div")); // DOM 要素ノード
    this.textNode = document.createTextNode("");      // テキストノード

    this.obj.appendChild(this.textNode);
    this.obj.setAttribute("id", this.id);
    this.addTypes.apply(this, this.types);
    this.setParent(parentId);
} TetrisDOMElement.inherit(TetrisObject);

// 親要素を設定・親要素に追加
TetrisDOMElement.prototype.setParent = function(id) {
    if (typeof id != "string") { return; }
    this.parentId = id;
    $(this.parentId).appendChild(this.obj);
};

// 指定描画種類追加・削除
TetrisDOMElement.prototype.addTypes = function(/* types */) {
    var length = arguments.length;
    if (length <= 0) { return; } // 追加なしなら何もしない
    for (var i = 0; i < length; i++) {
        this.obj.addClassName(arguments[i]);
    }
    // push でもよいが、ネイティブでやった方がいいと思われるため
    this.types = this.types.concat(arguments);
};
TetrisDOMElement.prototype.removeTypes = function(/* types */) {
    var length = arguments.length;
    if (length <= 0) { return; } // 削除なしなら何もしない
    for (var i = 0; i < length; i++) {
        this.types.without(arguments[i]);
        this.obj.removeClassName(arguments[i]);
    }
};

// 描画・削除
TetrisDOMElement.prototype.draw = function(/* types */) {
    this.isDraw = true;
    this.addTypes.apply(this, arguments);
};
TetrisDOMElement.prototype.undraw = function(/* types */) {
    this.isDraw = false;
    this.removeTypes.apply(this, arguments);
};

// ブロック描画・削除
TetrisDOMElement.prototype.drawBlock = function(/* types */) {
    this.isBlock = true;
    this.draw.apply(this, arguments);
};
TetrisDOMElement.prototype.undrawBlock = function(/* types */) {
    this.isBlock = false
    this.undraw.apply(this, arguments);
};

// ゴースト描画・削除
TetrisDOMElement.prototype.drawGhost = function(/* types */) {
    if (this.isBlock) { return; }
    this.isGhost = true;
    this.draw.apply(this, $A(arguments).concat(this.ELEMENT.GHOST_TYPE));
};
TetrisDOMElement.prototype.undrawGhost = function(/* types */) {
    this.isGhost = false;
    this.undraw.apply(this, $A(arguments).concat(this.ELEMENT.GHOST_TYPE));
};

// テキストを設定
TetrisDOMElement.prototype.setText = function(text) {
    this.textNode.nodeValue = text;
};

// to と自身の属性を swap
TetrisDOMElement.prototype.swap = function(to) {
    var drawTypes    = this.drawTypes;
    var elementTypes = this.elementTypes;
    var isDraw       = this.isDraw;
    // to -> for
    this.drawTypes    = to.drawTypes
    this.elementTypes = to.elementTypes;
    this.isDraw       = to.isDraw;
    // for -> to
    to.drawTypes    = drawTypes;
    to.elementTypes = elementTypes;
    to.isDraw       = isDraw;
};


// ----------------------------------------------------------------------------
// TetrisElement
// 描画単位オブジェクト
// 継承：TetrisDOMElement
// ----------------------------------------------------------------------------
function TetrisElement(/* arguments */) {
    TetrisDOMElement.apply(this, arguments);
    this.isFixed = false; // 位置固定フラグ
} TetrisElement.inherit(TetrisDOMElement);

// 固定状態のスイッチ
TetrisElement.prototype.fix   = function() { this.isFixed = true; };  // 固定
TetrisElement.prototype.unfix = function() { this.isFixed = false; }; // 解放

// to と自身の属性を swap する
TetrisElement.prototype.swap = function(to) {
    TetrisDOMElement.prototype.swap.apply(this, [to]);
    var isFixed = this.isFixed;
    // to -> for
    this.isFixed = to.isFixed;
    // for -> to
    to.isFixed = isFixed;
};


// ----------------------------------------------------------------------------
// TetrisField
// 描画オブジェクト2次元配列
// 継承：TetrisElement
// ----------------------------------------------------------------------------
function TetrisField(width, height /* TetrisElement arguments */) {
    TetrisElement.apply(this, $A(arguments).selectItem(2));
    this.width  = width;  // セル領域横幅
    this.height = height; // セル領域縦幅
} TetrisField.inherit(TetrisElement);

// セル生成
TetrisField.prototype.create = function() {
    for (var y = 0; y < this.height; y++) {
        for (var x = 0; x < this.width; x++) {
            // 初回だけ Array を生成
            if (y <= 0) { this[x] = []; }
            this[x][y] = new TetrisElement(this._getElementId(x, y), this.id);
            this[x][y].draw("tetris-cell");
        }
    }
};

// 要素の ID を生成
TetrisField.prototype._getElementId = function(x, y) {
    return this.id+"-"+x+"-"+y; // ID 末尾に座標を付与
};

// 行入替
TetrisField.prototype.swapLine = function(before, after) {
    for (var x = 0; x < this.width; x++) {
        this[x][before].swap(this[x][after]);
    }
};

// 行削除
TetrisField.prototype.undrawLine = function(line) {
    for (var x = 0; x < this.width; x++) {
        this[x][line].undraw();
    }
};


// ----------------------------------------------------------------------------
// TetrisTime
// 時間管理
// 継承：TetrisElement
// ----------------------------------------------------------------------------
function TetrisTime() {
    TetrisElement.apply(this, [this.TIME.ID, this.TIME.PARENT_ID]);
    this.date           = null; // Date オブジェクト
    this.executer       = null; // TetrisExecuter オブジェクト
    this.bindedFunction = null; // タイマイベントにバインドする関数
} TetrisTime.inherit(TetrisElement);

// タイマを生成する
// 但し start は行わない
TetrisTime.prototype.create = function() {
    this.date     = new Date(0);
    this.executer = new TimerExecuter(this.callback.bind(this),
                                      this.TIME.INTERVAL);
    this.setText(this.toString());
    this.draw();
};

// タイマ動作時のコールバック関数
TetrisTime.prototype.callback = function() {
    this.date.setTime(this.date.getTime()+this.TIME.INTERVAL);
    this.setText(this.toString());
    if (typeof this.bindedFunction == "function") {
        this.bindedFunction(this.date);
    }
};

// 引数として与えられた Function オブジェクトを動作にバインドする
TetrisTime.prototype.bind = function(f) {
    if (typeof f != "function") { return; }
    this.bindedFunction = f;
};

// 整形済時間取得
// Date オブジェクトの仕様により、最大表示は 23:59:59
// Milliseconds は インターバルに依存
TetrisTime.prototype.toString = function() {
    var hour = this.date.getUTCHours();
    var min  = this.date.getUTCMinutes();
    var sec  = this.date.getUTCSeconds();
    var msec = this.date.getUTCMilliseconds();
    hour = (hour < 10) ? "0"+hour : hour;
    min  = (min < 10) ? "0"+min : min;
    sec  = (sec < 10) ? "0"+sec : sec;
    msec = (msec < 10) ? "00"+msec : (msec < 100) ? "0"+msec : msec;
    return hour+":"+min+":"+sec+":"+msec;
};

// 動作制御
TetrisTime.prototype.start = function() { this.executer.start(); }; // 開始
TetrisTime.prototype.pause = function() { this.executer.pause(); }; // 一時停止
TetrisTime.prototype.stop  = function() { this.executer.stop(); };  // 停止


// ----------------------------------------------------------------------------
// TetrisScore
// スコア管理
// 継承 - TetrisElement
// ----------------------------------------------------------------------------
function TetrisScore() {
    TetrisElement.apply(this, [this.SCORE.ID, this.SCORE.PARENT_ID]);
} TetrisScore.inherit(TetrisElement);

// 生成
TetrisScore.prototype.create = function() {
    this.reset();
    this.draw();
};

// リセット
TetrisScore.prototype.reset = function() {
    this.point = 0;
    this.setText(this.point);
};

// 加算
TetrisScore.prototype.plus = function(type) {
    // type に指定された倍数分
    this.point += this.point * this.SCORE.ACCEL[type];
};


// ----------------------------------------------------------------------------
// TetrisGameField
// ゲーム領域
// 継承：TetrisField
// ----------------------------------------------------------------------------
function TetrisGameField() {
    TetrisField.apply(this, [this.GAME.WIDTH, this.GAME.HEIGHT,
                             this.GAME.ID, this.GAME.PARENT_ID]);
} TetrisGameField.inherit(TetrisField);


// ----------------------------------------------------------------------------
// TetrisNextField
// 次ブロック領域
// 継承：TetrisField
// ----------------------------------------------------------------------------
function TetrisNextField() {
    TetrisField.apply(this, [this.NEXT.WIDTH, this.NEXT.HEIGHT,
                             this.NEXT.ID, this.NEXT.PARENT_ID]);
} TetrisNextField.inherit(TetrisField);


// ----------------------------------------------------------------------------
// TetrisBlock
// ブロック
// 継承 - TetrisObject
// ----------------------------------------------------------------------------
function TetrisBlock(field) {
    TetrisObject.apply(this);
    this.shape;                             // 形状データ
    this.field;                             // フィールド空間
    this.position        = {x:0, y:0, r:0}; // ブロック空間の原点座標
    this.contactPosition = {x:0, y:0, r:0}; // フィールド上での接地予想座標
    this.firstPosition   = {x:0, y:0, r:0}; // フィールド初期位置
    this.isDrawGhost     = false;           // ゴースト描画フラグ
    this.contacts;                          // fixed 位置キャッシュ
    this.firstContacts;                     // 接地予想座標キャッシュ

    this.setField(field);
} TetrisBlock.inherit(TetrisObject);

// 生成
TetrisBlock.prototype.create = function(field) {
    this.setField(field);
    this.setFirstPosition();
    this.reset();
};

// ブロック情報リセット
TetrisBlock.prototype.reset = function() {
    this.setShape();
    this.setContacts();
    this.moveAbs(this.firstPosition.x, this.firstPosition.y);
};

// フィールド情報の変更
TetrisBlock.prototype.setField = function(field) {
    if (typeof field != "object") { return; }
    this.field = field;
};

// フィールド情報から初期位置を算出
TetrisBlock.prototype.setFirstPosition = function() {
    this.firstPosition.x = Math.floor(this.field.width/2-this.BLOCK.WIDTH/2);
};

// 形状・回転状態の取得（乱数による分散）
TetrisBlock.prototype.setShape = function() {
    // Todo:
    // 乱数じゃなくて「嫌らしい出し方」ができるようになりたい
    // テトリス狙いだとわかれば縦棒を出し惜しむ、とか
    // AI 的な判断が必須？
    this.shape = this.BLOCK.SHAPES[ // 形状
        Math.floor(Math.random() * (this.BLOCK.SHAPES.length-1))
    ];
    // 回転状態
    var index = Math.floor(Math.random() * this.shape.DATA.length);
    this.shape.DATA.initIndex();
    this.shape.DATA.setIndex(index);
    this.position.r = index;

    // debug only
    this.shape = this.BLOCK.SHAPES[4];
    this.shape.DATA.setIndex(0);
    this.position.r = 0;
};

// 既固定ブロックないしフィールド端に接するかチェック（キャッシュ版）
// バグあり
//   px : x 方向絶対座標
//   py : y 方向絶対座標
//   pr : 回転方向絶対移動量
TetrisBlock.prototype._isContact = function(px, py, pr) {
    try {
        return this.contacts[px][py][pr]; // フィールド内
    } catch (e) {
        // NONE
    }
    return true;
};

// 既固定ブロックないしフィールド端に接するかチェック（未キャッシュ版）
//   px : x 方向絶対座標
//   py : y 方向絶対座標
//   pr : 回転方向絶対移動量
TetrisBlock.prototype.isContact = function(px, py, pr) {
    var status = false;
    var pos    = this.shape.DATA.getItem(pr);
    var length = pos.length;
    for (var i = 0; i < length; i++) {
        if (this.field[pos[i].x+px]              === undefined ||
            this.field[pos[i].x+px][pos[i].y+py] === undefined ||
            this.field[pos[i].x+px][pos[i].y+py].isFixed) {
            status = true;
            break;
        }
    }
    return status;
};

// 接地予想座標を全調査
TetrisBlock.prototype.setContacts = function() {
    var nx = this.field.width;       // x 軸
    var ny = this.field.height;      // y 軸
    var nr = this.shape.DATA.length; // 回転単位数

    // アルゴリズムは線形探索
    // 二分探索だと列中間に固定ブロックがあるパターンに対応できない
    this.contacts = new Array(nx);
    for (var x = 0; x < nx; x++) {         // x 方向
        this.contacts[x] = new Array(ny);
        for (var y = 0; y < ny; y++) {     // y 方向
            this.contacts[x][y] = new Array(nr);
            for (var r = 0; r < nr; r++) { // r 方向
                this.contacts[x][y][r] = this._isContact(x, y, r);
            }
        }
    }
};

// 現状のまま落下した場合接地する予想位置
TetrisBlock.prototype.getContactPosition = function() {
    this.contactPosition.x = this.position.x;
    this.contactPosition.y = this.position.y;
    this.contactPosition.r = this.position.r;
    // 現在位置から落下方向（y 軸加算）に線形検索
    for (var y = this.position.y; y < this.field.height; y++) {
        // いちばん y 座標が小さいもの（＝初ヒット）をセレクト
        if (this.isContact(this.position.x, y, this.position.r)) {
            this.contactPosition.y = y-1;
            break;
        }
    }
    return this.contactPosition;
};

// 絶対座標算出
TetrisBlock.prototype.getAbsolutePosition = function(rx, ry, rr) {
    var abs = {x:0, y:0, r:0};
    abs.x = this.position.x + rx;
    abs.y = this.position.y + ry;
    abs.r = this.shape.DATA.getIndex(this.shape.DATA.getIndex()+rr);
    return abs;
};

// 相対位置移動
//   rx : x 方向相対移動量
//   ry : y 方向相対移動量
//   rr : 回転方向相対移動量
TetrisBlock.prototype.move = function(rx, ry, rr) {
    var pos = this.getAbsolutePosition(rx, ry, rr);
    // 衝突解析
    dump("x:"+pos.x+" y:"+pos.y+" r:"+pos.r+"\n");
    dump("contact: "+this.isContact(pos.x, pos.y, pos.r)+"\n");
    if (this.isContact(pos.x, pos.y, pos.r)) { return; } // 移動不可
    // 問題なし -> 座標移動・回転・再描画
    this.undraw();
    this.position.x = pos.x;
    this.position.y = pos.y;
    this.position.r = pos.r;
    this.shape.DATA.setIndex(pos.r);
    this.draw();
};

// 絶対位置移動
//   px : x 方向絶対座標
//   py : y 方向絶対座標
//   pr : 回転方向絶対移動量（双方向連結循環リストなので、相対と同義）
TetrisBlock.prototype.moveAbs = function(px, py, pr) {
    return this.move(px-this.position.x, py-this.position.y, pr);
};

// 位置固定・解除
TetrisBlock.prototype.fix   = function() { this._fixEngine(true); };
TetrisBlock.prototype.unfix = function() { this._fixEngine(false); };
TetrisBlock.prototype._fixEngine = function(type) { // 実作業ルーチン
    var fixType = type ? "fix" : "unfix";
    var pos     = this.shape.DATA.getItem();
    var length  = pos.length;
    for (var i = 0; i < length; i++) {
        this.field[pos[i].x+this.position.x]
                  [pos[i].y+this.position.y]
                  [fixType]();
    }
};

// セル上に描画・削除
TetrisBlock.prototype.draw   = function() { this._drawEngine(true); };
TetrisBlock.prototype.undraw = function() { this._drawEngine(false); };
TetrisBlock.prototype._drawEngine = function(type) { // 実作業ルーチン
    var drawType = type ? "draw" : "undraw";
    var pos      = this.shape.DATA.getItem();
    var length   = pos.length;
    this.getContactPosition();
    for (var i = 0; i < length; i++) {
        this.field[pos[i].x+this.position.x]
                  [pos[i].y+this.position.y]
                  [drawType+"Block"](this.shape.TYPE);
    }
    // ゴースト描画
    if (this.isDrawGhost) {
        for (var i = 0; i < length; i++) {
            this.field[pos[i].x+this.position.x]
                      [pos[i].y+this.position.y]
                      [drawType+"Ghost"](this.shape.TYPE);
        }
    }
};

// ゴースト描画 start/stop/switch
TetrisBlock.prototype.startDrawGhost = function() {
    this._switchDrawGhost(true);
};
TetrisBlock.prototype.stopDrawGhost = function() {
    this._switchDrawGhost(false);
};
TetrisBlock.prototype.switchDrawGhost = function() {
    this._switchDrawGhost(!this.isDrawGhost);
};
TetrisBlock.prototype._switchDrawGhost = function(type) {
    if (this.isDrawGhost === type) { return; } // 現状と同じなら何もしない
    this.undraw();
    this.isDrawGhost = type;
    this.draw();
};

// 各種移動
TetrisBlock.prototype.rollCW    = function() { this.move( 0,  0,  1); };
TetrisBlock.prototype.rollCCW   = function() { this.move( 0,  0, -1); };
TetrisBlock.prototype.moveUp    = function() { this.move( 0, -1); };
TetrisBlock.prototype.moveDown  = function() { this.move( 0,  1); };
TetrisBlock.prototype.moveLeft  = function() { this.move(-1,  0); };
TetrisBlock.prototype.moveRight = function() { this.move( 1,  0); };

// 瞬時接地
TetrisBlock.prototype.fallDown = function() {
    this.moveAbs(this.contactPosition.x,
                 this.contactPosition.y,
                 this.contactPosition.r);
};


// ----------------------------------------------------------------------------
// Tetris
// テトリスゲーム
// 継承：TetrisEvent
// ----------------------------------------------------------------------------
function Tetris() {
    TetrisEvent.apply(this);
    this.gameField  = new TetrisGameField();
    this.nextField  = new TetrisNextField();
    this.block      = new TetrisBlock();
    this.nextBlock  = new TetrisBlock();
    this.score      = new TetrisScore();
    this.time       = new TetrisTime();
} Tetris.inherit(TetrisEvent);

// 生成
Tetris.prototype.create = function () {
    TetrisEvent.prototype.create.apply(this);
    this.gameField.create();
    this.nextField.create();
    this.block.create(this.gameField);
    this.block.startDrawGhost();
    this.nextBlock.create(this.nextField);
    this.time.create();
};

Tetris.prototype.switchDrawGhost = function(event) {
    this.block.switchDrawGhost();
};
Tetris.prototype.resetBlock = function(event) { this.block.reset(); };
Tetris.prototype.moveUp     = function(event) { this.block.moveUp(); };
Tetris.prototype.moveLeft   = function(event) { this.block.moveLeft(); };
Tetris.prototype.moveRight  = function(event) { this.block.moveRight(); };
Tetris.prototype.moveDown   = function(event) { this.block.moveDown(); };
Tetris.prototype.fallDown   = function(event) { this.block.fallDown(); };
Tetris.prototype.rollCCW    = function(event) { this.block.rollCCW(); };
Tetris.prototype.rollCW     = function(event) { this.block.rollCW(); };
Tetris.prototype.start      = function(event) { this.time.start(); };
Tetris.prototype.pause      = function(event) { this.time.pause(); };
Tetris.prototype.help       = function(event) { alert("help"); };
Tetris.prototype.yes        = function(event) { alert("yes"); };
Tetris.prototype.no         = function(event) { alert("no"); }


// ----------------------------------------------------------------------------
// create
// ----------------------------------------------------------------------------
Event.observe(window, "load", function(event) {
    var tetris = new Tetris();
    tetris.create();
}, false);
