NScripterで数独を解く。


タイトル通り。
下記のサンプルでは、数独 -無料で数独パズルをプレイ、プリントそしてシェアの#6948を解いている。

luaで解説。

local game = field.new()で新しい盤面オブジェクトを作る。
game.write(x, y, num)で、盤面に既にわかっている数値を書き込む。
game:run()で解く。

NScripterで解説。

new_fieldで盤面を初期化。
cell_write x,y,numで盤面に数値を書き込む。
runで残りを計算する。

限界について

ルールの性質上、初期値があまりにも少ないと絞り込めなくなるが、その場合、このアルゴリズムは解けるところまで解いて終了する。

計算方法について

二つのアルゴリズムを使う。

その1

1.あるセルが決定すると、それは周囲のセルの可能性を減らす。
2.あるセルの可能性が一つになった場合、そのセルは残った可能性に決定される。1に戻る。

その2

1.あるグループ(列/行/3*3のクラスタ)それぞれについて見る。
2.そのグループの中で、ある値になる可能性を持つセルが一つしかなければ、その値に決定される。

感想

デバッグ時間の方が長かったよ!

00.txt

*define
deletemenu
game
*start
; 6948
new_field
cell_write 1,1,8
cell_write 1,3,1
cell_write 1,4,7
cell_write 1,5,9
cell_write 1,8,6
cell_write 2,6,5
cell_write 2,9,1
cell_write 3,2,3
cell_write 4,1,2
cell_write 4,2,6
cell_write 4,6,8
cell_write 4,7,4
cell_write 5,5,7
cell_write 6,3,3
cell_write 6,4,1
cell_write 6,6,9
cell_write 6,8,7
cell_write 6,9,2
cell_write 7,8,8
cell_write 8,1,7
cell_write 8,4,2
cell_write 9,2,9
cell_write 9,5,6
cell_write 9,6,3
cell_write 9,7,2
cell_write 9,9,7
click
run
click
end

system.lua

--system
NL_dofile("cell.lua")
NL_dofile("field.lua")

local game

NSExec("luasub new_field")
function NSCOM_new_field()
	game = field.new()
end

NSExec("luasub cell_write")
function NSCOM_cell_write()
	local x = NSPopInt()
	NSPopComma()
	local y = NSPopInt()
	NSPopComma()
	game:write(x, y, NSPopInt())
end

NSExec("luasub run")
function NSCOM_run()
	game:run()
end

field.lua

-- field
module("field", package.seeall)

local index = {}
local class = {__index=index}

index.write = function(self, x, y, num)
	return self.field[x][y]:decide_number(num)
end

index.decide = function(self, cell) -- セルの数値が決まったら、その他のセルを処理する。
	NSExec("caption \"decide\"")
	for x = 1, 9 do
		local cell2 = self.field[x][cell.y]
		if cell.x ~= cell2.x then cell2:delete_pos(cell.decide) end
	end
	for y = 1, 9 do
		local cell2 = self.field[cell.x][y]
		if cell.y ~= cell2.y then cell2:delete_pos(cell.decide) end
	end
	for x = cell.sx, cell.ex do
		for y = cell.sy, cell.ey do
			local cell2 = self.field[x][y]
			if cell.x ~= cell2.x then
				if cell.y ~= cell2.y then
					cell2:delete_pos(cell.decide)
				end
			end
		end
	end
end

index.run = function(self)
	local queue = self.queue
	while true do
		self:all_group_calc()
		local cell = table.remove(queue)
		if type(cell) == "nil" then break end
		cell:calc()
	end
end

index.all_group_calc = function(self)
	local group
	for x = 1, 9 do
		group = {}
		for y = 1, 9 do group[y] = self.field[x][y] end
		self:group_calc(group)
	end
	for y = 1, 9 do
		group = {}
		for x = 1, 9 do group[y] = self.field[x][y] end
		self:group_calc(group)
	end
	for sx = 0, 2 do
		for sy = 0, 2 do
			group = {}
			for ex = 1, 3 do
				for ey = 1, 3 do
					group[1+#group] = self.field[sx*3+ex][sy*3+ey]
				end
			end
			self:group_calc(group)
		end
	end
end

index.group_calc = function(self, group) -- グループ計算
	-- ある一つのグループを調べる。その中で、あるセルのみが持つ可能性を調べる。
	local pos_list = {}
	for pos = 1, 9 do
		pos_list[pos] = {}
		for dummy, cell in ipairs(group) do
			if cell.decide then
				-- 既に決定したセルなら何もしない。
			else
				if cell.pos[pos] then -- その可能性があるなら。
					pos_list[pos][1+#pos_list[pos]] = cell
				end
			end
		end
	end
	for pos = 1, 9 do
		if 1 == #pos_list[pos] then
			pos_list[pos][1]:decide_number(pos)
		end
	end
end

function new()
	local self = setmetatable({}, class)
	self.field = {}
	for x = 1, 9 do
		self.field[x] = {}
		for y = 1, 9 do
			self.field[x][y] = cell.new(self, x, y)
		end
	end
	self.queue = {}
	NSUpdate()
	return self
end

cell.lua

-- cell
module("cell", package.seeall)

local index = {}
local class = {__index=index}

index.delete_pos = function(self, pos) -- 可能性を削除する。
	if self.pos[pos] then else return 0 end -- 既に削除されていれば何もしない。

	NSExec("caption \"pos("..tostring(self.x)..","..tostring(self.y)..")="..tostring(pos).."\"")

	self.pos[pos] = nil
	self.count = self.count - 1
	self:print() -- セルを表示
	if self.count == 1 then -- もし、残った可能性が一つになったら、フィールドのチェックキューに追加。
		local field = self.field
		local queue = field.queue
		queue[1+#queue] = self
	end
	return self.count
end

index.decide_number = function(self, num) -- セルの数値を決定する
	if self.pos[num] then else return false end -- 入れられる数値しか入れない。
	if self.decide then return false end -- 既に決定していたら何もしない。

	NSExec("caption \"decide("..tostring(self.x)..","..tostring(self.y)..")="..tostring(num).."\"")

	self.decide = num -- 数値を決定する。
	self.pos = {}
	self.pos[num] = true
	self.count = 1
	self:print() -- セルを表示

	self.field:decide(self) -- 他のセルを処理する。

	return true
end

index.calc = function(self) -- セルの数値を決定できるかどうか。決定できたら、決定する。
	if self.count ~= 1 then return false end
	if self.decide then return false end -- 既に決定されている。

	NSExec("caption \"calc("..tostring(self.x)..","..tostring(self.y)..")\"")

	local num = 0
	for i = 1, 9 do
		num = i
		if self.pos[i] then break end
	end
	return self:decide_number(num)
end

local alpha_count = 16
local alpha_base = 255 - 9 * alpha_count
local cell_size = 50

local han2zen = {}
han2zen[1] = "1"
han2zen[2] = "2"
han2zen[3] = "3"
han2zen[4] = "4"
han2zen[5] = "5"
han2zen[6] = "6"
han2zen[7] = "7"
han2zen[8] = "8"
han2zen[9] = "9"
index.print = function(self) -- セルを表示する。
	NSExec("caption \"print\"")
	if self.decide then -- 既に決定している。
		NSSpMove(self.sp2, self.px, self.py, 255)
		local str = ":s/"..tostring(cell_size)..","..tostring(cell_size)..",0;#000000"..han2zen[self.decide]
		NSSpLoad(self.sp1, str)
		NSSpMove(self.sp1, self.px, self.py, 255)
		NSSpVisible(self.sp1, true)
	else -- 未決
		local str = ""
		for i = 1, 9 do if self.pos[i] then str = str .. han2zen[i] else str = str .. " " end end
		str = "strsp "..tostring(self.sp1)..",\""..str.."\","..tostring(self.px)..","..tostring(self.py)..",3,3,"..tostring(math.floor(cell_size/3))..","..tostring(math.floor(cell_size/3))..",0,0,0,0,#000000"
		NSExec(str)
		NSSpMove(self.sp2, self.px, self.py, alpha_base + self.count * alpha_count)
	end
	NSUpdate()
	NSSleep(1)
end

local sp_num = 0

function new(field, x, y)
	local self = setmetatable({}, class)
	self.x = x -- セルのx
	self.y = y -- セルのy
	self.field = field -- セルの所属するフィールド
	self.count = 9 -- セルの可能性の数
	local pos = {}
	for i = 1, 9 do pos[i] = true end
	self.pos = pos -- セルの可能性

	-- グループ処理
	self.sx = 4; self.ex = 6
	if x < 4 then self.sx = 1; self.ex = 3 end
	if x > 6 then self.sx = 7; self.ex = 9 end
	self.sy = 4; self.ey = 6
	if y < 4 then self.sy = 1; self.ey = 3 end
	if y > 6 then self.sy = 7; self.ey = 9 end
	

	-- 表示用パラメータ
	self.px = cell_size * ( x - 1 ) -- セルの表示x座標
	self.py = cell_size * ( y - 1 ) -- セルの表示y座標
	self.sp1 = sp_num -- セルのスプライト№
	sp_num = sp_num + 1
	self.sp2 = sp_num -- 土台のスプライト№
	sp_num = sp_num + 1
	
	-- セルを準備する。
	NSSpLoad(self.sp2, ":c;>"..tostring(cell_size)..","..tostring(cell_size)..",#FFFFFF")
	NSSpMove(self.sp2, self.px, self.py, 255)
	NSSpVisible(self.sp2, true)
	self:print()
	
	-- 返す。
	return self
end