パーフェクトシャッフル

http://d.hatena.ne.jp/nishiohirokazu/20100107/1262835414
↑が奇麗すぎるので、できないかと思ってやってみた。

が、どうしても円形にする方法がわからない。
しかたがないので5角形で止まっているところまで。

実は時系列的には上の記事で書いたベジェが後になる。
今度は2次ベジェ曲線を使ってやってみる予定。

var ceil = Math.ceil;
var round = Math.round;
var cos = Math.cos;
var sin = Math.sin;
var tan = Math.tan;
var pow = Math.pow;
var sqrt = Math.sqrt;
var pi = Math.PI;
var pi_3 = Math.PI/3;

function perfectshuffle(arr){
  var narr = [];
  var m = ceil(arr.length/2);
  var a1 = arr.slice(0,m);
  var a2 = arr.slice(m);
  for(var i=0;0<a1.length || 0<a2.length;i++){
    var t = (i & 1) == 0 ? a2 : a1;
    if(t.length < 1){continue}
    narr.push(t.shift());
  }
  return narr;
}
function colorgenerate(n){
  var color = [];
  for(var i=0;i<n;i++){
    var t = 5/3 * pi * i / n;
    var rgb = [
      255 * (cos(t + 0 * pi_3) + 0.5),
      255 * (cos(t + 4 * pi_3) + 0.5),
      255 * (cos(t + 2 * pi_3) + 0.5),
    ];
    for(var c=0;c<rgb.length;c++){
      if(rgb[c] < 0){rgb[c] = 0}
      if(255 < rgb[c]){rgb[c] = 255}
      rgb[c] = round(rgb[c])
    }
    color[i] = "rgba("+rgb.join(',')+",255)";
  }  
  return color;
}
function initialize(){
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.fillRect(0,0,400,400);

  var drawline = function(p1,p2,color){
    ctx.beginPath();
    console.log(color);
    ctx.strokeStyle = color || 'red';
    ctx.moveTo(p1[0],p1[1]);
    ctx.lineTo(p2[0],p2[1]);
    ctx.moveTo(p1[0],p1[1]);
    ctx.closePath();
    ctx.stroke();
  };
  var drawcurve = function(p1,cp,p2,color){
    ctx.beginPath();
    ctx.strokeStyle = color;
    ctx.moveTo(p1[0],p1[1]);
    ctx.quadraticCurveTo(cp[0],cp[1],p2[0],p2[1]);
    ctx.moveTo(p1[0],p1[1]);
    ctx.closePath();
    ctx.stroke();
  };

  var cnt = 5;
  var d = 6;      
  var center = [200,200];
  var target = [];
  var next;
  var cards = 30;
  var color = colorgenerate(cards);

  for(var i=0;i<cards;i++){target[i] = i}

  for(var j=0;j<cnt;j++){
    next = perfectshuffle(target);
    for(var i=0;i<target.length;i++){
      var t = 0;
      for(;t<next.length;t++){
        if(next[t] == target[i]){
          break;
        }
      }
      var r1,r2;
      r1 = d * (i+0.5);
      r2 = d * (t+0.5);
      drawline(
        [r1 * cos(2 * pi * j / cnt) + center[0],r1 * -sin(2 * pi * j / cnt) + center[1]],
        [r2 * cos(2 * pi * (j+1) / cnt) + center[0],r2 * -sin(2 * pi * (j+1) / cnt) + center[1]],
        color[target[i]]
      );          
    }
    target = next;
  }
}
window.addEventListener('load',initialize,false);

[追記]できた。

[追記]線と線の接続部分が汚いのでソースを書き直した。

■注意点

  1. 2次ベジェ曲線の制御点で使用されている1.4は表示されたグラフィックを見ながら調整したものである。
  2. 色生成の引数の透過度0.03、及びimagedataの明さ調整の為の乗数24はグラフィックを身ながら調整したものである。
var ceil = Math.ceil;
var round = Math.round;
var cos = Math.cos;
var sin = Math.sin;
var tan = Math.tan;
var max = Math.max;
var pow = Math.pow;
var sqrt = Math.sqrt;
var pi = Math.PI;
var pi_3 = Math.PI/3;

function perfectshuffle(arr){
  var narr = [];
  var m = ceil(arr.length/2);
  var a1 = arr.slice(0,m);
  var a2 = arr.slice(m);
  for(var i=0;0<a1.length || 0<a2.length;i++){
    var t = (i & 1) == 0 ? a2 : a1;
    if(t.length < 1){continue}
    narr.push(t.shift());
  }
  return narr;
}
function colorgenerate(n,opacity){
  var color = [];
  if(opacity == undefined){
    opacity = 1.0;
  }
  for(var i=0;i<n;i++){
    var t = 5/3 * pi * i / n;
    var rgb = [
      255 * (cos(t + 0 * pi_3) + 0.5),
      255 * (cos(t + 4 * pi_3) + 0.5),
      255 * (cos(t + 2 * pi_3) + 0.5),
    ];
    for(var c=0;c<rgb.length;c++){
      if(rgb[c] < 0){rgb[c] = 0}
      if(255 < rgb[c]){rgb[c] = 255}
      rgb[c] = round(rgb[c])
    }
    color[i] = "rgba("+rgb.join(',')+","+opacity+")";
  }  
  return color;
}
function initialize(){
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  ctx.fillRect(0,0,430,430);

  var cnt = 5;
  var d = 6;      
  var center = [215,215];
  var target = [];
  var next;
  var linewidth = 4;
  var cards = 30;
  var color = colorgenerate(cards,0.03);

  for(var i=0;i<cards;i++){target[i] = i}
  var r1,r2,r3;
  var list = [];
  var next = target;
  for(var i=0;i<5;i++){
    list.push(next)
    next=perfectshuffle(next);
  }
  var m = 0;
  for(var i=0;i<target.length;i++){
    m = i;
    ctx.beginPath();
    for(var t=0;t<list.length;t++){
      var next = t+1 < cnt ? list[t+1] : list[0]; 
      var n = 0;
      for(;n<next.length;n++){
        if(list[t][m] == next[n]){
          break;
        }
      }
      r1 = d * (m+0.5) + 20;
      r2 = d * (n+0.5) + 20;
      r3 = d * max(m,n) + 1.4 * max(m,n) + 20;
      ctx.strokeStyle = color[i];
      ctx.lineWidth = 4;
      if(t == 0){
        ctx.moveTo(r1 * cos(2 * pi * t / cnt) + center[0],r1 * -sin(2 * pi * t / cnt) + center[1]);
      }
      ctx.quadraticCurveTo(
        r3 * cos(2 * pi * (t+0.5) / cnt) + center[0],r3 * -sin(2 * pi * (t+0.5) / cnt) + center[1],
        r2 * cos(2 * pi * (t+1) / cnt) + center[0],r2 * -sin(2 * pi * (t+1) / cnt) + center[1]
      )
      m = n;
    }
    ctx.closePath();
    ctx.stroke();
  }
  // [参考] http://nullpo.s101.xrea.com/gomi/canvas_filter_test.js
  var imagedata = ctx.getImageData(0,0,430,430);
  var br = 24;
  for(var i=0;i<imagedata.data.length;i+=4){
    var r = imagedata.data[i];
    var g = imagedata.data[i+1];
    var b = imagedata.data[i+2];
    var a = imagedata.data[i+3];
    r *= br;
    g *= br;
    b *= br;
    if(255 < r){r = 255};if(255 < g){g = 255};if(255 < b){b = 255}
    imagedata.data[i] = r;
    imagedata.data[i+1] = g;
    imagedata.data[i+2] = b;
  }
  ctx.putImageData(imagedata,0,0);
}
window.addEventListener('load',initialize,false);

canvasピクセル単位で処理を行う為に、getImageDataやputImageDataを使ってみた。
getImageDataメソッドで取得した CanvasPixelArrayは4つで1ピクセルの情報を扱う。

[参考]

50枚のカードを8回シャッフルするバージョンはこちら