package { import flash.display.Sprite; import flash.display.Shape; import flash.display.StageScaleMode; import flash.display.SimpleButton; import flash.text.TextField; import flash.text.TextFieldType; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.text.TextLineMetrics; import flash.text.TextFormatAlign; import flash.events.Event; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.events.IOErrorEvent; import flash.events.SampleDataEvent; import flash.ui.Mouse; import flash.ui.MouseCursor; import flash.net.FileReference; import flash.net.FileFilter; import flash.media.Sound; import flash.media.SoundChannel; import flash.utils.*; [SWF(width=480, height=330, frameRate=30, backgroundColor=0xCCFFFF)] public class roulette extends Sprite { private var sel_back:Array = []; // 選択肢背景 private var sel_clip:Array = []; // クリッピング用マスク private var sel_text:Array = []; // テキスト private var sel_bdr:Sprite; // 確定の表示 private var dmy_back:Shape = null; // ダミーの背景(選択肢なしの場合用) private var start:SimpleButton; // スタート ボタン private var stop:SimpleButton; // ストップ ボタン private var load:SimpleButton; // 選択肢ボタン private var err:TextField; // エラー表示 private var sel:TextField; // 選択肢リスト private var div:Array = new Array(3); // 分割数選択 private var div_fmt:TextFormat; // 分割数選択 テキスト フォーマット private var rot_tmr:Timer; // 回転用タイマー private var stp_tmr:Timer; // 確定表示用タイマー private var file:FileReference; // ファイル読み込み private var snd:Sound; // サウンド private var snd_ch:SoundChannel; // サウンド チャネル private var colors:Array = [0xFF9999, 0x99FF99, 0xFFFF99, 0x9999FF, 0xFF99FF, 0x99FFFF, // 背景色 0xFFCC99, 0x99CCFF, 0xFF99CC, 0x99FFCC, 0xCC99FF, 0xCCFF99]; private var sel_str:Array = ["カツ丼", "カレーライス", "ラーメン", "ハンバーガー"]; // 選択肢文字列 private var n_div:int; // 分割数 private var theta:Number; // 回転角度 private var n_sel:int = 0; // 選択肢数 private var n_pie:int; // 盤の分割数 private var speed:Number; // 速度 private var decel:Number; // 減速割合 private var freq:Array; // 音階周波数テーブル [48] が 440Hz private var tunes:Array = [ // 曲テーブル "*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" ]; private var i_tune:int; // 曲インデックス private var notes:String; // 楽譜 private var i_note:int; // 楽譜インデックス private var note_oct:int; // 音符オクターブ番号 private var note_len:int; // 音符/休符 長さ private var n_i:Array = [9, 11, 0, 2, 4, 5, 7]; // 音名 - 周波数インデックス 変換 private var note_a1:Number; // 音符 1 サンプル分の角度 private var note_cnt:int; // 音符 サンプル数カウント private const UNIT_LEN:int = 2400; public function roulette():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); stage.scaleMode = StageScaleMode.NO_SCALE; var fmt:TextFormat; var i:int; // 盤の固定表示部 var s:Shape = new Shape(); s.x = 140; s.y = 140; with(s.graphics) { lineStyle(0, 0x000000); drawCircle(0, 0, 120); lineStyle(); beginFill(0xFF0000); moveTo(114, 0); lineTo(130, -6); lineTo(130, 6); lineTo(114, 0); endFill(); } addChild(s); // スタート ボタン start = create_button("スタート", 72, 32, 0x000000, 0x33FF33, 0xFFFFFF, 0x009900, start_click); start.x = 50; start.y = 280; addChild(start); // ストップ ボタン stop = create_button("ストップ", 72, 32, 0x000000, 0xFF6633, 0xFFFFFF, 0x993300, stop_click); stop.x = 158; stop.y = 280; stop.enabled = stop.mouseEnabled = false; addChild(stop); // 選択肢ボタン load = create_button("選択肢", 64, 28, 0x000000, 0xFFFF00, 0xFFFFFF, 0x999900, load_click); load.x = 300; load.y = 20; addChild(load); // エラー表示 err = new TextField(); with(err) { x = 372; y = 24; type = TextFieldType.DYNAMIC; selectable = false; visible = false; text = "読み込みエラー"; fmt = new TextFormat(); with(fmt) { size = 12; color = 0xFF0000; } setTextFormat(fmt); } addChild(err); // 選択肢リスト sel = new TextField(); with(sel) { x = 300; y = 60; width = 160; type = TextFieldType.DYNAMIC; selectable = false; multiline = true; background = true; backgroundColor = 0xFFFFFF; border = true; borderColor = 0x666666; fmt = new TextFormat(); with(fmt) { align = TextFormatAlign.LEFT; leading = 0; color = 0x000000; } defaultTextFormat = fmt; // 高さ調整 text = ""; var tlm:TextLineMetrics = getLineMetrics(0); height = 12 * tlm.height + 4; } addChild(sel); sel.text = sel_str.join("\n"); // 選択肢サンプル // 分割数選択 div_fmt = new TextFormat(); with(div_fmt) { align = TextFormatAlign.LEFT; size = 12; color = 0x000000; } for(i = 0; i < 3; i++) { div[i] = new TextField(); with(div[i]) { y = 60 + sel.height + 20; type = TextFieldType.DYNAMIC; selectable = false; autoSize = TextFieldAutoSize.LEFT; background = true; addEventListener(MouseEvent.MOUSE_DOWN, div_mouse_down); addEventListener(MouseEvent.MOUSE_UP, div_mouse_up1); addEventListener(MouseEvent.MOUSE_OVER, div_mouse_over); addEventListener(MouseEvent.MOUSE_OUT, div_mouse_out); } addChild(div[i]); } // 分割なし with(div[0]) { x = 300; backgroundColor = 0xCCCCFF; text = "分割なし"; setTextFormat(div_fmt); } // 2分割 with(div[1]) { x = 300 + div[0].width + 10; backgroundColor = 0xCCCCFF; text = "2分割"; setTextFormat(div_fmt); } // 3分割 with(div[2]) { x = div[1].x + div[1].width + 10; backgroundColor = 0x000099; text = "3分割"; div_fmt.color = 0xFFFFFF; setTextFormat(div_fmt); } n_div = 3; wheel(); // 盤面作成 // 音階周波数テーブル作成 freq = new Array(88); for(i = 0; i < 88; i++) freq[i] = 440 * Math.pow(2, (i - 48) / 12); rot_tmr = new Timer(5, 0); rot_tmr.addEventListener(TimerEvent.TIMER, rotate); stp_tmr = new Timer(80, 9); stp_tmr.addEventListener(TimerEvent.TIMER, stopped); snd = new Sound(); snd.addEventListener(SampleDataEvent.SAMPLE_DATA, snd_sample_data); } // 盤面を作成する private function wheel():void { var i:int, j:int; // 現在の部品削除 if(n_sel) { for(i = 0; i < n_sel; i++) { removeChild(sel_back[i]); if(n_pie > 1) removeChild(sel_clip[i]); } for(i = 0; i < n_pie; i++) removeChild(sel_text[i]); removeChild(sel_bdr); } else { if(dmy_back) { removeChild(dmy_back); dmy_back = null; } } if(!(n_sel = sel_str.length)) { // 選択肢数 // 選択肢なし dmy_back = new Shape(); with(dmy_back) { x = 140; y = 140; with(graphics) { lineStyle(); beginFill(0xFFFFFF); drawCircle(0, 0, 120); endFill(); } } addChildAt(dmy_back, 0); start.enabled = start.mouseEnabled = false; return; } start.enabled = start.mouseEnabled = true; if(n_sel > 12 / n_div) n_sel = 12 / n_div; n_pie = n_sel * n_div; // 盤の分割数 sel_back.length = sel_clip.length = n_sel; sel_text.length = n_pie; var tan:Number; var y_2:Number; if(n_pie > 2) { tan = Math.tan(Math.PI / n_pie); y_2 = 121 * tan; } var x1:Number, y1:Number; var m:Shape; // テキスト var fmt:TextFormat; fmt = new TextFormat(); with(fmt) { align = TextFormatAlign.LEFT; color = 0x000000; } for(i = 0; i < n_sel; i++) { // サイズ調整(最小は 8 ピクセルとする) var txt1:TextField = new TextField(); var size:int; var h_2:Number; with(txt1) { type = TextFieldType.DYNAMIC; selectable = false; autoSize = TextFieldAutoSize.LEFT; text = sel_str[i]; for(size = 14; size >= 8; size--) { fmt.size = size; setTextFormat(fmt); h_2 = txt1.height / 2; x1 = (n_pie > 2) ? h_2 / tan : 0; var w:Number = x1 + txt1.width; if(w * w + h_2 * h_2 <= 120 * 120) break; } } for(j = 0; j < n_div; j++) { var base:Sprite = new Sprite(); with(base) { x = 140; y = 140; rotationZ = - (j * n_sel + i) * 360 / n_pie; var txt2:TextField = new TextField(); with(txt2) { var x2:Number = Math.sqrt(120 * 120 - h_2 * h_2); if(size == 7) { // 8 ピクセルでも収まりきらない x = x1; width = x2 - x1; } else { x = (x1 + x2 - txt1.width) / 2; width = txt1.width; } y = - h_2; height = txt1.height; type = TextFieldType.DYNAMIC; selectable = false; text = sel_str[i]; setTextFormat(fmt); } addChild(txt2); } addChildAt(base, 0); sel_text[j * n_sel + i] = base; } } // 背景 for(i = 0; i < n_sel; i++) { // 色 var back:Shape = new Shape(); with(back) { x = 140; y = 140; with(graphics) { lineStyle(); beginFill(colors[i]); drawCircle(0, 0, 120); endFill(); } if(n_pie > 1) { // クリップ var mg:Sprite = new Sprite(); with(mg) { x = 140; y = 140; rotation = - i * 360 / n_pie; if(n_pie == 2) { // 半円 for(j = 0; j < n_div; j++) { m = new Shape(); with(m.graphics) { lineStyle(); beginFill(0x000000); drawRect(0, -120, 120, 240); endFill(); } m.rotation = j * 360 / n_div; addChild(m); } } else { for(j = 0; j < n_div; j++) { m = new Shape(); with(m.graphics) { lineStyle(); beginFill(0x000000); moveTo(0, 0); lineTo(121, - y_2); lineTo(121, y_2); lineTo(0, 0); endFill(); } m.rotation = j * 360 / n_div; addChild(m); } } } addChild(mg); sel_clip[i] = mask = mg; } } addChildAt(back, 0); sel_back[i] = back; } // 確定の表示 sel_bdr = new Sprite(); with(sel_bdr) { x = 140; y = 140; visible = false; // 半径 with(graphics) { lineStyle(2, 0xFF0000); if(n_pie > 1) { if(n_pie == 2) { // 半円 moveTo(0, -120); lineTo(0, 120); } else { x1 = 120 * Math.cos(Math.PI / n_pie); y1 = 120 * Math.sin(Math.PI / n_pie); moveTo(x1, - y1); lineTo(0, 0); lineTo(x1, y1); } } } // 円弧 var arc:Shape = new Shape(); with(arc) { x = 0; y = 0; with(graphics) { lineStyle(2, 0xFF0000); drawCircle(0, 0, 120); if(n_pie > 1) { // クリップ m = new Shape(); m.x = 0; m.y = 0; with(m.graphics) { lineStyle(); beginFill(0x000000); if(n_pie == 2) { // 半円 drawRect(0, -120, 121, 240); } else { moveTo(0, 0); lineTo(121, - y_2); lineTo(121, y_2); lineTo(0, 0); } endFill(); } addChild(m); mask = m; } } } addChild(arc); } addChild(sel_bdr); theta = 0; } // ボタンを作成する private function create_button(lbl:String, w:int, h:int, fore_u:uint, back_u:uint, fore_d:uint, back_d:uint, btn_click:Function):SimpleButton { var btn:SimpleButton = new SimpleButton(); with(btn) { upState = overState = hitTestState = draw_button(lbl, w, h, fore_u, back_u); downState = draw_button(lbl, w, h, fore_d, back_d); tabEnabled = false; addEventListener(MouseEvent.CLICK, btn_click); } return btn; } // ボタンのイメージを作成する private function draw_button(lbl:String, w:int, h:int, fore:uint, back:uint):Sprite { var s:Sprite = new Sprite(); with(s.graphics) { lineStyle(); beginFill(back); drawRoundRect(0, 0, w, h, 10); endFill(); } var txt:TextField = new TextField(); with(txt) { type = TextFieldType.DYNAMIC; text = lbl; selectable = false; autoSize = TextFieldAutoSize.LEFT; var fmt:TextFormat = new TextFormat(); with(fmt) { size = 14; bold = true; color = fore; } setTextFormat(fmt); x = (w - width) >> 1; y = (h - height) >> 1; } s.addChild(txt); return s; } // ファイル読み込み private function load_click(e:MouseEvent):void { err.visible = false; file = new FileReference(); with(file) { addEventListener(Event.SELECT, load_select); addEventListener(Event.CANCEL, load_cancel); addEventListener(Event.COMPLETE, load_complete); addEventListener(IOErrorEvent.IO_ERROR, load_io_error); } mouseChildren = false; file.browse([new FileFilter("テキスト ファイル(*.text,*.txt)", "*.text;*.txt"), new FileFilter("すべてのファイル", "*")]); } private function load_select(e:Event):void { file.load(); } private function load_cancel(e:Event):void { with(file) { removeEventListener(Event.SELECT, load_select); removeEventListener(Event.CANCEL, load_cancel); removeEventListener(Event.COMPLETE, load_complete); removeEventListener(IOErrorEvent.IO_ERROR, load_io_error); } file = null; mouseChildren = true; } private function load_complete(e:Event):void { var str:String = file.data.readMultiByte(file.data.length, ""); load_cancel(null); sel_str.length = 0; // クリア // 改行コード CR+LF,CR を LF に変換 // Flex 3 では,正規表現を使ったときの“\r”の扱いがおかしいようなので, // ここでは正規表現は使わない. str = str.split("\r\n").join("\n"); str = str.split("\r").join("\n"); sel_str = str.split("\n"); if(sel_str.length) { if(!sel_str[sel_str.length - 1].length) sel_str.length--; } if(sel_str.length > 12) sel_str.length = 12; sel.text = sel_str.join("\n"); wheel(); // 盤面作成 } private function load_io_error(e:IOErrorEvent):void { load_cancel(null); err.visible = true; sel_str.length = 0; // クリア sel.text = ""; wheel(); // 盤面作成 } // スタート private function start_click(e:MouseEvent):void { start.enabled = start.mouseEnabled = load.enabled = load.mouseEnabled = div[0].mouseEnabled = div[1].mouseEnabled = div[2].mouseEnabled = false; stop.enabled = stop.mouseEnabled = true; sel_bdr.visible = false; speed = 2; decel = 1; rot_tmr.reset(); rot_tmr.start(); i_tune = int(Math.random() * tunes.length); i_note = 0; note_len = 0; snd_ch = snd.play(); } // ストップ private function stop_click(e:MouseEvent):void { stop.enabled = stop.mouseEnabled = false; decel = 0.996 + Math.random() * 0.002; // 減速割合 snd_ch.stop(); snd_ch = null; } // 分割数選択 private function div_mouse_down(e:MouseEvent):void { var new_n_div:int; if (e.currentTarget == div[0]) new_n_div = 1; else if(e.currentTarget == div[1]) new_n_div = 2; else new_n_div = 3; if(new_n_div != n_div) { div[n_div - 1].backgroundColor = 0xCCCCFF; div_fmt.color = 0x000000; div[n_div - 1].setTextFormat(div_fmt); n_div = new_n_div; div[n_div - 1].backgroundColor = 0x000099; div_fmt.color = 0xFFFFFF; div[n_div - 1].setTextFormat(div_fmt); if(n_sel) wheel(); // 盤面作成 } stage.addEventListener(MouseEvent.MOUSE_UP, div_mouse_up2); } private function div_mouse_up1(e:MouseEvent):void { Mouse.cursor = MouseCursor.BUTTON; } private function div_mouse_up2(e:MouseEvent):void { stage.removeEventListener(MouseEvent.MOUSE_UP, div_mouse_up2); if(!div[0].hitTestPoint(e.stageX, e.stageY) && !div[1].hitTestPoint(e.stageX, e.stageY) && !div[2].hitTestPoint(e.stageX, e.stageY)) Mouse.cursor = MouseCursor.AUTO; } private function div_mouse_over(e:MouseEvent):void { if(!e.buttonDown) Mouse.cursor = MouseCursor.BUTTON; } private function div_mouse_out(e:MouseEvent):void { if(!e.buttonDown) Mouse.cursor = MouseCursor.AUTO; } // 回転 private function rotate(e:TimerEvent):void { var a:Number = 360 / n_pie; var n1:int = int((theta + a / 2) / a); var n2:int; speed *= decel; if((theta += speed) > 360) { theta -= 360; } else { if(decel < 1) { // ストップ中 if((n2 = int((theta + a / 2) / a)) != n1) { // 選択肢の境を越えた i_tune = -1; snd.play(); } } } var i:int; for(i = 0; i < n_sel; i++) sel_clip[i].rotation = theta + i * a; for(i = 0; i < n_pie; i++) sel_text[i].rotationZ = theta - i * a; if(speed < 0.01) { // 停止 rot_tmr.stop(); sel_bdr.rotation = theta - n2 * a; stp_tmr.reset(); stp_tmr.start(); } } // 確定表示 private function stopped(e:TimerEvent):void { if(stp_tmr.currentCount & 0x1) { sel_bdr.visible = true; i_tune = -2; snd.play(); if(stp_tmr.currentCount == stp_tmr.repeatCount) { start.enabled = start.mouseEnabled = load.enabled = load.mouseEnabled = div[0].mouseEnabled = div[1].mouseEnabled = div[2].mouseEnabled = true; return; } } else { sel_bdr.visible = false; } } // サウンド private function snd_sample_data(e:SampleDataEvent):void { var cnt:int; var val:Number; switch(i_tune) { case -1: // 減速中の音 for(cnt = 4000; cnt >= 0; cnt--) { val = Math.sin((cnt % 50) * Math.PI / 25) * 0.3; e.data.writeFloat(val); e.data.writeFloat(val); } i_tune = -3; break; case -2: // 確定時の音 for(cnt = 3600; cnt >= 0; cnt--) { val = Math.sin((cnt % 30) * Math.PI / 15) * 0.3; e.data.writeFloat(val); e.data.writeFloat(val); } i_tune = -3; break; case -3: // 自動的に再生が停止する break; default: // 曲 for(cnt = 8192; cnt; cnt--) { if(!note_len) { // 1 音符/休符 終わり for(; ; ) { var i:int; if(!i_note) { // 曲終わり // 次の曲を選択 if((i = int(Math.random() * (tunes.length - 1))) >= i_tune) i++; notes = tunes[i_tune = i]; note_oct = 4; } var c:String = 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[i] / 44100 * 2 * Math.PI; } // 音符/休符の長さ var n:String = ""; 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) * UNIT_LEN)) { note_cnt = 0; break; } } } } } } if(note_a1 == 0) { // 休符 val = 0; } else { val = Math.sin(note_cnt++ * note_a1) * 0.3; if(note_len < 20) val *= note_len / 20; } e.data.writeFloat(val); e.data.writeFloat(val); note_len--; } } } } }