roulette.htm

戻る

<!DOCTYPE HTML>
<HTML LANG="ja">

<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>ルーレット</TITLE>
</HEAD>

<BODY STYLE="background-color:#CCFFFF">

<DIV ALIGN=CENTER>
<BR>

<FORM>
<TABLE><TR>
<TD ALIGN=CENTER VALIGN=TOP>
<DIV STYLE="position:relative">
<CANVAS ID="img" WIDTH=240 HEIGHT=240></CANVAS>
<CANVAS ID="det" WIDTH=242 HEIGHT=242 STYLE="position:absolute; left:-1px; top:-1px; visibility:hidden"></CANVAS>
<IMG SRC="images/pointer.png" WIDTH=16 HEIGHT=12 STYLE="position:absolute; left:234px; top:114px">
</DIV>
<BR>
<INPUT TYPE=BUTTON ID="start" VALUE="スタート" onClick="start_stop()" STYLE="font-size:large">
</TD>
<TD WIDTH=30></TD>
<TD ALIGN=LEFT VALIGN=TOP>
<INPUT TYPE=BUTTON ID="load" VALUE="選択肢" onClick="load_sel()"><BR>
<DIV STYLE="width:12em; overflow:hidden; background-color:white; border:solid 1px; padding:2px">
<PRE ID="sel" STYLE="margin:0; cursor:default; -moz-user-select:none"></PRE>
</DIV><BR>
<LABEL><INPUT ID="div_1" TYPE=RADIO NAME="div" onClick="sel_div(1)">分割なし</LABEL>
<LABEL><INPUT ID="div_2" TYPE=RADIO NAME="div" onClick="sel_div(2)">2分割</LABEL>
<LABEL><INPUT ID="div_3" TYPE=RADIO NAME="div" CHECKED onClick="sel_div(3)">3分割</LABEL>
</TD>
</TR></TABLE>
</FORM>

</DIV>

<PRE ID="mea" STYLE="float:left; margin:0; visibility:hidden"></PRE><!-- テキスト サイズ計測用 -->

<SCRIPT TYPE="text/javascript">
<!--

// ファイル読み込み
function load_sel() {
  elem_file = document.createElement("INPUT");
  elem_file.type = "FILE";
  elem_file.onchange = file_sel;
  elem_file.click();
}

function file_sel() {
  file_reader = new FileReader();
  file_reader.onload = file_sel2;
  file_reader.readAsText(elem_file.files.item(0), "Shift_JIS");
}

function file_sel2() {
  sel_str = file_reader.result.split(/\r\n|\n|\r/);
  file_reader = undefined;

  if(!sel_str[sel_str.length - 1].length)
    sel_str.length--;
  var cnt = 12 - sel_str.length;
  if(cnt < 0)
    sel_str.length = 12;
  var str = sel_str.join("\u200b\n");
  for(; cnt > 0; cnt--)
    str += "\u200b\n";
  elem_sel.textContent = str + "\u200b";

  if(!sel_str.length) {  // 選択肢なし
    elem_det.style.visibility = "hidden";

    with(ctx_img) {
      clearRect(0, 0, 240, 240);
      beginPath();
      arc(120, 120, 119.5, 0, PI2, false);
      fillStyle = "white";
      fill();
      stroke();
    }

    n_sel = 0;

    elem_start.disabled = true;
    return;
  }
  elem_start.disabled = false;

  wheel();  // 盤面作成
}

// 分割数選択
function sel_div(n) {
  n_div = n;

  if(n_sel)  // 選択肢あり
    wheel();  // 盤面作成
}

// スタート/ストップ
function start_stop() {
  if(speed) {  // 回転中
    // 曲を停止
    elem_aud.pause();
    elem_aud.volume = 0;
    elem_aud = new Audio();
    elem_aud.mozSetup(1, 44100);
    clearTimeout(tune_id);

    elem_start.disabled = true;

    decel = 0.996 + Math.random() * 0.002;  // 減速割合

    // 減速中の音 作成
    var i, j;
    pcm[0] = pcm[24] = 0;
    pcm[12] = 0.3;
    pcm[36] = -0.3;
    for(i = 1, j = 64; i < 12; i++, j += 64)
      pcm[24 + i] = pcm[48 - i] = - (pcm[i] = pcm[24 - i] = sin[j]);
    for(i = 48; i < 3840; i++)
      pcm[i] = pcm[i - 48];
  }
  else {
    elem_det.style.visibility = "hidden";

    elem_start.value = "ストップ";
    elem_load.disabled = elem_div_1.disabled = elem_div_2.disabled = elem_div_3.disabled = true;

    speed = Math.PI / 90;
    decel = 1;

    i_tune = Math.floor(Math.random() * tunes.length);
    i_note = 0;
    note_len = 0;

    // 曲を開始
    elem_aud = new Audio();
    elem_aud.mozSetup(1, 44100);
    samples_written = 0;
    tune();

    int_id = setInterval(rotate, 16);
  }
}

// 回転
function rotate() {
  var prev_theta = theta;
  var n;
  speed *= decel;
  if((theta += speed) > PI2) {
    theta -= PI2;
  }
  else {
    if(decel < 1) {  // 減速中
      if((n = Math.floor((theta + angle_2) / angle)) != Math.floor((prev_theta + angle_2) / angle)) {
                                                                                // 選択肢の境を越えた
        // 減速中の音
        for(var pcm_off = 0;
              (pcm_off += elem_aud.mozWriteAudio(pcm.subarray(pcm_off, 3840 - pcm_off))) < 3840; )
          ;
      }
    }
  }

  draw_wheel();  // 盤面を描画する

  if(speed < 0.0002) {
    // 停止
    clearInterval(int_id);

    speed = 0;

    // 確定の表示
    with(ctx_det) {
      clearRect(0, 0, 242, 242);
      beginPath();
      if(n_pie == 1) {
        arc(121, 121, 119.5, 0, PI2, false);
      }
      else {
        moveTo(121, 121);
        var a = theta - n * angle;
        arc(121, 121, 119.5, - angle_2 + a, angle_2 + a, false);
        closePath();
      }
      stroke();
    }

    // 確定時の音 作成
    var i, j;
    pcm[0] = pcm[16] = 0;
    pcm[8] = 0.3;
    pcm[24] = -0.3;
    for(i = 1, j = 96; i < 8; i++, j += 96)
      pcm[16 + i] = pcm[32 - i] = - (pcm[i] = pcm[16 - i] = sin[j]);
    for(i = 32; i < 3584; i++)
      pcm[i] = pcm[i - 32];

    det_cnt = 9;
    int_id = setInterval(stopped, 80);
  }
}

// 確定表示
function stopped() {
  if(det_cnt-- & 0x1) {
    elem_det.style.visibility = "visible";

    // 確定時の音
    for(var pcm_off = 0;
          (pcm_off += elem_aud.mozWriteAudio(pcm.subarray(pcm_off, 3584 - pcm_off))) < 3584; )
      ;

    if(!det_cnt) {  // 確定表示終了
      clearInterval(int_id);

      elem_aud = undefined;

      elem_start.value = "スタート";
      elem_start.disabled
        = elem_load.disabled = elem_div_1.disabled = elem_div_2.disabled = elem_div_3.disabled = false;
    }
  }
  else {
    elem_det.style.visibility = "hidden";
  }
}

// 盤面を作成する
function wheel() {
  elem_det.style.visibility = "hidden";

  n_sel = sel_str.length;  // 選択肢数
  if(n_sel > 12 / n_div)
    n_sel = 12 / n_div;
  n_pie = n_sel * n_div;  // 盤の分割数

  angle = (angle_2 = Math.PI / n_pie) * 2;  // 扇形の角度とその 1/2
  if(n_pie > 2) {
    tan = Math.tan(angle_2);
    var y_2 = 121 * tan;
  }

  var elem_mea = document.getElementById("mea");
  elem_mea.style.font = elem_pie[0].getContext("2d").font;
  for(var i = 0; i < n_sel; i++) {
    with(elem_pie[i].getContext("2d")) {
      // 背景
      fillStyle = colors[i];
      if(n_pie > 2) {
        clearRect(0, 0, 120, 240);
        beginPath();
        moveTo(0, 120);
        lineTo(121, 120 - y_2);
        lineTo(121, 120 + y_2);
        fill();
      }
      else {
        fillRect(0, 0, 120, 240);
      }

      // テキスト
      // サイズ調整(最小は 8 ピクセルとする)
      elem_mea.textContent = sel_str[i];
      for(size = 14; size >= 8; size--) {
        elem_mea.style.fontSize = String(size) + "px";
        var h_2 = elem_mea.offsetHeight / 2;
        var x1 = (n_pie > 2) ? h_2 / tan : 0;
        var w = x1 + elem_mea.offsetWidth;
        if(w * w + h_2 * h_2 <= 14161/* 119*119 */)
          break;
      }
      var x2 = Math.sqrt(14161/* 119*119 */ - h_2 * h_2);
      var x;
      if(size == 7) {  // 8 ピクセルでも収まりきらない
        x = x1;
        save();
        beginPath();
        rect(0, 0, x2, 240);
        clip();
      }
      else {
        x = (x1 + x2 - elem_mea.offsetWidth) / 2;
      }
      fillStyle = "black";
      font = elem_mea.style.font;
      fillText(sel_str[i], x, 120);
      if(size == 7)
        restore();
    }
  }

  theta = 0;

  draw_wheel();  // 盤面を描画する
}

// 盤面を描画する
function draw_wheel() {
  with(ctx_img) {
    clearRect(0, 0, 240, 240);

    beginPath();
    arc(120, 120, 119.5, 0, PI2, false);

    // 各扇形
    save();
    clip();
    fillStyle = (n_sel == 1) ? colors[0] : "white";
    fillRect(0, 0, 240, 240);
    var i_pie = 0;
    for(var cnt = n_div; cnt; cnt--) {
      for(var i = 0; i < n_sel; i++) {
        save();
        translate(120, 120);
        rotate(theta - i_pie * angle);
        drawImage(elem_pie[i], 0, -120);
        restore();

        i_pie++;
      }
    }
    restore();

    // 周囲
    stroke();
  }
}

// 曲
function tune() {
  while(samples_written - elem_aud.mozCurrentSampleOffset() < 22050/* 500ms */) {
    for(var i_pcm = 0; i_pcm < 3840; i_pcm++) {
      var i;

      if(!note_len) {  // 1 音符/休符 終わり
        for(; ; ) {
          if(!i_note) {  // 曲終わり
            // 次の曲を選択
            if((i = Math.floor(Math.random() * (tunes.length - 1))) >= i_tune)
              i++;
            notes = tunes[i_tune = i];
            note_oct = 4;
          }

          var c = notes.charAt(i_note);  // 楽譜から 1 文字取り出し
          if(++i_note == notes.length)
            i_note = 0;

          if(c == "*") {
            // オクターブの設定
            if(!i_note)
              continue;
            c = notes.charAt(i_note);  // 次の 1 文字取り出し
            if(++i_note == notes.length)
              i_note = 0;
            if(c >= "0" && c <= "8")
              note_oct = c.charCodeAt() - "0".charCodeAt();
          }
          else {
            if(c >= "A" && c <= "G" || c == "_") {
              if(c == "_") {  // 休符
                note_a1 = 0;
              }
              else {
                if(!i_note)
                  continue;
                i = note_oct * 12 + 3 + n_i[c.charCodeAt() - "A".charCodeAt()];
                switch(notes.charAt(i_note)) {  // 次の 1 文字を調べる
                case "+":  // ♯
                  i++;
                  if(++i_note == notes.length)
                    i_note = 0;
                  break;
                case "-":  // ♭
                  i--;
                  if(++i_note == notes.length)
                    i_note = 0;
                  break;
                }
                if(i >= 0 && i < 88)
                  note_a1 = freq_a[i];
              }
              // 音符/休符の長さ
              var n = "";
              while(i_note) {
                c = notes.charAt(i_note);  // 次の 1 文字を調べる
                if(c < "0" || c > "9")
                  break;
                n += c;
                if(++i_note == notes.length)
                  i_note = 0;
              }
              if(n.length) {
                if((note_len = parseInt(n, 10) * UNIT_LEN)) {
                  note_cnt = 0;
                  break;
                }
              }
            }
          }
        }
      }

      if(note_a1 == 0) {  // 休符
        val = 0;
      }
      else {
        i = Math.round(note_cnt++ * note_a1) % 3072;
        if     (i < 768)
          val = sin[i];
        else if(i < 1536)
          val = sin[1536 - i];
        else if(i < 2304)
          val = - sin[i - 1536];
        else
          val = - sin[3072 - i];
        if(note_len < 20)
          val *= note_len / 20;
      }
      pcm[i_pcm] = val;

      note_len--;
    }
    for(var pcm_off = 0; ; ) {
      var written = elem_aud.mozWriteAudio(pcm.subarray(pcm_off, 3840 - pcm_off));
      samples_written += written;
      if((pcm_off += written) == 3840)
        break;
    }
  }

  tune_id = setTimeout(tune, 100);
}

  colors = ["#FF9999", "#99FF99", "#FFFF99", "#9999FF", "#FF99FF", "#99FFFF",   // 背景色
            "#FFCC99", "#99CCFF", "#FF99CC", "#99FFCC", "#CC99FF", "#CCFF99"];
  sel_str = ["カツ丼", "カレーライス", "ラーメン", "ハンバーガー"];  // 選択肢文字列
  tunes = [  // 曲テーブル
    "*4G3_1G4G4E4G4A4G4E7_1E4D11_1E4D7_1G3_1G4G4E4G4A4G4E7_1D8E4D4C11_1"
  +   "G3_1G4G4E4G4A4G4E7_1E4D11_1E4D7_1G3_1G4G4E4G4A4G4E7_1D8E4D4C11_1",
    "*4C2D2E4G4G6A2G4E4C6D2E4E4D4C4D11_1C2D2E4G4G6A2G4E4C6D2E4E4D4D4C11_1"
  +   "C2D2E4G4G6A2G4E4C6D2E4E4D4C4D11_1C2D2E4G4G6A2G4E4C6D2E4E4D4D4C11_1",
    "*4G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2*4G2G2G2_2G2G2*5C2_2E2_2D2_2*4B2_2G2_2"
  +   "G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2C2E2G10F2E2D2C2_2E2_2C2_2"
  + "*4G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2*4G2G2G2_2G2G2*5C2_2E2_2D2_2*4B2_2G2_2"
  +   "G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2C2E2G10F2E2D2C2_2E2_2C2_2",
    "*4A2G2C2_2*5C2_2C2_2*4A2G2C2_2*5C2_2C2_2*4A2G2C2_2*5C2_2*3A2_2*5C2_2*3G2_2*4B2_2B2_2"
  + "A2G2*3G2_2*4B2_2B2_2A2G2*3G2_2*4B2_2B2_2A2G2*3G2_2*4B2_2*3A2_2*4B2_2C2_2*5C2_2C2_2"
  + "*4A2G2*5E2_2C2_2C2_2*4A2G2*5E2_2C2_2C2_2*4A2G2*5E2_2C2_2G2_2C2_2A2_2*4B2_2B2_2"
  + "A2G2*5A2_2*4B2_2B2_2A2G2*5A2_2*4B2_2B2_2A2G2*5A2_2*4B2_2*5G2_2*4B2_2*5E2_2C2_2C2_2"
  ];
  n_i = [9, 11, 0, 2, 4, 5, 7];  // 音名 - 周波数インデックス 変換

  UNIT_LEN = 2400;

  // 盤面表示用 Canvas
  (ctx_img = (elem_img = document.getElementById("img")).getContext("2d")).strokeStyle = "black";

  // 作業用 Canvas
  elem_pie = new Array();
  elem_pie.length = 12;
  for(i = 0; i < 12; i++) {
    with(elem_pie[i] = document.createElement("CANVAS")) {
      width = 120;
      height = 240;
      with(getContext("2d")) {
        textAlign = "left";
        textBaseline = "middle";
      }
    }
  }

  // 確定表示用 Canvas
  with(ctx_det = (elem_det = document.getElementById("det")).getContext("2d")) {
    strokeStyle = "red";
    lineWidth = 2;
  }

  // 選択肢
  (elem_sel = document.getElementById("sel"))
    .textContent = sel_str.join("\n")
                     + "\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b";  // 選択肢サンプル

  n_div = 3;  // 分割数

  PI2 = 2 * Math.PI;

  wheel();  // 盤面作成

  speed = 0;

  // 音階周波数テーブル作成
  // [48] が 440Hz
  // sin テーブルのインデックスに対応する値に換算して格納する
  freq_a = new Float32Array(88);
  a = 440 / 44100 * 3072;
  for(i = 0; i < 88; i++)
    freq_a[i] = a * Math.pow(2, (i - 48) / 12);

  // sin テーブル作成
  sin = new Float32Array(769);
  sin[0] = 0;
  sin[768] = 0.3;
  for(i = 1; i < 768; i++)
    sin[i] = Math.sin(i * Math.PI / 1536) * 0.3;

  pcm = new Float32Array(3840);  // PCM データ

  elem_start = document.getElementById("start");
  elem_load = document.getElementById("load");
  elem_div_1 = document.getElementById("div_1");
  elem_div_2 = document.getElementById("div_2");
  elem_div_3 = document.getElementById("div_3");

  // ページを再ロードしたときのため
  document.forms[0].reset();
  elem_start.disabled
    = elem_load.disabled = elem_div_1.disabled = elem_div_2.disabled = elem_div_3.disabled = false;

//-->
</SCRIPT>

</BODY>

</HTML>