In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

【算数】虫食い算

マウスかキーボードで操作します。
難易度はかなりまちまちです。
Get Adobe Flash player
by kura07 15 Jan 2012
    Embed
/**
 * Copyright kura07 ( http://wonderfl.net/user/kura07 )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/oEoA
 */

package {

    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.display.Shape;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.ColorTransform;
    import caurina.transitions.Tweener;
    import caurina.transitions.properties.DisplayShortcuts;
    
    [SWF(width = 465, height = 465, backgroundColor = 0x000000, frameRate = 60)]
    
    public class Mushikuizan extends Sprite {
        
        private var spHissan:Hissan;
        private var spAnswer:Hissan;
        private var spList:NumList;
        private var tfNotice:Notice;
        private var btOperation:OperationButton;
        private const NUM_W:uint = 20;    // デジタル数字の幅
        private const W:uint = 35;    // 余白を含めた幅
        private const H:uint = 55;    // 余白を含めた高さ
        private const S:Number = .15;    // ラインのためのスペース(数字の高さ=1)
        private const M:uint = 2;    // 虫穴の余白
        private const DIGIT:uint = 2;    // 桁数。難易度と処理速度的に2か3のみ
        
        public function Mushikuizan():void {
            
            init();
            draw();
            createQuestion();
            
        }
        
        private function init():void {
            
            Tweener.registerSpecialPropertySplitter("_scale", DisplayShortcuts._scale_splitter);
            Num.init(W, H, M, NUM_W);
            Hissan.init(W, H, NUM_W, DIGIT, S);
            NumList.init(W);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            
        }
        
        private function draw():void {
            
            // 背景対策
            var sh:Shape = new Shape();
            sh.graphics.beginFill(0x000000); sh.graphics.drawRect(0, 0, 465, 465);
            stage.addChild(sh);
            
            // 筆算
            stage.addChild(spHissan = new Hissan());
            spHissan.x = int(465 / 4 - W * DIGIT); spHissan.y = int(400 / 2 - H * (3 + DIGIT) / 2 - S);
            stage.addChild(spAnswer = new Hissan()).visible = false;
            spAnswer.x = int(465 / 4 * 3 - W * DIGIT); spAnswer.y = int(400 / 2 - H * (3 + DIGIT) / 2 - S);
            
            // お知らせ
            stage.addChild(tfNotice = new Notice());
            tfNotice.x = 465 / 4 * 3; tfNotice.y = 190;
            
            // 数字リスト
            stage.addChild(spList = new NumList());
            spList.y = 400;
            listNums = spList.draw(onListClick);
            
            // ギブアップ・次ボタン
            btOperation = new OperationButton("Give\nup", 400 + 65 / 2 - OperationButton.SIZE / 2);
            btOperation.x = 400 + 65 / 2 - OperationButton.SIZE / 2; btOperation.enabled = false;
            btOperation.addEventListener(MouseEvent.CLICK, giveUp);
            stage.addChild(btOperation);
            
        }
        
        // 問題作成
        private var answer:Array/*uint*/ = [];    // 答えが段ごとに入ってる
        private var nums:Array/*Array*/ = [];    // 段ごとの数字の配列が入ってる。虫食いは「.」
        private var disNums:Array/*Num*/ = [];    // 数字のSpriteがはいってる
        private var listNums:Array/*Hole*/ = [];    // リストの数字のSpriteが入ってる
        private function createQuestion():void {
            
            answer = []; nums = []; disNums = []; selectedHole = -1;
            spAnswer.visible = false; tfNotice.removeText();
            
            var i:uint, j:uint;
            
            // 掛け合わせる二つの数(一段目の先頭と二段目は0を含まない)
            var a:uint, b:uint; nums.push([], []);
            while (uint(Math.log(a) * Math.LOG10E) < DIGIT - 1) {
                a *= 10; b *= 10;
                a += a ? Math.random() * 10 : Math.random() * 9 + 1;
                b += Math.random() * 9 + 1;
                nums[0].push(a % 10); nums[1].push(b % 10);
            }
            answer.push(a, b);
            
            // 途中計算
            for (i = 0; i < DIGIT; i++) {
                answer.push(a * (uint(b / Math.pow(10, i)) % 10));
                nums[2 + i] = (answer[2 + i] + "").split("");
            }
            
            // 答え
            answer.push(a * b); nums.push([]);
            nums[2 + DIGIT] = (answer[2 + DIGIT] + "").split("");
            
            vermiculate(nums);
            
        }
        
        // 虫食い
        private function vermiculate(nums:Array/*Array*/):void {
            
            var i:uint, j:uint;
            const _nums:Array/*Array*/ = $Array.deepCopy(nums);
            
            const numsNum:uint = (function(a:Array/*Array*/):uint {
                var i:uint = 0;
                a.forEach(function(aa:Array, ...r:Array):void { i += aa.length; } );
                return i;
            })(nums);
            
            // 虫食いにしていく候補のリストを決定
            const vlist:Array/*uint*/ = [];
            while (vlist.length < numsNum) vlist.push(vlist.length);
            for (i = 0; i < numsNum; i++) vlist.push(vlist.splice(uint((numsNum - i) * Math.random()), 1)[0]);
            
            // 順に虫食いできるか確認
            while (vlist.length) {
                i = 0; j = vlist.shift();
                while (nums[i].length <= j) { j -= nums[i++].length; }
                if ((nums[0] + nums[1]).toString().match(/\./g).length >= 4 && i < 2) continue;    // 掛け合わせる2数は虫食い4つまで(DIGIT3のとき)
                nums[i][j] = ".";
                if (!isUnique(nums)) nums[i][j] = _nums[i][j];
            }
            
            disNums = spHissan.draw(nums);
            const _disNums:Array/*Num*/ = spAnswer.draw(nums);
            
            for (i = 0; i < disNums.length; i++) {
                // アニメーション
                Tweener.addTween(disNums[i], {
                    x:disNums[i].x , y:disNums[i].y, _scale:1,
                    time:.5, delay:((DIGIT * 2 - uint(disNums[i].x / W) - 1) + uint(disNums[i].y / H)) * .2
                });
                disNums[i].x += W / 2;
                disNums[i].y += H / 2;
                disNums[i].scaleX = disNums[i].scaleY = 0;
                
                // 虫食いにイベントを追加
                if (disNums[i] is Hole) {
                    disNums[i].addEventListener(MouseEvent.CLICK, onHoleClick);
                    disNums[i].buttonMode = true;
                    $.addRollHandler(disNums[i]);
                }
            }
            
            // ギブボタンの有効化
            Tweener.addTween( { }, {
                onComplete:function():void {
                    btOperation.enabled = true;
                }, time:(3 * DIGIT + 1) * .2 + .5
            });
            
            // 正解は埋める
            for (i = 0; i < _disNums.length; i++) if (_disNums[i] is Hole) {
                _disNums[i].inputNum(uint(answer.join("").charAt(i)), 0xaaaaaa);
                _disNums[i].graphics.clear();
            }
            
        }
        
        // ユニークな解が求まるか
        // 総当たりでやるという手抜き方法なので、とても遅い。
        private function isUnique(nums:Array/*Array*/):Boolean {
            
            var i:uint, j:uint, re:RegExp;
            
            // $Array.encompassに渡す配列作成
            const arys:Array/*Array*/ = [], a0to9:Array/*uint*/ = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
            for (i = 0; i < 2; i++) for (j = 0; j < DIGIT; j++) arys.push(nums[i][j] == "." ? a0to9.slice(i || j == 0 ? 1 : 0) : [nums[i][j]]);
            
            // 上二段の解候補リスト
            var list:Array/*Array*/ = $Array.encompass.apply(null, arys);
            if (list.length == 1) return true;
            
            // 扱いやすくするために、DIGIT桁の数もあらかじめ用意 list = [[1, 2, 3, 4, 12, 34], [1, 2, 3, 5, 12, 35]];など
            list.forEach(function(a:Array, ...r:Array):void {
                for (var k:uint = 0, m:uint = 0, n:uint = 0; k < DIGIT; k++) {
                    m *= 10; m += a[k]; n *= 10; n += a[DIGIT + k];
                }
                a.push(m, n);
            });
            
            // 途中計算が合うかどうか
            for (i = 0; i < DIGIT; i++) {
                re = new RegExp("^" + nums[2 + i].join("") + "$");
                list = list.filter(function(a:Array, ...r:Array):Boolean {
                    return re.test(a[DIGIT * 2] * a[DIGIT * 2 - 1 - i] + "");
                });
                if (list.length == 1) return true;
            }
            
            // 計算結果が合うかどうか
            re = new RegExp("^" + nums[2 + DIGIT].join("") + "$")
            list = list.filter(function(a:Array, ...r:Array):Boolean {
                return re.test(a[DIGIT * 2] * a[DIGIT * 2 + 1] + "");
            });
            if (list.length == 1) return true;
            
            return false;
            
        }
        
        // ギブアップ
        private function giveUp(e:MouseEvent):void {
            
            // 虫食いのイベントを解除
            for (var i:uint = 0; i < disNums.length; i++) if (disNums[i] is Hole) {
                disNums[i].removeEventListener(MouseEvent.CLICK, onHoleClick);
                disNums[i].buttonMode = false;
                $.removeRollHandler(disNums[i]);
            }
            
            // リストを無効化
            selectedHole = -1;
            spList.enabled = false;
            
            // 正解を表示
            tfNotice.removeText();
            spAnswer.visible = true;
            
            // 次へボタンを表示
            showNextButton();
            
        }
        
        // 正解したとき
        private function onCorrectAnswer():void {
            
            // お知らせ
            tfNotice.setText(
                ["Good job", "Nice", "Super", "Wonderful", "Excellent", "Cool"][uint(Math.random() * 6)] + "!"
                , 0xffffff
            );
            
            // 虫食いのイベントを解除
            for (var i:uint = 0; i < disNums.length; i++) if (disNums[i] is Hole) {
                disNums[i].removeEventListener(MouseEvent.CLICK, onHoleClick);
                disNums[i].buttonMode = false;
                disNums[i].graphics.clear();
                $.removeRollHandler(disNums[i]);
            }
            
            // 次へボタンを表示
            showNextButton();
            
        }
        
        // 次へボタンを表示
        private function showNextButton():void {
            
            btOperation.removeEventListener(MouseEvent.CLICK, giveUp);
            
            btOperation.text = "NEXT";
            (function():void {
                Tweener.addTween(btOperation, {
                    y:btOperation.y - 8, time:.25, transition:"easeOutQuad"
                });
                Tweener.addTween(btOperation, {
                    y:btOperation.y, time:.25, delay:.25, transition:"easeInQuad", onComplete:arguments.callee
                });
            })();
            
            btOperation.addEventListener(MouseEvent.CLICK, goNext);
            
        }
        
        // 次の問題へ移るアニメーション
        private function goNext(e:MouseEvent):void {
            
            // ボタンの無効化
            btOperation.removeEventListener(MouseEvent.CLICK, goNext);
            Tweener.removeTweens(btOperation);
            btOperation.resetYpos();
            btOperation.enabled = false;
            
            // 数字
            for (var i:uint = 0; i < disNums.length; i++) {
                // アニメーション
                Tweener.addTween(disNums[i], {
                    x:disNums[i].x + W / 2 , y:disNums[i].y + H / 2, _scale:0,
                    time:.5, delay:((DIGIT * 2 - uint(disNums[i].x / W) - 1) + uint(disNums[i].y / H)) * .2
                });
            }
            
            // 次の問題
            Tweener.addTween( { }, { onComplete:showNext, time:(3 * DIGIT + 1) * .2 + .5 } );
            
        }
        
        private function showNext():void {
            
            // ボタン
            btOperation.text = "Give\nup";
            btOperation.addEventListener(MouseEvent.CLICK, giveUp);
            
            // 新しい問題
            createQuestion();
            
        }
        
        // 虫食いをクリックしたとき
        private var selectedHole:int = -1;
        private function onHoleClick(e:MouseEvent):void {
            if (selectedHole >= 0) {
                disNums[selectedHole].transform.colorTransform = new ColorTransform();
                $.addRollHandler(disNums[selectedHole]);
                if (selectedHole == disNums.indexOf(e.currentTarget)) {
                    selectedHole = -1;
                    spList.enabled = false;
                    return;
                }
            }
            selectedHole = disNums.indexOf(e.currentTarget);
            $.removeRollHandler(disNums[selectedHole]);
            (e.currentTarget as Hole).transform.colorTransform = new ColorTransform(1, 1, 1, 1, 0x80, 0, 0, 0);
            spList.enabled = true;
        }
        
        // 数字リストをクリックしたとき
        private function onListClick(e:MouseEvent):void {
            
            // 穴埋めアニメーション
            disNums[selectedHole].inputNum((e.currentTarget as Hole).num, 0xff5555);
            if ((e.currentTarget as Hole).num >= 0) {
                spHissan.setChildIndex(disNums[selectedHole], spHissan.numChildren - 1);
                Tweener.addTween(disNums[selectedHole].digitNum, {
                    x:disNums[selectedHole].digitNum.x,
                    y:disNums[selectedHole].digitNum.y,
                    time:.5,
                    transition:"easeOutCubic"
                });
                disNums[selectedHole].digitNum.x
                    += (spList.x + (e.currentTarget as Hole).x) - (spHissan.x + disNums[selectedHole].x);
                disNums[selectedHole].digitNum.y
                    += (spList.y + (e.currentTarget as Hole).y) - (spHissan.y + disNums[selectedHole].y);
            }
            
            // 選択状態を解除
            $.click(disNums[selectedHole]);
            
            // 正解か確認
            const yourAnswer:String = disNums.map(function(n:Num, ...r:Array):int { return n.num; } ).join("");
            
            // 正解
            if (yourAnswer == answer.join("")) onCorrectAnswer();
            
            // 全部埋まってるけど不正解
            else if (yourAnswer.search("-") == -1) {
                tfNotice.setText("wrong...", 0xaaaaff);
            }
        }
        
        // キーボードを入力したとき
        private function onKeyDown(e:KeyboardEvent):void {
            const c:uint = e.keyCode;
            if (selectedHole == -1) return;
            if (c == 27) $.click(disNums[selectedHole]);
            if (c == 32) $.click(listNums[0]);
            if (96 <= c && c <= 105) $.click(listNums[c - 96 + 1]);
            if (48 <= c && c <= 57) $.click(listNums[c - 48 + 1]);
        }
        
    }
    
}

import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.display.Shape;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.geom.ColorTransform;

class Num extends Sprite {
    protected static var _W:uint, _H:uint, _M:uint, _NUM_W:uint;
    public static function init(W:uint, H:uint, M:uint, NUM_W:uint):void {
        _W = W; _H = H; _M = M; _NUM_W = NUM_W;
    }
    protected var _num:int;
    public function get num():int { return _num; }
}

class DigitalNum extends Num {
    private static const _NUM_DATA:Vector.<uint> = Vector.<uint>([95, 12, 118, 124, 45, 121, 123, 29, 127, 125, 172]);    // デジタル数字のデータ([10]は×のマーク)    
    private static const _COMMANDS:Vector.<int> = Vector.<int>([1, 2, 2, 2, 2, 2, 2]);
    private static const _V_LINE:Vector.<Number> = Vector.<Number>([0, .08, .16, .24, .16, .76, 0, .92, -.16, .76, -.16, .24, 0, .08]); // 縦線のデータ
    private static const _H_LINE:Vector.<Number> = Vector.<Number>([.08, 0, .24, .16, .76, .16, .92, 0, .76, -.16, .24, -.16, .08, 0]);  // 縦線のデータ
    function DigitalNum(num:uint, x:Number = 0, y:Number = 0, fillColor:uint = 0x55ff55) {
        const data:uint = _NUM_DATA[_num = num];
        graphics.beginFill(fillColor);
        if (data & 0x01) graphics.drawPath(_COMMANDS, _lineVec(_V_LINE, 0, 0));
        if (data & 0x02) graphics.drawPath(_COMMANDS, _lineVec(_V_LINE, 0, 1));
        if (data & 0x04) graphics.drawPath(_COMMANDS, _lineVec(_V_LINE, 1, 0));
        if (data & 0x08) graphics.drawPath(_COMMANDS, _lineVec(_V_LINE, 1, 1));
        if (data & 0x10) graphics.drawPath(_COMMANDS, _lineVec(_H_LINE, 0, 0));
        if (data & 0x20) graphics.drawPath(_COMMANDS, _lineVec(_H_LINE, 0, 1));
        if (data & 0x40) graphics.drawPath(_COMMANDS, _lineVec(_H_LINE, 0, 2));
        if (data & 0x80) graphics.drawPath(_COMMANDS, _lineVec(_H_LINE, 1, 1));
        this.x = x; this.y = y;
    }
    private function _lineVec(line:Vector.<Number>, posX:int, posY:int):Vector.<Number> {
        return line.map(function(n:Number, i:uint, vec:Vector.<Number>):Number { return (n + (i % 2 ? posY : posX)) * _NUM_W; } );
    }
    
    public static function placeDigitNums(sp:Sprite, num:String, x:Number, y:Number):Array {    // 末尾数字の左上基準
        const digit:uint = num.length, ary:Array/*Num*/ = [];
        for (var i:uint = 0; i < digit; i++) {
            ary.push(placeDigitNum(sp, num.charAt(digit - i - 1), x - i, y));
        }
        return ary.reverse();
    }
    public static function placeDigitNum(sp:Sprite, num:*, x:Number, y:Number):Num {    // 左上基準
        if (num == ".") return sp.addChild(new Hole(_W * x, _H * y)) as Hole;
        else return sp.addChild(new DigitalNum(uint(num), _W * x + (_W - _NUM_W) / 2, _H * y + (_H - _NUM_W * 2) / 2)) as DigitalNum;
        return null;
    }
}

class Hole extends Num {
    function Hole(x:Number = 0, y:Number = 0, fillColor:uint = 0x000000, lineColor:uint = 0xffffff) {
        this.x = x; this.y = y; _num = -1;
        graphics.beginFill(fillColor); graphics.lineStyle(1, lineColor); graphics.drawRect(_M, _M, _W - _M * 2, _H - _M * 2);
    }
    private var _digitNum:DigitalNum;
    public function get digitNum():DigitalNum { return _digitNum; }
    public function inputNum(num:int, numColor:uint):void {
        if (_digitNum) removeChild(_digitNum);
        if ((_num = num) >= 0) _digitNum = addChild(new DigitalNum(num, (_W - _NUM_W) / 2, _H / 2 - _NUM_W, numColor)) as DigitalNum;
        else _digitNum = null;
    }
}

class Hissan extends Sprite {
    private static var _W:uint, _H:uint, _S:Number, _NUM_W:uint, _DIGIT:uint;
    public static function init(W:uint, H:uint, NUM_W:uint, DIGIT:uint, S:Number):void {
        _W = W; _H = H; _NUM_W = NUM_W; _DIGIT = DIGIT; _S = S;
    }
    public function draw(nums:Array/*Array*/):Array {
        
        var i:uint, sh:Shape, dn:DigitalNum, g:Graphics;
        const r:uint = _DIGIT * 2;    // 右端
        const ary:Array/*Num*/ = []
        
        removeChildren(); graphics.clear();
        
        // 掛け合わせる二つの数
        ary.push.apply(null, DigitalNum.placeDigitNums(this, nums[0].join(""), r - 1, 0));
        ary.push.apply(null, DigitalNum.placeDigitNums(this, nums[1].join(""), r - 1, 1));
        
        // 「×」
        addChild(dn = new DigitalNum(10));
        dn.rotation = 45; dn.scaleX = dn.scaleY = Math.SQRT1_2;
        dn.x = _W * (r - _DIGIT - 1 / 2); dn.y = _H * 3 / 2 - _NUM_W;
        
        // 横線
        addChild(sh = new Shape()); g = sh.graphics;
        g.lineStyle(1, 0xffffff);
        g.moveTo(_W * (r - _DIGIT - 1), _H * 2 + _S * _H / 2); g.lineTo(_W * r, _H * 2 + _S * _H / 2);
        
        // 途中計算
        for (i = 0; i < _DIGIT; i++) {
            ary.push.apply(null, DigitalNum.placeDigitNums(this, nums[2 + i].join(""), r - i - 1, (2 + i) + _S));
        }
        
        // 横線
        addChild(sh = new Shape()); g = sh.graphics;
        g.lineStyle(1, 0xffffff);
        g.moveTo(_W * (r - nums[2 + _DIGIT].length), _H * (2 + _DIGIT) + _S * _H * 3 / 2); g.lineTo(_W * r, _H * (2 + _DIGIT) + _S * _H * 3 / 2);
        
        // 答え
        ary.push.apply(null, DigitalNum.placeDigitNums(this, nums[2 + _DIGIT].join(""), r - 1, (2 + _DIGIT) + _S * 2));
        
        return ary;
        
    }
}

class Notice extends TextField {
    function Notice() {
        autoSize = TextFieldAutoSize.CENTER;
        selectable = false;
    }
    public function setText(text:String, color:uint):void {
        defaultTextFormat = new TextFormat(null, 20, color, true);
        this.text = text;
    }
    public function removeText():void {
        text = "";
    }
}

class NumList extends Sprite {
    private static var _W:uint;
    public static function init(W:uint):void {
        _W = W;
    }
    public function draw(onListClickHandler:Function):Array {
        const ary:Array/*Hole*/ = [];
        graphics.lineStyle(1, 0xaaaaaa); graphics.moveTo(0, 0); graphics.lineTo(465, 0);
        for (var i:int = -1, h:Hole; i <= 9; i++) {
            ary.push(addChild(h = new Hole(_W * (i + 1), 5)));
            h.inputNum(i, 0x5555ff);
            $.addRollHandler(h); h.buttonMode = true;
            h.addEventListener(MouseEvent.CLICK, onListClickHandler);
        }
        enabled = false;
        return ary;
    }
    private var _enabled:Boolean = true;
    public function get enabled():Boolean { return _enabled; };
    public function set enabled(_enabled:Boolean):void {
        mouseChildren = tabChildren = _enabled; alpha = _enabled ? 1 : .3;
        this._enabled = _enabled;
    }
}

class OperationButton extends Sprite {
    public static const SIZE:uint = 45;
    private var _tf:TextField;
    private var _y:Number;
    function OperationButton(text:String = "", y:Number = 0) {
        graphics.lineStyle(1, 0xffffff); graphics.beginFill(0x000000); graphics.drawRect(0, 0, SIZE, SIZE);
        buttonMode = true;
        addChild(_tf = new TextField()); _tf.selectable = false;
        _tf.defaultTextFormat = new TextFormat("_ゴシック", 12, 0xffffff, true);
        _tf.x = SIZE / 2; _tf.width = 0; _tf.autoSize = TextFieldAutoSize.CENTER;
        _tf.mouseEnabled = false;
        this.text = text;
        $.addRollHandler(this);
        this.y = _y = y;
    }
    public function set text(_text:String):void {
        _tf.text = _text;
        _tf.y = SIZE / 2 - _tf.height / 2;
    }
    public function resetYpos():void { y = _y; }
    private var _enabled:Boolean = true;
    public function get enabled():Boolean { return _enabled; };
    public function set enabled(_enabled:Boolean):void {
        if (_enabled) $.addRollHandler(this);
        else $.removeRollHandler(this);
        mouseEnabled = tabEnabled = _enabled; alpha = _enabled ? 1 : .3;
        this._enabled = _enabled;
    }
}

// 配列の関数
class $Array {
    public static function encompass(...arys:Array):Array {
        const ary:Array = [];
        (function(n:uint, a:Array):void {
            if (n == arys.length) ary.push(a);
            else for (var i:uint = 0; i < arys[n].length; i++) arguments.callee(n + 1, a.concat(arys[n][i]));
        } )(0, []);
        return ary;
    }
    public static function deepCopy(ary:Array):Array {
        return ary.map(function(item:*, ...r:Array):* {
            return item is Array ? deepCopy(item) : item;
        });
    }
}

// 便利関数とか
class $ {
    public static function click(io:InteractiveObject):void {
        io.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
    }
    public static function addRollHandler(sp:Sprite):void {
        sp.addEventListener(MouseEvent.ROLL_OVER, _onRollOverHandler);
        sp.addEventListener(MouseEvent.ROLL_OUT, _onRollOutHandler);
    }
    public static function removeRollHandler(sp:Sprite):void {
        sp.dispatchEvent(new MouseEvent(MouseEvent.ROLL_OUT));
        sp.removeEventListener(MouseEvent.ROLL_OVER, _onRollOverHandler);
        sp.removeEventListener(MouseEvent.ROLL_OUT, _onRollOutHandler);
    }
    private static function _onRollOverHandler(e:MouseEvent):void {
        (e.currentTarget as Sprite).transform.colorTransform = new ColorTransform(1, 1, 1, 1, 0x50, 0x50, 0x50, 0);
    }
    private static function _onRollOutHandler(e:MouseEvent):void {
        (e.currentTarget as Sprite).transform.colorTransform = new ColorTransform();
    }
}