このブログは、旧・はてなダイアリー「檜山正幸のキマイラ飼育記 メモ編」(http://d.hatena.ne.jp/m-hiyama-memo/)のデータを移行・保存したものであり、今後(2019年1月以降)更新の予定はありません。

今後の更新は、新しいブログ http://m-hiyama-memo.hatenablog.com/ で行います。

タートル・モナド

<html><!-- turtle-monad.html -->
  <head>
    <title>Turtle Monad</title>

    <script src="TurtleMonad.js" ></script>
    <script>
      var _canvas = null;
      var _ctx = null;
      function initCanvas() {
        _canvas = document.getElementById("canvas"); 
        _ctx = _canvas.getContext('2d');
      }
    </script>

    <style>
      #canvas {border: 1px solid blue;
    </style>

</head>

<body onload="initCanvas()">
<h1>Turtle Monad</h1>

<canvas id="canvas" width="300" height="300" >
  <h1>キャンバスがサポートされていません。</h2>
</canvas>

</body>
</html>
/* TurtleMonad.js */

// オペレーション・モノイド
var TurtleOp = {
  // 定数
  GO : 1, // 直進
  ROTATE : 2, // 回転

  // モノイド単位
  unit : function() {
    return [];
  },
  // モノイド乗法
  mult : function(a, b) {
    return Array.concat(a, b); // firefox用
  }
};

// タートル・オブジェクト(シングルトン)
var Turtle = {
  // 定数
  QUEUE_MODE : 1, // オペレーション・リクエストをキューイングする
  DIRECT_MODE : 0, // オペレーション・リクエストをすぐに実行する

  // フィールド、ここでは宣言しているだけ、初期化はinitで行う
  _mode : undefined, // 実行モード
  _canvas : undefined, // 描画キャンバス
  _width : undefined, // キャンバス幅
  _height : undefined, // キャンバス高さ
  _ctx : undefined, // 描画コンテキスト
  _x : undefined, // 現在位置のx-座標
  _y : undefined, // 現在位置のy-座標
  _dir : undefined, // 頭が向いている方向(弧度法)
  _operations : undefined // 累積オペレーション値
};


/*  以下にメソッド群 */

Turtle.init = function() {
  Turtle._mode = Turtle.QUEUE_MODE;
  // 大域変数 _canvas, _ctx はHTML側でセットしてくれている
  Turtle._canvas = _canvas;
  Turtle._ctx = _ctx;
  Turtle._width = Turtle._canvas.width;
  Turtle._height = Turtle._canvas.height;
  Turtle._x = 0;
  Turtle._y = 0;
  Turtle._dir = (1/2)*Math.PI;
  Turtle._operations = TurtleOp.unit();
  // 座標を使いやすいようにセットする
  Turtle._ctx.setTransform(1, 0, 0, -1,
    Math.round(Turtle._width/2),
    Math.round(Turtle._height/2)
  );
  // 初期状態の描画
  Turtle.draw();
};

Turtle.setMode = function(mode) {
  Turtle._mode = mode;
};

Turtle.operate = function (op) {
  var old = Turtle._operations;
  Turtle._operations = TurtleOp.mult(old, op);
  if (Turtle._mode == Turtle.DIRECT_MODE) {
    Turtle.execOperations();
  }
};

Turtle.draw = function() {
  // パラメータの設定
  var dirHead = Turtle._dir;
  var dirRight = Turtle._dir + (Math.PI* (3/4));
  var dirLeft = Turtle._dir - (Math.PI* (3/4));
  var x = Turtle._x;
  var y = Turtle._y;
  var r = 10;

  // 描画の実行
  var ctx = Turtle._ctx;
  ctx.clearRect(-Turtle._width/2, -Turtle._height/2,
			Turtle._width, Turtle._height);
  ctx.beginPath();
  ctx.moveTo(x + r*Math.cos(dirHead),  y + r*Math.sin(dirHead));
  ctx.lineTo(x + r*Math.cos(dirRight), y + r*Math.sin(dirRight));
  ctx.lineTo(x + r*Math.cos(dirLeft),  y + r*Math.sin(dirLeft));
  ctx.lineTo(x + r*Math.cos(dirHead),  y + r*Math.sin(dirHead));
  ctx.fill();
};

// タートルの直進 (アクションの実行)
Turtle._action_go = function (r) {
  var x = Turtle._x;
  var y = Turtle._y;
  var dir = Turtle._dir;
  Turtle._x = x + r*Math.cos(dir);
  Turtle._y = y + r*Math.sin(dir);
};

// タートルの回転 (アクションの実行)
Turtle._action_rotate = function (arc) {
  var dir = Turtle._dir;
  Turtle._dir = dir + arc;
};

// キューイングされたオペレーションの実行
Turtle.execOperations  = function() {
  var ops = Turtle._operations;
  for (var i = 0; i < ops.length; i++) {
    var op = ops[i];
    var action = op[0];
    var param = op[1];
    switch (action) {
      case TurtleOp.GO:
	Turtle._action_go(param); break;
      case TurtleOp.ROTATE:
	Turtle._action_rotate(param); break;
      default: throw "Unbelievable!";
    }
  }
  // 累積されたタートル操作リクエストをクリアする
  Turtle._operations = TurtleOp.unit();
  // 描画
  Turtle.draw();
};


/* 対話的に使用する便利関数群 */
/*
 * start()  -- 初期化
 * fd(r)    -- タートル前方に直進 forward
 * bk(r)    -- タートル後方に直進 back
 * rt(a)    -- タートル右に回転 right 角度aは360度方式
 * lt(a)    -- タートル左に回転 left 角度aは360度方式
 * x()      -- 溜まっているオペレーション・リクエストの実行
 * q()      -- キューの表示
 * qmode(f) -- キューイングするかどうかの設定
 *             fがtrueならキューイングモードになる
 */

// initって名前はナニカとかち合っているようだ(恐い)
function start() {
  Turtle.init();
}

function fd(r) {
  Turtle.operate([[TurtleOp.GO, r]]);
}

function bk(r) {
  Turtle.operate([[TurtleOp.GO, -r]]);
}

function rt(a) {
  Turtle.operate([[TurtleOp.ROTATE, -(a/360)*(2*Math.PI)]]);
}

function lt(a) {
  Turtle.operate([[TurtleOp.ROTATE, (a/360)*(2*Math.PI)]]);
}

function x() {
  Turtle.execOperations();
}

function q() {
  return Turtle._operations;
}

function qmode(f) {
  if (f) {
    Turtle.setMode(Turtle.QUEUE_MODE);
  } else {
    x(); // キューをフラッシュする
    Turtle.setMode(Turtle.DIRECT_MODE);
  }
};