1. Moved sudoku.lua to the root 2. Create Makefule 3. Add love binaries in `static-binaries` (they are not static yet) 4. Worked on fullscreen mode. (WIP)
511 lines
13 KiB
Lua
511 lines
13 KiB
Lua
-- dbg = require ("debugger")
|
|
-- dbg.auto_where = 2
|
|
|
|
function table2string(t)
|
|
local myself = table2string
|
|
local l = {}
|
|
local n = 0
|
|
for k, v in pairs(t) do
|
|
n = n + 1
|
|
if type(v) == "table" then
|
|
l[n] = k .. ': ' .. myself(v)
|
|
elseif type(v) == "string" then
|
|
l[n] = k .. ': ' .. '"' .. v .. '"'
|
|
else
|
|
l[n] = k .. ': ' .. v
|
|
end
|
|
end
|
|
return "{" .. table.concat(l, ", ") .. "}"
|
|
end
|
|
|
|
function find(array, element)
|
|
local index = 0
|
|
for i = 1, #array do
|
|
if array[i] == element then
|
|
index = i
|
|
break
|
|
end
|
|
end
|
|
return index
|
|
end
|
|
|
|
function createEmptyBoard()
|
|
local b = {}
|
|
for i = 1, 9 do
|
|
b[i] = {}
|
|
for j = 1, 9 do
|
|
b[i][j] = 0
|
|
end
|
|
end
|
|
return b
|
|
end
|
|
|
|
function cloneBoard(b)
|
|
local c = {}
|
|
for i = 1, 9 do
|
|
c[i] = {}
|
|
for j = 1, 9 do
|
|
c[i][j] = b[i][j]
|
|
end
|
|
end
|
|
return c
|
|
end
|
|
|
|
function loadBoard(fn)
|
|
local boards = {}
|
|
local r = 0
|
|
local board = {}
|
|
for line in love.filesystem.lines(fn) do
|
|
if line:gsub("%s+", "") == "" then
|
|
table.insert(boards, board)
|
|
board = {}
|
|
r = 0
|
|
else
|
|
r = r + 1
|
|
local c = 0
|
|
board[r] = {}
|
|
for item in line:gmatch("%w+") do
|
|
c = c + 1
|
|
board[r][c] = tonumber(item)
|
|
end
|
|
end
|
|
end
|
|
table.insert(boards, board)
|
|
return boards
|
|
end
|
|
|
|
function saveBoard(fn, b1, b2)
|
|
local f = io.open(fn, "w")
|
|
for row = 1, 9 do
|
|
f:write(table.concat(b1[row], " "))
|
|
f:write("\n")
|
|
end
|
|
f:write("\n")
|
|
for row = 1, 9 do
|
|
f:write(table.concat(b2[row], " "))
|
|
f:write("\n")
|
|
end
|
|
f:close()
|
|
end
|
|
|
|
function showBoard(board)
|
|
for row = 1, 9 do
|
|
print(table.concat(board[row], " "))
|
|
end
|
|
end
|
|
|
|
function checkBoardsEqual(b1, b2)
|
|
for i = 1, 9 do
|
|
for j = 1, 9 do
|
|
if b1[i][j] ~= b2[i][j] then return false end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function iterZones(board)
|
|
local row = 0
|
|
local column = 0
|
|
local block = 0
|
|
return function()
|
|
local a = {}
|
|
local p = {}
|
|
if row < 9 then
|
|
row = row + 1
|
|
for i = 1, 9 do
|
|
a[i] = board[row][i]
|
|
p[i] = {row, i}
|
|
end
|
|
return a, p
|
|
end
|
|
if column < 9 then
|
|
column = column + 1
|
|
for i = 1, 9 do
|
|
a[i] = board[i][column]
|
|
p[i] = {i, column}
|
|
end
|
|
return a, p
|
|
end
|
|
if block < 9 then
|
|
block = block + 1
|
|
local dx = math.floor((block - 1) / 3)
|
|
local dy = (block - 1) % 3
|
|
local n = 0
|
|
for i = 1, 3 do
|
|
for j = 1, 3 do
|
|
local x = i + 3 * dx
|
|
local y = j + 3 * dy
|
|
n = n + 1
|
|
a[n] = board[x][y]
|
|
p[n] = {x, y}
|
|
end
|
|
end
|
|
return a, p
|
|
end
|
|
end
|
|
end
|
|
|
|
function checkAUX(zone, pos)
|
|
local isComplete = true
|
|
local ok = true
|
|
local conflictList = {}
|
|
local n = 0
|
|
for i = 1, 8 do
|
|
for j = i+1, 9 do
|
|
if zone[i] == 0 then
|
|
isComplete = false
|
|
elseif zone[i] == zone[j] then
|
|
ok = false
|
|
n = n + 1
|
|
conflictList[n] = {x1=pos[i][1], y1=pos[i][2], x2=pos[j][1], y2=pos[j][2]}
|
|
end
|
|
end
|
|
end
|
|
return ok, isComplete, conflictList
|
|
end
|
|
|
|
function checkBoard(board)
|
|
local ok = true
|
|
local isComplete = true
|
|
local conflictList = {}
|
|
for zone, pos in iterZones(board) do
|
|
local tOk, tIsComplete, tConflictList = checkAUX(zone, pos)
|
|
if not tOk then ok = false end
|
|
if not tIsComplete then isComplete = false end
|
|
for _, conflict in ipairs(tConflictList) do
|
|
table.insert(conflictList, conflict)
|
|
end
|
|
end
|
|
if not ok then isComplete = false end
|
|
return ok, isComplete, conflictList
|
|
end
|
|
|
|
function calCellValidOptions(board, x, y)
|
|
if board[x][y] ~= 0 then return {} end
|
|
local u = {}
|
|
local v = {}
|
|
for i = 1, 9 do
|
|
if board[x][i] ~= 0 then table.insert(u, board[x][i]) end
|
|
if board[i][y] ~= 0 then table.insert(u, board[i][y]) end
|
|
end
|
|
local dx = math.floor((x - 1) / 3) * 3
|
|
local dy = math.floor((y - 1) / 3) * 3
|
|
for i = 1, 3 do
|
|
for j = 1, 3 do
|
|
local a = board[i+dx][j+dy]
|
|
if a ~= 0 then table.insert(u, a) end
|
|
end
|
|
end
|
|
for i = 1, 9 do
|
|
local found = false
|
|
for _, elm in ipairs(u) do
|
|
if elm == i then found = true; break end
|
|
end
|
|
if not found then table.insert(v, i) end
|
|
end
|
|
return v
|
|
end
|
|
|
|
function buildSearchSpace(board)
|
|
local s = {}
|
|
for i = 1, 9 do
|
|
s[i] = {}
|
|
for j = 1, 9 do
|
|
s[i][j] = calCellValidOptions(board, i, j)
|
|
end
|
|
end
|
|
return s
|
|
end
|
|
|
|
function findPairsIndSearchSpace(board, searchSpace)
|
|
local r = {}
|
|
local zoneIndex = 0
|
|
for zone, pos in iterZones(board) do
|
|
zoneIndex = zoneIndex + 1
|
|
for i = 1, 8 do
|
|
local x1 = pos[i][1]
|
|
local y1 = pos[i][2]
|
|
local s1 = searchSpace[x1][y1]
|
|
if #s1 == 2 then
|
|
for j = i + 1, 9 do
|
|
local x2 = pos[j][1]
|
|
local y2 = pos[j][2]
|
|
local s2 = searchSpace[x2][y2]
|
|
if #s2 == 2 then
|
|
if s1[1] == s2[1] and s1[2] == s2[2] then
|
|
-- if zoneIndex <= 18 or x1 ~= x2 and y1 ~= y2 then
|
|
table.insert(r, {zoneIndex=zoneIndex, cells={i, j}, pair=s1})
|
|
-- print("----------")
|
|
-- print("zone: ", zoneIndex)
|
|
-- print(table2string(zone))
|
|
-- print(i, j)
|
|
-- print(table2string(s1))
|
|
-- end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return r
|
|
end
|
|
|
|
function showSearchSpace(s)
|
|
local hl = ("+-------+-------+-------+-------+-------+-------+-------+-------+-------+")
|
|
print(hl)
|
|
for i = 1, 27 do
|
|
local x = math.floor((i - 1) / 3) + 1
|
|
local a = (i - 1) % 3
|
|
local row = {}
|
|
for j = 1, 27 do
|
|
local y = math.floor((j - 1) / 3) + 1
|
|
local k = ((j - 1) % 3 + 1) + 3 * a
|
|
local found = false
|
|
for _, m in ipairs(s[x][y]) do
|
|
if m == k then found = true; break end
|
|
end
|
|
if found then
|
|
table.insert(row, k)
|
|
else
|
|
table.insert(row, " ")
|
|
end
|
|
if j % 3 == 0 then table.insert(row, "|") end
|
|
end
|
|
print("| "..table.concat(row, " "))
|
|
if i % 3 == 0 then print(hl) end
|
|
end
|
|
end
|
|
|
|
function getZoneByIndex(board, zoneIndex)
|
|
local index = (zoneIndex - 1) % 9 + 1
|
|
local block = math.floor((zoneIndex - 1) / 9) + 1
|
|
local a = {}
|
|
local p = {}
|
|
if block == 1 then
|
|
for i = 1, 9 do
|
|
a[i] = board[index][i]
|
|
p[i] = {index, i}
|
|
end
|
|
elseif block == 2 then
|
|
for i = 1, 9 do
|
|
a[i] = board[i][index]
|
|
p[i] = {index, i}
|
|
end
|
|
elseif block == 3 then
|
|
local dx = math.floor((index - 1) / 3)
|
|
local dy = (index - 1) % 3
|
|
local n = 0
|
|
for i = 1, 3 do
|
|
for j = 1, 3 do
|
|
local x = i + 3 * dx
|
|
local y = j + 3 * dy
|
|
n = n + 1
|
|
a[n] = board[x][y]
|
|
p[n] = {x, y}
|
|
end
|
|
end
|
|
end
|
|
return a, p
|
|
end
|
|
|
|
function getZonePosByIndex(zoneIndex)
|
|
local index = (zoneIndex - 1) % 9 + 1
|
|
local block = math.floor((zoneIndex - 1) / 9) + 1
|
|
local p = {}
|
|
if block == 1 then
|
|
for i = 1, 9 do
|
|
p[i] = {index, i}
|
|
end
|
|
elseif block == 2 then
|
|
for i = 1, 9 do
|
|
p[i] = {index, i}
|
|
end
|
|
elseif block == 3 then
|
|
local dx = math.floor((index - 1) / 3)
|
|
local dy = (index - 1) % 3
|
|
local n = 0
|
|
for i = 1, 3 do
|
|
for j = 1, 3 do
|
|
local x = i + 3 * dx
|
|
local y = j + 3 * dy
|
|
n = n + 1
|
|
p[n] = {x, y}
|
|
end
|
|
end
|
|
end
|
|
return p
|
|
end
|
|
|
|
function iterZonePos(zoneIndex)
|
|
local p = getZonePosByIndex(zoneIndex)
|
|
local i = 0
|
|
return function()
|
|
if i < 9 then
|
|
i = i + 1
|
|
return p[i][1], p[i][2]
|
|
end
|
|
end
|
|
end
|
|
|
|
function fillSingles(board, searchSpace)
|
|
local count = 0
|
|
for i = 1, 9 do
|
|
for j = 1, 9 do
|
|
if #searchSpace[i][j] == 1 then
|
|
board[i][j] = searchSpace[i][j][1]
|
|
count = count + 1
|
|
end
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
function applyFillSinglesRepeatedly(board, s)
|
|
local s = s or buildSearchSpace(board)
|
|
local count = fillSingles(board, s)
|
|
local total = count
|
|
while count ~= 0 do
|
|
s = buildSearchSpace(board)
|
|
count = fillSingles(board, s)
|
|
total = total + count
|
|
end
|
|
return total
|
|
end
|
|
|
|
function eliminatePairsInSearchSpaceAUX(board, searchSpace, pair, zoneIndex, cells)
|
|
local _, pos = getZoneByIndex(board, zoneIndex)
|
|
local count = 0
|
|
for i = 1, 9 do
|
|
if i ~= cells[1] and i ~= cells[2] then
|
|
local x = pos[i][1]
|
|
local y = pos[i][2]
|
|
local del = {}
|
|
for index, v in ipairs(searchSpace[x][y]) do
|
|
if v == pair[1] or v == pair[2] then
|
|
count = count + 1
|
|
table.insert(del, index)
|
|
end
|
|
end
|
|
for _, index in ipairs(del) do
|
|
table.remove(searchSpace[x][y], index)
|
|
end
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
function eliminatePairsInSearchSpace(board, searchSpace)
|
|
local p = findPairsIndSearchSpace(board, searchSpace)
|
|
local count = 0
|
|
for _, m in ipairs(p) do
|
|
count = count + eliminatePairsInSearchSpaceAUX(board, searchSpace, m.pair, m.zoneIndex, m.cells)
|
|
end
|
|
return count
|
|
end
|
|
|
|
function allPairs(board)
|
|
local s = buildSearchSpace(board)
|
|
applyFillSinglesRepeatedly(board, s)
|
|
eliminatePairsInSearchSpace(board, s)
|
|
local count = applyFillSinglesRepeatedly(board, s)
|
|
while count ~= 0 do
|
|
-- print("--------------------------------------------------")
|
|
-- showBoard(board)
|
|
-- showSearchSpace(s)
|
|
-- print(count)
|
|
-- io.read(1)
|
|
s = buildSearchSpace(board)
|
|
eliminatePairsInSearchSpace(board, s)
|
|
count = applyFillSinglesRepeatedly(board, s)
|
|
end
|
|
end
|
|
|
|
function findFirstEmptyCell(board)
|
|
for i = 1, 9 do
|
|
for j = 1, 9 do
|
|
if board[i][j] == 0 then
|
|
return i, j
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function checkSearchSpace(board, searchSpace)
|
|
local s = searchSpace or buildSearchSpace(board)
|
|
for i = 1, 9 do
|
|
for j = 1, 9 do
|
|
if board[i][j] == 0 then
|
|
if #s[i][j] == 0 then return false end
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function findFirstLeastOption(searchSpace)
|
|
local min = 9
|
|
local x, y
|
|
for i = 1, 9 do
|
|
for j = 1, 9 do
|
|
local len = #searchSpace[i][j]
|
|
if len > 0 and len < min then
|
|
min = len
|
|
x = i
|
|
y = j
|
|
end
|
|
end
|
|
end
|
|
return x, y, min
|
|
end
|
|
|
|
function backTrace(trace)
|
|
while true do
|
|
local currentStep = trace[#trace]
|
|
applyFillSinglesRepeatedly(currentStep.board)
|
|
local ok, finished = checkBoard(currentStep.board)
|
|
if not ok then rollBack(trace); goto endOfTheLoop end
|
|
if finished then return currentStep.board end
|
|
local s = buildSearchSpace(currentStep.board)
|
|
ok = checkSearchSpace(currentStep.board, s)
|
|
if not ok then rollBack(trace); goto endOfTheLoop end
|
|
if not currentStep.pointer then
|
|
local x, y = findFirstLeastOption(s)
|
|
currentStep.options = s[x][y]
|
|
currentStep.pointer = 0
|
|
currentStep.x = x
|
|
currentStep.y = y
|
|
end
|
|
currentStep.pointer = currentStep.pointer + 1
|
|
if currentStep.pointer > #currentStep.options then rollBack(trace); goto endOfTheLoop end
|
|
local nextStep = {}
|
|
nextStep.board = cloneBoard(currentStep.board)
|
|
nextStep.board[currentStep.x][currentStep.y] = currentStep.options[currentStep.pointer]
|
|
trace[#trace+1] = nextStep
|
|
::endOfTheLoop::
|
|
end
|
|
end
|
|
|
|
function rollBack(trace)
|
|
assert(#trace > 1, "Something is very wrong!")
|
|
table.remove(trace)
|
|
end
|
|
|
|
function solve(board)
|
|
local t = {}
|
|
t[1] = {board=cloneBoard(board),}
|
|
return backTrace(t)
|
|
end
|
|
|
|
return {
|
|
loadBoard = loadBoard,
|
|
cloneBoard = cloneBoard,
|
|
createEmptyBoard = createEmptyBoard,
|
|
calCellValidOptions = calCellValidOptions,
|
|
checkBoard = checkBoard,
|
|
applyFillSinglesRepeatedly = applyFillSinglesRepeatedly,
|
|
showBoard = showBoard,
|
|
checkBoardsEqual = checkBoardsEqual,
|
|
saveBoard = saveBoard,
|
|
solve = solve,
|
|
}
|