数独ソルバー
やばい、SAMPLE[9]が解けない・・・
問題入力をキャンセルできるようにした。
数独を解きます。 ソースきたない。
//やばい、SAMPLE[9]が解けない・・・
//問題入力をキャンセルできるようにした。
//数独を解きます。 ソースきたない。
package
{
//import events.AllSolvedEvent;
//import events.UnsolvableEvent;
import flash.display.Graphics;
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.Timer;
import org.libspark.betweenas3.BetweenAS3;
import org.libspark.betweenas3.easing.Linear;
import org.libspark.betweenas3.tweens.ITween;
//import sudoku.AbstractDisplayedCell;
//import sudoku.Manager;
//import sudoku.SudokuQueue;
/**
* 「ブッ解く」と心の中で思ったならッ! その時スデに解答は終わっているんだッ!
* ...
* @author @kndys
*/
[SWF(width="465",height="465",backgroundColor="0xffffff",frameRate="30")]
public class Sudoku extends Sprite
{
private const SAMPLE: Array = [
"200008000643000500000001006000081000020009050070000000503040000001300000000068020",
"071000000000003050005000206030065700000000301004000090000000002020400590900070000",
"087004000000005600006000200006720000090000100400000080000049050082000006001000090",
"270000030094508000000010000006800900000000000005007200000040000000602170060000038",
"000000006600500000482090000007200000013000840000006500000030514000009007100000000",
"104000000003000006000020000025090360000702000098060510000050000900000800000000407",
"400000007007900300002000600005000030001504600080000700001000200002008400900000005",
"600000700000001000420003000260408000050000030000091068000800015000700000005000009",
"070042000800000030204500000020500900005000704000093000000051000900000000807400000",
"000800060000903205000004070400070300000000000008010009010500000306802000020003000",
"008000300090500000004000200070400000080310000506009000003000100200000960070100008",
"001030000070000040300070000800050200000103000004060009000060007050000080000010200",
"080007020100400000906000000004000100050000090003000600000000305000002004010800070",
"003000207006000800040003000004000500003000200006000900000100080004000500609000100",
"008700060000500300400009000020000510000000000064000030000900005004008000020007100",
"410000600000050800080040020000007500005060009001008000982000000000010040000607000"
];
private var manager:Manager;
private var queue:SudokuQueue;
private var cells:Vector.<AbstractDisplayedCell>;
private var timer:Timer;
private var inputButtons:Sprite;
private var inputtingCell:uint;
private var useSample:Boolean;
private var isPlaying:Boolean;
public function Sudoku():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
manager = Manager.getInstance();
queue = SudokuQueue.getInstance();
cells = new Vector.<AbstractDisplayedCell>();
makeInput();
useSample = true;
isPlaying = false;
var q:String = SAMPLE[int(SAMPLE.length*Math.random())];
var cell:AbstractDisplayedCell;
var i:int;
var myself:Sprite = this;
for (i = 0; i < 81; i++)
{
cell = new BasicCell();
cell.index = i;
cell.x = cell.width * ( 3 * (uint(i / 9) % 3) + i % 3) + 10;
cell.y = cell.height * (3 * uint(i / 27) + uint(i / 3) % 3) + 10;
cell.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void {
var c:AbstractDisplayedCell;
if (!isPlaying)
{
if (!(myself.contains(inputButtons)))
{
c = AbstractDisplayedCell(e.currentTarget);
inputtingCell = c.index;
inputButtons.scaleX = inputButtons.scaleY = 0;
myself.addChild(inputButtons);
inputButtons.x = c.x + 22.5;
inputButtons.y = c.y + 22.5;
BetweenAS3.to(inputButtons, {
scaleX:1.0, scaleY:1.0, x:(c.x - 7.5), y:(c.y - 7.5)
}, 0.4, Linear.linear).play();
}
else
{
c = cells[inputtingCell];
var tw:ITween = BetweenAS3.to(inputButtons, {
scaleX:0.0, scaleY:0.0, x:(inputButtons.x + 30), y:(inputButtons.y + 30)
}, 0.4, Linear.linear);
tw.onPlay = function():void {
c.initialize();
}
tw.onComplete = function():void{
myself.removeChild(inputButtons);
}
tw.play();
}
}
});
addChild(cell);
cells.push(cell);
}
var lines:Sprite = new Sprite();
var gra:Graphics = lines.graphics;
gra.lineStyle(2, 0);
for (i = 0; i < 4; i++ )
{
gra.moveTo(10, cell.height * 3 * i + 10);
gra.lineTo(cell.width * 9 + 10, cell.height * 3 * i + 10);
gra.moveTo(cell.width * 3 * i + 10, 10);
gra.lineTo(cell.width * 3 * i + 10, cell.height * 9 + 10);
}
addChild(lines);
var startButton:TextField = new TextField();
startButton.selectable = false;
startButton.autoSize = TextFieldAutoSize.CENTER;
startButton.text = "START";
startButton.x = 10;
startButton.y = cell.height * 9 + 20;
startButton.border = true;
startButton.borderColor = 0x0;
addChild(startButton);
startButton.addEventListener(MouseEvent.CLICK, start);
var pauseButton:TextField = new TextField();
pauseButton.selectable = false;
pauseButton.autoSize = TextFieldAutoSize.CENTER;
pauseButton.text = "PAUSE";
pauseButton.x = startButton.x + startButton.width + 20;
pauseButton.y = cell.height * 9 + 20;
pauseButton.border = true;
pauseButton.borderColor = 0x0;
addChild(pauseButton);
pauseButton.addEventListener(MouseEvent.CLICK, pause);
var resetButton:TextField = new TextField();
resetButton.selectable = false;
resetButton.autoSize = TextFieldAutoSize.CENTER;
resetButton.text = "RESET";
resetButton.x = pauseButton.x + pauseButton.width + 20;
resetButton.y = cell.height * 9 + 20;
resetButton.border = true;
resetButton.borderColor = 0x0;
addChild(resetButton);
resetButton.addEventListener(MouseEvent.CLICK, reset);
queue.addEventListener(UnsolvableEvent.TYPE, unsolved);
queue.addEventListener(AllSolvedEvent.TYPE, allSolved);
timer = new Timer(25);
timer.addEventListener(TimerEvent.TIMER, queue.shift);
}
private function makeInput():void
{
inputButtons = new Sprite();
inputButtons.alpha = 0.8;
inputButtons.graphics.beginFill(0xffffff);
inputButtons.graphics.drawRect(0, 0, 60, 60);
inputButtons.graphics.endFill();
var tf:TextFormat = new TextFormat(null, 18);
for (var i:int = 0; i < 9; i++)
{
var numeric:TextField = new TextField();
numeric.selectable = false;
numeric.x = 20 * (i % 3);
numeric.y = 20 * int(i / 3);
numeric.width = 20;
numeric.height = 20;
numeric.text = String(i + 1);
numeric.setTextFormat(tf);
inputButtons.addChild(numeric);
}
var myself:Sprite = this;
inputButtons.addEventListener(MouseEvent.CLICK, function(e:Event):void {
if (myself.contains(inputButtons) && !isPlaying)
{
var n:uint = (inputButtons.mouseX / 20 >> 0) +(inputButtons.mouseY / 20 >> 0) * 3;
var c:AbstractDisplayedCell = cells[inputtingCell];
c.initialize(n + 1);
c.decide(n + 1);
inputButtons.x = c.x - 7.5;
inputButtons.y = c.y - 7.5;
var tw:ITween = BetweenAS3.to(inputButtons, {
scaleX:0.0, scaleY:0.0, x:(c.x + 22.5), y:(c.y + 22.5)
}, 0.4, Linear.linear);
tw.onComplete = function():void {
myself.removeChild(inputButtons);
}
tw.play();
useSample = false;
}
});
}
private function unsolved(e:Event):void
{
/*解けない問題だったときのイベント*/
trace(e);
trace(queue);
timer.stop();
}
private function allSolved(e:Event):void
{
/*最後まで解けたときのイベント*/
trace(e);
trace(queue);
timer.stop();
}
private function start(e:Event = null):void
{
if (!isPlaying)
{
if (this.contains(inputButtons))
{
var c:AbstractDisplayedCell = cells[inputtingCell];
inputButtons.x = c.x - 7.5;
inputButtons.y = c.y - 7.5;
var tw:ITween = BetweenAS3.to(inputButtons, {
scaleX:0.0, scaleY:0.0, x:(c.x + 22.5), y:(c.y + 22.5)
}, 0.4, Linear.linear);
var myself:Sprite = this;
tw.onComplete = function():void {
myself.removeChild(inputButtons);
}
tw.play();
}
if(useSample)
{
var q:String = SAMPLE[int(SAMPLE.length * Math.random())];
for (var i:int = 0; i < 81; i++)
{
cells[i].initialize(parseInt(q.charAt(i)));
}
}
manager.init(cells);
manager.solve();
isPlaying = true;
}
if(!timer.running) timer.start();
}
private function pause(e:Event = null):void
{
if (isPlaying && timer.running) timer.stop();
}
private function reset(e:Event = null):void
{
if (timer.running) timer.stop();
queue.flush();
BetweenAS3.delay(BetweenAS3.func(function():void{
for (var i:int = 0; i < 81; i++)
{
cells[i].initialize();
}
}), 0.5).play();
isPlaying = false;
}
}
}
import flash.display.Sprite;
import flash.display.Graphics;
import flash.display.Shape;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.utils.Dictionary;
import org.libspark.betweenas3.BetweenAS3;
import org.libspark.betweenas3.easing.Linear;
import org.libspark.betweenas3.tweens.ITween;
import org.libspark.betweenas3.tweens.ITweenGroup;
//package sudoku
//{
//import events.UnsolvableEvent;
//import flash.display.Sprite;
class AbstractDisplayedCell extends Sprite
{
public var index:uint;
protected var _decided: Boolean;
protected var _answer:uint;
/**
* 1..9の初期値を与えると解答済みになるように。
* @param n
*/
public function AbstractDisplayedCell(n:uint = 0)
{
super();
initialize(n);
}
/**
* セル初期化
* @param n
*/
public function initialize(n:uint = 0):void
{
_decided = (n <= 9) && (n >= 1);
if ( _decided )
{
_answer = n;
}
else
{
_answer = 0;
}
}
/**
* 初期状態で決定済みのセル
*/
public function get initiallyDecided():Boolean
{
return _decided;
}
/**
* 初期状態で与えられた解答
*/
public function get initialAnswer():uint
{
return _answer;
}
/**
* セル選択時の動作
*/
public function selectCell():void { }
public function unselectCell():void { }
/**
* 選択セルの候補を1つ選択する動作
* @param n 選択候補(1..9)
*/
public function selectSubject(n:uint):void { }
public function unselectSubject(n:uint):void { }
/**
* 選択セルの候補を一つ削除する動作
* @param n (1..9)
*/
public function removeSubject(n:uint):void { }
/**
* セル参照時の動作
*/
public function referCell():void { }
public function unreferCell():void { }
/**
* 参照セルの候補を確認する動作
* @param n (1..9)
*/
public function checkSubject(n:uint):void { }
/**
* マス目に入る数字を決定する動作
* @param n 解答(1..9)
*/
public function decide(n:uint):void { }
/**
* エラー発生時
*/
public function error():void { }
}
//}
//package
//{
//import sudoku.AbstractDisplayedCell;
class BasicCell extends AbstractDisplayedCell
{
private var subjects:Vector.<TextField>;
private var bg:Shape;
private static var tf:TextFormat;
public function BasicCell(n:uint = 0)
{
super(n);
bg = new Shape();
addChildAt(bg, 0);
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1, 0xcccccc);
g.beginFill(0xffffff, 1);
g.drawRect(0, 0, 45, 45);
g.endFill();
}
override public function initialize(n:uint = 0):void
{
super.initialize(n);
if(bg) bg.graphics.clear();
if (tf == null)
{
tf = new TextFormat(null, 10, 0x808080, true, false, false, null, null, TextFormatAlign.CENTER, 0, 0, 0, 0);
}
var sub:TextField;
for each (sub in subjects)
{
if (contains(sub)) removeChild(sub);
}
subjects = new Vector.<TextField>();
for (var i:int = 0; i < 9; i++)
{
sub = new TextField();
sub.selectable = false;
sub.x = 15 * (i % 3);
sub.y = 15 * int(i / 3);
sub.width = 15;
sub.height = 15;
sub.text = String(i + 1);
sub.setTextFormat(tf);
this.addChild(sub);
subjects.push(sub);
}
}
override public function selectCell():void
{
bg.alpha = 0;
var g:Graphics = bg.graphics;
g.clear();
g.lineStyle(1, 0xff0000);
g.beginFill(0xff8080);
g.drawRect(0, 0, 45, 45);
g.endFill();
var tw:ITween = BetweenAS3.tween(bg, { alpha:1.0 }, { alpha:0.0 }, 0.4, Linear.linear);
tw.play();
}
override public function unselectCell():void
{
var tw:ITween = BetweenAS3.tween(bg, { alpha:0.0 }, { alpha:1.0 }, 0.2, Linear.linear);
tw.onComplete = function():void {
bg.graphics.clear();
}
tw.play();
}
override public function referCell():void
{
bg.alpha = 0;
var g:Graphics = bg.graphics;
g.clear();
g.lineStyle(1, 0x00ff00);
g.beginFill(0xffff80);
g.drawRect(0, 0, 45, 45);
g.endFill();
var tw:ITween = BetweenAS3.to(bg, { alpha:1.0 }, 0.2, Linear.linear);
tw.play();
}
override public function unreferCell():void
{
var tw:ITween = BetweenAS3.to(bg, { alpha:0.0 }, 0.1, Linear.linear);
tw.onComplete = function():void {
bg.graphics.clear();
}
tw.play();
}
override public function selectSubject(n:uint):void
{
var tw:ITween = BetweenAS3.to(subjects[n -1], { textColor: 0xff }, 0.1, Linear.linear);
tw.play();
}
override public function unselectSubject(n:uint):void
{
var tw:ITween = BetweenAS3.to(subjects[n -1], { textColor : 0x808080 }, 0.1, Linear.linear);
tw.play();
}
override public function removeSubject(n:uint):void
{
var sub:TextField = subjects[n - 1];
var tw:ITween = BetweenAS3.to(sub, { alpha :0.0 }, 0.2, Linear.linear);
tw.onComplete = function():void {
if (contains(sub))
{
removeChild(sub);
}
}
tw.play();
}
override public function checkSubject(n:uint):void
{
var tw:ITween = BetweenAS3.tween(subjects[n -1], { alpha: 1.0 }, { alpha: 0.2 }, 0.05, Linear.linear);
tw = BetweenAS3.repeat(tw, 2);
tw.play();
}
override public function decide(n:uint):void
{
var idx:int = n -1;
var tweens:Array = [];
var i:int;
for (i = 0; i < 9; i++)
{
if (i == idx)
{
continue;
}
tweens.push(BetweenAS3.to(subjects[i], { alpha:0.0 }, 0.4, Linear.linear));
}
var sub:TextField = subjects[idx];
if (_decided)
{
tweens.push(BetweenAS3.to(sub, { scaleX:3.0, scaleY:3.0, x:0, y:0}, 0.4, Linear.linear));
}
else
{
tweens.push(BetweenAS3.to(sub, { scaleX:3.0, scaleY:3.0, x:0, y:0, textColor:0 }, 0.4, Linear.linear));
}
var tw:ITween = BetweenAS3.parallelTweens(tweens);
tw.onComplete = function():void {
for (i = 0; i < 9; i++)
{
if (i == idx || !contains(subjects[i]))
{
continue;
}
removeChild(subjects[i]);
}
}
tw.play();
}
override public function error():void
{
var tweens:Array = [];
for (var i:int = 0; i < 9; i++)
{
tweens.push(BetweenAS3.to(subjects[i], { alpha:0.3 }, 1.0, Linear.linear));
}
bg.alpha = 0;
var g:Graphics = bg.graphics;
g.clear();
g.lineStyle(1, 0x0);
g.beginFill(0x404040);
g.drawRect(0, 0, 45, 45);
g.endFill();
tweens.push(BetweenAS3.to(bg, { alpha:1.0 }, 1.0, Linear.linear));
var tw:ITween = BetweenAS3.parallelTweens(tweens);
tw.play();
}
}
//}
//package sudoku
//{
//import events.AllSolvedEvent;
//import events.UnsolvableEvent;
//import utils.TermaryConverter;
class Manager
{
private static var QUE:SudokuQueue;
private static var instance:Manager;
private var cells:Vector.<SudokuCell>;
private var cellsSets:Vector.<CellsSet>;
private var allSolved:Boolean;
public function Manager()
{
if (QUE == null)
{
QUE = SudokuQueue.getInstance();
}
}
/**
* シングルトンなインスタンスを得る
* @return
*/
public static function getInstance():Manager
{
if (instance == null)
{
instance = new Manager();
}
return instance;
}
/**
* セルのセット群を初期化する
* @param dCells 画面に表示されるセルのVector
* @return
*/
public function init(dCells:Vector.<AbstractDisplayedCell>):Boolean
{
if (dCells == null || dCells.length < 81)
{
return false;
}
//表示セルを元に新しいセル群を作成する
cells = new Vector.<SudokuCell>();
var i:int;
var m:uint;
var n:uint;
for (i = 0; i < 81; i++)
{
cells.push(new SudokuCell(dCells[i]));
}
/*
* 縦・横・箱型のセル9個セットにどのように格納するか。
* 0..80 のインデックスがあるが、これを3進法に直すと
* 0000..2222 と表せることを利用する。
*/
var conv:Function = TermaryConverter.convertFromDigits;
cellsSets = new Vector.<CellsSet>();
for (i = 0; i < 9; i++)
{
m = i / 3;
n = i % 3;
//1行9個入りセット9行
cellsSets.push(new CellsSet(
cells[conv(m, 0, n, 0)], cells[conv(m, 0, n, 1)], cells[conv(m, 0, n, 2)],
cells[conv(m, 1, n, 0)], cells[conv(m, 1, n, 1)], cells[conv(m, 1, n, 2)],
cells[conv(m, 2, n, 0)], cells[conv(m, 2, n, 1)], cells[conv(m, 2, n, 2)]
));
//1列9個入りセット9列
cellsSets.push(new CellsSet(
cells[conv(0, m, 0, n)], cells[conv(0, m, 1, n)], cells[conv(0, m, 2, n)],
cells[conv(1, m, 0, n)], cells[conv(1, m, 1, n)], cells[conv(1, m, 2, n)],
cells[conv(2, m, 0, n)], cells[conv(2, m, 1, n)], cells[conv(2, m, 2, n)]
));
//1箱9個入りセット9箱
cellsSets.push(new CellsSet(
cells[conv(m, n, 0, 0)], cells[conv(m, n, 0, 1)], cells[conv(m, n, 0, 2)],
cells[conv(m, n, 1, 0)], cells[conv(m, n, 1, 1)], cells[conv(m, n, 1, 2)],
cells[conv(m, n, 2, 0)], cells[conv(m, n, 2, 1)], cells[conv(m, n, 2, 2)]
));
}
allSolved = false;
return true;
}
/**
* ここでかなりの回数の演算を行って先に数独は解いてしまう。
* その後遅れてqueueが順に実行されることになる。
* @return
*/
public function solve():void {
while (!allSolved)
{
if (!standardCycle())
{
if (!nextCycle())
{
if (!alternativeCylce())
{
QUE.push(QUE.dispatchEvent, new UnsolvableEvent());
trace("今回の問題、解けませんのであしからず。");
return;
}
}
}
}
trace("解けた。");
QUE.push(QUE.dispatchEvent, new AllSolvedEvent());
}
private function standardCycle():Boolean
{
var cs: CellsSet;
var bool:Boolean = false;
allSolved = true;
for each(cs in cellsSets)
{
bool = cs.standardCheck() || bool;
allSolved = allSolved && cs.isCompleted;
}
return bool || allSolved;
}
private function nextCycle():Boolean
{
var cs: CellsSet;
allSolved = true;
for each(cs in cellsSets)
{
allSolved = allSolved && cs.isCompleted;
if (cs.nextCheck()) {
return true;
}
}
return allSolved;
}
private function alternativeCylce():Boolean
{
var cs: CellsSet;
allSolved = true;
for each(cs in cellsSets)
{
allSolved = allSolved && cs.isCompleted;
if (cs.alternativeCheck()) {
return true;
}
}
return allSolved;
}
}
//}
//package sudoku
//{
//import events.UnsolvableEvent;
//import flash.events.EventDispatcher;
/**
* 9個のセルからなるセット
* 含まれるセルの確認と操作を行うことにする。
*/
class CellsSet
{
private var hasCompleted:Boolean;
private var vector: Vector.<SudokuCell>;
private var decidedCell:uint;
/**
* コンストラクタに管理させたい9個組のセルを渡す
*
* @param cell0
* @param cell1
* @param cell2
* @param cell3
* @param cell4
* @param cell5
* @param cell6
* @param cell7
* @param cell8
*/
public function CellsSet(
cell0:SudokuCell, cell1:SudokuCell, cell2:SudokuCell,
cell3:SudokuCell, cell4:SudokuCell, cell5:SudokuCell,
cell6:SudokuCell, cell7:SudokuCell, cell8:SudokuCell)
{
vector = new Vector.<SudokuCell>;
vector.push(cell0, cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8);
hasCompleted = false;
decidedCell = 0;
}
/**
* 基本的なチェック方法
* 未決定セルを順に選択し、決定セルの数字を候補から削除する。
* @return 決定セルが増えたかどうか。
*/
public function standardCheck():Boolean
{
if (decidedCell == 9)
{
return false;
}
var nowDecided:int = 0;
for each (var c:SudokuCell in vector)
{
if (c.isFixed)
{
nowDecided++;
}
}
if (nowDecided == decidedCell)
{
return false;
}
var bool:Boolean = false;
var selected: SudokuCell;
var refered: SudokuCell;
var newDecided:Boolean = false;
SELECT:
for each( selected in vector)
{
if (selected.isFixed)
{
continue SELECT;
}
selected.selectCell();
REFER:
for each( refered in vector)
{
if (selected == refered || !refered.isFixed || selected.checkedCells[refered] === true )
{
continue REFER;
}
//選択セルが未決定なら候補の削除(候補が残ってるかどうかには関わらず)
refered.referCell();
var n: uint = refered.answer;
bool = selected.remove(n) || bool;
selected.checkedCells[refered] = true;
//未決定→決定の変化があったら
if (selected.isFixed)
{
refered.unreferCell(); //参照解除
newDecided = true;
checkOverlap(selected);
break REFER;
}
refered.unreferCell();
}
selected.unselectCell();
}
if (newDecided) standardCheck();
decidedCell = 0;
for each (c in vector)
{
if (c.isFixed)
{
decidedCell++;
}
}
return bool;
}
private function checkOverlap(selected:SudokuCell):void {
if (!selected.isFixed)
{
return;
}
for each(var cell:SudokuCell in vector)
{
if (cell == selected)
{
continue;
}
//解答の重複チェック
if (cell.isFixed && selected.answer == cell.answer)
{
cell.referCell();
selected.error();//queueにはイベント発生タスクが挿入される。
}
}
}
/**
* ある数字候補を一つのセルだけが持っていた場合、そのセルは決定。
* @return
*/
public function nextCheck():Boolean {
if (decidedCell == 9)
{
return false;
}
var bool:Boolean = false;
var refered: SudokuCell;
var has:uint;
var holder:SudokuCell;
//var newDecided:Boolean = false;
for (var n:int = 1; n <= 9; n++)
{
has = 0;
holder = null;
REFER:
for each( refered in vector)
{
if (refered.isFixed)
{
continue REFER;
}
if (refered.hasSubject(n))
{
if (has == 0)
{
holder = refered;
has++;
}
else if (has >= 1)
{
holder = null;
has++;
break REFER;
}
}
}
if (has==1)
{
for each(refered in vector)
{
if (refered != holder)
{
refered.referCell();
}
}
holder.selectCell();
holder.removeOthers(n);
checkOverlap(holder);
holder.unselectCell();
for each(refered in vector)
{
if (refered != holder)
{
refered.unreferCell();
}
}
bool = true;
}
}
decidedCell = 0;
for each (var c:SudokuCell in vector)
{
if (c.isFixed)
{
decidedCell++;
}
}
return bool;
}
/**
* 最後の解法。だいたいの問題はこの方法を使えば解ける。
* @return 決定セルの数に変化があったか
*/
public function alternativeCheck():Boolean {
if (decidedCell == 9)
{
return false;
}
var bool:Boolean = false;
var referedA: SudokuCell;
var referedB: SudokuCell;
var hash:uint = 0;
var selected:SudokuCell;
var newDecided:Boolean = false;
REFER_A:
for each( referedA in vector)
{
if (referedA.isFixed || referedA.leftSubjects != 2)
{
continue REFER_A;
}
REFER_B:
for each( referedB in vector)
{
if (referedA == referedB || referedB.isFixed || referedB.leftSubjects != 2)
{
continue REFER_B;
}
referedA.referCell();
referedB.referCell();
//残りの候補数が2で、かつ同じ候補を残す2マスを探す
if (referedA.hash == referedB.hash)
{
hash = referedA.hash;
SELECT:
for each(selected in vector)
{
if (selected == referedA || selected == referedB)
{
continue SELECT;
}
else
{
selected.selectCell();
bool = selected.removeByHash(hash) || bool;
if (selected.isFixed)
{
newDecided = true;
}
selected.unselectCell();
}
}
}
referedB.unreferCell();
referedA.unreferCell();
}
}
if (newDecided) standardCheck();
decidedCell = 0;
for each (var c:SudokuCell in vector)
{
if (c.isFixed)
{
decidedCell++;
}
}
return bool;
}
/**
* このセットが全て解答済みかどうか
*/
public function get isCompleted():Boolean
{
return decidedCell == 9;
}
}
//}
//package sudoku
//{
//import events.UnsolvableEvent;
//import flash.utils.Dictionary;
/**
* 単体セルのコントロール部分。
* 他のセルの参照などは避ける。
* 基本的に選択対象は未決定セル、参照対象は決定セル。
* ただし第二相では未決定セルも参照する。
*/
class SudokuCell
{
private static var QUE:SudokuQueue;
private var displayed:AbstractDisplayedCell;
private var subjects:Vector.<Boolean>; //現在の候補
private var selectedSubject:uint;
private var left:uint; //残り候補の数
private var fixedNumber: int; //解答
private const fixedHash:Vector.<uint> = Vector.<uint>([1, 2, 4, 8, 16, 32, 64, 128, 256]); //解答の判別に使用するハッシュ数列
private var isSelected:Boolean;
private var isRefered:Boolean;
public var checkedCells:Dictionary;
public function SudokuCell(cell:AbstractDisplayedCell)
{
if (QUE == null)
{
QUE = SudokuQueue.getInstance();
}
subjects = new Vector.<Boolean>();
for (var i:int = 0; i < 9; i++)
{
subjects[i] = true;
}
left = 9;
displayed = cell;
isSelected = false;
isRefered = false;
checkedCells = new Dictionary();
if (displayed.initiallyDecided)
{
setQuestion();
}
}
private function setQuestion():void
{
selectCell();
//初期条件として決定済みのセルかどうか
var ans:int = displayed.initialAnswer;
var idx:int = ans - 1;
for (var i:int = 0; i < 9; i++)
{
if (i != idx)
{
subjects[i] = false;
}
}
fixedNumber = ans;
left = 1;
decide(ans);
unselectCell();
}
/**
* 候補の数字を一つ削除する
* @param n 削除する候補(1..9)
*/
public function remove(n:int):Boolean {
if (!isSelected) throw Error("候補の削除はセル選択時のみ有効");
if (fixedNumber > 0) return false; //既に決定済みなら終了
if (n < 1) return false;
if (n > 10) return false;
if (!subjects[n - 1]) return false; //既に削除されていたら終了
selectSubject(n);
subjects[n - 1] = false;
removeSubject(n);
left--;
if (left == 1) {
//var h:uint = hash;
for (var i:int = 0; i < 9; i++)
{
if (subjects[i])//if (h == fixedHash[i])
{
var ans:int = i + 1;
fixedNumber = ans;
decide(ans);
}
}
}
return true;
}
/**
* ある候補を残して他を削除
* @param n
*/
public function removeOthers(n:int):Boolean
{
if (!isSelected) throw Error("候補の削除はセル選択時のみ有効");
if (fixedNumber > 0) return false; //既に決定済みなら終了
if (n < 1) return false;
if (n > 10) return false;
var idx:int = n - 1;
if (!subjects[idx]) return false; //既に削除されていたら終了
for (var i:int = 0; i < 9; i++)
{
if (i != idx)
{
subjects[i] = false;
}
}
fixedNumber = n;
left = 1;
decide(n);
return true;
}
/**
* 残りの候補が同じかどうかを示すハッシュ値(2進数)
*/
public function get hash():uint {
if (!(isSelected || isRefered)) throw Error("ハッシュ値の呼び出しはセル選択または参照時にのみ有効");
var hash:uint = 0;
var bit:uint = 1;
for (var i:int = 0; i < 9; i++)
{
if (subjects[i])
{
var n:int = i + 1;
selectSubject(n);
checkSubject(n);
unselectSubject(n);
hash += bit;
}
bit <<= 1;
}
return hash;
}
/**
* ハッシュの値を利用して重複する候補を削除する。
* 強力で危険。
* @param hash
*/
public function removeByHash(hash:uint):Boolean
{
if (!isSelected) throw Error("候補の削除はセル選択時のみ有効");
if (fixedNumber > 0) return false; //既に決定済みなら終了
var bool:Boolean = false;
for (var i:int = 0; i < 9; i++)
{
if ((hash & 1) == 1)
{
if (subjects[i])
{
var n:int = i + 1;
bool = remove(n) || bool;
}
}
hash >>>= 1;
}
return bool;
}
/**
* マス目に入る数字が決まったかどうか。
*/
public function get isFixed(): Boolean
{
return (left == 1) && (fixedNumber != 0);
}
/**
* マス目に入る解答。未決定なら0。
*/
public function get answer(): uint
{
return fixedNumber;
}
/**
* 残りの候補の数
*/
public function get leftSubjects(): uint
{
return left;
}
/**
* 候補を持っているかどうか
*/
public function hasSubject(n:uint):Boolean
{
return subjects[n - 1];
}
/**
* セル選択時の動作
*/
public function selectCell():void
{
if (!isSelected)
{
isSelected = true;
QUE.push(displayed.selectCell);
}
}
public function unselectCell():void
{
if (isSelected)
{
isSelected = false;
QUE.push(displayed.unselectCell);
}
}
/**
* 選択セルの候補を1つ選択する動作
* @param n 選択候補
*/
private function selectSubject(n:uint):void
{
if (isSelected && selectedSubject == 0)
{
selectedSubject = n;
QUE.push(displayed.selectSubject, n);
}
}
private function unselectSubject(n:uint):void
{
if (isSelected && selectedSubject == n)
{
selectedSubject = 0;
QUE.push(displayed.unselectSubject, n);
}
}
/**
* 選択セルの候補を一つ削除する動作
* @param n
*/
private function removeSubject(n:uint):void
{
if (isSelected)
{
if (selectedSubject == n)
{
unselectSubject(n);
}
QUE.push(displayed.removeSubject, n);
}
}
/**
* セル参照時の動作
*/
public function referCell():void
{
if (!(isRefered || isSelected))
{
isRefered = true;
QUE.push(displayed.referCell);
}
}
public function unreferCell():void
{
if (isRefered)
{
isRefered = false;
QUE.push(displayed.unreferCell);
}
}
/**
* 選択または参照セルの候補を確認する動作
* @param n
*/
private function checkSubject(n:uint):void
{
if (isSelected || isRefered)
{
QUE.push(displayed.checkSubject, n);
}
}
/**
* マス目に入る数字を決定する動作
* @param n 解答
*/
private function decide(n:uint):void
{
if (isSelected)
{
QUE.push(displayed.decide, n);
}
}
/**
* エラー発生時
*/
public function error():void
{
if (isSelected)
{
QUE.push(displayed.error);
QUE.push(QUE.dispatchEvent, new UnsolvableEvent());
}
}
public function toString():String
{
return this.answer.toString();
}
}
//}
//package sudoku
//{
//import flash.events.Event;
//import flash.events.EventDispatcher;
/**
* セルの表示に関するFIFOなキュースタック。
* shift()と同時に関数を実行する。
*/
class SudokuQueue extends EventDispatcher
{
private static var instance:SudokuQueue; //Singleton
private var queue:Vector.<Task>
public function SudokuQueue()
{
queue = new Vector.<Task>();
}
/**
* シングルトンを取得する方法
* @return
*/
public static function getInstance():SudokuQueue
{
if (instance == null)
{
instance = new SudokuQueue();
}
return instance;
}
/**
* 新しいタスクの追加
* @param cell 対象表示セル
* @param func 実行関数
* @param arg 対象となる候補
*/
public function push(func:Function, arg:* = null):void
{
queue.push(new Task(func, arg));
}
/**
* Vectorの先頭Taskを削除し、同時に実行する。
*/
public function shift(e:Event=null):void
{
if (queue.length == 0)
{
return;
}
var t:Task = queue.shift();
t.call();
}
/**
* flush
* @return
*/
public function flush():void
{
queue = new Vector.<Task>();
}
override public function toString():String
{
return "left tasks: " + queue.length;
}
}
//}
//import sudoku.AbstractDisplayedCell;
class Task
{
private var _func:Function;
private var _arg:* ;
public function Task(func:Function, arg:* = null)
{
_func = func;
_arg = arg;
}
public function call():void {
if (_arg == null)
{
_func.call();
}
else
{
_func.call(null, _arg);
}
}
}
//package utils
//{
class TermaryConverter
{
public function TermaryConverter()
{
}
/**
* 3進数に表した時の任意の桁の数字(0..2)を返す
* @param n 変換する符号なし整数値
* @param digit 対象とする桁数
* @return
*/
public static function getDigitOfUnsignedInt(n:uint, digit:uint):int
{
if (digit != 0)
{
n %= Math.pow(3, digit);
}
n %= 3;
return n;
}
public static function convertFromDigits(...digits):uint
{
var result:uint = 0;
var len:int = digits.length;
var pos:int = len;
for (var i:int = 0; i < len; i++)
{
pos--;
result += uint(digits[i]) * Math.pow(3, pos);
}
return result;
}
}
//}
//package events
//{
//import flash.events.Event;
class AllSolvedEvent extends Event
{
public static const TYPE:String = "All Solved";
public function AllSolvedEvent(type:String = TYPE, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
public override function clone():Event
{
return new AllSolvedEvent(type, bubbles, cancelable);
}
public override function toString():String
{
return formatToString("AllSolvedEvent", "type", "bubbles", "cancelable", "eventPhase");
}
}
//}
//package events
//{
import flash.events.Event;
class UnsolvableEvent extends Event
{
public static const TYPE:String = "unsolvable";
public function UnsolvableEvent(type:String = TYPE , bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
public override function clone():Event
{
return new UnsolvableEvent(type, bubbles, cancelable);
}
public override function toString():String
{
return formatToString("Unsolvable", "type", "bubbles", "cancelable", "eventPhase");
}
}
//}