【算数】虫食い算
マウスかキーボードで操作します。
難易度はかなりまちまちです。
/**
* 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();
}
}