2013年12月10日火曜日

CodeIQ|ITエンジニアのための実務スキル評価サービスで挑戦した問題、川頭 信之さん (@nkawagashira)、からの挑戦者求む!【言語不問】再帰の海に棲むドラゴンを描いてみよう by @nkawagashira 川頭 信之│CodeIQ

C曲線の宮殿を旅して、ドラゴンを探し出そう!

の解答の解説、09再帰の海に棲むドラゴンを描いてみよう【問題と解説】 - スピード冒険野郎のブログ

【解説】再帰の海に住むドラゴンを描いてみよう
今回は再帰関数を用いてC曲線とドラゴン曲線を描く問題です。
準備問題は言語やOS等によって異なりますので、個々に対応してください。
タートルグラフィックスは直前の描画終了点を記憶し、描画の長さlengthと描画の方向角度thetaを入力変数として描画を行うシステムです。

が出てたので、挑戦した時を思い出しながら書いてみた。

Google Chrome、Safari(特にChrome)の場合、strokeをその都度に変更して、setTimeoutを書き加えたら、一気に描画されるのではなく、再帰関数(左側から)で描画されるていく様子(左側から)が分かるようになった。strokeを毎回呼び出すようにしだけでは、徐々に表示されるようにはならなかった。

png形式のイメージを取得については、Firefoxはcanvasを右クリックで直接png形式の画像として保存することができた。Chrome、Safariでpng形式の画像を取得するには変換する必要があった。

徐々に描かれていくのと、一度で描く、2つのバージョンを書いてみた。

まず1つ目。

再帰関数で徐々にパスの輪郭が描かれていく版。(strokeの呼び出しが描画する線の数だけの回数必要なので、後述コードより描画の完成に時間がかかる。Google Chromeでは滑らかに、Safariでは滑らかではないけど徐々に描かれていくことを確認できた。Firefoxではstrokeの呼び出しが増えた分(あるいはsetTimeoutが起因?)、描画速度が落ちただけで、後述のコードと描画結果は変わらなかった。ということで、描画結果だけを見たい場合や、Firefoxで実行する場合はこのコードではなく後述の2つ目コードで。Internet Explorerは確認してないから、実行するならとりあえずは後記のコードの方がいいかも。)

C曲線の描画

コード(BBEdit)

var ccurve_canvas = document.getElementById('ccurve_canvas'),
    $ccurve_canvas = $('#ccurve_canvas'),
    x0 = 0,
    y0 = 0,
    ccurve_ctx = ccurve_canvas.getContext('2d'),
    ccurve_canvas_width = $('#ccurve_canvas_width').val(),
    ccurve_canvas_height = $('#ccurve_canvas_height').val(),
    ccurve_canvas_begin_x = parseFloat($('#ccurve_canvas_begin_x').val()),
    ccurve_canvas_begin_y = parseFloat($('#ccurve_canvas_begin_y').val()),
    ccurve_begin_length = parseFloat($('#ccurve_begin_length').val()),
    ccurve_begin_angle = parseFloat($('#ccurve_begin_angle').val()),
    ccurve_min_length = parseFloat($('#ccurve_min_length').val()),
    tm,
    init = function (width, height, canvas) {
        canvas.attr({'width': width, 'height': height});
    },
    setInitialPoint = function (x, y, ctx) {
        ctx.moveTo(x, y);
        x0 = x;
        y0 = y; 
    },
    plotLine = function (length, theta, ctx) {
        x0 += length * Math.cos(theta);
        y0 += length * Math.sin(theta);
        ctx.lineTo(x0, y0);
        ctx.stroke();
    },
    ccurve = function (length, theta) {
        var length_tmp,
            theta_left,
            theta_right;
        if (length <= ccurve_min_length) {
            tm = setTimeout(function() {
                plotLine(length, theta, ccurve_ctx);
            }, 0);
            return;
        }
        length_tmp = length * Math.SQRT1_2;
        theta_left = theta + Math.PI / 4;
        theta_right = theta - Math.PI / 4;
        ccurve(length_tmp, theta_left);
        ccurve(length_tmp, theta_right);
    },
    erase = function () {
        $ccurve_canvas.attr('height', '0');
    };
init(ccurve_canvas_width, ccurve_canvas_height, $ccurve_canvas);
ccurve_ctx.translate(0, ccurve_canvas_height);
ccurve_ctx.scale(1, -1);
setInitialPoint(ccurve_canvas_begin_x, ccurve_canvas_begin_y, ccurve_ctx);
ccurve(ccurve_begin_length, ccurve_begin_angle);
$('#ccurve_canvas_erase').click(erase);

キャンバス

描画の開始座標

初期値の線

描画下限値:

C曲線のpng形式のイメージを取得 (右クリックでダウンロード可能)

コード(BBEdit)

var $ccurve_div = $('#ccurve_div'),
    save = function () {
        var $img = $('<img>'),
            ccurve_canvas = document.getElementById('ccurve_canvas');
        $img.attr({'alt':'ccurve', 'src':ccurve_canvas.toDataURL()});
        $ccurve_div.html($img);
    },
    erase = function () {
        $ccurve_div.html('');
    };
save();
$('#ccurve_div_erase').click(erase);

ドラゴン曲線の描画

コード(BBEdit)

var dragon_canvas = document.getElementById('dragon_canvas'),
    $dragon_canvas = $('#dragon_canvas'),
    x0 = 0,
    y0 = 0,
    dragon_ctx = dragon_canvas.getContext('2d'),
    dragon_canvas_width = $('#dragon_canvas_width').val(),
    dragon_canvas_height = $('#dragon_canvas_height').val(),
    dragon_canvas_begin_x = parseFloat($('#dragon_canvas_begin_x').val()),
    dragon_canvas_begin_y = parseFloat($('#dragon_canvas_begin_y').val()),
    dragon_begin_length = parseFloat($('#dragon_begin_length').val()),
    dragon_begin_angle = parseFloat($('#dragon_begin_angle').val()),
    dragon_min_length = parseFloat($('#dragon_min_length').val()),
    init = function (width, height, canvas) {
        canvas.attr({'width': width, 'height': height});
    },
    setInitialPoint = function (x, y, ctx) {
        ctx.moveTo(x, y);
        x0 = x;
        y0 = y; 
    },
    plotLine = function (length, theta, ctx) {
        x0 += length * Math.cos(theta);
        y0 += length * Math.sin(theta);
        ctx.lineTo(x0, y0);
        ctx.stroke();
    },
    dragon = function (length, theta, flip) {
        var length_tmp,
            theta_left,
            theta_right;
        if (length <= dragon_min_length) {
            setTimeout(function (){
                plotLine(length, theta, dragon_ctx);
            }, 0);
            return;
        }
        length_tmp = length * Math.SQRT1_2;
        theta_left = theta + Math.PI / 4;
        theta_right = theta - Math.PI / 4;
        if (flip === 1) {
            dragon(length_tmp, theta_left, 1);
            dragon(length_tmp, theta_right, -1);
        } else {
            dragon(length_tmp, theta_right, 1);
            dragon(length_tmp, theta_left, -1);
        }
    },
    erase = function () {
        $dragon_canvas.attr('height', '0');
    };
init(dragon_canvas_width, dragon_canvas_height, $dragon_canvas);
dragon_ctx.translate(0, dragon_canvas_height);
dragon_ctx.scale(1, -1);
setInitialPoint(dragon_canvas_begin_x, dragon_canvas_begin_y, dragon_ctx);
dragon(dragon_begin_length, dragon_begin_angle, 1);
$('#dragon_canvas_clear').click(erase);

キャンバス

描画の開始座標

初期値の線

描画下限値:

ドラゴン曲線のpng形式のイメージを取得 (右クリックでダウンロード可能)

コード(BBEdit)

var $dragon_div = $('#dragon_div'),
    save = function () {
        var $img = $('<img>'),
            dragon_canvas = document.getElementById('dragon_canvas');
        $img.attr({'alt': 'dragon', 'src': dragon_canvas.toDataURL()});
        $dragon_div.html($img);
    },
    erase = function () {
        $dragon_div.html('');
    };
save();
$('#dragon_div_erase').click(erase);

2つ目。

最後にまとめてパスの輪郭を描く版(前記のコードの数カ所コメントアウトし、strokeメソッドを最後の方に書き加えて、呼び出しが1回で済むように修正。)

C曲線の描画

コード(BBEdit)

var ccurve1_canvas = document.getElementById('ccurve1_canvas'),
    $ccurve1_canvas = $('#ccurve1_canvas'),
    x0 = 0,
    y0 = 0,
    ccurve1_ctx = ccurve1_canvas.getContext('2d'),
    ccurve1_canvas_width = $('#ccurve1_canvas_width').val(),
    ccurve1_canvas_height = $('#ccurve1_canvas_height').val(),
    ccurve1_canvas_begin_x = parseFloat($('#ccurve1_canvas_begin_x').val()),
    ccurve1_canvas_begin_y = parseFloat($('#ccurve1_canvas_begin_y').val()),
    ccurve1_begin_length = parseFloat($('#ccurve1_begin_length').val()),
    ccurve1_begin_angle = parseFloat($('#ccurve1_begin_angle').val()),
    ccurve1_min_length = parseFloat($('#ccurve1_min_length').val()),
    init = function (width, height, canvas) {
        canvas.attr({'width': width, 'height': height});
    },
    setInitialPoint = function (x, y, ctx) {
        ctx.moveTo(x, y);
        x0 = x;
        y0 = y; 
    },
    plotLine = function (length, theta, ctx) {
        x0 += length * Math.cos(theta);
        y0 += length * Math.sin(theta);
        ctx.lineTo(x0, y0);
        // ctx.stroke();
    },
    ccurve1 = function (length, theta) {
        var length_tmp,
            theta_left,
            theta_right;
        if (length <= ccurve1_min_length) {
            // setTimeout(function() {
                plotLine(length, theta, ccurve1_ctx);
            // }, 0);
            return;
        }
        length_tmp = length * Math.SQRT1_2;
        theta_left = theta + Math.PI / 4;
        theta_right = theta - Math.PI / 4;
        ccurve1(length_tmp, theta_left);
        ccurve1(length_tmp, theta_right);
    },
    stop = function () {
        clearTimeout(tm);
    },
    erase = function () {
        $ccurve1_canvas.attr('height', '0');
    };
init(ccurve1_canvas_width, ccurve1_canvas_height, $ccurve1_canvas);
ccurve1_ctx.translate(0, ccurve1_canvas_height);
ccurve1_ctx.scale(1, -1);
setInitialPoint(ccurve1_canvas_begin_x, ccurve1_canvas_begin_y, ccurve1_ctx);
$('#ccurve1_stop').click(stop);
ccurve1(ccurve1_begin_length, ccurve1_begin_angle);
ccurve1_ctx.stroke();
$('#ccurve1_canvas_erase').click(erase);

キャンバス

描画の開始座標

初期値の線

描画下限値:

C曲線のpng形式のイメージを取得 (右クリックでダウンロード可能)

コード(BBEdit)

var $ccurve1_div = $('#ccurve1_div'),
    save = function () {
        var $img = $('<img>'),
            ccurve1_canvas = document.getElementById('ccurve1_canvas');
        $img.attr({'alt':'ccurve1', 'src':ccurve1_canvas.toDataURL()});
        $ccurve1_div.html($img);
    },
    erase = function () {
        $ccurve1_div.html('');
    };
save();
$('#ccurve1_div_erase').click(erase);

ドラゴン曲線の描画

コード(BBEdit)

var dragon1_canvas = document.getElementById('dragon1_canvas'),
    $dragon1_canvas = $('#dragon1_canvas'),
    x0 = 0,
    y0 = 0,
    dragon1_ctx = dragon1_canvas.getContext('2d'),
    dragon1_canvas_width = $('#dragon1_canvas_width').val(),
    dragon1_canvas_height = $('#dragon1_canvas_height').val(),
    dragon1_canvas_begin_x = parseFloat($('#dragon1_canvas_begin_x').val()),
    dragon1_canvas_begin_y = parseFloat($('#dragon1_canvas_begin_y').val()),
    dragon1_begin_length = parseFloat($('#dragon1_begin_length').val()),
    dragon1_begin_angle = parseFloat($('#dragon1_begin_angle').val()),
    dragon1_min_length = parseFloat($('#dragon1_min_length').val()),
    init = function (width, height, canvas) {
        canvas.attr({'width': width, 'height': height});
    },
    setInitialPoint = function (x, y, ctx) {
        ctx.moveTo(x, y);
        x0 = x;
        y0 = y; 
    },
    plotLine = function (length, theta, ctx) {
        x0 += length * Math.cos(theta);
        y0 += length * Math.sin(theta);
        ctx.lineTo(x0, y0);
    },
    dragon1 = function (length, theta, flip) {
        var length_tmp,
            theta_left,
            theta_right;
        if (length <= dragon1_min_length) {
            // setTimeout(function (){
                plotLine(length, theta, dragon1_ctx);
            // }, 0);
            return;
        }
        length_tmp = length * Math.SQRT1_2;
        theta_left = theta + Math.PI / 4;
        theta_right = theta - Math.PI / 4;
        if (flip === 1) {
            dragon1(length_tmp, theta_left, 1);
            dragon1(length_tmp, theta_right, -1);
        } else {
            dragon1(length_tmp, theta_right, 1);
            dragon1(length_tmp, theta_left, -1);
        }
    },
    erase = function () {
        $dragon1_canvas.attr('height', '0');
    };
init(dragon1_canvas_width, dragon1_canvas_height, $dragon1_canvas);
dragon1_ctx.translate(0, dragon1_canvas_height);
dragon1_ctx.scale(1, -1);
setInitialPoint(dragon1_canvas_begin_x, dragon1_canvas_begin_y, dragon1_ctx);
dragon1(dragon1_begin_length, dragon1_begin_angle, 1);
dragon1_ctx.stroke();
$('#dragon1_canvas_clear').click(erase);

キャンバス

描画の開始座標

初期値の線

描画下限値:

ドラゴン曲線のpng形式のイメージを取得 (右クリックでダウンロード可能)

コード(BBEdit)

var $dragon1_div = $('#dragon1_div'),
    save = function () {
        var $img = $('<img>'),
            dragon1_canvas = document.getElementById('dragon1_canvas');
        $img.attr({'alt': 'dragon1', 'src': dragon1_canvas.toDataURL()});
        $dragon1_div.html($img);
    },
    erase = function () {
        $dragon1_div.html('');
    };
save();
$('#dragon1_div_erase').click(erase);

0 コメント:

コメントを投稿