// ----------------------------------------------------------------------------
// tetris.js
// DOM Tetris
// Version 1.4
// DOM によるテトリスの実装
// Copyright (C) 2005-2006 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.
// ----------------------------------------------------------------------------
// GNU 一般公衆利用許諾契約書の複製物は http://eof.my-sv.net/data/gpl に存在し
// ます。
// ----------------------------------------------------------------------------
// 意見・バグ報告は taku.eof@gmail.com まで。
// Subject に必ず「DOM Tetris」という文字列を含めてください。
// 存在しない場合は単にメールを無視します。
// ----------------------------------------------------------------------------
// DOM Tetris には以下の追加 JavaScript ライブラリが必要です。
//   - Advance Array
//   - Advance Date
// ----------------------------------------------------------------------------
// 動作確認 UA
//   - Mozilla Firefox 1.5.0.1 (Gecko/20060124 rv:1.8.0.1)
//   - Opera 8.52
//   - Microsoft Internet Explorer 6.0
// ----------------------------------------------------------------------------
// 動作不能確認 UA
//   - Opera 9.0 TP1
// ----------------------------------------------------------------------------
// TODO
//   - ゲームオーバ判定の改良
//   - 動作速度向上
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// addEvent
// Version 2.0
// DOM 汎用イベントリスナ
// ----------------------------------------------------------------------------
function addEvent(obj, type, listener, bool)
{
  if (obj.addEventListener) {
    return obj.addEventListener(type, listener, bool);
  } else if (obj.attachEvent) {
    return obj.attachEvent("on"+type, listener);
  } else {
    return null;
  }
}


// ----------------------------------------------------------------------------
// TetrisEvent
// イベントコントロール
// ----------------------------------------------------------------------------
function TetrisEvent() {
  this.events;

  this.init();
} // TetrisEvent END

TetrisEvent.prototype = {
  interval : 250,

  // keyCode 対応
  keys : {
    // 編集キー
    13 : "enter", 32 : "space",
    // 方向キー
    37 : "left", 38 : "up", 39 : "right", 40 : "down",
    // アルファベット
    65 : "a", 66 : "b", 67 : "c", 68 : "d", 69 : "e",
    70 : "f", 71 : "g", 72 : "h", 73 : "i", 74 : "j",
    75 : "k", 76 : "l", 77 : "m", 78 : "n", 79 : "o",
    80 : "p", 81 : "q", 82 : "r", 83 : "s", 84 : "t",
    85 : "u", 86 : "v", 87 : "w", 88 : "x", 89 : "y",
    90 : "z"
  },

  init : function() {
    var obj = this;

    this.initEvents();

    addEvent(document, "keydown", function(event) {
      var o = obj.events[obj.keys[event.keyCode]];
      if (o !== undefined && o.stop != true && o.timeoutId === null) {
        function timeoutFunc() {
          // setTimeout すると最低2回 event を実行することになる
          // 1回実行した後に event が null になった場合を考慮
          if (o.event === null) return;
          o.event();
          o.timeoutId = setTimeout(timeoutFunc, obj.interval);
        } timeoutFunc();
      }
    }, false);

    addEvent(document, "keyup", function(event) {
      var o = obj.events[obj.keys[event.keyCode]];
      if (o !== undefined && o.event !== null && o.stop != true
          && o.timeoutId !== null) {
        clearTimeout(o.timeoutId);
        o.timeoutId = null;
      }
    }, false);
  },

  // キー別イベントアイテム生成
  // non-static 性を保持
  initEvents : function() {
    this.events = new Object();
    for (var name in this.keys) {
      this.events[this.keys[name]] = {
        event     : null,
        stop      : false,
        timeoutId : null
      };
    }
  },

  reset : function() {
//    for (var name in this.events) {
//      this.events[name].stop      = false;
//      clearTimeout(this.events[name].timeoutId);
//      this.events[name].timeoutId = null;
//    }
  },

  add : function(name, event) {
    this.events[name].event = event;
  },

  remove : function(name) {
    this.events[name].event = null;
    this.start(name);
  },

  start : function(name) {
    this.events[name].stop = false;
  },

  stop : function(name) {
    this.events[name].stop = true;
  },

  startAll : function(exceptions) {
    for (var name in this.events) {
      this.start(name);
    }
    for (var i = 0; i < exceptions.length; i++) {
      this.stop(exceptions[i]);
    }
  },

  stopAll : function(exceptions) {
    for (var name in this.events) {
      this.stop(name);
    }
    for (var i = 0; i < exceptions.length; i++) {
      this.start(exceptions[i]);
    }
  }
}; // TetrisEvent.prototype END


// ----------------------------------------------------------------------------
// TetrisTimer
// タイマ
// ----------------------------------------------------------------------------
function TetrisTimer(root)
{
  this.root;
  this.node;
  this.textNode;
  this.passTime;
  this.timeoutId;
  this.event;
  this.interval;
  this.date;

  this.border = {
    passTime    : 0,
    point       : 0,
    minInterval : 0
  },

  this.init(root);
} // TetrisTimer END

TetrisTimer.prototype = {
  classValue : "tetris-timer",

  accelerator : { // 加速乗数
    interval : 0.98,
    passTime : 1.98,
    point    : 1.98
  },

  init : function(root) {
    this.root               = root;
    this.passTime           = 0;
    this.timeoutId          = null;
    this.border.passTime    = 30000;
    this.border.point       = 1000;
    this.border.minInterval = 10;
    this.date               = new Date();

    this.initNode();
  },

  initNode : function() {
    this.node = document.createElement("div");
    this.node.setAttribute("class", this.classValue);
    this.node.setAttribute("className", this.classValue); // BUG IE6
    this.node.setAttribute("id", this.classValue);

    var h2 = document.createElement("h2");
    var p  = document.createElement("p");

    this.textNode = document.createTextNode("00:00:00");

    h2.appendChild(document.createTextNode("Time"));
    p.appendChild(this.textNode);
    this.node.appendChild(h2);
    this.node.appendChild(p);
    this.root.appendChild(this.node);
  },

  addEvent : function(event, interval) {
    this.event    = event;
    this.interval = interval;
  },

  start : function() {
    var obj = this;
    function timerEvent() {
      obj.event(obj);
      obj.date.setTime(obj.passTime - (32400000)); // 時間差対策
      obj.passTime += obj.interval; // 経過時間加算
      obj.textNode.nodeValue = obj.date.getPreHours()   + ":"
                             + obj.date.getPreMinutes() + ":"
                             + obj.date.getPreSeconds();
      obj.timeoutId = setTimeout(timerEvent, Math.floor(obj.interval));
    } timerEvent();
  },

  reset : function() {
    this.stop();
    this.passTime = 0;
    this.textNode.nodeValue = "00:00:00";
  },

  stop : function() {
    clearTimeout(this.timeoutId);
    this.timeoutId = null;
  },

  isAcceleratePoint : function(point) {
    if (point >= this.border.point) {
      this.border.point *= this.accelerator.point;
      return true;
    }
    return false;
  },

  isAccelerateTime : function() {
    if (this.passTime >= this.border.passTime) {
      this.border.passTime *= this.accelerator.passTime;
      return true;
    }
    return false;
  },

  // 加速
  accelerate : function(point) {
    if (this.isAcceleratePoint(point)) {
      this.interval *= this.accelerator.interval;
    }
    if (this.isAccelerateTime()) {
      this.interval *= this.accelerator.interval;
    }
    if (this.interval < this.border.minInterval) {
      this.interval = 0;
    }
  }
}; // TetrisTimer.prototype END


// ----------------------------------------------------------------------------
// TetrisScore
// スコア
// ----------------------------------------------------------------------------
function TetrisScore(root)
{
  this.root;
  this.node;
  this.textNode;
  this.point;
  this.plusPoint;

  this.init(root);
} // TetrisScore END

TetrisScore.prototype = {
  classValue       : "tetris-score",
  bonusMultiplier  : 1.5, // ボーナス得点乗数
  defaultPlusPoint : 100, // 初期加算ポイント

  init : function(root) {
    this.root = root;

    this.node = document.createElement("div");
    this.node.setAttribute("class", this.classValue);
    this.node.setAttribute("className", this.classValue); // BUG IE6
    this.node.setAttribute("id", this.classValue);

    var h2 = document.createElement("h2");
    var p  = document.createElement("p");

    this.textNode = document.createTextNode("0");
    this.point = 0;
    this.plusPoint = this.defaultPlusPoint;

    h2.appendChild(document.createTextNode("Score"));
    p.appendChild(this.textNode);
    this.node.appendChild(h2);
    this.node.appendChild(p);
    root.appendChild(this.node);
  },

  reset : function(root) {
    this.textNode.nodeValue = "0";
    this.point = 0;
  },

  calc : function(line) {
    // 2 行以上の同時消しの場合はボーナスを加味する
    var bonus = (line > 1) ? (this.bonusMultiplier * (line-1)) : 1;
    this.point += this.plusPoint * line * bonus;
    this.textNode.nodeValue = this.point;
  },

  // ボーナス得点を加算ポイントに加味する
  increaseBonus : function() {
    this.plusPoint *= this.bonusMultiplier;
  }
}; // TetrisScore.prototype END


// ----------------------------------------------------------------------------
// TetrisMessage
// メッセージ
// ----------------------------------------------------------------------------
function TetrisMessage(root)
{
  this.root;

  this.init(root);
} // TetrisMessage END

TetrisMessage.prototype = {
  rootClassValue : "tetris",
  classValue     : "tetris-message",

  init : function(root) {
    this.root = root;
    this.hide(); // Error メッセージを隠す
  },

  show : function(type) {
    this.root.setAttribute("class", this.rootClassValue+" "+type);
    // BUG IE6
    this.root.setAttribute("className", this.rootClassValue+" "+type);
  },

  hide : function() {
    this.root.setAttribute("class", this.rootClassValue);
    // BUG IE6
    this.root.setAttribute("className", this.rootClassValue);
  }
}; // TetrisMessage.prototype END


// ----------------------------------------------------------------------------
// ブロック
// 変数がそのまま即時利用可能な値となる
// 色は CSS により決定される -> class 属性を利用
// ----------------------------------------------------------------------------
function TetrisBlock(field)
{
  this.field;     // 親フィールド
  this.shape;     // 形状データ
  this.border = { // 境界セル位置
    top    : null,
    left   : null,
    right  : null,
    bottom : null
  };
  this.position = { // 原点からの位置
    y : 0,
    x : 0
  };

  this.init(field);
} // TetrisBlock END

TetrisBlock.prototype = {
  // 形状情報
  // name         : 形状に対応した名称 -> class 属性値に使用される
  // orientations : 存在座標
  // 1行が1アイテム
  // math.random を用いて orientations.length の間で乱数生成
  //   -> 取り出したアイテムを用いる
  SHAPES : [
    {name         : "I",
     classValue   : "tetris-block-I",
     orientations : [[[0,0],[0,1],[0,2],[0,3]],
                     [[0,0],[1,0],[2,0],[3,0]]]},
    {name         : "J",
     classValue   : "tetris-block-J",
     orientations : [[[0,0],[0,1],[0,2],[1,2]],
                     [[0,1],[1,1],[2,1],[2,0]],
                     [[0,0],[1,0],[1,1],[1,2]],
                     [[0,0],[0,1],[1,0],[2,0]]]},
    {name         : "L",
     classValue   : "tetris-block-L",
     orientations : [[[0,0],[1,0],[0,1],[0,2]],
                     [[0,0],[0,1],[1,1],[2,1]],
                     [[1,0],[1,1],[1,2],[0,2]],
                     [[0,0],[1,0],[2,0],[2,1]]]},
    {name         : "O",
     classValue   : "tetris-block-O",
     orientations : [[[0,0],[1,0],[0,1],[1,1]]]},
    {name         : "S",
     classValue   : "tetris-block-S",
     orientations : [[[0,0],[1,0],[1,1],[2,1]],
                     [[1,0],[1,1],[0,1],[0,2]]]},
    {name         : "T",
     classValue   : "tetris-block-T",
     orientations : [[[1,0],[1,1],[1,2],[0,1]],
                     [[0,0],[1,0],[2,0],[1,1]],
                     [[0,0],[0,1],[0,2],[1,1]],
                     [[0,1],[1,1],[2,1],[1,0]]]},
    {name         : "Z",
     classValue   : "tetris-block-Z",
     orientations : [[[0,0],[0,1],[1,1],[1,2]],
                     [[1,0],[2,0],[0,1],[1,1]]]},
  ],

  // 初期化
  init : function(field) {
    this.field      = field;
    this.position.y = field.firstY;
    this.position.x = field.firstX;
    this.getShape();
    this.move(0, 0);
  },

  // フィールド変更
  // nextBlock から game への変更などに使用
  changeField : function(field) {
    this.undraw();
    this.field      = field;
    this.position.y = field.firstY;
    this.position.x = field.firstX;
    this.move(0, 0);
  },

  // 形状取得
  // 乱数により形状と回転 状態 を決定する
  getShape : function() {
    this.shape = this.copyShape(
      Math.floor(Math.random() * (this.SHAPES.length-1))
    );
    // 双方向循環連結リスト初期化
    this.shape.orientations.setLook(
      Math.floor(Math.random() * (this.shape.orientations.length-1))
    );
  },

  // SHAPE のひとつを丸ごとコピー
  // = で参照しただけではコピーしたことにならない
  // 双方向連結リストの内部データが SHAPES に残留することを防ぐ
  copyShape : function(num) {
    var shape = {
      name         : this.SHAPES[num].name,
      classValue   : this.SHAPES[num].classValue,
      orientations : null
    };
    var os  = this.SHAPES[num].orientations;
    var nos = os.length;

    // 各値をコピー
    shape.orientations = new Array(nos);
    for (var i = 0; i < nos; i++) {
      var no = os[i].length;
      shape.orientations[i] = new Array(no);
      for (var j = 0; j < no; j++) {
        var n = os[i][j].length;
        shape.orientations[i][j] = new Array(n);
        for (var k = 0; k < n; k++) {
          shape.orientations[i][j][k] = os[i][j][k];
        }
      }
    }

    return shape;
  },

  // ゲームオーバかチェック
  isGameOver : function() {
    // 何もせずともブロックが存在するならゲームオーバ
    return this.isContact(0, 0);
  },

  // 既固定ブロックないしフィールド端に接するかチェック
  isContact : function(py, px) {
    var o    = this.shape.orientations[this.shape.orientations.look];
    var no   = o.length;
    var cell = this.field.cell.item;
    var y    = this.position.y;
    var x    = this.position.x;

    // undefined     -> セルが存在しない   -> フィールド端よりはみ出る
    // block == true -> ブロックが存在する -> そのセルは上書禁止
    for (var i = 0; i < no; i++) {
      if (   cell[o[i][0]+y+py]  === undefined
          || cell[o[i][0]+y+py][o[i][1]+x+px] === undefined
          || cell[o[i][0]+y+py][o[i][1]+x+px].block == true) {
        return true;
      }
    }
    return false;
  },

  // 時計方向に回転
  rollCW : function() {
    this.undraw();
    this.shape.orientations.past();
    if (this.isContact(0, 0)) this.shape.orientations.back();
    this.draw();
  },

  // 反時計方向に回転
  rollCCW : function() {
    this.undraw();
    this.shape.orientations.back();
    if (this.isContact(0, 0)) this.shape.orientations.past();
    this.draw();
  },

  // 座標を移動させ、cell へ実際に描写する
  //   py : y 方向加算量
  //   px : x 方向加算量
  move : function(py, px) {
    if (this.isContact(py, px)) { return false; }

    var o  = this.shape.orientations[this.shape.orientations.look];
    var no = o.length;

    this.undraw(); // 跡を残さない
    this.position.y += py;
    this.position.x += px;
    this.draw();

    return true;
  },

  // 位置固定
  fix : function() {
    var o  = this.shape.orientations[this.shape.orientations.look];
    var no = o.length;
    var y  = this.position.y;
    var x  = this.position.x;

    for (var i = 0; i < no; i++) {
      this.field.cell.fix(o[i][0]+y, o[i][1]+x);
    }
  },

  // 位置固定解除
  unfix : function() {
    var o  = this.shape.orientations[this.shape.orientations.look];
    var no = o.length;
    var y  = this.position.y;
    var x  = this.position.x;

    for (var i = 0; i < no; i++) {
      this.field.cell.unfix(o[i][0]+y, o[i][1]+x);
    }
  },

  // cell 上に描写
  draw : function() {
    var o  = this.shape.orientations[this.shape.orientations.look];
    var no = o.length;
    var cv = this.shape.classValue;
    var y  = this.position.y;
    var x  = this.position.x;

    for (var i = 0; i < no; i++) {
      this.field.cell.draw(o[i][0]+y, o[i][1]+x, cv);
    }
  },

  // cell 上から消去
  undraw : function() {
    var o  = this.shape.orientations[this.shape.orientations.look];
    var no = o.length;
    var y  = this.position.y;
    var x  = this.position.x;

    // class を元に戻す
    for (var i = 0; i < no; i++) {
      this.field.cell.undraw(o[i][0]+y, o[i][1]+x);
    }
  },

  moveDown  : function() {
    if (this.move(1, 0)) { return true; }
    this.fix();
    return false;
  },
  moveLeft  : function() { return this.move(0, -1); },
  moveRight : function() { return this.move(0, 1);  }
}; // TetrisBlock.prototype END


// ----------------------------------------------------------------------------
// TetrisCell
// セル
// ----------------------------------------------------------------------------
function TetrisCell(root, name, height, width)
{
  this.root;
  this.id;
  this.name; // セルの名称
  this.item; // セルの中身
  this.width;
  this.height;

  this.init(root, name, height, width);
} // TetrisCell END

TetrisCell.prototype = {
  init : function(root, name, height, width) {
    this.id     = "tetris-"+name+"-";
    this.root   = root;
    this.name   = name;
    this.height = height;
    this.width  = width;

    this.initCell();
  },

  initCell : function() {
    var root   = this.root;
    var id     = this.id;
    var width  = this.width;
    var height = this.height;
    var item   = new Array(width);

    for (var y = 0; y < height; y++) {
      root.appendChild(document.createElement("div"));
      root.childNodes[y].setAttribute("id", id+y);
      root.childNodes[y].setAttribute("class", "tetris-row");
      // BUG IE6
      root.childNodes[y].setAttribute("className", "tetris-row");
      item[y] = new Array(height);
      // セル生成
      for (var x = 0; x < width; x++) {
        item[y][x] = document.createElement("span");
        root.childNodes[y].appendChild(item[y][x]);
        item[y][x].setAttribute("id", id+y+"-"+x);
        item[y][x].setAttribute("class", "tetris-cell");
        // BUG IE6
        item[y][x].setAttribute("className", "tetris-cell");
        item[y][x].block = false;
      }
    }

    this.root = root;
    this.item = item;
  },

  reset : function() {
    var width  = this.width;
    var height = this.height;
    var item   = this.item;

    for (var y = 0; y < height; y++) {
      for (var x = 0; x < width; x++) {
        this.undraw(y, x);
        this.unfix(y, x);
      }
    }
  },

  // 位置固定
  fix : function(y, x) {
    this.item[y][x].block = true;
  },

  // 位置固定解除
  unfix : function(y, x) {
    this.item[y][x].block = false;
  },

  // cell 上に描写
  draw : function(y, x, classValue) {
    classValue = "tetris-cell " + classValue;
    this.item[y][x].setAttribute("class", classValue);
    this.item[y][x].setAttribute("className", classValue);
  },

  // cell 上から削除
  undraw : function(y, x) {
    this.item[y][x].setAttribute("class", "tetris-cell");
    this.item[y][x].setAttribute("className", "tetris-cell");
  },

  // cell の指定ラインから削除
  undrawLine : function(y) {
    for (var x = 0; x < this.width; x++) {
      this.undraw(y, x);
    }
  },

  // cell の指定ライン位置固定解除
  unfixLine : function(y) {
    for (var x = 0; x < this.width; x++) {
      this.unfix(y, x);
    }
  },

  // 行の入れ替え
  swapLine : function(before, after) {
    var beforeClass;
    var beforeClassName;
    var beforeBlock;
    var afterClass;
    var afterClassName;
    var afterBlock;
    for (var x = 0; x < this.width; x++) {
      beforeClass     = this.item[before][x].getAttribute("class");
      beforeClassName = this.item[before][x].getAttribute("className");
      beforeBlock     = this.item[before][x].block;
      afterClass      = this.item[after][x].getAttribute("class");
      afterClassName  = this.item[after][x].getAttribute("className");
      afterBlock      = this.item[after][x].block;
      this.item[before][x].setAttribute("class", afterClass);
      this.item[before][x].setAttribute("className", afterClassName);
      this.item[before][x].block = afterBlock;
      this.item[after][x].setAttribute("class", beforeClass);
      this.item[after][x].setAttribute("className", beforeClassName);
      this.item[after][x].block = beforeBlock;
    }
  },

  // 指定ラインがブロックでいっぱいになっているか
  isFillLine : function(y) {
    for (var x = 0; x < this.width; x++) {
      if (this.item[y][x].block == false) {
        return false;
      }
    }
    return true;
  }
}; // TetrisCell.prototype END


// ----------------------------------------------------------------------------
// TetrisGame
// ゲームフィールド
// ----------------------------------------------------------------------------
function TetrisGame(root)
{
  this.root;
  this.node;
  this.cell;

  this.init(root);
} // TetrisGame END

TetrisGame.prototype = {
  classValue : "tetris-game",
  name       : "game",
  height     : 20,
  width      : 10,
  firstY     : 0,
  firstX     : 4,

  init : function(root) {
    this.root = root;

    this.node = document.createElement("div");
    this.node.setAttribute("class", this.classValue);
    // BUG IE6
    this.node.setAttribute("className", this.classValue);
    this.node.setAttribute("id", this.classValue);

    this.cell = new TetrisCell(this.node, this.name, this.height, this.width);

    this.root.appendChild(this.node);
  }
}; // TetrisGame.prototype END


// ----------------------------------------------------------------------------
// TetrisNextBlock
// 次ブロック表示フィールド
// ----------------------------------------------------------------------------
function TetrisNextBlock(root)
{
  this.root;
  this.node;
  this.cell;

  this.init(root);
} // TetrisNextBlock END

TetrisNextBlock.prototype = {
  classValue : "tetris-next_block",
  name       : "next_block",
  height     : 4,
  width      : 4,
  firstY     : 0,
  firstX     : 0,

  init : function(root) {
    this.root = root;

    this.node = document.createElement("div");
    this.node.setAttribute("class", this.classValue);
    // BUG IE6
    this.node.setAttribute("className", this.classValue);
    this.node.setAttribute("id", this.classValue);

    this.cell = new TetrisCell(this.node, this.name, this.height, this.width);

    this.root.appendChild(this.node);
  }
}; // TetrisNextBlock.prototype END


// ----------------------------------------------------------------------------
// Tetris
// テトリス本体
// ----------------------------------------------------------------------------
function Tetris(root)
{
  this.root;

  this.block = {
    now  : null, // 現在 cell 上で動作しているブロック
    next : null  // 次に落ちてくるブロック
  };

  this.game;
  this.nextBlock;
  this.event;
  this.message;
  this.score;
  this.timer;

  // 状態情報
  this.state = {help : false, pause : false};

  this.init(root);
} // Tetris END

Tetris.prototype = {
  init : function(root) {
    this.root      = root;
    this.game      = new TetrisGame(this.root);
    this.nextBlock = new TetrisNextBlock(this.root);
    this.event     = new TetrisEvent();
    this.message   = new TetrisMessage(this.root);
    this.score     = new TetrisScore(this.root);
    this.timer     = new TetrisTimer(this.root);
    this.addAllEvents();
    this.start();
  },

  // 全イベントセット
  addAllEvents : function() {
    var obj = this;
    // 時間軸実行
    this.timer.addEvent(function(timerObj) {obj.next(timerObj);}, 1000);
    // イベント
    this.event.add("down",  function() {obj.next();});
    this.event.add("left",  function() {obj.block.now.moveLeft();});
    this.event.add("right", function() {obj.block.now.moveRight();});
    this.event.add("x",     function() {obj.block.now.rollCW();});
    this.event.add("z",     function() {obj.block.now.rollCCW();});
    this.event.add("r",     function() {obj.reset();});
    this.event.add("p",     function() {obj.pause();});
    this.event.add("h",     function() {obj.help();});
  },

  // 全イベントスタート
  // 例外的にスタートしないものは引数で渡す
  startAllEvents : function() {
    this.timer.start();
    this.event.startAll(arguments);
  },

  // 全イベント停止
  // 例外的に停止しないものは引数で渡す
  stopAllEvents : function() {
    this.timer.stop();
    this.event.stopAll(arguments);
  },

  // 一時停止
  pause : function() {
    this.state.pause = !this.state.pause;
    if (this.state.pause) {
      this.stopAllEvents("p");
      this.message.show("pause");
    } else {
      this.message.hide();
      this.startAllEvents();
    }
  },

  // ヘルプ表示
  help : function() {
    this.state.help = !this.state.help;
    if (this.state.help) {
      this.stopAllEvents("h");
      this.message.show("help");
    } else {
      this.message.hide();
      this.startAllEvents();
    }
  },

  // リセットの本処理
  resetSub : function() {
    // TetrisBlock
    delete this.block.now;
    delete this.block.next;
    // TetrisCell
    this.game.cell.reset();
    this.nextBlock.cell.reset();
    // TetrisTimer
    this.timer.reset();
    // TetrisScore
    this.score.reset();
    // TetrisEvent
    this.event.reset();
    // Restart
    this.start();
  },

  // ゲームリセット
  reset : function() {
    this.stopAllEvents();
    this.message.show("reset");
    var c = confirm("ゲームをリセットします。よろしいですか？");
    this.message.hide();
    if (c) {
      this.resetSub();
    } else {
      this.startAllEvents();
    }
  },

  // ゲームスタート
  start : function() {
    this.block.now  = new TetrisBlock(this.game);
    this.block.next = new TetrisBlock(this.nextBlock);

    this.stopAllEvents("enter");
    this.message.show("start");

    var obj = this;
    this.event.add("enter", function() {
      obj.event.remove("enter");
      obj.message.hide("start");
      obj.startAllEvents();
    });
  },

  // ゲームオーバ
  over : function() {
    this.stopAllEvents("enter");
    this.message.show("over");

    var obj = this;
    this.event.add("enter", function() {
      obj.event.remove("enter");
      obj.message.hide("over");
      obj.resetSub();
    });
  },

  // 次のブロックを落とす
  next : function(timerObj) {
    // time の中で動作するとき、時間 0 の時は動作しない
    if (timerObj !== undefined && timerObj.passTime == 0) { return; }
    // 地につかないうちは下げさせる
    if (this.block.now.moveDown()) { return; }

    var cell       = this.game.cell;
    var height     = this.game.height
    var scorePoint = 0; // 同時に消した行数

    // 行が全部ブロックで埋まっているかチェック
    for (var y = 0; y < height; y++) {
      if (cell.isFillLine(y)) {
        cell.undrawLine(y);
        cell.unfixLine(y);
        // 全ての行を swap
        for (var before = y; before > 0; before--) {
          cell.swapLine(before, before-1);
        }
        scorePoint++;
      }
    }
    // スコア計算
    this.score.calc(scorePoint);
    // 加速
    this.timer.accelerate(this.score.point);
    // 新しいブロックへの入れ替え
    this.block.next.changeField(this.game);
    this.block.now = this.block.next;
    delete this.block.next;
    this.block.next = new TetrisBlock(this.nextBlock);
    // ゲームオーバ判定
    if (this.block.now.isGameOver()) { this.over(); }
  }
}; // Tetris.prototype END
