Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -------------------------------------------------------------------------=---
- -- Name: wxluasudoku.wx.lua
- -- Purpose: wxLuaSudoku - a wxLua program to generate/solve/play Sudoku puzzles
- -- Author: John Labenski
- -- Created: 2006
- -- Copyright: (c) 2006 John Labenski. All rights reserved.
- -- Licence: wxWindows licence
- -------------------------------------------------------------------------=---
- -- Load the wxLua module, does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit
- package.cpath = package.cpath..";./?.dll;./?.so;../lib/?.so;../lib/vc_dll/?.dll;../lib/bcc_dll/?.dll;../lib/mingw_dll/?.dll;"
- require("wx")
- -- Coding notes:
- -- All non gui sudoku functions are in the "sudoku" table and all gui related
- -- functions are in the "sudokuGui" table.
- -- sudoku.GetXXX() retrieve precalculated or set values from the sudoku table
- -- sudoku.SetXXX() set values to the sudoku table
- -- sudoku.FindXXX() search the table for "stuff", but do not modify it, returns result
- -- sudoku.CalcXXX() search the table for "stuff" and store the values to it for GetXXX functions
- sudoku = sudoku or {} -- the table to hold the sudoku solver functions
- -- ============================================================================
- -- A simple function to implement "cond ? A : B", eg "result = iff(cond, A, B)"
- -- note all terms must be able to be evaluated
- function iff(cond, A, B) if cond then return A else return B end end
- -- make the number or bool into a bool or number
- function inttobool(n)
- if (n == nil) or (n == 0) then return false end
- return true
- end
- function booltoint(n)
- if (n == nil) or (n == false) then return 0 end
- return 1
- end
- -- ============================================================================
- -- Simple functions to count the total number of elements in a table
- -- Notes about speed : using for loop and pairs() in TableCount is 2X faster
- -- than using while loop and next(table). However using next() is slightly
- -- faster than using for k,v in pairs(t) do return true end in TableIsEmpty.
- function TableCount(atable)
- local count = 0
- for k, v in pairs(atable) do count = count + 1 end
- return count
- end
- function TableIsEmpty(atable)
- return next(atable) == nil
- end
- -- Set a value in the table or subtable, first making sure that the subtables
- -- are created first. Modifies the input table.
- -- TableSetValue(3, atable, "How", "Are", 4, "You") ==> atable.How.Are[4].You = 3
- function TableSetValue(value, atable, ...)
- if type(atable) ~= "table" then atable = {} end
- local t = atable -- t moves up levels through atable
- local args = {...}
- for n = 1, #args-1 do
- local a = args[n]
- if not t[a] then t[a] = {} end
- t = t[a]
- end
- t[args[#args]] = value
- end
- -- Remove a value in the table or subtable, first making sure that the subtables
- -- exist. Modifies the input table.
- -- TableRemoveValue(atable, false, "How", "Are", 4, "You") ==> atable.How.Are[4].You = nil
- function TableRemoveValue(atable, only_if_empty, ...)
- if type(atable) ~= "table" then return end
- local t = atable -- t moves up levels through atable
- local args = {...}
- for n = 1, #args-1 do
- t = t[args[n]]
- if not t then return end -- already gone
- end
- if (not only_if_empty) or ((type(t[args[#args]]) == "table") and TableIsEmpty(t[args[#args]])) then
- t[args[#args]] = nil
- end
- end
- -- ============================================================================
- -- completely dump the contents of a table
- -- atable is the input table to dump the contents of
- -- prefix is a string prefix for debugging purposes
- -- tablelevel is tracker for recursive calls to TableDump (do not use initially)
- function TableDump(atable, prefix, tablelevel)
- local function print_val(v)
- local t = type(v)
- if t == "number" then
- return tostring(v)
- elseif t == "string" then
- return "\""..v.."\""
- end
- return "'"..tostring(v).."'"
- end
- prefix = prefix or ""
- if tablelevel == nil then
- tablelevel = ""
- print(prefix.."-Dumping Table "..tostring(atable))
- end
- prefix = prefix.." "
- local n = 0
- for k, v in pairs(atable) do
- n = n + 1
- print(string.format("%s %d: %s[%s] = %s", prefix, n, tablelevel, print_val(k), print_val(v)))
- if type(v) == "table" then
- TableDump(v, prefix.." ", tablelevel.."["..print_val(k).."]")
- end
- end
- end
- -- ============================================================================
- -- Make a deep copy of a table, including all sub tables, fails on recursive tables.
- -- returns a new table
- function TableCopy(atable)
- if not atable then return nil end
- local newtable = {}
- for k, v in pairs(atable) do
- if type(v) == "table" then
- newtable[k] = TableCopy(v)
- else
- newtable[k] = v
- end
- end
- return newtable
- end
- -- Merge the two tables together, adding or replacing values in original_table
- -- with those in new_table, returns a new table and doesn't modify inputs
- -- fails on recursive tables, returns the new table
- function TableMerge(new_table, original_table)
- new_table = new_table or {}
- local out_table = TableCopy(original_table or {})
- for k, v in pairs(new_table) do
- if type(v) == "table" then
- if out_table[k] and (type(out_table[k]) == "table") then
- out_table[k] = TableMerge(v, out_table[k])
- elseif out_table[k] then
- local ov = out_table[k]
- out_table[k] = TableCopy(v)
- table.insert(out_table[k], ov)
- else
- out_table[k] = TableCopy(v)
- end
- elseif out_table[k] and (type(out_table[k]) == "table") then
- table.insert(out_table[k], v)
- else
- out_table[k] = v
- end
- end
- return out_table
- end
- -- ============================================================================
- -- Flags for the sudokuTable.flags[flag] = true/false/nil
- sudoku.ELIMINATE_NAKED_PAIRS = 1 -- for sudoku.CalcAllPossible(sudokuTable)
- sudoku.ELIMINATE_HIDDEN_PAIRS = 2 -- set to true to have it run
- sudoku.ELIMINATE_NAKED_TRIPLETS = 3 -- for sudoku.CalcAllPossible(sudokuTable)
- sudoku.ELIMINATE_HIDDEN_TRIPLETS = 4 -- set to true to have it run
- sudoku.ELIMINATE_NAKED_QUADS = 5 -- for sudoku.CalcAllPossible(sudokuTable)
- sudoku.ELIMINATE_HIDDEN_QUADS = 6 -- set to true to have it run
- sudoku.FILENAME = 7 -- store the fileName from Open/Save functions
- sudoku.ELIMINATE_FLAG_MIN = 1 -- for iterating the ELIMINATE flags
- sudoku.ELIMINATE_FLAG_MAX = 6
- -- given the upper left cell of block you can iterate through the block using
- -- for n = 1, 9 do cell = n + block_cell + sudoku.LinearBlockCellTable[n] ... end
- sudoku.LinearBlockCellTable = { -1, -1, -1, 5, 5, 5, 11, 11, 11 }
- sudoku.CellToRowTable = {}
- sudoku.CellToColTable = {}
- sudoku.BlockToRowTable = {}
- sudoku.BlockToColTable = {}
- sudoku.CellToBlockTable = {}
- sudoku.BlockToCellTable = {}
- for cell = 1, 81 do
- local row = math.floor(cell/9.1)+1
- local col = cell-(row-1)*9
- sudoku.CellToRowTable[cell] = row
- sudoku.CellToColTable[cell] = col
- local block_row = math.floor(row/3.5)+1
- local block_col = math.floor(col/3.5)+1
- sudoku.CellToBlockTable[cell] = (block_row-1)*3 + block_col
- end
- for block = 1, 9 do
- local row = math.floor(block/3.5)*3+1
- local col = math.fmod(block-1,3)*3+1
- sudoku.BlockToRowTable[block] = row
- sudoku.BlockToColTable[block] = col
- sudoku.BlockToCellTable[block] = (row-1)*9 + col
- end
- -- ============================================================================
- -- Create a sudoku table to be used with the rest of the sudoku functions
- function sudoku.CreateTable()
- local sudokuTable =
- {
- values = {}, -- array (1-81) of values[cell#] = value (0 means unset)
- row_values = {}, -- array (1-9) of values[row#][value] = { cell1, cell2... }
- col_values = {}, -- array (1-9) of values[col#][value] = { cell1, cell2... }
- block_values = {}, -- array (1-9) of values[block#][value] = { cell1, cell2... }
- possible = {}, -- possible values per cell, possible[cell# 1-81] = { val1, val2... }
- invalid = {}, -- array (1-81) of known invalid[cell#] = true/nil
- flags = {} -- extra flags for puzzle, eg. ELIMINATE_NAKED_PAIRS
- }
- for i = 1, 81 do
- sudokuTable.values[i] = 0 -- initialize to unknown
- sudokuTable.possible[i] = {} -- initialize to empty
- end
- for i = 1, 9 do
- sudokuTable.row_values[i] = {}
- sudokuTable.col_values[i] = {}
- sudokuTable.block_values[i] = {}
- end
- sudoku.UpdateTable(sudokuTable)
- return sudokuTable
- end
- -- Update all the values in the table using only the cell values, modifies input sudokuTable.
- function sudoku.UpdateTable(sudokuTable)
- sudoku.CalcRowColBlockValues(sudokuTable)
- sudoku.CalcInvalidCells(sudokuTable)
- sudoku.CalcAllPossible(sudokuTable)
- end
- -- Set the values table in the sudokuTable and update everything, modifies input sudokuTable.
- function sudoku.SetValues(sudokuTable, values)
- sudokuTable.values = values
- sudoku.UpdateTable(sudokuTable)
- end
- -- Open a sudoku table from a file, the file should be formatted as 9x9 numbers
- -- with 9 numbers per row and 9 columns.
- -- returns a sudoku.CreateTable() with the values set and "" on success
- -- or nil, error_message on failure
- function sudoku.Open(fileName)
- local values = {}
- local value_count = 0 -- number of cols in line
- local row_count = 0 -- number of rows read
- local line_n = 0 -- actual line number in file
- for line in io.lines(fileName) do
- line_n = line_n + 1
- local col_count = 0
- for k, v in string.gmatch(line, "%d") do
- k = tonumber(k)
- if (k >= 0) and (k <= 9) then
- table.insert(values, k)
- col_count = col_count + 1
- value_count = value_count + 1
- else
- return nil, string.format("Error loading sudoku file : '%s' invalid number '%d' on line %d.", fileName, k, line_n)
- end
- end
- if col_count == 9 then
- row_count = row_count + 1
- elseif (col_count ~= 0) and (col_count ~= 9) then
- return nil, string.format("Error loading sudoku file : '%s' on line %d.\nExpecting 9 columns, found %d.", fileName, line_n, col_count)
- end
- end
- if line_n == 0 then
- return nil, string.format("Error opening sudoku file : '%s'.", fileName)
- elseif row_count ~= 9 then
- return nil, string.format("Error loading sudoku file : '%s', expected 9 rows, found %d.", fileName, row_count)
- elseif value_count ~= 81 then
- return nil, string.format("Error loading sudoku file : '%s', expected 81 numbers, found %d.", fileName, value_count)
- end
- local s = sudoku.CreateTable()
- s.flags[sudoku.FILENAME] = fileName
- sudoku.SetValues(s, values)
- return s, ""
- end
- -- Save a sudoku grid as a 9x9 comma separated table to a file, returns success
- function sudoku.Save(sudokuTable, fileName)
- local f = io.open(fileName, "w+")
- if not f then return false end
- local str = sudoku.ToString(sudokuTable)
- f:write(str)
- io.close(f)
- sudokuTable.flags[sudoku.FILENAME] = fileName
- return true
- end
- -- ============================================================================
- -- Neatly print the sudoku grid
- function sudoku.PrintGrid(sudokuTable)
- local str = string.rep("-", 13).."\n"
- for r = 1, 9 do
- str = str.."|"
- for c = 1, 9 do
- local v = " "
- if sudoku.HasValue(sudokuTable, r, c) then
- v = tostring(sudoku.GetValue(sudokuTable, r, c))
- end
- str = str..v
- if math.fmod(c, 3) == 0 then str = str.."|" end
- end
- str = str.."\n"
- if math.fmod(r, 3) == 0 then str = str..string.rep("-", 13).."\n" end
- end
- print(str)
- end
- -- Write the grid itself to a string as 9x9 with a space/line separating blocks
- function sudoku.ToString(sudokuTable)
- local str = ""
- for r = 1, 9 do
- for c = 1, 9 do
- local v = "0"
- if sudoku.HasValue(sudokuTable, r, c) then
- v = tostring(sudoku.GetValue(sudokuTable, r, c))
- end
- str = str..v..","
- if math.fmod(c, 3) == 0 then str = str.." " end
- end
- str = str.."\n"
- if (r < 9) and (math.fmod(r, 3) == 0) then str = str.."\n" end
- end
- return str
- end
- -- Neatly print the possible values for each cell (you must calculate it first)
- function sudoku.PrintPossible(sudokuTable)
- local str = string.rep("-", 103).."\n"
- for r = 1, 9 do
- str = str.."|"
- for c = 1, 9 do
- local has_value = sudoku.HasValue(sudokuTable, r, c)
- if has_value then str = str.."<" else str = str.."[" end
- local p = sudoku.GetPossible(sudokuTable, r, c)
- for i = 1, 9 do
- str = str..(p[i] or " ")
- end
- if has_value then str = str..">" else str = str.."]" end
- if math.fmod(c, 3) == 0 then str = str.."|" end
- end
- str = str.."\n"
- if math.fmod(r, 3) == 0 then str = str..string.rep("-", 103).."\n" end
- end
- print(str)
- end
- -- ============================================================================
- -- Convert a row, col cell index (1-9) into a linear position in the grid (1-81)
- function sudoku.RowColToCell(row, col)
- return (row-1)*9 + col
- end
- -- Convert a linear cell index (1-81) into a row, col cell index (1-9)
- function sudoku.CellToRowCol(cell)
- return sudoku.CellToRowTable[cell], sudoku.CellToColTable[cell]
- end
- function sudoku.CellToRow(cell)
- return sudoku.CellToRowTable[cell]
- end
- function sudoku.CellToCol(cell)
- return sudoku.CellToColTable[cell]
- end
- -- ============================================================================
- -- Check the validity of rows, cols, cells, blocks, values
- function sudoku.IsValidValueN(value)
- return (value >= 1) and (value <= 9)
- end
- -- ============================================================================
- -- Convert a row, col cell index (1-9) into the linear block number (1-9)
- function sudoku.RowColToBlock(row, col)
- return sudoku.CellToBlockTable[sudoku.RowColToCell(row, col)]
- end
- -- Get the block (1-9) that this cell (1-81) is in
- function sudoku.CellToBlock(cell)
- return sudoku.CellToBlockTable[cell]
- end
- -- Get the upper left cell of this block
- function sudoku.BlockToCell(block)
- return sudoku.BlockToCellTable[block]
- end
- -- Convert a linear block index (1-9) into upper left row, col cell index (1-9)
- function sudoku.BlockToRowCol(block)
- return sudoku.BlockToRowTable[block], sudoku.BlockToColTable[block]
- end
- function sudoku.BlockToRow(block)
- return sudoku.BlockToRowTable[block]
- end
- function sudoku.BlockToCol(block)
- return sudoku.BlockToColTable[block]
- end
- -- Get the upper left row, col cell of the block given by row, col
- function sudoku.RowColToBlockRowCol(row, col)
- local block = sudoku.RowColToBlock(row, col)
- return sudoku.BlockToRowTable[block], sudoku.BlockToColTable[block]
- end
- -- Generate a table of {[cell] = {hash table of cells that are in the row, col, block of this cell}}
- sudoku.cellToRowColBlockCellsTable = {}
- for cell = 1, 81 do
- local row, col = sudoku.CellToRowCol(cell)
- local block_cell = sudoku.BlockToCell(sudoku.CellToBlock(cell))
- sudoku.cellToRowColBlockCellsTable[cell] = {}
- for rcb = 1, 9 do
- local c = sudoku.RowColToCell(rcb, col)
- sudoku.cellToRowColBlockCellsTable[cell][c] = true
- c = sudoku.RowColToCell(row, rcb)
- sudoku.cellToRowColBlockCellsTable[cell][c] = true
- c = rcb + block_cell + sudoku.LinearBlockCellTable[rcb]
- sudoku.cellToRowColBlockCellsTable[cell][c] = true
- end
- end
- -- Generate a table of {[cell] = {array of cells that are in the row, col, block of this cell}}
- sudoku.cellToRowColBlockCellsArray = {}
- for cell = 1, 81 do
- sudoku.cellToRowColBlockCellsArray[cell] = {}
- for k, v in pairs(sudoku.cellToRowColBlockCellsTable[cell]) do
- table.insert(sudoku.cellToRowColBlockCellsArray[cell], k)
- end
- end
- sudoku.RowCellTable = {}
- sudoku.ColCellTable = {}
- sudoku.BlockCellTable = {}
- sudoku.BlockCellShiftTable = {0, 3, 6, 27, 30, 33, 54, 57, 60}
- for n = 1, 9 do
- local nn = (n-1)*9
- sudoku.RowCellTable[n] = {1+nn, 2+nn, 3+nn, 4+nn, 5+nn, 6+nn, 7+nn, 8+nn, 9+nn}
- nn = n - 1
- sudoku.ColCellTable[n] = {1+nn, 10+nn, 19+nn, 28+nn, 37+nn, 46+nn, 55+nn, 64+nn, 73+nn}
- nn = sudoku.BlockCellShiftTable[n]
- sudoku.BlockCellTable[n] = {1+nn, 2+nn, 3+nn, 10+nn, 11+nn, 12+nn, 19+nn, 20+nn, 21+nn}
- end
- -- ============================================================================
- -- Get the cell value at a specific row, col
- function sudoku.GetValue(sudokuTable, row, col)
- return sudoku.GetCellValue(sudokuTable, sudoku.RowColToCell(row, col))
- end
- -- Set the cell value at a specific row, col, modifies input sudokuTable.
- function sudoku.SetValue(sudokuTable, row, col, value)
- local cell = sudoku.RowColToCell(row, col)
- local block = sudoku.CellToBlock(cell)
- local old_value = sudokuTable.values[cell]
- if not sudoku.IsValidValueN(value) then value = 0 end
- sudokuTable.values[cell] = value
- --remove the old_value from the row, col, block values
- if sudoku.IsValidValueN(old_value) then
- if sudokuTable.row_values[row] and sudokuTable.row_values[row][old_value] then
- sudokuTable.row_values[row][old_value][cell] = nil
- if TableIsEmpty(sudokuTable.row_values[row][old_value]) then sudokuTable.row_values[row][old_value] = nil end
- end
- if sudokuTable.col_values[col] and sudokuTable.col_values[col][old_value] then
- sudokuTable.col_values[col][old_value][cell] = nil
- if TableIsEmpty(sudokuTable.col_values[col][old_value]) then sudokuTable.col_values[col][old_value] = nil end
- end
- if sudokuTable.block_values[block] and sudokuTable.block_values[block][old_value] then
- sudokuTable.block_values[block][old_value][cell] = nil
- if TableIsEmpty(sudokuTable.block_values[block][old_value]) then sudokuTable.block_values[block][old_value] = nil end
- end
- end
- --add new value to the row, col, block values
- if value ~= 0 then
- if not sudokuTable.row_values[row] then
- sudokuTable.row_values[row] = {[value] = {[cell] = cell}}
- elseif not sudokuTable.row_values[row][value] then
- sudokuTable.row_values[row][value] = {[cell] = cell}
- else
- sudokuTable.row_values[row][value][cell] = cell
- end
- if not sudokuTable.col_values[col] then
- sudokuTable.col_values[col] = {[value] = {[cell] = cell}}
- elseif not sudokuTable.col_values[col][value] then
- sudokuTable.col_values[col][value] = {[cell] = cell}
- else
- sudokuTable.col_values[col][value][cell] = cell
- end
- if not sudokuTable.block_values[block] then
- sudokuTable.block_values[block] = {[value] = {[cell] = cell}}
- elseif not sudokuTable.block_values[block][value] then
- sudokuTable.block_values[block][value] = {[cell] = cell}
- else
- sudokuTable.block_values[block][value][cell] = cell
- end
- end
- end
- -- Does the cell have a value at a specific row, col
- function sudoku.HasValue(sudokuTable, row, col)
- return sudoku.HasCellValue(sudokuTable, sudoku.RowColToCell(row, col))
- end
- -- Set the cell value at a specific cell
- function sudoku.GetCellValue(sudokuTable, cell)
- return sudokuTable.values[cell]
- end
- -- Set the cell value at a specific cell, modifies input sudokuTable.
- function sudoku.SetCellValue(sudokuTable, cell, value)
- local row, col = sudoku.CellToRowCol(cell)
- sudoku.SetValue(sudokuTable, row, col, value)
- end
- -- Does the cell have a value at a specific cell
- function sudoku.HasCellValue(sudokuTable, cell)
- return sudoku.IsValidValueN(sudokuTable.values[cell])
- end
- -- ============================================================================
- -- Set the row_values, col_values, block_values tables of the input sudokuTable
- -- eg. row values table is row_values[row#][value][cell#] = cell#
- -- if no value then row_values[row#][value] = nil
- function sudoku.CalcRowColBlockValues(sudokuTable)
- sudokuTable.row_values = {}
- sudokuTable.col_values = {}
- sudokuTable.block_values = {}
- for cell = 1, 81 do
- local row, col = sudoku.CellToRowCol(cell)
- local block = sudoku.CellToBlock(cell)
- if not sudokuTable.row_values[row] then sudokuTable.row_values[row] = {} end
- if not sudokuTable.col_values[col] then sudokuTable.col_values[col] = {} end
- if not sudokuTable.block_values[block] then sudokuTable.block_values[block] = {} end
- local value = sudoku.GetCellValue(sudokuTable, cell)
- if sudoku.IsValidValueN(value) then
- if not sudokuTable.row_values[row][value] then
- sudokuTable.row_values[row][value] = {[cell] = cell}
- else
- sudokuTable.row_values[row][value][cell] = cell
- end
- if not sudokuTable.col_values[col][value] then
- sudokuTable.col_values[col][value] = {[cell] = cell}
- else
- sudokuTable.col_values[col][value][cell] = cell
- end
- if not sudokuTable.block_values[block][value] then
- sudokuTable.block_values[block][value] = {[cell] = cell}
- else
- sudokuTable.block_values[block][value][cell] = cell
- end
- end
- end
- end
- -- ============================================================================
- -- Can this value be put into this cell given the other existing values?
- function sudoku.IsValidValue(sudokuTable, row, col, value)
- if sudokuTable.row_values[row][value] or
- sudokuTable.col_values[col][value] or
- sudokuTable.block_values[sudoku.RowColToBlock(row, col)][value] then
- return false
- end
- return true
- end
- -- Find all the invalid cells by looking for duplicates, modifies input sudokuTable.
- -- fills sudokuTable.invalid table with the values
- function sudoku.CalcInvalidCells(sudokuTable)
- sudokuTable.invalid = {} -- reset to all good
- for n = 1, 9 do
- for i, cell_table in pairs(sudokuTable.row_values[n]) do
- if TableCount(cell_table) > 1 then
- for j, cell in pairs(cell_table) do
- sudokuTable.invalid[cell] = true
- end
- end
- end
- for i, cell_table in pairs(sudokuTable.col_values[n]) do
- if TableCount(cell_table) > 1 then
- for j, cell in pairs(cell_table) do
- sudokuTable.invalid[cell] = true
- end
- end
- end
- for i, cell_table in pairs(sudokuTable.block_values[n]) do
- if TableCount(cell_table) > 1 then
- for j, cell in pairs(cell_table) do
- sudokuTable.invalid[cell] = true
- end
- end
- end
- end
- end
- -- ============================================================================
- -- Get the possible values at a specific row, col cell
- -- Must be previously set from sudoku.CalcAllPossible
- function sudoku.GetPossible(sudokuTable, row, col)
- return sudokuTable.possible[sudoku.RowColToCell(row, col)]
- end
- function sudoku.GetCellPossible(sudokuTable, cell)
- return sudokuTable.possible[cell]
- end
- -- Set the possible values at a specific row, col cell. Modifies input sudokuTable.
- function sudoku.SetPossible(sudokuTable, row, col, possibleTable)
- sudokuTable.possible[sudoku.RowColToCell(row, col)] = possibleTable
- end
- function sudoku.SetCellPossible(sudokuTable, cell, possibleTable)
- sudokuTable.possible[cell] = possibleTable
- end
- -- Remove a possible value at a specific row, col cell only. Modifies input sudokuTable.
- function sudoku.RemovePossible(sudokuTable, row, col, value)
- return sudoku.RemoveCellPossible(sudokuTable, sudoku.RowColToCell(row, col), value)
- end
- function sudoku.RemoveCellPossible(sudokuTable, cell, value)
- sudokuTable.possible[cell][value] = nil
- end
- -- Remove a possible values from the row, col, block. Modifies input sudokuTable.
- -- if exceptTable then don't remove it from exceptTable[cell#] = true
- function sudoku.RemovePossibleAll(sudokuTable, cell, value, exceptTable, break_if_empty)
- exceptTable = exceptTable or {}
- break_if_empty = break_if_empty or false
- for i, c in ipairs(sudoku.cellToRowColBlockCellsArray[cell]) do
- if (not exceptTable[c]) and sudokuTable.possible[c][value] then
- sudokuTable.possible[c][value] = nil
- if break_if_empty and (not sudoku.HasCellValue(sudokuTable, c)) and TableIsEmpty(sudokuTable.possible[c]) then
- return
- end
- end
- end
- end
- -- Remove a possible values from the row. Modifies input sudokuTable.
- -- if exceptTable then don't remove it from exceptTable[cell#] = true
- function sudoku.RemovePossibleRow(sudokuTable, row, value, exceptTable)
- exceptTable = exceptTable or {}
- for col = 1, 9 do
- local cell = sudoku.RowColToCell(row, col)
- if (not exceptTable[cell]) and sudokuTable.possible[cell][value] then
- sudokuTable.possible[cell][value] = nil
- end
- end
- end
- -- Remove a possible values from the col. Modifies input sudokuTable.
- -- if exceptTable then don't remove it from exceptTable[cell#] = true
- function sudoku.RemovePossibleCol(sudokuTable, col, value, exceptTable)
- exceptTable = exceptTable or {}
- for row = 1, 9 do
- local cell = sudoku.RowColToCell(row, col)
- if (not exceptTable[cell]) and sudokuTable.possible[cell][value] then
- sudokuTable.possible[cell][value] = nil
- end
- end
- end
- -- Remove a possible values from the block. Modifies input sudokuTable.
- -- if exceptTable then don't remove it from exceptTable[cell#] = true
- function sudoku.RemovePossibleBlock(sudokuTable, block, value, exceptTable)
- exceptTable = exceptTable or {}
- local block_cell = sudoku.BlockToCell(block)
- for n = 1, 9 do
- local cell = n + block_cell + sudoku.LinearBlockCellTable[n]
- if (not exceptTable[cell]) and sudokuTable.possible[cell][value] then
- sudokuTable.possible[cell][value] = nil
- end
- end
- end
- -- Get the count of all possible values for rows, cols, and blocks
- -- returns 3 tables row_possible[row#][value] = #times possible value occurs in row
- -- and the same for col_possible, block_possible
- -- if no possible values (all values set) then row_possible[row#] = nil
- function sudoku.FindPossibleCountRowColBlock(sudokuTable)
- local row_possible = {}
- local col_possible = {}
- local block_possible = {}
- for cell = 1, 81 do
- local row, col = sudoku.CellToRowCol(cell)
- local block = sudoku.CellToBlock(cell)
- local cell_possible = sudoku.GetCellPossible(sudokuTable, cell)
- for pvalue, is_possible in pairs(cell_possible) do
- if not row_possible[row] then row_possible[row] = {} end
- if not col_possible[col] then col_possible[col] = {} end
- if not block_possible[block] then block_possible[block] = {} end
- row_possible[row][pvalue] = (row_possible[row][pvalue] or 0) + 1
- col_possible[col][pvalue] = (col_possible[col][pvalue] or 0) + 1
- block_possible[block][pvalue] = (block_possible[block][pvalue] or 0) + 1
- end
- end
- return row_possible, col_possible, block_possible
- end
- -- Find all the possible values for row, col cell
- -- returns a table of possible[value] = true
- function sudoku.FindPossibleCell(sudokuTable, row, col)
- local possible = {}
- -- gather up all the set values in row, col, and block
- local rowValues = sudokuTable.row_values[row]
- local colValues = sudokuTable.col_values[col]
- local blockValues = sudokuTable.block_values[sudoku.RowColToBlock(row, col)]
- -- remove the set values from the possible values
- for v = 1, 9 do
- if (rowValues[v] == nil) and (colValues[v] == nil) and (blockValues[v] == nil) then
- possible[v] = v
- end
- end
- return possible
- end
- -- Find all the possible values for the whole table by filling out the
- -- possible table in the input sudokuTable. Modifies input sudokuTable.
- function sudoku.CalcAllPossible(sudokuTable)
- for cell = 1, 81 do
- local row, col = sudoku.CellToRowCol(cell)
- local possible = {}
- if not sudoku.HasCellValue(sudokuTable, cell) then
- local block = sudoku.CellToBlock(cell)
- for v = 1, 9 do
- if (sudokuTable.row_values[row][v] == nil) and
- (sudokuTable.col_values[col][v] == nil) and
- (sudokuTable.block_values[block][v] == nil) then
- possible[v] = v
- end
- end
- end
- sudoku.SetCellPossible(sudokuTable, cell, possible)
- end
- -- this function checks flags to see if it should run
- sudoku.RemovePossibleGroups(sudokuTable)
- end
- -- Find all the possible pairs, triplets, quads in the table
- -- must run CalcAllPossible first, does not eliminate any.
- -- returns 3 tables, possible_pairs.rows[row#][key] = { cell1, cell2... },
- -- possible_pairs.cols[col#][key] = { cell1, cell2... },
- -- possible_pairs.blocks[block#][key] = { cell1, cell2... },
- -- and the same for possible_triplets, possible_quads
- -- key is constructed from the number group as string.char(val1, val2...)
- sudoku.FindAllPossibleGroups_Cache = {}
- function sudoku.FindAllPossibleGroups(sudokuTable)
- local possible_pairs = {rows = {}, cols = {}, blocks = {}}
- local possible_triplets = {rows = {}, cols = {}, blocks = {}}
- local possible_quads = {rows = {}, cols = {}, blocks = {}}
- local char0 = string.byte("0")
- local cache_key_flags = 1*booltoint(sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_PAIRS]) +
- 2*booltoint(sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS]) +
- 4*booltoint(sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_QUADS])
- local cache_keys = { 10^1, 10^2, 10^3, 10^4, 10^5, 10^6, 10^7, 10^8, 10^9 }
- local function add_possible(atable, rcb_key, key, cell)
- local a = atable[rcb_key]
- if not a then
- atable[rcb_key] = { [key] = {cell} }
- elseif not a[key] then
- a[key] = {cell}
- else
- a[key][#a[key]+1] = cell
- end
- end
- for cell = 1, 81 do
- local row, col = sudoku.CellToRowCol(cell)
- local block = sudoku.CellToBlock(cell)
- local cell_possible = sudoku.GetCellPossible(sudokuTable, cell)
- local cell_possible_table = {}
- local cache_key = cache_key_flags
- -- convert key, value table to indexed table and a key for the cache
- local count = 0
- for n = 1, 9 do
- if cell_possible[n] then
- cell_possible_table[#cell_possible_table+1] = char0+n
- cache_key = cache_key + cache_keys[n]
- count = count + 1
- end
- end
- local possible_pairs_keys = {}
- local possible_triplets_keys = {}
- local possible_quads_keys = {}
- -- either use the cached key table or create a new key table for the possible
- -- Note: cache cuts time for 100 calls to this fn w/ empty puzzle from 8 to 1 sec
- if (count > 1) and sudoku.FindAllPossibleGroups_Cache[cache_key] then
- possible_pairs_keys = sudoku.FindAllPossibleGroups_Cache[cache_key].possible_pairs
- possible_triplets_keys = sudoku.FindAllPossibleGroups_Cache[cache_key].possible_triplets
- possible_quads_keys = sudoku.FindAllPossibleGroups_Cache[cache_key].possible_quads
- elseif (count > 1) then
- local elim_pairs = (count == 2) or sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_PAIRS]
- local elim_triplets = (count == 3) or sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS]
- local elim_quads = (count == 4) or sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_QUADS]
- for i = 1, count do
- for j = i+1, count do
- local pkey = string.char(cell_possible_table[i], cell_possible_table[j])
- if elim_pairs then
- possible_pairs_keys[#possible_pairs_keys+1] = pkey
- end
- for k = j+1, count do
- local tkey = pkey..string.char(cell_possible_table[k])
- if elim_triplets then
- possible_triplets_keys[#possible_triplets_keys+1] = tkey
- end
- if elim_quads then
- for l = k+1, count do
- local qkey = tkey..string.char(cell_possible_table[l])
- possible_quads_keys[#possible_quads_keys+1] = qkey
- end
- end
- end
- end
- end
- sudoku.FindAllPossibleGroups_Cache[cache_key] = {}
- sudoku.FindAllPossibleGroups_Cache[cache_key].possible_pairs = possible_pairs_keys
- sudoku.FindAllPossibleGroups_Cache[cache_key].possible_triplets = possible_triplets_keys
- sudoku.FindAllPossibleGroups_Cache[cache_key].possible_quads = possible_quads_keys
- end
- for k, key in pairs(possible_pairs_keys) do
- add_possible(possible_pairs.rows, row, key, cell)
- add_possible(possible_pairs.cols, col, key, cell)
- add_possible(possible_pairs.blocks, block, key, cell)
- end
- for k, key in pairs(possible_triplets_keys) do
- add_possible(possible_triplets.rows, row, key, cell)
- add_possible(possible_triplets.cols, col, key, cell)
- add_possible(possible_triplets.blocks, block, key, cell)
- end
- for k, key in pairs(possible_quads_keys) do
- add_possible(possible_quads.rows, row, key, cell)
- add_possible(possible_quads.cols, col, key, cell)
- add_possible(possible_quads.blocks, block, key, cell)
- end
- end
- return possible_pairs, possible_triplets, possible_quads
- end
- -- Find all the naked and hidden pairs, triplets, quads in the table
- -- must run CalcAllPossible first, does not eliminate any.
- -- returns 2 tables, naked.rows[row#][key] = { cell1, cell2... },
- -- naked.cols[col#][key] = { cell1, cell2... },
- -- naked.blocks[block#][key] = { cell1, cell2... },
- -- and the same for hidden
- -- key is constructed from the number group as string.char(val1, val2...)
- function sudoku.FindAllNakedHiddenGroups(sudokuTable, find_all)
- local flags = sudokuTable.flags
- if find_all == true then
- sudokuTable.flags = TableCopy(flags) -- unref the table
- -- turn all ELIMINATE_XXX on
- for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
- sudokuTable.flags[n] = true
- end
- end
- local row_possible, col_possible, block_possible = sudoku.FindPossibleCountRowColBlock(sudokuTable)
- local possible_pairs, possible_triplets, possible_quads = sudoku.FindAllPossibleGroups(sudokuTable)
- local char0 = string.byte("0")
- local all_groups = { [2] = 36, [3] = 84, [4] = 126 } -- eg. 9!/(2! * (9-2)!)
- if find_all == true then
- sudokuTable.flags = flags -- put the flags back to how they were
- end
- local naked =
- {
- pairs = {rows = {}, cols = {}, blocks = {}, cells = {}},
- triplets = {rows = {}, cols = {}, blocks = {}, cells = {}},
- quads = {rows = {}, cols = {}, blocks = {}, cells = {}}
- }
- local hidden =
- {
- pairs = {rows = {}, cols = {}, blocks = {}, cells = {}},
- triplets = {rows = {}, cols = {}, blocks = {}, cells = {}},
- quads = {rows = {}, cols = {}, blocks = {}, cells = {}}
- }
- -- cache all the cell possible value counts
- local cell_possible_count = {}
- for n = 1, 81 do
- cell_possible_count[n] = TableCount(sudoku.GetCellPossible(sudokuTable, n) or {})
- end
- local function dofind(rcb_table, num, key, cell_table_, rcb, rcb_possible)
- local naked_cell_table = {}
- local naked_cell_count = 0
- local hidden_cell_table = {}
- local hidden_cell_count = 0
- local is_hidden = true
- -- can only be exactly as many nums in key as in rcb for hidden
- for n = 1, num do
- if rcb_possible[string.byte(key, n)-char0] ~= num then
- is_hidden = false
- break
- end
- end
- for n, cell in ipairs(cell_table_) do
- if cell_possible_count[cell] == num then
- naked_cell_table[#naked_cell_table+1] = cell
- naked_cell_count = naked_cell_count + 1
- end
- if is_hidden then
- hidden_cell_table[#hidden_cell_table+1] = cell
- hidden_cell_count = hidden_cell_count + 1
- end
- end
- -- has to be at least the same cell_count as num, if more then error, but...
- if (naked_cell_count >= num) then
- if not rcb_table.naked_table[rcb] then rcb_table.naked_table[rcb] = {} end
- rcb_table.naked_table[rcb][key] = naked_cell_table
- local cell_table = rcb_table.naked_table_base.cells
- for n, cell in pairs(naked_cell_table) do
- if not cell_table[cell] then cell_table[cell] = {} end
- table.insert(cell_table[cell], key)
- end
- end
- -- has to be at least the same cell_count as num, if more then error, but...
- if is_hidden and (hidden_cell_count >= num) then
- if not rcb_table.hidden_table[rcb] then rcb_table.hidden_table[rcb] = {} end
- rcb_table.hidden_table[rcb][key] = hidden_cell_table
- local cell_table = rcb_table.hidden_table_base.cells
- for n, cell in pairs(hidden_cell_table) do
- if not cell_table[cell] then cell_table[cell] = {} end
- table.insert(cell_table[cell], key)
- end
- end
- end
- local function find(naked_table, hidden_table, possible_table, num)
- local rcb_table = {}
- rcb_table.naked_table_base = naked_table
- rcb_table.hidden_table_base = hidden_table
- rcb_table.naked_table = naked_table.rows
- rcb_table.hidden_table = hidden_table.rows
- for row, key_table in pairs(possible_table.rows) do
- for key, cell_table in pairs(key_table) do
- dofind(rcb_table, num, key, cell_table, row, row_possible[row])
- end
- end
- rcb_table.naked_table = naked_table.cols
- rcb_table.hidden_table = hidden_table.cols
- for col, key_table in pairs(possible_table.cols) do
- for key, cell_table in pairs(key_table) do
- dofind(rcb_table, num, key, cell_table, col, col_possible[col])
- end
- end
- rcb_table.naked_table = naked_table.blocks
- rcb_table.hidden_table = hidden_table.blocks
- for block, key_table in pairs(possible_table.blocks) do
- for key, cell_table in pairs(key_table) do
- dofind(rcb_table, num, key, cell_table, block, block_possible[block])
- end
- end
- return naked_table, hidden_table
- end
- naked.pairs, hidden.pairs = find(naked.pairs, hidden.pairs, possible_pairs, 2)
- naked.triplets, hidden.triplets = find(naked.triplets, hidden.triplets, possible_triplets, 3)
- naked.quads, hidden.quads = find(naked.quads, hidden.quads, possible_quads, 4)
- return naked, hidden
- end
- -- ============================================================================
- -- Find all pairs, triplets, quads of values in rows and reset the possible
- -- values for the row to exclude these values. Modifies input sudokuTable.
- function sudoku.RemovePossibleGroups(sudokuTable)
- -- must have at least one flag set
- local has_elim_flags = false
- for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
- if sudokuTable.flags[n] == true then
- has_elim_flags = true
- break
- end
- end
- if has_elim_flags == false then
- return
- end
- local naked, hidden = sudoku.FindAllNakedHiddenGroups(sudokuTable, false)
- local char0 = string.byte("0")
- local function clear_possible(group_table, num, remove_fn)
- for n = 1, 9 do
- if group_table[n] then
- for key, cell_table in pairs(group_table[n]) do
- local exceptTable = {}
- for k, v in pairs(cell_table) do
- exceptTable[v] = v
- end
- for k = 1, num do
- local val = string.byte(key, k)-char0
- remove_fn(sudokuTable, n, val, exceptTable)
- end
- end
- end
- end
- end
- if (sudokuTable.flags[sudoku.ELIMINATE_NAKED_PAIRS] == true) then
- clear_possible(naked.pairs.rows, 2, sudoku.RemovePossibleRow)
- clear_possible(naked.pairs.cols, 2, sudoku.RemovePossibleCol)
- clear_possible(naked.pairs.blocks, 2, sudoku.RemovePossibleBlock)
- end
- if (sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_PAIRS] == true) then
- clear_possible(hidden.pairs.rows, 2, sudoku.RemovePossibleRow)
- clear_possible(hidden.pairs.cols, 2, sudoku.RemovePossibleCol)
- clear_possible(hidden.pairs.blocks, 2, sudoku.RemovePossibleBlock)
- end
- if (sudokuTable.flags[sudoku.ELIMINATE_NAKED_TRIPLETS] == true) then
- clear_possible(naked.triplets.rows, 3, sudoku.RemovePossibleRow)
- clear_possible(naked.triplets.cols, 3, sudoku.RemovePossibleCol)
- clear_possible(naked.triplets.blocks, 3, sudoku.RemovePossibleBlock)
- end
- if (sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS] == true) then
- clear_possible(hidden.triplets.rows, 3, sudoku.RemovePossibleRow)
- clear_possible(hidden.triplets.cols, 3, sudoku.RemovePossibleCol)
- clear_possible(hidden.triplets.blocks, 3, sudoku.RemovePossibleBlock)
- end
- if (sudokuTable.flags[sudoku.ELIMINATE_NAKED_QUADS] == true) then
- clear_possible(naked.quads.rows, 4, sudoku.RemovePossibleRow)
- clear_possible(naked.quads.cols, 4, sudoku.RemovePossibleCol)
- clear_possible(naked.quads.blocks, 4, sudoku.RemovePossibleBlock)
- end
- if (sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_QUADS] == true) then
- clear_possible(hidden.quads.rows, 4, sudoku.RemovePossibleRow)
- clear_possible(hidden.quads.cols, 4, sudoku.RemovePossibleCol)
- clear_possible(hidden.quads.blocks, 4, sudoku.RemovePossibleBlock)
- end
- end
- -- ============================================================================
- -- Find all the cells that only have a single possible value and set it.
- -- Modifies input sudokuTable.
- function sudoku.SolveScanSingles(sudokuTable)
- sudoku.CalcAllPossible(sudokuTable)
- local changed_cells = {}
- for row = 1, 9 do
- for col = 1, 9 do
- if not sudoku.HasValue(sudokuTable, row, col) then
- local possible = sudoku.GetPossible(sudokuTable, row, col)
- local count = 0
- local value = nil
- for pvalue, is_possible in pairs(possible) do -- count possible values
- count = count + 1
- value = pvalue
- end
- if count == 1 then
- local cell = sudoku.RowColToCell(row, col)
- sudoku.SetValue(sudokuTable, row, col, value)
- sudoku.RemovePossibleAll(sudokuTable, cell, value)
- changed_cells[cell] = value
- end
- end
- end
- end
- if TableIsEmpty(changed_cells) then changed_cells = nil end -- reset if not used
- return changed_cells
- end
- -- Find all the cells that only have a single possible value per row and set it.
- -- Modifies input sudokuTable.
- function sudoku.SolveScanRows(sudokuTable)
- sudoku.SolveScanRowsCols(sudokuTable, true)
- end
- -- Find all the cells that only have a single possible value per col and set it
- -- Modifies input sudokuTable.
- function sudoku.SolveScanCols(sudokuTable)
- sudoku.SolveScanRowsCols(sudokuTable, false)
- end
- function sudoku.SolveScanRowsCols(sudokuTable, scan_rows)
- sudoku.CalcAllPossible(sudokuTable)
- local changed_cells = {}
- for row_col1 = 1, 9 do
- local row = nil -- set row or col depending on scan_rows
- local col = nil
- if scan_rows then row = row_col1 else col = row_col1 end
- local possible = {} -- all the possible values along the row or col
- for i = 1, 9 do possible[i] = {} end
- -- fill possible[pvalue] = { cell1, cell2... } along row or col
- for row_col2 = 1, 9 do
- if scan_rows then col = row_col2 else row = row_col2 end
- if not sudoku.HasValue(sudokuTable, row, col) then
- local cell_possible = sudoku.GetPossible(sudokuTable, row, col)
- for pvalue, is_possible in pairs(cell_possible) do
- table.insert(possible[pvalue], sudoku.RowColToCell(row, col))
- end
- end
- end
- -- iterate through the values and if only one possibility set it
- for value = 1, 9 do
- if TableCount(possible[value]) == 1 then
- local cell = possible[value][1]
- sudoku.SetCellValue(sudokuTable, cell, value)
- sudoku.RemovePossibleAll(sudokuTable, cell, value)
- changed_cells[cell] = value
- end
- end
- end
- if TableIsEmpty(changed_cells) then changed_cells = nil end -- reset if not used
- return changed_cells
- end
- -- Find all the cells that only have a single possible value per block and set it
- -- Modifies input sudokuTable.
- function sudoku.SolveScanBlocks(sudokuTable)
- sudoku.CalcAllPossible(sudokuTable)
- local changed_cells = {}
- for block = 1, 9 do
- local block_row, block_col = sudoku.BlockToRowCol(block)
- local possible = {}
- for i = 1, 9 do possible[i] = {} end
- -- fill possible[pvalue] = { cell1, cell2... } for whole block
- for row = block_row, block_row+2 do
- for col = block_col, block_col+2 do
- if not sudoku.HasValue(sudokuTable, row, col) then
- local cell_possible = sudoku.GetPossible(sudokuTable, row, col)
- for pvalue, is_possible in pairs(cell_possible) do
- table.insert(possible[pvalue], sudoku.RowColToCell(row, col))
- end
- end
- end
- end
- -- iterate through the values and if only one possibility set it
- for value = 1, 9 do
- if TableCount(possible[value]) == 1 then
- local cell = possible[value][1]
- sudoku.SetCellValue(sudokuTable, cell, value)
- sudoku.RemovePossibleAll(sudokuTable, cell, value)
- changed_cells[cell] = value
- end
- end
- end
- if TableIsEmpty(changed_cells) then changed_cells = nil end -- reset if not used
- return changed_cells
- end
- -- Find all the cells that only have a single possible value per row, col, block and set it
- -- Modifies input sudokuTable.
- function sudoku.SolveScan(sudokuTable)
- local changed_single = {}
- local changed_rows = {}
- local changed_cols = {}
- local changed_blocks = {}
- local changed_cells = {} -- total cells changed
- local count = 0
- local function add_changed(changed_table, changed_cells)
- if changed_table then
- changed_cells = TableMerge(changed_table, changed_cells)
- end
- return changed_cells
- end
- while (count < 10000) and (changed_single or changed_rows or changed_cols or changed_blocks) do
- changed_single = sudoku.SolveScanSingles(sudokuTable)
- changed_rows = sudoku.SolveScanRows(sudokuTable)
- changed_cols = sudoku.SolveScanCols(sudokuTable)
- changed_blocks = sudoku.SolveScanBlocks(sudokuTable)
- changed_cells = add_changed(changed_single, changed_cells)
- changed_cells = add_changed(changed_rows, changed_cells)
- changed_cells = add_changed(changed_cols, changed_cells)
- changed_cells = add_changed(changed_blocks, changed_cells)
- count = count + 1
- end
- if TableIsEmpty(changed_cells) then changed_cells = nil end -- nothing done
- return count, changed_cells
- end
- -- Brute force recursive solver, returns a new table and does not the input sudokuTable.
- -- (call with only the SudokuTable, don't enter other parameters)
- function sudoku.SolveBruteForce(sudokuTable, backwards)
- -- first time through find possible to limit choices, subsequent calls ok
- local s = sudoku.CreateTable()
- -- finding all the possibilities is slow, but at least do solve scan
- --s.flags[sudoku.ELIMINATE_NAKED_PAIRS] = true
- --s.flags[sudoku.ELIMINATE_HIDDEN_PAIRS] = true
- --s.flags[sudoku.ELIMINATE_NAKED_TRIPLETS] = true
- --s.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS] = true
- --s.flags[sudoku.ELIMINATE_NAKED_QUADS] = true
- --s.flags[sudoku.ELIMINATE_HIDDEN_QUADS] = true
- s.values = TableCopy(sudokuTable.values)
- sudoku.CalcRowColBlockValues(s)
- sudoku.CalcAllPossible(s)
- sudoku.SolveScan(s)
- -- table consists of guesses[cell] = #num
- -- guesses.current is current guess #
- local guesses = { current = 0 }
- for n = 1, 81 do guesses[n] = 0 end
- -- we don't need these for this and they just slow TableCopy down
- -- they're recreated at the end using UpdateTable
- s.row_values = nil
- s.col_values = nil
- s.block_values = nil
- s.invalid = nil
- s.flags = nil
- return sudoku.DoSolveBruteForce(sudokuTable, backwards, s, guesses, 1)
- end
- function sudoku.DoSolveBruteForce(sudokuTable, backwards, simpleTable, guesses, cell)
- local s = simpleTable
- local g, empty_possible
- if sudoku.SolveBruteForceHook then
- if not sudoku.SolveBruteForceHook(guesses, cell) then
- return nil, guesses, cell
- end
- end
- while cell <= 81 do
- if not sudoku.HasCellValue(s, cell) then
- local possible = sudoku.GetCellPossible(s, cell)
- --for k, v in pairs(possible) do -- use for loop to ensure direction
- local start_n = iff(backwards, 9, 1)
- local end_n = iff(backwards, 1, 9)
- local dir_n = iff(backwards, -1, 1)
- for n = start_n, end_n, dir_n do
- if possible[n] then
- -- try a number and remove it as a possibility
- sudoku.RemoveCellPossible(s, cell, n)
- -- start a new table and test out this guess
- local s1 = TableCopy(s)
- -- don't use SetValue since we only care about possible
- s1.values[cell] = n --sudoku.SetValue(s1, row, col, n)
- sudoku.RemovePossibleAll(s1, cell, n, nil, true)
- guesses[cell] = guesses[cell] + 1
- guesses.current = guesses.current + 1
- -- check for nil return from RemovePossibleAll for break_if_empty
- if s1 then
- s1, g = sudoku.DoSolveBruteForce(sudokuTable, backwards, s1, guesses, cell+1)
- -- if s1 then success! we're all done
- if s1 then
- -- copy all original data back and just set the values
- local s2 = TableCopy(sudokuTable)
- sudoku.SetValues(s2, s1.values)
- return s2, g
- end
- end
- end
- end
- return nil, guesses -- tried all values for cell with no solution
- end
- cell = cell + 1
- end
- local s2 = TableCopy(sudokuTable)
- sudoku.SetValues(s2, s.values)
- return s2, guesses
- end
- -- Does this puzzle have a unique solution. It works by trying the brute force
- -- solution method iterating from low to high numbers and then from high to low.
- -- This should always find if there are at least two solutions.
- -- returns nil if no solution or [s1, s2] if at least two solutions, else
- -- just the single unique solution.
- -- Returns a new solved table or nil on failure, doesn't modify input sudokuTable.
- function sudoku.IsUniquePuzzle(sudokuTable)
- local s1, g1 = sudoku.SolveBruteForce(sudokuTable, false)
- if not s1 then return nil end
- local s2, g2 = sudoku.SolveBruteForce(sudokuTable, true)
- if not s2 then return nil end
- if not sudoku.IsSamePuzzle(s1, s2) then return s1, s2 end
- return s1
- end
- -- Do these two puzzles have the same cell values? Returns true/false.
- function sudoku.IsSamePuzzle(s1, s2)
- for cell = 1, 81 do
- if sudoku.GetCellValue(s1, cell) ~= sudoku.GetCellValue(s2, cell) then
- return false
- end
- end
- return true
- end
- -- ============================================================================
- -- Create a full puzzle with all the values
- -- returns sudokuTable, count where count is the number of iterations
- function sudoku.GeneratePuzzle()
- local cell = 0
- local count = 0
- local stuck = {} -- count how many times we've backtracked stuck[cell/9] = count
- math.randomseed(os.time())
- local sudokuTable = sudoku.CreateTable()
- while cell < 81 do
- cell = cell + 1
- count = count + 1
- if sudoku.GeneratePuzzleHook then
- if not sudoku.GeneratePuzzleHook(count, cell) then
- return nil, count
- end
- end
- local value = math.random(9)
- local row, col = sudoku.CellToRowCol(cell)
- if sudoku.IsValidValue(sudokuTable, row, col, value) then
- sudoku.SetCellValue(sudokuTable, cell, value)
- sudoku.RemovePossibleAll(sudokuTable, cell, value)
- else
- -- try other values starting at value+1 and wrapping around
- local set_value = false
- local i = value + 1
- if i > 9 then i = 1 end
- while i ~= value do
- if sudoku.IsValidValue(sudokuTable, row, col, i) then
- sudoku.SetCellValue(sudokuTable, cell, i)
- sudoku.RemovePossibleAll(sudokuTable, cell, i)
- set_value = true
- break
- end
- i = i + 1
- if i > 9 then i = 1 end
- end
- -- whoops, go back a row or more and start over just to be sure
- if not set_value then
- local block = math.floor(cell/9) + 1
- stuck[block] = (stuck[block] or 0) + 1
- local goback = 9
- if stuck[block] and (stuck[block] > 5) then
- goback = 2 * stuck[block] + 1
- end
- local cell_start = cell - goback
- if cell_start < 1 then
- cell_start = 1
- elseif cell_start < 10 then
- stuck = {} -- really start all over
- end
- for i = cell_start, cell do
- sudoku.SetCellValue(sudokuTable, i, 0)
- end
- cell = cell_start - 1
- end
- end
- end
- return sudokuTable, count
- end
- function sudoku.GeneratePuzzleDifficulty(sudokuTable, num_cells_to_keep, ensure_unique)
- if num_cells_to_keep < 1 then
- return sudoku.CreateTable()
- end
- if ensure_unique == nil then ensure_unique = true end
- math.randomseed(os.time()+1)
- local trial = 1
- local i = 0
- local count = 0
- local soln = TableCopy(sudokuTable)
- local cellTable = {}
- for n = 1, 81 do cellTable[n] = n end
- local cell_count, cell_n, cell
- while i < 81 - num_cells_to_keep do
- cell_count = #cellTable
- -- restart this function if we run out of cells to try
- if (cell_count == 0) or (cell_count + i < num_cells_to_keep) then
- trial = trial + 1
- i = 0
- count = 0
- cellTable = {}
- for n = 1, 81 do cellTable[n] = n end
- cell_count = #cellTable
- cell_n = math.random(cell_count)
- elseif cell_count == 1 then
- cell_n = 1
- else
- cell_n = math.random(cell_count)
- end
- cell = cellTable[cell_n]
- count = count + 1
- if sudoku.GeneratePuzzleDifficultyHook then
- if not sudoku.GeneratePuzzleDifficultyHook(count, i, cell, cell_count, trial) then
- return nil, count
- end
- end
- if ensure_unique == true then
- -- test if soln going forward is same as soln backwards and original
- local s = TableCopy(sudokuTable)
- sudoku.SetCellValue(s, cell, 0)
- local soln1 = sudoku.SolveBruteForce(s, false)
- if not soln1 then return nil, count end
- if not sudoku.IsSamePuzzle(soln, soln1) then
- table.remove(cellTable, cell_n)
- else
- local soln2 = sudoku.SolveBruteForce(s, true)
- if not soln2 then return nil, count end
- if not sudoku.IsSamePuzzle(soln, soln2) then
- table.remove(cellTable, cell_n)
- else
- table.remove(cellTable, cell_n)
- sudoku.SetCellValue(sudokuTable, cell, 0)
- i = i + 1
- end
- end
- else
- table.remove(cellTable, cell_n)
- sudoku.SetCellValue(sudokuTable, cell, 0)
- i = i + 1
- end
- end
- return sudokuTable, count
- end
- -- ============================================================================
- -- ============================================================================
- sudokuGUIxpmdata =
- {
- "16 15 7 1",
- " c None",
- "a c Black",
- "b c #808080",
- "c c #FFFF00",
- "d c #FF0000",
- "e c #0000FF",
- "g c #00FF00",
- " aaaaaaaaaaaaa ",
- " addaggaeeaccab ",
- " addaggaeeaccab ",
- " aaaaaaaaaaaaab ",
- " accaddaggaeeab ",
- " accaddaggaeeab ",
- " aaaaaaaaaaaaab ",
- " aeeaccaddaggab ",
- " aeeaccaddaggab ",
- " aaaaaaaaaaaaab ",
- " aggaeeaccaddab ",
- " aggaeeaccaddab ",
- " aaaaaaaaaaaaab ",
- " bbbbbbbbbbbbb ",
- " "
- }
- -- NOTE: HTML generated using NVU and "tidy -wrap 79 -i -omit -o l.html input.html"
- sudokuGUIhelp =
- [[
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <meta name="generator" content=
- "HTML Tidy for Linux/x86 (vers 1st December 2004), see www.w3.org">
- <title>wxLuaSudoku</title>
- <meta content="John Labenski" name="author">
- <meta content="Documentation for the wxLuaSudoku program" name=
- "description">
- <body bgcolor="#FFFFCC">
- <h1>wxLuaSudoku</h1>Copyright : John Labenski, 2006<br>
- License : wxWindows license.<br>
- <br>
- <i>This program is free software; you can redistribute it and/or modify it
- under the terms of the wxWindows License; either version 3 of the License,
- or (at your option) any later version.<br>
- <br>
- This program is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the wxWindows License for more
- details.</i><br>
- <br>
- If you use this program and find any bugs or have added any features that
- you feel may be generally useful, please feel free to contact me by e-mail
- at jrl1[at]sourceforge[dot]net or on the wxlua-users mailing list.<br>
- <h2>Purpose</h2>The purpose of this program is to demonstrate programming in
- wxLua. It may not be the best, fastest, or most complete sudoku solver
- program available, but the code should be easily understandable with a
- straightforward implementation. However, you may find that it is capable
- enough and since it's free, you got what you paid for.<br>
- <br>
- wxLua is a binding of the wxWidgets cross-platform GUI library to the Lua
- programming language. It is available as an application, library, or source
- code for MSW, Unix like systems, and OSX. More information about wxLua can
- be found on <a href=
- "http://wxlua.sourceforge.net">wxlua.sourceforge.net</a>. Information about
- the wxWidgets library can be found on <a href=
- "http://www.wxwidgets.org">www.wxwidgets.org</a> and the Lua programming
- language on <a href="http://www.lua.org">www.lua.org</a>.<br>
- <h2>Program features</h2>
- <ul>
- <li>Enter your own initial values for a puzzle to solve
- <li>Generate random unique new puzzles
- <li>Load/Save puzzles to files
- <li>Print the puzzle
- <li>Undo/Redo of the steps you've taken
- <li>Mark errors and/or mistakes
- <li>Show calculated possible values for each cell
- <li>Pencil marks, possible values for cells that you have determined
- <li>Automatic marking of hidden/naked pairs, triplets, and quads of
- possible values
- <li>Eliminate possible values by finding hidden/naked pairs, triplets, and
- quads of possible values
- <li>Solve the puzzle by scanning for values, can use eliminate
- naked/hidden groups to do better
- <li>Solve the puzzle by brute force, by guessing values from the possible
- values
- <li>No installer, registry entries, or other nonsense. Merely delete the
- program when you're done with it.
- </ul>
- <h2>How to play</h2>Sudoku is a puzzle game of choosing where to place the
- numbers 1 to 9 in a 9x9 grid of cells. There are many varieties of this
- game, but wxLuaSudoku only works with the 9x9 grid. The grid of 81 cells has
- 9 rows, 9 columns, and 9 blocks that consist of smaller 3x3 grids of cells.
- Each cell in the grid can take a value of 1 to 9, but there are limitations.
- Each row, column, and block must have the full array of numbers from 1 to 9.
- Therefore, there can be no duplicate values in any row, column, or block.
- For a well formed puzzle there's just one possible solution for the initial
- seed of values. There are numerous tutorials on the internet, for example
- <a href=
- "http://en.wikipedia.org/wiki/Sudoku">http://en.wikipedia.org/wiki/Sudoku</a>,
- so lets move on...<br>
- <h2><a name="Some_Sudoku_terms" id="Some_Sudoku_terms"></a>Some Sudoku
- terms</h2>
- <ul>
- <li><b>Initial values :</b> These are values for the cells that the puzzle
- starts with. The minimum number of initial values that a puzzle can start
- with is thought to be 17 in order for the puzzle to have a single unique
- solution.
- <li><b>Possible values :</b> These are values that an empty cell might
- take given the existing values in the row, col, and block that the cell is
- in.
- <li>
- <b>Naked pairs, triplets, quads :</b> When looking though the possible
- values for the cells you may find that two or more cells have the
- identical possible values and only those values. Those would be called
- naked because they're immediately visible.
- <ul>
- <li>Pair example: If two cells in a row have the possible values of 2
- and 4 then either 2 or 4 has to be in those two cells. You can
- therefore eliminate 2 and 4 from the possible values in the rest of
- the row and if they're both in the same block, the block too.
- </ul>
- <li>
- <b>Hidden pairs, triplets, quads :</b> A hidden group is defined as a
- set of numbers that appear in as many cells as the size of the group.
- They are similar to naked groups, but there may also be some other
- possible values that make them a little harder to find.
- <ul>
- <li>Pair example: If two cells in a row have the possible values 1, 2,
- 4 and 1, 2, 4, 5, 6 respectively and the numbers 2 and 4 do not appear
- in any other cell in the row you know that either 2 or 4 must go into
- either cell and eliminate the other possible values from those two
- cells.
- </ul>
- </ul>
- <h2>Entering values</h2>Enter values into the cells by clicking on a cell to
- highlight it and then type a number. Use 0, space, or any non number to
- clear the cell's value. The cells that the puzzle is initialized with are
- read-only, meaning that you cannot change their values unless you are
- actually creating a puzzle using the menu item <i>File->Create</i>.
- Navigate through the cells with the cursor keys or mouse.<br>
- <h2><a name="Pencil_marks" id="Pencil_marks"></a>Pencil marks</h2>You can
- annotate the empty cells with a list of possible values that may work by
- checking the menu item <i>Possible->Show/Edit pencil marks</i>. If you
- want initialize the pencil marks to be all selected, none selected, or set
- to the calculated values use the menu items under <i>Possible->Pencil
- marks</i>. In order to toggle the values, hold down the shift key and all of
- the possible values will be shown. Click on a value to either select or
- deselect it. Alternatively, you can use shift-Number to toggle the selection
- state of the pencil marks.<br>
- <br>
- Note that this is different than the menu item <i>Possible->Show
- calculated possible</i> in that, obviously, this will show a list of
- calculated possible values that could go into the cells based on the current
- state of the puzzle. You can switch between the two views or not show the
- possible values altogether by unchecking both of them.
- <h2><a name="Creating_a_puzzle" id="Creating_a_puzzle"></a>Creating a
- puzzle</h2>To create a new puzzle by hand check the menu item
- <i>File->Create</i>. You've now put the program in a state where any
- values that you enter will be the initial values of the puzzle. Error
- checking of invalid values, values that appear more than once in the same
- row, column, or block, is automatically turned on since it's fairly
- pointless to create puzzles that cannot be solved, though this program
- generously allows you to do so. Enter the values as described above and when
- done be sure to uncheck the <i>File->Create</i> item. The program will
- then try to find a solution to the puzzle and verify that there is only one
- unique solution.<br>
- <br>
- If you want to check the state of the puzzle during creation to confirm that
- it has a unique solution, use the <i>Solve->Verify unique solution</i>
- menu item. If you're determined to create an invalid sudoku puzzle this
- program will not stop you and you can merely cancel out of the warning
- dialogs to continue and you're on your own. However, showing mistakes will
- be disabled and "solving" using the scanning technique may yield strange
- results that are mathematically consistent with the rules of the game, but
- bring you no closer to the nonexistent solution. If at some later point you
- wish to have mistakes shown using <i>View->Mark mistakes</i>, the program
- will try to solve the initial puzzle again, with the same result, no
- solution.<br>
- <br>
- If you want to start with a completely empty grid use <i>File->New</i>.
- If on the other hand, you want to start with a completely filled grid of
- random values, use <i>File->Generate</i> with the number of cell values
- to show set to 81, all shown.
- <h2>Cell markings</h2>The colors and fonts for the cells can be changed with
- the preferences dialog from the menu item <i>View->Preferences</i>. The
- values in the cells have one color if it's an initial value and a different
- color if you've entered the value into the cell during play. The possible
- values that could go into a particular cell use a smaller font and can be
- drawn as a 3x3 grid or in a single line which may be useful if you want to
- print it and work it out by hand.<br>
- <br>
- Possible groups of values for the cells may be set to be marked if they are
- naked and or hidden. Note: It may happen that, for example, there's a
- triplet of possible values in a row and some of the values are shared by a
- pair in a column. In that case the pair values get the appropriate markings
- overriding the triplet markings since it's usually best to try to work with
- them first. Also, if you get really off track the program will happily mark
- groups that would obviously make no sense if there weren't any mistakes.
- This is by design since some people apparently find it interesting to do
- whatever it is that they want to do.
- <h2>Menu item descriptions</h2>All items followed by ... denote that a
- dialog will be shown that can be canceled before any action is taken.
- <ul>
- <li>
- <b>File</b>
- <ul>
- <li><b>New...</b> : Clear the whole puzzle to an uninitialized state so
- that you can then "Create" it from scratch.
- <li>
- <b>Create...</b> : Check this item to enter the initial values,
- uncheck when done to play.
- <ul>
- <li>See the section on <a href="#Creating_a_puzzle">Creating a
- puzzle</a>.
- </ul>
- <li>
- <b>Generate...</b> : Have the program create a new random unique
- puzzle for you.
- <ul>
- <li>Set the number of cell values to show, the more that are
- visible, the easier it'll probably be.
- <li>This program does not try to categorize how difficult the
- generated puzzle is.
- <li>Note: If you have less than 17 cells shown there will not be a
- unique solution.
- <li>The less cells you have shown the longer it will take to find
- a unique puzzle, but the program will try for as long as it takes.
- If you get impatient, just cancel the generation and you will be
- asked if you want the program to just randomly remove cell values
- which may or may not yield a unique solution.
- </ul>
- <li>
- <b>Open...</b> : Open a puzzle from a file, the puzzle should be a
- 9x9 grid of numbers separated by spaces or commas.
- <ul>
- <li>After opening, the program will try to solve the puzzle and let
- you know if there is a problem, such as a non unique solution. You
- can just cancel out if you'd like to skip this check.
- </ul>
- <li><b>Save as...</b> : Save the current puzzle to disk.
- <li><b>Page Setup...</b> : Adjust the paper and margins for printing.
- <li><b>Print Preview...</b> : Preview what the printout will look
- like.
- <li><b>Print...</b> : Print the current puzzle, WYSIWYG, what you see
- is what you get.
- <li><b>Exit</b> : Exit the program.
- </ul>
- <li>
- <b>Edit</b>
- <ul>
- <li><b>Copy puzzle</b> : Copy the puzzle to the clipboard to paste
- into other programs.
- <li><b>Reset...</b> : Clear all the values you've entered to return
- back to the initial state. This is the same as just undoing back to
- the beginning.
- <li><b>Undo/Redo</b> : You can undo and redo values that you've
- entered into the puzzle. If you undo a couple steps and enter a new
- value you cannot then redo back to where you were.
- <li><b>Preferences</b> : Set the colors to use and whatnot in a
- dialog. See the <a href="#Preferences_dialog">preferences dialog</a>
- section.
- <li>
- <b>Save preferences</b> : Save the current preferences, including
- what you've set to show and exclude.
- <ul>
- <li>This program does not automatically save the preferences on
- exit, but rather only when you explicitly request it to do so.
- <li>MSW : preferences are stored in "Documents and
- Settings\username\wxLuaSudoku.ini"
- <li>Unix : preferences are stored in "~/.wxLuaSudoku.ini"
- <li>You may delete these files if you want to reset the program to
- the defaults or are done using it.
- </ul>
- </ul>
- <li>
- <b>View</b>
- <ul>
- <li><b>Mark errors</b> : Mark the cells that have obviously invalid
- values, not invalid from the standpoint of it being the "correct"
- value, but whether or not it's in conflict with another value that has
- already exists in same row, column, or block in the puzzle.
- <li>
- <b>Mark mistakes</b> : Mark the cells that have "wrong" values that
- will not lead to a solution by comparing the values to a
- precalculated solution.
- <ul>
- <li>If the solution has not been already found, perhaps you've
- canceled the initial check, the program will have to work it out.
- If you cancel this process and then want any mistakes shown again,
- it'll have to try to solve it again. In the case that the program
- cannot find a solution or there isn't a unique solution, a warning
- dialog will be shown and marking the mistakes will be
- automatically unchecked.
- </ul>
- <li><b>Show toolbar</b> : Show or hide the toolbar at the top of the
- program.
- <li><b>Show statusbar</b> : Show or hide the statusbar at the bottom
- of the program.
- </ul>
- <li>
- <b>Possible</b>
- <ul>
- <li>
- <b>Show calculated possible</b> : Show the possible values that the
- program has calculated for the empty cells.
- <ul>
- <li>Note that this does not take into account any invalid or
- erroneous values you may have entered, it just shows you possible
- values given the current state of the puzzle.
- </ul>
- <li>
- <b>Show/Edit pencil marks</b> : Allows you to enter the possible
- values for the cells by hand.
- <ul>
- <li>See the section above about <a href="#Pencil_marks">entering
- pencil marks</a>.
- <li>You can show the calculated possible values or the pencil
- marks or neither, but not both at the same time.
- </ul>
- <li><b>Show possible line</b> : Show the possible values in a single
- line, useful if you want to print it out to play later.
- <li>
- <b>Pencil marks</b>
- <ul>
- <li><b>Clear all...</b> : Clear all the pencil marks for the whole
- puzzle.
- <li><b>Set all...</b> : Set all values for the pencil marks for
- the whole puzzle.
- <li><b>Calculate...</b> : Set all the pencil marks to the same
- values as the calculated possible values.
- </ul>
- <li>
- <b>Mark groups</b>
- <ul>
- <li><b>Mark naked groups</b> : Mark all naked groups of cells;
- pairs, triplets, and quads.
- <li><b>Mark hidden groups</b> : Mark all hidden groups of cells;
- pairs, triplets, and quads.
- <li>... mark each group individually
- <li><i>Note: See Solve->Eliminate to remove possible values
- that can be excluded once the groups have been found. You do not
- have to show the groups to eliminate values or vice versa.</i>
- </ul>
- </ul>
- <li>
- <b>Solve</b>
- <ul>
- <li>
- <b>Verify unique solution...</b> : Use the brute force solver to
- find if there is more than one solution for the initial values of
- the puzzle or if there is a solution at all.
- <ul>
- <li>Typically this is not necessary since the program
- automatically tries to verify the puzzle after opening or creating
- a puzzle, but if you had canceled the check you can verify it by
- hand using this.
- </ul>
- <li>
- <b>Show solution</b> : Show the solution to the puzzle that should
- have been automatically found after opening, creating, or generating
- a puzzle.
- <ul>
- <li>If you had canceled out of the puzzle verification procedure
- the program will have to solve it using the initial values in the
- puzzle.
- <li>Use undo to return back to playing the game if you wish to
- just take a peek at some answers.
- </ul>
- <li>
- <b>Eliminate groups</b>
- <ul>
- <li><b>Eliminate naked groups</b> : Remove all calculated possible
- values that can be excluded by evaluating the naked groups of
- pairs, triplets, and quads. This does not work on the pencil
- marks. See the <a href="#Some_Sudoku_terms">Sudoku terms</a>
- section about naked groups.
- <li><b>Eliminate hidden groups</b> : Remove all calculated
- possible values that can be excluded by evaluating the hidden
- groups of pairs, triplets, and quads. This does not work on the
- pencil marks. See the <a href="#Some_Sudoku_terms">Sudoku
- terms</a> section about hidden groups.
- <li>... eliminate each group individually
- <li><i>Note: This updates the View->Show calculated possible
- and also for solving using Solve (scan ...) since the more
- possible values you remove the easier it is to narrow down the
- correct values.</i>
- </ul>
- <li><i><u>Note on solving</u>: Solving works on the current puzzle
- state which means that if you have inadvertently entered a wrong value
- the solver stops at the point where no new values can be placed. The
- values it finds will be logically correct based on the rules of the
- game, but probably wrong. Use mark mistakes if you want to check how
- you're doing.</i>
- <li><b>Solve (scan singles)</b> : Fill in the values for all cells
- that can only take one value.
- <li><b>Solve (scan rows)</b> : Fill in the values of cells that are
- the only ones in the row to have a particular possible value.
- <li><b>Solve (scan cols)</b> : Same for columns
- <li><b>Solve (scan blocks)</b> : Same for blocks
- <li><i><u>Note on solve scanning</u> : You can try iterating through
- scanning singles, rows, columns, blocks to see which new values can be
- found after other values have been set.</i>
- <li>
- <b>Solve (scanning)</b> : Try solving the puzzle by scanning over
- and over until no new cell values can be found.
- <ul>
- <li>Check different eliminate groups to allow the solver to take
- advantage of the reduced number of possible values.
- </ul>
- <li>
- <b>Solve (brute force)</b> : Solve the puzzle by guessing values
- from the possible values using the current puzzle values.
- <ul>
- <li>This will fail if the puzzle cannot be solved (maybe you've
- made a mistake?)
- </ul>
- </ul>
- <li>
- <b>Help</b>
- <ul>
- <li><b>About...</b> : A simple about this program dialog.
- <li><b>Help...</b> : Show this document.
- </ul>
- </ul>
- <h2><a name="Preferences_dialog" id="Preferences_dialog"></a>Preferences
- dialog</h2>In the preferences dialog you can adjust the colors to use for
- the different elements in a cell. A sample cell is shown to give you an idea
- of what it'll look like when done. Note that some fonts you may choose will
- not render properly or may be badly placed in the cells. This is a problem
- with the font itself, just pick a different font. Also, bitmapped fonts only
- come in a limited number of sizes and cannot be scaled to arbitrary sizes,
- again, pick another font that works better on your system.<br>
- <br>
- Additionally, a simpler interface to the menu items <i>Possible->Mark
- groups</i> and <i>Solve->Eliminate groups</i> is provided to make it
- easier to check and uncheck multiple items.
- ]]
- -- Simple way to generate unique window or menu ids and ensure they're in order
- function NewID()
- if not sudokuGUI_ID_New then sudokuGUI_ID_New = wx.wxID_HIGHEST end
- sudokuGUI_ID_New = sudokuGUI_ID_New + 1
- return sudokuGUI_ID_New
- end
- sudokuGUI =
- {
- frame = nil, -- The wxFrame of the program
- panel = nil, -- The main wxPanel child of the wxFrame
- cellWindows = {}, -- The 81 grid cell wxWindows, children of panel
- cellTextCtrl = nil, -- Single wxTextCtrl editor for entering cell values
- focused_cell_id = 0, -- window id of the currently focused cell, 0 for none
- block_refresh = false, -- temporarily block refreshing when true
- filePath = "", -- last opened filePath
- fileName = "", -- last opened fileName
- printData = wx.wxPrintData(),
- pageSetupData = wx.wxPageSetupDialogData(),
- config = nil, -- the wxFileConfig for saving preferences
- Colours = {}, -- table of wxColours to use, indexes below
- VALUE_COLOUR = 1,
- INIT_VALUE_COLOUR = 2,
- POSS_VALUE_COLOUR = 3,
- INVALID_VALUE_COLOUR = 4,
- BACKGROUND_COLOUR = 5,
- ODD_BACKGROUND_COLOUR = 6,
- FOCUS_CELL_COLOUR = 7,
- NAKED_PAIRS_COLOUR = 8,
- NAKED_TRIPLETS_COLOUR = 9,
- NAKED_QUADS_COLOUR = 10,
- HIDDEN_PAIRS_COLOUR = 11,
- HIDDEN_TRIPLETS_COLOUR = 12,
- HIDDEN_QUADS_COLOUR = 13,
- COLOUR_MAX = 13,
- -- A large wxFont for the cell values
- valueFont = { wxfont = nil, size = 8, width = 0, height = 0 },
- -- A smallish wxFont for the possible values
- possibleFont = { wxfont = nil, size = 6, width = 0, height = 0 },
- valueFont_cache = {}, -- valueFont_cache[size] = { width, height }
- possibleFont_cache = {}, -- possibleFont_cache[size] = { width, height }
- -- calc positions once in PaintCell
- -- recalc if any of these parameters change
- -- pos[value].x and .y are upper left corners
- possiblePosCache = { pos = {}, width = 1, height = 1, line = false, cell_width = 1, cell_height = 1 },
- query_save_prefs = true, -- tell user that prefs are stored as fileconfig
- ID_NEW = NewID(),
- ID_CREATE = NewID(),
- ID_GENERATE = NewID(),
- ID_OPEN = NewID(),
- ID_SAVEAS = NewID(),
- ID_PAGESETUP = NewID(),
- ID_PRINTSETUP = NewID(),
- ID_PRINTPREVIEW = NewID(),
- ID_PRINT = NewID(),
- ID_EXIT = wx.wxID_EXIT,
- ID_COPY_PUZZLE = NewID(),
- ID_RESET = NewID(),
- ID_UNDO = NewID(),
- ID_REDO = NewID(),
- ID_PREFERENCES = NewID(), -- show preferences dialog
- ID_SAVE_PREFERENCES = NewID(), -- save preferences
- ID_SHOW_TOOLBAR = NewID(),
- ID_SHOW_TOOLBAR_LABELS = NewID(),
- ID_SHOW_STATUSBAR = NewID(),
- ID_SHOW_ERRORS = NewID(), -- Show errors in the grid
- ID_SHOW_MISTAKES = NewID(), -- Show mistakes in the grid
- ID_SHOW_POSSIBLE = NewID(), -- Show the possible values
- ID_SHOW_USER_POSSIBLE = NewID(), -- Show the user's possible values
- ID_SHOW_POSSIBLE_LINE = NewID(), -- Show the possible values in a line
- ID_USER_POSSIBLE_MENU = NewID(),
- ID_USER_POSSIBLE_CLEAR = NewID(),
- ID_USER_POSSIBLE_SETALL = NewID(),
- ID_USER_POSSIBLE_INIT = NewID(),
- ID_SHOW_MENU = NewID(),
- ID_SHOW_NAKED = NewID(), -- mark naked groups
- ID_SHOW_HIDDEN = NewID(), -- mark hidden groups
- ID_SHOW_NAKEDPAIRS = NewID(), -- mark naked pairs
- ID_SHOW_HIDDENPAIRS = NewID(), -- mark hidden pairs
- ID_SHOW_NAKEDTRIPLETS = NewID(), -- mark naked triplets
- ID_SHOW_HIDDENTRIPLETS = NewID(), -- mark hidden triplets
- ID_SHOW_NAKEDQUADS = NewID(), -- mark naked quads
- ID_SHOW_HIDDENQUADS = NewID(), -- mark hidden quads
- ID_VERIFY_PUZZLE = NewID(),
- ID_SHOW_SOLUTION = NewID(), -- Show the solution to the puzzle
- ID_ELIMINATE_MENU = NewID(),
- ID_ELIMINATE_NAKED = NewID(), -- eliminate naked groups
- ID_ELIMINATE_HIDDEN = NewID(), -- eliminate hidden groups
- ID_ELIMINATE_NAKEDPAIRS = NewID(), -- eliminate naked pairs
- ID_ELIMINATE_HIDDENPAIRS = NewID(), -- eliminate hidden pairs
- ID_ELIMINATE_NAKEDTRIPLETS = NewID(), -- eliminate naked triplets
- ID_ELIMINATE_HIDDENTRIPLETS = NewID(), -- eliminate hidden triplets
- ID_ELIMINATE_NAKEDQUADS = NewID(), -- eliminate naked quads
- ID_ELIMINATE_HIDDENQUADS = NewID(), -- eliminate hidden quads
- ID_SOLVE_SCANSINGLES = NewID(), -- Solve for single lone values
- ID_SOLVE_SCANROWS = NewID(), -- Solve for single values in rows
- ID_SOLVE_SCANCOLS = NewID(), -- Solve for single values in cols
- ID_SOLVE_SCANBLOCKS = NewID(), -- Solve for single values in blocks
- ID_SOLVE_SCANNING = NewID(), -- Solve the puzzle by scanning
- ID_SOLVE_BRUTEFORCE = NewID(), -- Solve the puzzle by brute force
- ID_ABOUT = NewID(),
- ID_HELP = NewID(),
- menuCheckIDs = {}, -- the current state of the check menu items
- -- GTK doesn't like to access them in EVT_PAINT
- -- we don't have to set them since nil == false
- sudokuTables = {}, -- A table of the sudoku tables for undoing, should always be 1
- sudokuTables_pos = 0, -- Current position in the tables
- sudokuSolnTable = nil, -- solution to the current puzzle
- nonunique_init_puzzle = nil, -- nil for don't know, is true/false once verified
- possNakedTable = nil,
- possHiddenTable = nil,
- pencilMarks = {}, -- pencilMarks[cell][value] = true/nil
- pencilMarksNakedTable = nil,
- pencilMarksHiddenTable = nil,
- difficulty = 30, -- number of cells to keep for new puzzle
- sayings_n = 0, -- number of sayings, calculated later
- sayings = {
- "Pondering... ",
- "Musing... ",
- "Meditating... ",
- "Contemplating... ",
- "Reflecting... ",
- "Mulling... ",
- "Considering... ",
- "Deliberating... ",
- "Thinking... ",
- "Working... ",
- "Grinding... ",
- "Brooding... ",
- "Languishing? ",
- "Despairing... ",
- "To be, or not to be that is the question ",
- "Whether 'tis nobler in the mind to suffer ",
- "The slings and arrows of outrageous fortune, ",
- "Or to take arms against a sea of troubles, ",
- "And by opposing end them? ",
- "To die: to sleep; ",
- "No more; and by a sleep to say we end ",
- "The heart-ache and the thousand natural shocks",
- "That flesh is heir to, 'tis a consummation",
- "Devoutly to be wish'd. To die, to sleep;",
- "To sleep: perchance to dream:",
- "ay, there's the rub;",
- "For in that sleep of death what dreams may come",
- "When we have shuffled off this mortal coil,",
- "Must give us pause: there's the respect",
- "That makes calamity of so long life;",
- "For who would bear the whips and scorns of time,",
- "The oppressor's wrong, the proud man's contumely,",
- "The pangs of despised love, the law's delay,",
- "The insolence of office and the spurns",
- "That patient merit of the unworthy takes,",
- "When he himself might his quietus make",
- "With a bare bodkin? who would fardels bear,",
- "To grunt and sweat under a weary life,",
- "But that the dread of something after death,",
- "The undiscover'd country from whose bourn",
- "No traveller returns, puzzles the will",
- "And makes us rather bear those ills we have",
- "Than fly to others that we know not of?",
- "Thus conscience does make cowards of us all;",
- "And thus the native hue of resolution",
- "Is sicklied o'er with the pale cast of thought,",
- "And enterprises of great pitch and moment",
- "With this regard their currents turn awry,",
- "And lose the name of action.-- Soft you now!",
- "The fair Ophelia! Nymph, in thy orisons",
- "Be all my sins remember'd..."
- }
- }
- sudokuGUI.sayings_n = #sudokuGUI.sayings -- calc number of sayings
- for cell = 1, 81 do
- sudokuGUI.pencilMarks[cell] = {}
- end
- -- ----------------------------------------------------------------------------
- -- Run this function once to find the best font sizes to use and store them
- -- if height/width = nil then use a default size,
- -- else try to fit a font size to given height/width
- function sudokuGUI.GetCellBestSize(cell_width, cell_height)
- local dc = wx.wxClientDC(sudokuGUI.frame)
- local size = sudokuGUI.DoGetCellBestSize(dc, cell_width, cell_height,
- sudokuGUI.valueFont, sudokuGUI.possibleFont,
- sudokuGUI.valueFont_cache, sudokuGUI.possibleFont_cache)
- dc:delete() -- ALWAYS delete() any wxDCs created when done
- return size
- end
- function sudokuGUI.DoGetCellBestSize(dc, cell_width, cell_height,
- valueFont, possibleFont,
- valueFont_cache, possibleFont_cache) -- cache are optional
- local function GetBestFontSize(dc, width, height, fontTable, font_cache)
- local function DoGetBestFontSize(step, largest)
- for s = fontTable.size, largest, step do
- fontTable.size = s
- if font_cache[fontTable.size] then
- fontTable.width = font_cache[fontTable.size].width
- fontTable.height = font_cache[fontTable.size].height
- else
- fontTable.wxfont:SetPointSize(fontTable.size)
- dc:SetFont(fontTable.wxfont)
- fontTable.width, fontTable.height = dc:GetTextExtent("5")
- font_cache[fontTable.size] = {}
- font_cache[fontTable.size].width = fontTable.width
- font_cache[fontTable.size].height = fontTable.height
- end
- if (fontTable.height > height) or (fontTable.width > width) then
- break
- end
- end
- end
- fontTable.size = 2
- if not font_cache then font_cache = {} end -- use local font cache
- -- skip font sizes by 4 to get a rough estimate of the size
- DoGetBestFontSize(4, 1000)
- local largest = fontTable.size
- fontTable.size = iff(fontTable.size-3 > 1, fontTable.size-3, 2)
- -- get the best size to use
- DoGetBestFontSize(1, largest)
- -- use next smaller value that actually fits
- fontTable.size = iff(fontTable.size-1 > 1, fontTable.size-1, 2)
- fontTable.wxfont:SetPointSize(fontTable.size)
- fontTable.width = font_cache[fontTable.size].width
- fontTable.height = font_cache[fontTable.size].height
- return fontTable
- end
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE) then
- possibleFont = GetBestFontSize(dc, (cell_width-2)/10, (cell_height-2), possibleFont, possibleFont_cache)
- else
- possibleFont = GetBestFontSize(dc, (cell_width-2)/3, (cell_height-2)/3, possibleFont, possibleFont_cache)
- end
- valueFont = GetBestFontSize(dc, cell_width-4, cell_height-4, valueFont, valueFont_cache)
- return wx.wxSize(valueFont.height+4, valueFont.height+4)
- end
- -- ----------------------------------------------------------------------------
- -- Is this cell in an "odd" block for colouring the blocks
- function sudokuGUI.IsOddBlockCell(cell)
- return math.fmod(sudoku.CellToBlock(cell), 2) ~= 0
- end
- -- ----------------------------------------------------------------------------
- -- Create one of the 81 cell windows and connect the events to it
- function sudokuGUI.CreateCellWindow(parent, winID, size)
- local win = wx.wxWindow(parent, winID,
- wx.wxDefaultPosition, wx.wxDefaultSize,
- wx.wxWANTS_CHARS+wx.wxSIMPLE_BORDER) --wxSUNKEN_BORDER)
- -- set the background colour to reduce flicker
- if sudokuGUI.IsOddBlockCell(winID) then
- win:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
- else
- win:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
- end
- win:Connect(wx.wxEVT_ERASE_BACKGROUND, function(event) end)
- win:Connect(wx.wxEVT_PAINT, sudokuGUI.OnPaintCellWindow)
- win:Connect(wx.wxEVT_KEY_DOWN, sudokuGUI.OnKeyDownCellWindow )
- win:Connect(wx.wxEVT_KEY_UP, sudokuGUI.OnKeyUpCellWindow )
- win:Connect(wx.wxEVT_LEFT_DOWN, sudokuGUI.OnLeftClickCellWindow )
- win:Connect(wx.wxEVT_LEFT_DCLICK, sudokuGUI.OnLeftDClickCellWindow )
- return win
- end
- -- ----------------------------------------------------------------------------
- -- wxPaintEvent handler for all of the cell windows
- function sudokuGUI.OnPaintCellWindow(event)
- local win = event:GetEventObject():DynamicCast("wxWindow")
- -- ALWAYS create a wxPaintDC in a wxEVT_PAINT handler, even if unused
- local dc = wx.wxPaintDC(win)
- if not sudokuGUI.block_refresh then
- local cell = win:GetId()
- local width, height = win:GetClientSizeWH()
- sudokuGUI.PaintCell(dc, cell, width, height, sudokuGUI.valueFont, sudokuGUI.possibleFont)
- end
- dc:delete() -- ALWAYS delete() any wxDCs created when done
- end
- function sudokuGUI.PaintCell(dc, cell, width, height, valueFont, possibleFont)
- -- clear the window before drawing
- dc:SetPen(wx.wxTRANSPARENT_PEN)
- local bgColour
- if sudokuGUI.focused_cell_id ~= cell then
- if sudokuGUI.IsOddBlockCell(cell) then
- bgColour = sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR]
- else
- bgColour = sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR]
- end
- else
- bgColour = sudokuGUI.Colours[sudokuGUI.FOCUS_CELL_COLOUR]
- end
- local brush = wx.wxBrush(bgColour, wx.wxSOLID)
- dc:SetBrush(brush)
- dc:DrawRectangle(0, 0, width, height)
- brush:delete()
- local sudokuTable = sudokuGUI.GetCurrentTable()
- local value_str, is_init = sudokuGUI.GetCellValueString(cell)
- local has_cell_value = string.len(value_str) ~= 0
- -- Draw the possible values
- local show_possible = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE)
- local show_possible_user = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE)
- local show_possible_line = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE)
- if (show_possible or show_possible_user) and (not has_cell_value) then
- local possible = sudoku.GetCellPossible(sudokuTable, cell)
- if show_possible_user then
- possible = sudokuGUI.pencilMarks[cell]
- end
- dc:SetTextForeground(sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
- dc:SetFont(possibleFont.wxfont)
- -- find the positions of each possible value
- local pos = sudokuGUI.CalcPossiblePositions(width, height, possibleFont)
- local show_possible_user_all = false
- if show_possible_user and (cell == sudokuGUI.focused_cell_id) then
- show_possible_user_all = wx.wxGetKeyState(wx.WXK_SHIFT)
- end
- -- draw each one separately, even for line to ensure monospace
- for i = 1, 9 do
- if possible and possible[i] then
- dc:DrawText(tostring(i), pos[i].x, pos[i].y)
- elseif show_possible_user_all then
- dc:SetBackgroundMode(wx.wxSOLID)
- dc:SetTextForeground(bgColour)
- dc:SetTextBackground(sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
- dc:DrawText(tostring(i), pos[i].x, pos[i].y)
- dc:SetTextForeground(sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
- dc:SetTextBackground(bgColour)
- dc:SetBackgroundMode(wx.wxTRANSPARENT)
- end
- end
- local nakedTable = sudokuGUI.possNakedTable
- local hiddenTable = sudokuGUI.possHiddenTable
- if show_possible_user then
- nakedTable = sudokuGUI.pencilMarksNakedTable
- hiddenTable = sudokuGUI.pencilMarksHiddenTable
- end
- if nakedTable and hiddenTable then
- dc:SetBrush(wx.wxTRANSPARENT_BRUSH)
- local char0 = string.byte("0")
- local function draw_nakedhidden(colourID, num, key_table, hidden)
- if (not key_table) or (#key_table < 1) then return end
- local pen = wx.wxPen(sudokuGUI.Colours[colourID], 1, wx.wxSOLID)
- dc:SetPen(pen)
- for k = 1, #key_table do
- for n = 1, num do
- local val = string.byte(key_table[k], n)-char0
- if hidden ~= true then
- dc:DrawRectangle(pos[val].x, pos[val].y,
- possibleFont.width, possibleFont.height)
- else
- dc:DrawRoundedRectangle(pos[val].x, pos[val].y,
- possibleFont.width, possibleFont.height,
- 20)
- end
- end
- end
- pen:delete()
- end
- -- draw pair marker last so it's on top of the others
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_HIDDENQUADS) then
- draw_nakedhidden(sudokuGUI.HIDDEN_QUADS_COLOUR, 4, hiddenTable.quads.cells[cell], true)
- end
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_NAKEDQUADS) then
- draw_nakedhidden(sudokuGUI.NAKED_QUADS_COLOUR, 4, nakedTable.quads.cells[cell])
- end
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_HIDDENTRIPLETS) then
- draw_nakedhidden(sudokuGUI.HIDDEN_TRIPLETS_COLOUR, 3, hiddenTable.triplets.cells[cell], true)
- end
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_NAKEDTRIPLETS) then
- draw_nakedhidden(sudokuGUI.NAKED_TRIPLETS_COLOUR, 3, nakedTable.triplets.cells[cell])
- end
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_HIDDENPAIRS) then
- draw_nakedhidden(sudokuGUI.HIDDEN_PAIRS_COLOUR, 2, hiddenTable.pairs.cells[cell], true)
- end
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_NAKEDPAIRS) then
- draw_nakedhidden(sudokuGUI.NAKED_PAIRS_COLOUR, 2, nakedTable.pairs.cells[cell])
- end
- end
- end
- -- mark invalid cells, always mark invalid if creating
- local show_errors = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_ERRORS)
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE) then show_errors = true end
- if show_errors then show_errors = sudokuGUI.GetCurrentTable().invalid[cell] end
- local show_mistakes = sudokuGUI.sudokuSolnTable and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES)
- if show_errors then
- dc:SetPen(wx.wxPen(sudokuGUI.Colours[sudokuGUI.INVALID_VALUE_COLOUR], 1, wx.wxSOLID))
- dc:DrawLine(0, 0, width, height)
- dc:DrawLine(width, 0, 0, height)
- elseif show_mistakes then
- if sudoku.HasCellValue(sudokuTable, cell) and
- (sudoku.GetCellValue(sudokuGUI.sudokuSolnTable, cell) ~=
- sudoku.GetCellValue(sudokuTable, cell)) then
- local pen = wx.wxPen(sudokuGUI.Colours[sudokuGUI.INVALID_VALUE_COLOUR], 1, wx.wxSOLID)
- dc:SetPen(pen)
- pen:delete()
- dc:DrawLine(0, 0, width, height)
- end
- end
- -- Draw the set value, if any
- if has_cell_value then
- local fgColour = sudokuGUI.Colours[sudokuGUI.VALUE_COLOUR]
- if is_init then
- fgColour = sudokuGUI.Colours[sudokuGUI.INIT_VALUE_COLOUR]
- end
- dc:SetTextForeground(fgColour)
- dc:SetFont(valueFont.wxfont)
- dc:DrawText(value_str, width/2 - valueFont.width/2,
- height/2 - valueFont.height/2)
- end
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.CalcPossiblePositions(width, height, possibleFont)
- local pos = {}
- local show_possible_line = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE)
- if (sudokuGUI.possiblePosCache.pos and
- (sudokuGUI.possiblePosCache.cell_width == width) and
- (sudokuGUI.possiblePosCache.cell_height == height) and
- (sudokuGUI.possiblePosCache.width == possibleFont.width) and
- (sudokuGUI.possiblePosCache.height == possibleFont.height) and
- (sudokuGUI.possiblePosCache.line == show_possible_line)) then
- pos = sudokuGUI.possiblePosCache.pos
- else
- if show_possible_line then
- for i = 1, 9 do
- pos[i] = { x = (1+ (i-1)*possibleFont.width), y = 1 }
- end
- else
- local c = 0
- local horiz_space = (width - 3*possibleFont.width)/4
- local vert_space = (height - 3*possibleFont.height)/4
- local h_space = horiz_space
- local v_space = vert_space
- for j = 1, 3 do
- -- try to center it a little better
- if (j == 1) and (vert_space - math.floor(vert_space) > .3) then
- v_space = vert_space+1
- else
- v_space = vert_space
- end
- for i = 1, 3 do
- c = c + 1
- if (i == 1) and (horiz_space - math.floor(horiz_space) > .3) then
- h_space = horiz_space+1
- else
- h_space = horiz_space
- end
- pos[c] = { x = i*h_space + (i-1)*possibleFont.width,
- y = j*v_space + (j-1)*possibleFont.height }
- end
- end
- end
- -- cache these values for next cell
- sudokuGUI.possiblePosCache.pos = pos
- sudokuGUI.possiblePosCache.cell_width = width
- sudokuGUI.possiblePosCache.cell_height = height
- sudokuGUI.possiblePosCache.width = possibleFont.width
- sudokuGUI.possiblePosCache.height = possibleFont.height
- sudokuGUI.possiblePosCache.line = show_possible_line
- end
- return pos
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.ConnectPrintEvents(printOut)
- printOut:SetPageInfo(1, 1, 1, 1)
- printOut.HasPage = function(self, pageNum) return pageNum == 1 end
- --printOut.GetPageInfo = function(self) return 1, 1, 1, 1 end
- printOut.OnPrintPage = function(self, pageNum)
- local dc = self:GetDC()
- local ppiScr_width, ppiScr_height = self:GetPPIScreen()
- local ppiPrn_width, ppiPrn_height = self:GetPPIPrinter()
- local ppi_scale_x = ppiPrn_width/ppiScr_width
- local ppi_scale_y = ppiPrn_height/ppiScr_height
- -- Get the size of DC in pixels and the number of pixels in the page
- local dc_width, dc_height = dc:GetSize()
- local pagepix_width, pagepix_height = self:GetPageSizePixels()
- local dc_pagepix_scale_x = dc_width/pagepix_width
- local dc_pagepix_scale_y = dc_height/pagepix_height
- -- If printer pageWidth == current DC width, then this doesn't
- -- change. But w might be the preview bitmap width, so scale down.
- local dc_scale_x = ppi_scale_x * dc_pagepix_scale_x
- local dc_scale_y = ppi_scale_y * dc_pagepix_scale_y
- -- calculate the pixels / mm (25.4 mm = 1 inch)
- local ppmm_x = ppiScr_width / 25.4
- local ppmm_y = ppiScr_height / 25.4
- -- Adjust the page size for the pixels / mm scaling factor
- local pageMM_width, pageMM_height = self:GetPageSizeMM()
- local pagerect_x, pagerect_y = 0, 0
- local pagerect_w, pagerect_h = pageMM_width * ppmm_x, pageMM_height * ppmm_y
- -- get margins informations and convert to printer pixels
- local topLeft = sudokuGUI.pageSetupData:GetMarginTopLeft()
- local bottomRight = sudokuGUI.pageSetupData:GetMarginBottomRight()
- local top = topLeft:GetY() * ppmm_y
- local bottom = bottomRight:GetY() * ppmm_y
- local left = topLeft:GetX() * ppmm_x
- local right = bottomRight:GetX() * ppmm_x
- local printrect_x, printrect_y = left, top
- local printrect_w, printrect_h = pagerect_w-(left+right), pagerect_h-(top+bottom)
- dc:SetUserScale(dc_scale_x, dc_scale_y);
- local cell_width = (printrect_w)/11
- local cell_height = (printrect_h)/11
- if cell_width < cell_height then cell_height = cell_width end
- if cell_width > cell_height then cell_width = cell_height end
- -- calculate font sizes for the printout, copy font since we'll recalc the size
- local valueFont = { wxfont = wx.wxFont(sudokuGUI.valueFont.wxfont), size = 8, width = 0, height = 0 }
- local possibleFont = { wxfont = wx.wxFont(sudokuGUI.possibleFont.wxfont), size = 6, width = 0, height = 0 }
- sudokuGUI.DoGetCellBestSize(dc, cell_width, cell_height,
- valueFont, possibleFont)
- local function RowOrigin(row) return printrect_x + row*cell_height + row end
- local function ColOrigin(col) return printrect_y + col*cell_width + col end
- local old_focused_cell_id = sudokuGUI.focused_cell_id -- clear focus
- sudokuGUI.focused_cell_id = 0
- for row = 1, 9 do
- for col = 1, 9 do
- local x = ColOrigin(col)
- local y = RowOrigin(row)
- dc:SetDeviceOrigin(x*dc_scale_x, y*dc_scale_x)
- local cell = sudoku.RowColToCell(row, col)
- sudokuGUI.PaintCell(dc, cell, cell_width, cell_height, valueFont, possibleFont)
- end
- end
- valueFont.wxfont:delete()
- possibleFont.wxfont:delete()
- sudokuGUI.focused_cell_id = old_focused_cell_id -- restore focus
- dc:SetDeviceOrigin(0, 0)
- local borders = { [1]=true, [4]=true, [7]=true, [10]=true }
- for i = 1, 10 do
- local pen = wx.wxPen(wx.wxBLACK, iff(borders[i], 4, 2), wx.wxSOLID)
- dc:SetPen(pen)
- pen:delete()
- dc:DrawLine(ColOrigin(1), RowOrigin(i), ColOrigin(10), RowOrigin(i))
- dc:DrawLine(ColOrigin(i), RowOrigin(1), ColOrigin(i), RowOrigin(10))
- end
- return true
- end
- end
- function sudokuGUI.Print()
- local printDialogData = wx.wxPrintDialogData(sudokuGUI.printData)
- local printer = wx.wxPrinter(printDialogData)
- local luaPrintout = wx.wxLuaPrintout("wxLuaSudoku Printout")
- sudokuGUI.ConnectPrintEvents(luaPrintout)
- if printer:Print(sudokuGUI.frame, luaPrintout, true) == false then
- if printer:GetLastError() == wx.wxPRINTER_ERROR then
- wx.wxMessageBox("There was a problem printing.\n"..
- "Perhaps your current printer is not setup correctly?",
- "wxLuaSudoku Printout",
- wx.wxOK, sudokuGUI.frame)
- end
- else
- sudokuGUI.printData = printer:GetPrintDialogData():GetPrintData():Copy()
- end
- end
- function sudokuGUI.PrintPreview()
- luaPrintout = wx.wxLuaPrintout("wxLuaSudoku Print Preview")
- luaPrintPrintout = wx.wxLuaPrintout("wxLuaSudoku Printout")
- sudokuGUI.ConnectPrintEvents(luaPrintout)
- sudokuGUI.ConnectPrintEvents(luaPrintPrintout)
- local printDialogData = wx.wxPrintDialogData(sudokuGUI.printData):GetPrintData()
- local preview = wx.wxPrintPreview(luaPrintout, luaPrintPrintout, printDialogData)
- local result = preview:Ok()
- if result == false then
- wx.wxMessageBox("There was a problem previewing.\n"..
- "Perhaps your current printer is not setup correctly?",
- "wxLuaSudoku print preview error",
- wx.wxOK, sudokuGUI.frame)
- else
- local previewFrame = wx.wxPreviewFrame(preview, sudokuGUI.frame,
- "wxLuaSudoku print preview",
- wx.wxDefaultPosition, wx.wxSize(600, 600))
- previewFrame:Connect(wx.wxEVT_CLOSE_WINDOW,
- function (event)
- previewFrame:Destroy()
- event:Skip()
- end )
- previewFrame:Centre(wx.wxBOTH)
- previewFrame:Initialize()
- previewFrame:Show(true)
- end
- end
- function sudokuGUI.PrintSetup() -- FIXME DEPRICATED IN WXWIDGETS?
- local printDialogData = wx.wxPrintDialogDataFromPrintData(sudokuGUI.printData)
- local printerDialog = wx.wxPrintDialog(sudokuGUI.frame, printDialogData)
- printerDialog:GetPrintDialogData():SetSetupDialog(true)
- printerDialog:ShowModal()
- sudokuGUI.printData = printerDialog:GetPrintDialogData():GetPrintData():Copy()
- --printerDialog:Destroy()
- end
- function sudokuGUI.PageSetup()
- sudokuGUI.printData = sudokuGUI.pageSetupData:GetPrintData():Copy()
- local pageSetupDialog = wx.wxPageSetupDialog(sudokuGUI.frame, sudokuGUI.pageSetupData)
- pageSetupDialog:ShowModal()
- sudokuGUI.printData = pageSetupDialog:GetPageSetupDialogData():GetPrintData():Copy()
- sudokuGUI.pageSetupData = pageSetupDialog:GetPageSetupDialogData():Copy()
- --pageSetupDialog:Destroy()
- end
- -- ----------------------------------------------------------------------------
- -- Set the currently focused window, refresh new and old
- function sudokuGUI.SetFocusWindow(cell)
- local last_id = sudokuGUI.focused_cell_id
- sudokuGUI.focused_cell_id = iff((cell>=1) and (cell<=81), cell, 0)
- if sudokuGUI.cellWindows[last_id] then
- sudokuGUI.cellWindows[last_id]:Refresh()
- end
- if sudokuGUI.cellWindows[cell] then
- sudokuGUI.cellWindows[cell]:Refresh()
- end
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.OnKeyUpCellWindow(event)
- event:Skip()
- -- we don't care who actually got this event, just use the "focused cell"
- if (sudokuGUI.focused_cell_id < 1) then return end
- local key = event:GetKeyCode()
- if (sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) == true) and
- (key == wx.WXK_SHIFT) then
- sudokuGUI.cellWindows[sudokuGUI.focused_cell_id]:Refresh(false)
- return
- end
- end
- -- Left down click handler for the cell windows, hide the cell editor
- function sudokuGUI.OnKeyDownCellWindow(event)
- event:Skip()
- -- we don't care who actually got this event, just use the "focused cell"
- if (sudokuGUI.focused_cell_id < 1) then return end
- local key = event:GetKeyCode()
- if (sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) == true) and
- (key == wx.WXK_SHIFT) then
- sudokuGUI.cellWindows[sudokuGUI.focused_cell_id]:Refresh(false)
- end
- if event:HasModifiers() or event:AltDown() or event:ControlDown() then
- sudokuGUI.cellWindows[sudokuGUI.focused_cell_id]:Refresh(false)
- return
- end
- -- clear the current focused window
- if (key == wx.WXK_ESCAPE) then
- sudokuGUI.SetFocusWindow(0)
- return
- end
- -- the cursor keys move the focus cell
- local movefocus =
- {
- [wx.WXK_LEFT] = -1, [wx.WXK_NUMPAD_LEFT] = -1,
- [wx.WXK_UP] = -9, [wx.WXK_NUMPAD_UP] = -9,
- [wx.WXK_RIGHT] = 1, [wx.WXK_NUMPAD_RIGHT] = 1,
- [wx.WXK_DOWN] = 9, [wx.WXK_NUMPAD_DOWN] = 9,
- [wx.WXK_PAGEUP] = -9, [wx.WXK_PRIOR] = -9,
- [wx.WXK_PAGEDOWN] = 9, [wx.WXK_NEXT] = 9,
- [wx.WXK_NUMPAD_HOME] = -10,
- [wx.WXK_NUMPAD_PAGEUP] = -8, [wx.WXK_NUMPAD_PRIOR] = -8,
- [wx.WXK_NUMPAD_END] = 8,
- [wx.WXK_NUMPAD_PAGEDOWN] = 10, [wx.WXK_NUMPAD_NEXT] = 10,
- [wx.WXK_TAB] = 1,
- [wx.WXK_RETURN] = 1,
- [wx.WXK_NUMPAD_ENTER] = 1
- }
- if (key == wx.WXK_HOME) then
- sudokuGUI.SetFocusWindow(1)
- return
- elseif (key == wx.WXK_END) then
- sudokuGUI.SetFocusWindow(81)
- return
- elseif movefocus[key] then
- local cell = sudokuGUI.focused_cell_id + movefocus[key]
- if (cell >= 1) and (cell <= 81) then
- sudokuGUI.SetFocusWindow(cell)
- end
- return
- end
- -- translate number pad keys to numbers
- local numpad =
- {
- [wx.WXK_NUMPAD0] = 0,
- [wx.WXK_NUMPAD1] = 1,
- [wx.WXK_NUMPAD2] = 2,
- [wx.WXK_NUMPAD3] = 3,
- [wx.WXK_NUMPAD4] = 4,
- [wx.WXK_NUMPAD5] = 5,
- [wx.WXK_NUMPAD6] = 6,
- [wx.WXK_NUMPAD7] = 7,
- [wx.WXK_NUMPAD8] = 8,
- [wx.WXK_NUMPAD9] = 9,
- [wx.WXK_DELETE] = 0,
- [wx.WXK_BACK] = 0,
- [wx.WXK_SPACE] = 0,
- [wx.WXK_NUMPAD_INSERT] = 0,
- [wx.WXK_NUMPAD_DECIMAL] = 0,
- [wx.WXK_NUMPAD_DELETE] = 0,
- }
- local zero = string.byte("0")
- if numpad[key] then key = zero + numpad[key] end
- if (key < 32) or (key > 127) then return end -- normal ASCII key
- local one = string.byte("1")
- local nine = string.byte("9")
- if (key >= one) and (key <= nine) then
- key = key - one + 1
- else
- key = 0
- end
- if event:ShiftDown() and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) then
- if (key >= 1) and (key <= 9) then
- sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][key] = iff(sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][key], nil, key)
- sudokuGUI.UpdateTable()
- end
- else
- sudokuGUI.SetCellValue(sudokuGUI.focused_cell_id, key)
- end
- end
- -- ----------------------------------------------------------------------------
- -- Test to see if one of the possible values has been clicked on
- function sudokuGUI.HitTestPossibleValue(mx, my)
- local cell = sudokuGUI.focused_cell_id
- if (cell < 1) or (cell > 81) then return nil end
- local w = sudokuGUI.possiblePosCache.width
- local h = sudokuGUI.possiblePosCache.height
- local rect = wx.wxRect(0, 0, w, h)
- for n = 1, 9 do
- rect.X = sudokuGUI.possiblePosCache.pos[n].x
- rect.Y = sudokuGUI.possiblePosCache.pos[n].y
- if rect:Inside(mx, my) then
- return n
- end
- end
- return nil
- end
- -- ----------------------------------------------------------------------------
- -- Left down click handler for the cell windows, hide the cell editor
- function sudokuGUI.OnLeftClickCellWindow(event)
- event:Skip()
- local win = event:GetEventObject():DynamicCast("wxWindow")
- local winId = win:GetId()
- if sudokuGUI.cellTextCtrl then
- sudokuGUI.SaveCellTextCtrlValue()
- sudokuGUI.DestroyCellTextCtrl()
- end
- sudokuGUI.SetFocusWindow(winId)
- if event:ShiftDown() and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) then
- local p = sudokuGUI.HitTestPossibleValue(event:GetX(), event:GetY())
- if p then
- sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][p] = iff(sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][p], nil, p)
- sudokuGUI.cellWindows[winId]:Refresh(false)
- end
- end
- end
- -- Left double click handler for the cell windows, hide old, show new cell editor
- function sudokuGUI.OnLeftDClickCellWindow(event)
- event:Skip()
- local win = event:GetEventObject():DynamicCast("wxWindow")
- local winId = win:GetId()
- local winWidth, winHeight = win:GetSizeWH()
- if event:ShiftDown() then return end
- if sudokuGUI.cellTextCtrl then
- if sudokuGUI.cellTextCtrl:GetId() == winId then
- sudokuGUI.cellTextCtrl:Show(true)
- return
- end
- sudokuGUI.SaveCellTextCtrlValue()
- sudokuGUI.DestroyCellTextCtrl()
- end
- local is_creating = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE)
- local value, is_init = sudokuGUI.GetCellValueString(winId)
- if is_init and (not is_creating) then return end
- sudokuGUI.cellTextCtrl = wx.wxTextCtrl(win, winId, value,
- wx.wxPoint(0, 0), wx.wxSize(winWidth, winHeight),
- wx.wxTE_PROCESS_ENTER+wx.wxTE_CENTRE)
- sudokuGUI.cellTextCtrl:SetFont(sudokuGUI.valueFont.wxfont)
- sudokuGUI.cellTextCtrl:SetMaxLength(1)
- --local valid = wx.wxTextValidator(wx.wxFILTER_INCLUDE_LIST)
- --valid:SetIncludeList(
- --cellTextCtrl:SetValidator(valid)
- sudokuGUI.cellTextCtrl:Connect(winId, wx.wxEVT_COMMAND_TEXT_ENTER,
- function (event)
- local win = event:GetEventObject():DynamicCast("wxWindow")
- sudokuGUI.SaveCellTextCtrlValue()
- win:Show(false) -- just hide it, we'll destroy it later
- end)
- sudokuGUI.cellTextCtrl:Connect(wx.wxEVT_CHAR,
- function (event)
- if (event:GetKeyCode() == wx.WXK_ESCAPE) then
- sudokuGUI.cellTextCtrl:Show(false)
- sudokuGUI.cellTextCtrl:SetValue("")
- end
- event:Skip()
- end)
- end
- function sudokuGUI.DestroyCellTextCtrl()
- if sudokuGUI.cellTextCtrl then
- sudokuGUI.cellTextCtrl:Show(false)
- sudokuGUI.cellTextCtrl:Destroy()
- sudokuGUI.cellTextCtrl = nil
- end
- end
- -- Save the value of the text editor back to the grid
- function sudokuGUI.SaveCellTextCtrlValue()
- if not sudokuGUI.cellTextCtrl then return end
- local value = sudokuGUI.cellTextCtrl:GetValue()
- local cell = sudokuGUI.cellTextCtrl:GetId()
- sudokuGUI.SetCellValue(cell, value)
- end
- -- ----------------------------------------------------------------------------
- -- Set the value of the cell from the value string
- function sudokuGUI.SetCellValue(cell, value)
- -- fix the value to something reasonable
- if type(value) == "string" then
- if (value == "") or (value == " ") or (value == "0") then
- value = 0
- elseif (string.len(value) ~= 1) or (not string.find("123456789", value)) then
- return
- else
- value = tonumber(value)
- end
- end
- -- if value is still bad, just exit
- if not ((value == 0) or sudoku.IsValidValueN(value)) then return end
- local is_creating = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE)
- local row, col = sudoku.CellToRowCol(cell)
- local is_init = sudoku.HasCellValue(sudokuGUI.sudokuTables[1], cell)
- if is_creating then
- if sudoku.GetCellValue(sudokuGUI.sudokuTables[1], cell) ~= value then
- -- add the value to all the tables since it's an init value
- for n = 1, TableCount(sudokuGUI.sudokuTables) do
- sudoku.SetValue(sudokuGUI.sudokuTables[n], row, col, value)
- sudoku.UpdateTable(sudokuGUI.sudokuTables[n])
- end
- -- refresh all in case it's invalid also if not at 1st table update possible
- sudokuGUI.UpdateTable(true)
- sudokuGUI.sudokuSolnTable = nil -- don't know anymore
- end
- else
- local s = sudokuGUI.GetCurrentTable()
- if (not is_init) and (sudoku.GetCellValue(s, cell) ~= value) then
- local s = TableCopy(s)
- sudoku.SetValue(s, row, col, value)
- sudokuGUI.AddTable(s)
- end
- end
- sudokuGUI.UpdateGUI()
- end
- -- ----------------------------------------------------------------------------
- -- Get the initial sudoku table
- function sudokuGUI.GetInitTable()
- return sudokuGUI.sudokuTables[1]
- end
- -- Set the initial sudoku table, clearing all others
- function sudokuGUI.SetInitTable(sudokuTable, solnTable)
- sudokuGUI.sudokuSolnTable = solnTable
- sudokuGUI.sudokuTables_pos = 1
- sudokuGUI.sudokuTables = {}
- table.insert(sudokuGUI.sudokuTables, sudokuTable)
- sudokuGUI.UpdateTable() -- resets possible and refreshes too
- end
- -- ----------------------------------------------------------------------------
- -- Get the current sudoku table to use
- function sudokuGUI.GetCurrentTable()
- return sudokuGUI.sudokuTables[sudokuGUI.sudokuTables_pos]
- end
- -- Set the current sudoku table to this table
- function sudokuGUI.SetCurrentTable(sudokuTable)
- sudokuGUI.sudokuTables[sudokuGUI.sudokuTables_pos] = sudokuTable
- end
- -- ----------------------------------------------------------------------------
- -- Add a sudoku table to the list of tables, removing any past the current position,
- -- find possible, and refresh
- function sudokuGUI.AddTable(sudokuTable)
- while TableCount(sudokuGUI.sudokuTables) > sudokuGUI.sudokuTables_pos do
- table.remove(sudokuGUI.sudokuTables)
- end
- -- clear calculated values to save memory
- for n = 2, sudokuGUI.sudokuTables_pos do
- sudokuGUI.sudokuTables[n].row_values = {}
- sudokuGUI.sudokuTables[n].col_values = {}
- sudokuGUI.sudokuTables[n].block_values = {}
- sudokuGUI.sudokuTables[n].possible = {}
- sudokuGUI.sudokuTables[n].invalid = {}
- end
- table.insert(sudokuGUI.sudokuTables, sudokuTable)
- sudokuGUI.sudokuTables_pos = sudokuGUI.sudokuTables_pos + 1
- sudokuGUI.UpdateTable()
- end
- -- ----------------------------------------------------------------------------
- -- Get the value of the cell as a printable string
- function sudokuGUI.GetCellValueString(cell)
- local value = ""
- if sudoku.HasCellValue(sudokuGUI.sudokuTables[1], cell) then
- return tostring(sudoku.GetCellValue(sudokuGUI.sudokuTables[1], cell)), true
- elseif sudoku.HasCellValue(sudokuGUI.GetCurrentTable(), cell) then
- value = tostring(sudoku.GetCellValue(sudokuGUI.GetCurrentTable(), cell))
- end
- return value, false
- end
- -- ----------------------------------------------------------------------------
- -- refresh all the grid cells
- function sudokuGUI.Refresh()
- for i = 1, 81 do
- if sudokuGUI.cellWindows[i] then
- sudokuGUI.cellWindows[i]:Refresh(false)
- end
- end
- sudokuGUI.UpdateGUI()
- end
- -- ----------------------------------------------------------------------------
- -- Create a new empty puzzle
- function sudokuGUI.NewPuzzle()
- local ret = wx.wxMessageBox("Clear all the values in the current puzzle and start anew?\n"..
- "Use 'Create' to enter the initial values.",
- "wxLuaSudoku - New puzzle?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxOK then
- sudokuGUI.SetInitTable(sudoku.CreateTable(), nil)
- end
- end
- -- ----------------------------------------------------------------------------
- -- Create a puzzle by hand
- function sudokuGUI.CreatePuzzle(init)
- local enableIds =
- {
- sudokuGUI.ID_GENERATE,
- sudokuGUI.ID_OPEN,
- sudokuGUI.ID_SAVEAS,
- sudokuGUI.ID_RESET,
- sudokuGUI.ID_UNDO,
- sudokuGUI.ID_REDO,
- sudokuGUI.ID_SOLVE_SCANSINGLES,
- sudokuGUI.ID_SOLVE_SCANROWS,
- sudokuGUI.ID_SOLVE_SCANCOLS,
- sudokuGUI.ID_SOLVE_SCANBLOCKS,
- sudokuGUI.ID_SOLVE_SCANNING,
- sudokuGUI.ID_SOLVE_BRUTEFORCE
- }
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_CREATE, init)
- if init then
- local ret = wx.wxMessageBox(
- "Enter values in the cells to initialize the puzzle with.\n"..
- "Previous cell values will be overwritten.\n"..
- "Don't forget to uncheck 'Create' before playing.",
- "wxLuaSudoku - Initialize puzzle?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxCANCEL then
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_CREATE, false)
- return
- end
- else
- sudokuGUI.sudokuSolnTable = nil -- reset to unknown
- if not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid) then
- -- try to make them correct the puzzle
- local ret = wx.wxMessageBox(
- "The initial puzzle you've created has invalid values.\n"..
- "Press 'Ok' to correct them before continuing.\n"..
- "If you press 'Cancel' showing mistakes will be disabled and "..
- "don't blame me if things don't work out for you.",
- "wxLuaSudoku - Invalid initial puzzle!",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
- sudokuGUI.frame )
- if ret == wx.wxOK then
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_CREATE, true)
- init = true
- end
- else --if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES) then
- sudokuGUI.sudokuSolnTable = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
- end
- if (not sudokuGUI.sudokuSolnTable) and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES) then
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_MISTAKES, false)
- end
- end
- for n, id in pairs(enableIds) do
- sudokuGUI.frame:GetMenuBar():Enable(id, not init)
- sudokuGUI.frame:GetToolBar():EnableTool(id, not init)
- end
- sudokuGUI.UpdateTable()
- end
- -- ----------------------------------------------------------------------------
- -- Generate a new puzzle automatically
- function sudokuGUI.GeneratePuzzle()
- local keep = wx.wxGetNumberFromUser("Set the difficulty of the new puzzle by clearing cells.\n"..
- "Note: The minimum number of cells to show for a unique puzzle is 17.",
- "Number of cell values to show",
- "wxLuaSudoku - Generate puzzle?",
- sudokuGUI.difficulty, 1, 81,
- sudokuGUI.frame)
- if keep < 1 then return end -- canceled
- sudokuGUI.difficulty = keep
- local solve_progress = 0
- local start_time = os.time()
- local last_time = start_time
- local solve_ok = true
- local msg_idx = 1
- local progressDialog = wx.wxProgressDialog("wxLuaSudoku - Generating...",
- string.format("%s\nIteration # %d, current cell %d ", sudokuGUI.sayings[1], 0, 0),
- 1000, sudokuGUI.frame,
- wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
- -- define handler function here so it'll work w/o gui
- function sudoku.GeneratePuzzleHook(count, cell)
- if solve_ok == false then return false end -- canceled
- solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
- if solve_progress%10 ~= 0 then return true end
- if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
- msg_idx = msg_idx + 1
- last_time = os.time()
- end
- local msg = string.format("%s\nIteration # %d, current cell %d ", sudokuGUI.sayings[msg_idx], count, cell)
- solve_ok = progressDialog:Update(solve_progress, msg)
- return solve_ok
- end
- local s, count = sudoku.GeneratePuzzle()
- progressDialog:Destroy()
- if not s then return end
- -- have complete puzzle, now remove cells
- local diff_count = 0
- local diff_i = 0
- local diff_cell = 0
- local diff_open = 0
- local diff_trial = 0
- -- define handler function here so it'll work w/o gui
- function sudoku.GeneratePuzzleDifficultyHook(count, i, cell, open_cells, trial)
- diff_count = count
- diff_i = i
- diff_cell = cell
- diff_open = open_cells
- diff_trial = trial
- if solve_ok == false then return false end -- canceled
- if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
- msg_idx = msg_idx + 1
- last_time = os.time()
- end
- local msg = string.format("%s\nTrial %d, Iteration # %d, current cell %d, cells to go %d, available cells %d ", sudokuGUI.sayings[msg_idx], trial, count, cell, 81-keep-i, open_cells)
- solve_ok = progressDialog:Update(i, msg)
- return solve_ok
- end
- -- hook into brute force solver to update the generate puzzle progress dialog
- function sudoku.SolveBruteForceHook(guesses, cell)
- solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
- if solve_progress%10 ~= 0 then return true end
- return sudoku.GeneratePuzzleDifficultyHook(diff_count, diff_i, diff_cell, diff_open, diff_trial)
- end
- local ensure_unique = true
- while 1 do
- diff_count = 0
- diff_i = 0
- diff_cell = 0
- diff_open = 0
- diff_trial = 0
- solve_progress = 0
- start_time = os.time()
- last_time = start_time
- solve_ok = true
- msg_idx = 1
- local caption = "wxLuaSudoku - Ensuring unique solution..."
- if ensure_unique == false then
- caption = "wxLuaSudoku - Removing values randomly..."
- end
- progressDialog = wx.wxProgressDialog(caption,
- string.format("%s\nTrial %d, Iteration # %d, current cell %d, cells to go %d, available cells %d ", sudokuGUI.sayings[msg_idx], 0, count, 0, 81, 0),
- 81 - sudokuGUI.difficulty + 1, sudokuGUI.frame,
- wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
- local s1 = sudoku.GeneratePuzzleDifficulty(TableCopy(s), sudokuGUI.difficulty, ensure_unique)
- progressDialog:Destroy()
- if s1 then
- if ensure_unique then
- sudokuGUI.SetInitTable(s1, TableCopy(s))
- else
- -- verify the puzzle anyway to let them know the status
- local s2 = sudokuGUI.VerifyUniquePuzzle(s1)
- sudokuGUI.SetInitTable(s1, s2)
- end
- break
- else
- local ret = wx.wxMessageBox("The puzzle was not fully generated. "..
- "Press 'Ok' to randomly remove cell values which may or may not "..
- "yield a unique puzzle or 'Cancel' to abort",
- "wxLuaSudoku - Unfinished generation",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
- sudokuGUI.frame)
- if ret == wx.wxOK then
- ensure_unique = false
- else
- break
- end
- end
- end
- end
- -- ----------------------------------------------------------------------------
- -- Open a puzzle from a file
- function sudokuGUI.OpenPuzzle()
- local fileDialog = wx.wxFileDialog(sudokuGUI.frame, "Open file",
- sudokuGUI.filePath, sudokuGUI.fileName,
- "wxLuaSudoku files (*.sudoku)|*.sudoku|All files (*)|*",
- wx.wxOPEN + wx.wxFILE_MUST_EXIST)
- if fileDialog:ShowModal() == wx.wxID_OK then
- local fileName = fileDialog:GetPath()
- local fn = wx.wxFileName(fileName)
- sudokuGUI.filePath = fn:GetPath()
- sudokuGUI.fileName = fn:GetFullName()
- local s, msg = sudoku.Open(fileName)
- if s then
- sudokuGUI.frame:SetTitle("wxLuaSudoku - "..sudokuGUI.fileName)
- sudokuGUI.SetInitTable(s, nil)
- if not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid) then
- -- make them correct the puzzle
- local ret = wx.wxMessageBox(
- "The puzzle you've opened has invalid values.\n"..
- "Press 'Ok' to correct them using 'Create' before continuing "..
- "otherwise 'Cancel' to ignore them.",
- "wxLuaSudoku - Invalid puzzle",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
- sudokuGUI.frame )
- if ret == wx.wxOK then
- sudokuGUI.CreatePuzzle(true)
- end
- else --if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES) then
- sudokuGUI.sudokuSolnTable = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
- end
- else
- wx.wxMessageBox( msg,
- "wxLuaSudoku - Open file error",
- wx.wxOK + wx.wxICON_ERROR,
- sudokuGUI.frame )
- end
- end
- fileDialog:Destroy()
- end
- -- ----------------------------------------------------------------------------
- -- Save the puzzle to a file
- function sudokuGUI.SaveAsPuzzle()
- local fileDialog = wx.wxFileDialog(sudokuGUI.frame, "Save puzzle",
- sudokuGUI.filePath, sudokuGUI.fileName,
- "wxLuaSudoku files (*.sudoku)|*.sudoku|All files (*)|*",
- wx.wxSAVE + wx.wxOVERWRITE_PROMPT)
- local result = false
- if fileDialog:ShowModal() == wx.wxID_OK then
- local fileName = fileDialog:GetPath()
- local fn = wx.wxFileName(fileName)
- sudokuGUI.filePath = fn:GetPath()
- sudokuGUI.fileName = fn:GetFullName()
- result = sudoku.Save(sudokuGUI.GetCurrentTable(), fileName)
- if result then
- sudokuGUI.frame:SetTitle("wxLuaSudoku - "..sudokuGUI.fileName)
- else
- wx.wxMessageBox( "Unable to save file\n'"..fileName.."'",
- "wxLuaSudoku - Save file error",
- wx.wxOK + wx.wxICON_ERROR,
- sudokuGUI.frame )
- end
- end
- fileDialog:Destroy()
- return result
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.Undo()
- if sudokuGUI.sudokuTables_pos > 1 then
- sudokuGUI.sudokuTables_pos = sudokuGUI.sudokuTables_pos - 1
- sudokuGUI.UpdateTable(true)
- end
- end
- function sudokuGUI.Redo()
- if sudokuGUI.sudokuTables_pos < TableCount(sudokuGUI.sudokuTables) then
- sudokuGUI.sudokuTables_pos = sudokuGUI.sudokuTables_pos + 1
- sudokuGUI.UpdateTable(true)
- end
- end
- -- ----------------------------------------------------------------------------
- -- Try to fix the invalid cells if possible
- function sudokuGUI.FixInvalid(sudokuTable, show_dialog)
- local s = TableCopy(sudokuTable)
- local solnTable = sudokuGUI.sudokuSolnTable
- if solnTable and TableIsEmpty(solnTable.invalid) then
- for cell = 1, 81 do
- if sudoku.HasCellValue(s, cell) then
- local current_value = sudoku.GetCellValue(s, cell)
- local correct_value = sudoku.GetCellValue(solnTable, cell)
- if current_value ~= correct_value then
- sudoku.SetCellValue(s, cell, correct_value)
- end
- end
- end
- sudoku.UpdateTable(s)
- return s
- else
- if show_dialog then
- local msg = "The initial puzzle must be solved first.\n"..
- "Would you like me to try to solve it?"
- local flags = wx.wxYES_NO
- local invalid = true
- if solnTable and not TableIsEmpty(solnTable.invalid) then
- invalid = true
- end
- if invalid then
- msg = "The initial puzzle has invalid values.\n"..
- "Please correct them first using Create."
- flags = wx.wxOK
- end
- local ret = wx.wxMessageBox(msg,
- "wxLuaSudoku - Invalid puzzle",
- flags + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if not invalid and (ret == wx.wxOK) then
- s = sudokuGUI.SolveBruteForce(sudokuGUI.sudokuTables[1])
- if not s then
- wx.wxMessageBox("Unable to solve or or solving was aborted, giving up.",
- "wxLuaSudoku - Invalid puzzle",
- wx.wxOK + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- return nil
- end
- sudokuGUI.sudokuSolnTable = s
- sudoku.UpdateTable(sudokuGUI.sudokuSolnTable)
- return sudokuGUI.FixInvalid(sudokuTable, show_dialog)
- else
- return nil
- end
- else
- return nil
- end
- end
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.VerifyUniquePuzzle(sudokuTable)
- sudoku.CalcInvalidCells(sudokuTable)
- local invalid_count = TableCount(sudokuTable.invalid)
- if invalid_count > 0 then
- local ret = wx.wxMessageBox(
- string.format("The initial values of the puzzle are invalid.\n"..
- "There are %d cells with duplicate values.\n"..
- "Please select Create and fix them before trying to solve.\n", invalid_count),
- "wxLuaSudoku - Invalid puzzle",
- wx.wxOK + wx.wxICON_ERROR,
- sudokuGUI.frame )
- return
- end
- local solve_progress = 0
- local start_time = os.time()
- local last_time = start_time
- local solve_ok = true
- local msg_idx = 1
- -- define handler function here so it'll work w/o gui
- function sudoku.SolveBruteForceHook(guesses, cell)
- if solve_ok == false then return false end -- canceled
- solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
- if (solve_progress-1)%10 ~= 0 then return true end
- if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
- msg_idx = msg_idx + 1
- last_time = os.time()
- end
- local msg = string.format("%s\nIteration # %d, current cell %d ", sudokuGUI.sayings[msg_idx], guesses.current, cell)
- solve_ok = progressDialog:Update(solve_progress, msg)
- return solve_ok
- end
- local ret = wx.wxOK
- while ret == wx.wxOK do
- solve_progress = 0
- start_time = os.time()
- last_time = start_time
- solve_ok = true
- msg_idx = 1
- progressDialog = wx.wxProgressDialog("wxLuaSudoku - Verifying puzzle...",
- string.format("%s\nIteration # %d, current cell %d ", sudokuGUI.sayings[1], 0, 0),
- 1000, sudokuGUI.frame,
- wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
- local s1, s2 = sudoku.IsUniquePuzzle(sudokuTable)
- progressDialog:Destroy()
- if s1 and (s2 == nil) then
- return s1
- elseif solve_ok == false then
- ret = wx.wxMessageBox("The puzzle was not fully verified and therefore may not have a unique solution or a solution at all.\n"..
- "Press 'Ok' to restart checking or 'Cancel' to quit.",
- "wxLuaSudoku - Unfinished check",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
- sudokuGUI.frame)
- elseif s1 and s2 then
- wx.wxMessageBox("The puzzle does not have a unique solution.\n"..
- "Use 'Create' to fix the problem, showing mistakes will be disabled.",
- "wxLuaSudoku - Nonunique puzzle",
- wx.wxOK + wx.wxICON_ERROR,
- sudokuGUI.frame)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_MISTAKES, false)
- return nil
- else
- wx.wxMessageBox("The puzzle does not have a solution.\n"..
- "Use 'Create' to fix the problem, showing mistakes will be disabled.",
- "wxLuaSudoku - Unsolvable puzzle",
- wx.wxOK + wx.wxICON_ERROR,
- sudokuGUI.frame)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_MISTAKES, false)
- return nil
- end
- end
- return nil
- end
- -- ----------------------------------------------------------------------------
- -- Use the scanning method to solve it
- function sudokuGUI.SolveScanning()
- local s = TableCopy(sudokuGUI.GetCurrentTable())
- --[[
- local invalid = not TableIsEmpty(s.invalid)
- if invalid then
- if not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid) then
- local ret = wx.wxMessageBox("The initial values in the puzzle are invalid.\n"..
- "Please select Create and fix them before trying to solve.\n"..
- "Press 'Cancel' to try to solve it anyway.",
- "wxLuaSudoku - Invalid puzzle",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
- sudokuGUI.frame )
- if ret == wx.wxOK then return end
- else
- local ret = wx.wxMessageBox("The current puzzle has invalid cell values.\n"..
- "Would you like me to try to fix those cells or press cancel to abort solving.",
- "wxLuaSudoku - Invalid puzzle",
- wx.wxYES_NO + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxCANCEL then
- return
- elseif ret == wx.wxYES then
- local fixedS = sudokuGUI.FixInvalid(s, true)
- if fixedS then
- sudokuGUI.AddTable(fixedS)
- s = TableCopy(sudokuGUI.GetCurrentTable())
- else
- return
- end
- end
- end
- end
- ]]
- local count, changed_cells = sudoku.SolveScan(s)
- local changed_count = 0
- if changed_cells then
- sudokuGUI.AddTable(s)
- changed_count = TableCount(changed_cells)
- end
- local msg = string.format("Scanned rows, cols, and blocks %d times.\n"..
- "Found %d new values.\n"..
- "You may be able to do better using 'Eliminate groups'", count, changed_count)
- wx.wxMessageBox( msg,
- "wxLuaSudoku - Finished scanning",
- wx.wxOK + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- end
- -- ----------------------------------------------------------------------------
- -- Use the brute force method to solve it
- function sudokuGUI.SolveBruteForce(sudokuTable)
- local s
- if sudokuTable then
- s = TableCopy(sudokuTable)
- else
- s = TableCopy(sudokuGUI.GetCurrentTable())
- end
- --[[
- local invalid = not TableIsEmpty(s.invalid)
- if invalid then
- if (sudokuTable == nil) and (not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid)) then
- wx.wxMessageBox("The initial values in the puzzle are invalid.\n"..
- "Please select Create and fix them before trying to solve.",
- "wxLuaSudoku - Invalid puzzle",
- wx.wxOK + wx.wxICON_ERROR,
- sudokuGUI.frame )
- return
- end
- local ret = wx.wxMessageBox("The current puzzle has invalid cell values.\n"..
- "Would you like me to try to fix those cells or press cancel to abort solving.",
- "wxLuaSudoku - Invalid puzzle",
- wx.wxYES_NO + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxCANCEL then
- return
- elseif ret == wx.wxYES then
- local fixedS = sudokuGUI.FixInvalid(s, true)
- if fixedS then
- sudokuGUI.AddTable(fixedS)
- s = TableCopy(sudokuGUI.GetCurrentTable())
- else
- return
- end
- end
- end
- ]]
- local progressDialog = wx.wxProgressDialog("wxLuaSudoku - Solving...",
- string.format("%s\nIteration # %d, current cell %d ", sudokuGUI.sayings[1], 0, 0),
- 1000, sudokuGUI.frame,
- wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
- local solve_progress = 0
- local start_time = os.time()
- local last_time = start_time
- local solve_ok = true
- local msg_idx = 1
- -- define handler function here so it'll work w/o gui
- function sudoku.SolveBruteForceHook(guesses, cell)
- if solve_ok == false then return false end -- canceled
- solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
- if (solve_progress-1)%10 ~= 0 then return true end
- if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
- msg_idx = msg_idx + 1
- last_time = os.time()
- end
- local msg = string.format("%s\nIteration # %d, current cell %d ", sudokuGUI.sayings[msg_idx], guesses.current, cell)
- solve_ok = progressDialog:Update(solve_progress, msg)
- return solve_ok
- end
- -- "cheat" a little by using SolveScan to get easy to find values
- --local flags = TableCopy(s.flags)
- --for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
- -- s.flags[n] = true
- --end
- --local count, changed_cells = sudoku.SolveScan(s)
- local s, g = sudoku.SolveBruteForce(s)
- progressDialog:Destroy()
- if not s then
- if solve_ok then
- wx.wxMessageBox("Sorry, no solutions found!",
- "wxLuaSudoku - error",
- wx.wxOK + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- end
- elseif not sudokuTable then
- --s.flags = flags -- restore flags
- sudokuGUI.AddTable(s) -- we solved the current grid
- else
- --s.flags = flags -- restore flags
- return s -- we solved the input grid
- end
- end
- -- ----------------------------------------------------------------------------
- -- Reset the grid to the original values
- function sudokuGUI.ResetPuzzle(dont_query_user)
- dont_query_user = dont_query_user or false
- local ret = wx.wxOK
- if not dont_query_user then
- ret = wx.wxMessageBox("Reset the puzzle to the initial state?",
- "wxLuaSudoku - reset puzzle?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- end
- if ret == wx.wxCANCEL then
- return
- else
- sudokuGUI.sudokuTables_pos = 1
- while TableCount(sudokuGUI.sudokuTables) > 1 do
- table.remove(sudokuGUI.sudokuTables, 2)
- end
- end
- sudokuGUI.UpdateTable() -- redo it anyway
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.UpdateTable(refresh)
- if refresh == nil then refresh = true end
- local sudokuTable = sudokuGUI.GetCurrentTable()
- sudokuGUI.block_refresh = true
- local has_show_flag = false
- for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
- local id = n + sudokuGUI.ID_ELIMINATE_NAKEDPAIRS - sudoku.ELIMINATE_FLAG_MIN
- sudokuTable.flags[n] = sudokuGUI.IsCheckedMenuItem(id)
- local show_id = n + sudokuGUI.ID_SHOW_NAKEDPAIRS - sudoku.ELIMINATE_FLAG_MIN
- if (not has_show_flag) and (sudokuGUI.IsCheckedMenuItem(show_id) == true) then
- has_show_flag = true
- end
- end
- sudoku.UpdateTable(sudokuTable)
- if has_show_flag == true then
- -- swap out the possible table temporarily to calc pencil marks
- if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) then
- local p = sudokuTable.possible
- sudokuTable.possible = sudokuGUI.pencilMarks
- sudokuGUI.pencilMarksNakedTable, sudokuGUI.pencilMarksHiddenTable = sudoku.FindAllNakedHiddenGroups(sudokuTable, true)
- sudokuTable.possible = p
- else
- sudokuGUI.possNakedTable, sudokuGUI.possHiddenTable = sudoku.FindAllNakedHiddenGroups(sudokuTable, true)
- end
- end
- sudokuGUI.SetCurrentTable(sudokuTable)
- sudokuGUI.block_refresh = false
- if (refresh == true) then
- sudokuGUI.Refresh()
- end
- sudokuGUI.UpdateGUI()
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.UpdateGUI()
- local table_count = #sudokuGUI.sudokuTables
- local table_pos = sudokuGUI.sudokuTables_pos
- sudokuGUI.frame:GetMenuBar():Enable(sudokuGUI.ID_UNDO, table_pos > 1)
- sudokuGUI.frame:GetMenuBar():Enable(sudokuGUI.ID_REDO, table_pos < table_count)
- sudokuGUI.frame:GetToolBar():EnableTool(sudokuGUI.ID_UNDO, table_pos > 1)
- sudokuGUI.frame:GetToolBar():EnableTool(sudokuGUI.ID_REDO, table_pos < table_count)
- sudokuGUI.frame:SetStatusText(string.format("Step : %d", table_pos), 1)
- end
- -- ----------------------------------------------------------------------------
- -- The preference pages are in a table so they can be accessed easily
- sudokuGUI.PreferencesDialogPageUI = {}
- function sudokuGUI.PreferencesDialogPageUI.Create(parent)
- local panel = wx.wxPanel(parent, wx.wxID_ANY)
- local ID_LISTBOX = 10
- local ID_SAMPLE_TEXT = 11
- local ID_FONT_BUTTON = 12
- local ID_COLOUR_BUTTON = 13
- local ID_RESET_BUTTON = 14
- local listStrings = -- in same order as the colours
- {
- "Values",
- "Initial values",
- "Possible values",
- "Invalid values",
- "Background",
- "Odd background",
- "Focused cell",
- "Naked pairs",
- "Naked triplets",
- "Naked quads",
- "Hidden pairs",
- "Hidden triplets",
- "Hidden quads"
- }
- local listBoxValues = {}
- for n = 1, sudokuGUI.COLOUR_MAX do
- table.insert(listBoxValues, {colour = wx.wxColour(sudokuGUI.Colours[n])})
- end
- listBoxValues[sudokuGUI.VALUE_COLOUR].font = wx.wxFont(sudokuGUI.valueFont.wxfont)
- listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font = wx.wxFont(sudokuGUI.possibleFont.wxfont)
- local reset_fonts = true
- -- Create the dialog ------------------------------------------------------
- local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
- local fcFlexSizer = wx.wxFlexGridSizer( 1, 2, 0, 0 )
- fcFlexSizer:AddGrowableCol( 0 )
- fcFlexSizer:AddGrowableRow( 0 )
- local fcListBox = wx.wxListBox( panel, ID_LISTBOX, wx.wxDefaultPosition, wx.wxSize(80,100), listStrings, wx.wxLB_SINGLE )
- fcListBox:SetSelection(0)
- fcFlexSizer:Add( fcListBox, 0, wx.wxGROW+wx.wxALIGN_CENTER_HORIZONTAL+wx.wxALL, 5 )
- local fcBoxSizer = wx.wxBoxSizer( wx.wxVERTICAL )
- local sampleWin = wx.wxWindow(panel, ID_SAMPLE_TEXT, wx.wxDefaultPosition, wx.wxSize(140,140))
- fcBoxSizer:Add( sampleWin, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 );
- local fontButton = wx.wxButton( panel, ID_FONT_BUTTON, "Choose Font", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
- fcBoxSizer:Add( fontButton, 0, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL+wx.wxALL, 5 )
- local colourButton = wx.wxButton( panel, ID_COLOUR_BUTTON, "Choose Color", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
- fcBoxSizer:Add( colourButton, 0, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL+wx.wxALL, 5 )
- local resetButton = wx.wxButton( panel, ID_RESET_BUTTON, "Reset Value...", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
- fcBoxSizer:Add( resetButton, 0, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL+wx.wxALL, 5 )
- fcFlexSizer:Add( fcBoxSizer, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
- mainSizer:Add( fcFlexSizer, 1, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL, 5 )
- panel:SetSizer( mainSizer )
- sampleWin:Connect(wx.wxEVT_PAINT,
- function (event)
- local win = event:GetEventObject():DynamicCast("wxWindow")
- local sel = fcListBox:GetSelection() + 1
- local width, height = win:GetClientSizeWH()
- local dc = wx.wxPaintDC(win)
- local function SetFontSize(size, width, height, font)
- -- alternate way, but it fails for fonts that can't scale large enough
- --local f = wx.wxNullFont:NewSize(wx.wxSize(width, height), font:GetFamily(), font:GetStyle(), font:GetWeight(), font:GetUnderlined(), font:GetFaceName())
- --font:SetPointSize(f:GetPointSize())
- local font_width = 0
- local font_height = 0
- while (font_width < width) and (font_height < height) do
- font:SetPointSize(size)
- dc:SetFont(font)
- font_width, font_height = dc:GetTextExtent("5")
- size = size + 2
- if size > 200 then break end -- oops bad font?
- end
- font:SetPointSize(size-1)
- end
- -- clear background
- local c = listBoxValues[sudokuGUI.BACKGROUND_COLOUR].colour
- if (sel == sudokuGUI.ODD_BACKGROUND_COLOUR) or (sel == sudokuGUI.FOCUS_CELL_COLOUR) then
- c = listBoxValues[sel].colour
- end
- local brush = wx.wxBrush(c, wx.wxSOLID)
- dc:SetBrush(brush)
- brush:delete()
- dc:DrawRectangle(0, 0, width, height)
- -- draw possible values
- dc:SetTextForeground(listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].colour)
- local font = listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font
- if reset_fonts then SetFontSize(4, width/4, height/4, font) end
- dc:SetFont(font)
- local font_width, font_height = dc:GetTextExtent("5")
- local pos =
- {
- [1] = { x = 2, y = 2 },
- [3] = { x = width-font_width-2, y = 2 },
- [4] = { x = 2, y = (height-font_height)/2-2 },
- [6] = { x = width-font_width-2, y = (height-font_height)/2-2 },
- [7] = { x = 2, y = height-font_height-2 },
- [9] = { x = width-font_width-2, y = height-font_height-2 }
- }
- dc:SetBrush(wx.wxTRANSPARENT_BRUSH)
- local function DrawPossible(idx, n, value, hidden)
- dc:DrawText(value, pos[n].x, pos[n].y)
- local pen = wx.wxPen(listBoxValues[idx].colour, 1, wx.wxSOLID)
- dc:SetPen(pen); pen:delete()
- if hidden ~= true then
- dc:DrawRectangle(pos[n].x, pos[n].y, font_width, font_height)
- else
- dc:DrawRoundedRectangle(pos[n].x, pos[n].y, font_width, font_height, 20)
- end
- end
- DrawPossible(sudokuGUI.NAKED_PAIRS_COLOUR, 1, "2")
- DrawPossible(sudokuGUI.NAKED_TRIPLETS_COLOUR, 4, "3")
- DrawPossible(sudokuGUI.NAKED_QUADS_COLOUR, 7, "4")
- DrawPossible(sudokuGUI.HIDDEN_PAIRS_COLOUR, 3, "2", true)
- DrawPossible(sudokuGUI.HIDDEN_TRIPLETS_COLOUR, 6, "3", true)
- DrawPossible(sudokuGUI.HIDDEN_QUADS_COLOUR, 9, "4", true)
- -- draw invalid marker
- local pen = wx.wxPen(listBoxValues[sudokuGUI.INVALID_VALUE_COLOUR].colour, 1, wx.wxSOLID)
- dc:SetPen(pen); pen:delete()
- dc:DrawLine(0, 0, width, height)
- -- draw value
- if (sel == sudokuGUI.INIT_VALUE_COLOUR) then
- dc:SetTextForeground(listBoxValues[sudokuGUI.INIT_VALUE_COLOUR].colour)
- else
- dc:SetTextForeground(listBoxValues[sudokuGUI.VALUE_COLOUR].colour)
- end
- local old_font = font
- local font = listBoxValues[sudokuGUI.VALUE_COLOUR].font
- if reset_fonts then SetFontSize(old_font:GetPointSize(), width-2, height-2, font) end
- dc:SetFont(font)
- local font_width, font_height = dc:GetTextExtent("9")
- dc:DrawText("9", (width-font_width)/2, (height-font_height)/2)
- reset_fonts = false
- dc:delete()
- end)
- panel:Connect(ID_LISTBOX, wx.wxEVT_COMMAND_LISTBOX_SELECTED,
- function (event)
- local sel = event:GetSelection() + 1
- panel:FindWindow(ID_FONT_BUTTON):Enable(listBoxValues[sel].font ~= nil)
- colourButton:SetForegroundColour(listBoxValues[sel].colour)
- sampleWin:Refresh(false)
- end)
- panel:Connect(ID_FONT_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED,
- function (event)
- local sel = fcListBox:GetSelection() + 1
- local f = listBoxValues[sel].font
- f = wx.wxGetFontFromUser(panel, f)
- if f:Ok() then
- listBoxValues[sel].font:delete()
- listBoxValues[sel].font = f
- reset_fonts = true
- else
- f:delete()
- end
- sampleWin:Refresh(false)
- end)
- panel:Connect(ID_COLOUR_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED,
- function (event)
- local sel = fcListBox:GetSelection() + 1
- local c = listBoxValues[sel].colour
- c = wx.wxGetColourFromUser(panel, c)
- if c:Ok() then
- listBoxValues[sel].colour:delete()
- listBoxValues[sel].colour = c
- colourButton:SetForegroundColour(c)
- else
- c:delete()
- end
- sampleWin:Refresh(false)
- end)
- panel:Connect(ID_RESET_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED,
- function (event)
- local sel = fcListBox:GetSelection() + 1
- local ret = wx.wxMessageBox(
- "Press 'Yes' to reset all the colors and fonts or 'No' to reset only just the selected item.",
- "wxLuaSudoku - Reset colors or fonts?",
- wx.wxYES_NO + wx.wxCANCEL + wx.wxICON_INFORMATION,
- panel )
- if ret == wx.wxYES then
- for n = 1, sudokuGUI.COLOUR_MAX do
- listBoxValues[n].colour:delete()
- listBoxValues[n].colour = wx.wxColour(sudokuGUI.Colours_[n])
- end
- listBoxValues[sudokuGUI.VALUE_COLOUR].font:delete()
- listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font:delete()
- listBoxValues[sudokuGUI.VALUE_COLOUR].font = wx.wxFont(sudokuGUI.valueFont_wxfont_)
- listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font = wx.wxFont(sudokuGUI.possibleFont_wxfont_)
- elseif ret == wx.wxNO then
- listBoxValues[sel].colour:delete()
- listBoxValues[sel].colour = wx.wxColour(sudokuGUI.Colours_[sel])
- if (sel == sudokuGUI.VALUE_COLOUR) then
- listBoxValues[sel].font:delete()
- listBoxValues[sel].font = wx.wxFont(sudokuGUI.valueFont_wxfont_)
- elseif (sel == sudokuGUI.POSS_VALUE_COLOUR) then
- listBoxValues[sel].font:delete()
- listBoxValues[sel].font = wx.wxFont(sudokuGUI.possibleFont_wxfont_)
- end
- end
- colourButton:SetForegroundColour(listBoxValues[sel].colour)
- reset_fonts = true
- sampleWin:Refresh(false)
- end)
- function sudokuGUI.PreferencesDialogPageUI.Apply()
- for n = 1, sudokuGUI.COLOUR_MAX do
- sudokuGUI.Colours[n]:delete()
- sudokuGUI.Colours[n] = wx.wxColour(listBoxValues[n].colour)
- end
- -- copy the fonts since when applied their size will change
- sudokuGUI.valueFont.wxfont:delete()
- sudokuGUI.valueFont.wxfont = wx.wxFont(listBoxValues[sudokuGUI.VALUE_COLOUR].font)
- sudokuGUI.possibleFont.wxfont:delete()
- sudokuGUI.possibleFont.wxfont = wx.wxFont(listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font)
- sudokuGUI.valueFont_cache = {} -- clear cache so GetCellBestSize recreates it
- sudokuGUI.possibleFont_cache = {}
- for winID = 1, 81 do
- if sudokuGUI.IsOddBlockCell(winID) then
- sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
- else
- sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
- end
- end
- local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
- sudokuGUI.GetCellBestSize(width, height)
- sudokuGUI.Refresh()
- end
- function sudokuGUI.PreferencesDialogPageUI.Destroy()
- for n = 1, sudokuGUI.COLOUR_MAX do
- listBoxValues[n].colour:delete()
- if listBoxValues[n].font then listBoxValues[n].font:delete() end
- end
- end
- return panel
- end
- function sudokuGUI.CheckListBoxCheck(clBox, n_start, n_end, check)
- for n = n_start, n_end do
- clBox:Check(n, check)
- end
- end
- function sudokuGUI.CheckListBoxIsChecked(clBox, n_start, n_end)
- for n = n_start, n_end do
- if not clBox:IsChecked(n) then return false end
- end
- return true
- end
- sudokuGUI.PreferencesDialogPageShow = {}
- function sudokuGUI.PreferencesDialogPageShow.Create(parent)
- local panel = wx.wxPanel(parent, wx.wxID_ANY)
- local ID_LISTBOX = 10
- local listStrings =
- {
- "All naked groups",
- "All hidden groups",
- "Naked pairs",
- "Naked triplets",
- "Naked quads",
- "Hidden pairs",
- "Hidden triplets",
- "Hidden quads"
- }
- local listBoxValues =
- {
- sudokuGUI.ID_SHOW_NAKED,
- sudokuGUI.ID_SHOW_HIDDEN,
- sudokuGUI.ID_SHOW_NAKEDPAIRS,
- sudokuGUI.ID_SHOW_NAKEDTRIPLETS,
- sudokuGUI.ID_SHOW_NAKEDQUADS,
- sudokuGUI.ID_SHOW_HIDDENPAIRS,
- sudokuGUI.ID_SHOW_HIDDENTRIPLETS,
- sudokuGUI.ID_SHOW_HIDDENQUADS
- }
- -- Create the dialog ------------------------------------------------------
- local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
- local showListBox = wx.wxCheckListBox( panel, ID_LISTBOX, wx.wxDefaultPosition, wx.wxSize(80,100), listStrings, wx.wxLB_SINGLE )
- mainSizer:Add( showListBox, 1, wx.wxGROW+wx.wxALIGN_CENTER_HORIZONTAL+wx.wxALL, 5 )
- panel:SetSizer( mainSizer )
- for n = 1, showListBox:GetCount() do
- showListBox:Check(n-1, sudokuGUI.IsCheckedMenuItem(listBoxValues[n]))
- end
- panel:Connect(ID_LISTBOX, wx.wxEVT_COMMAND_CHECKLISTBOX_TOGGLED,
- function (event)
- local sel = event:GetSelection()
- local checked = showListBox:IsChecked(sel)
- local id = listBoxValues[sel+1]
- if id == sudokuGUI.ID_SHOW_NAKED then
- sudokuGUI.CheckListBoxCheck(showListBox, 2, 4, checked)
- elseif id == sudokuGUI.ID_SHOW_HIDDEN then
- sudokuGUI.CheckListBoxCheck(showListBox, 5, 7, checked)
- else
- showListBox:Check(0, sudokuGUI.CheckListBoxIsChecked(showListBox, 2, 4))
- showListBox:Check(1, sudokuGUI.CheckListBoxIsChecked(showListBox, 5, 7))
- end
- end)
- function sudokuGUI.PreferencesDialogPageShow.Apply()
- for n = 1, showListBox:GetCount() do
- sudokuGUI.CheckMenuItem(listBoxValues[n], showListBox:IsChecked(n-1))
- end
- sudokuGUI.UpdateTable()
- end
- function sudokuGUI.PreferencesDialogPageShow.Destroy()
- end
- return panel
- end
- sudokuGUI.PreferencesDialogPageSolve = {}
- function sudokuGUI.PreferencesDialogPageSolve.Create(parent)
- local panel = wx.wxPanel(parent, wx.wxID_ANY)
- local ID_LISTBOX = 10
- local listStrings =
- {
- "All naked groups",
- "All hidden groups",
- "Naked pairs",
- "Naked triplets",
- "Naked quads",
- "Hidden pairs",
- "Hidden triplets",
- "Hidden quads"
- }
- local listBoxValues =
- {
- sudokuGUI.ID_ELIMINATE_NAKED,
- sudokuGUI.ID_ELIMINATE_HIDDEN,
- sudokuGUI.ID_ELIMINATE_NAKEDPAIRS,
- sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS,
- sudokuGUI.ID_ELIMINATE_NAKEDQUADS,
- sudokuGUI.ID_ELIMINATE_HIDDENPAIRS,
- sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS,
- sudokuGUI.ID_ELIMINATE_HIDDENQUADS
- }
- -- Create the dialog ------------------------------------------------------
- local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
- local showListBox = wx.wxCheckListBox( panel, ID_LISTBOX, wx.wxDefaultPosition, wx.wxSize(80,100), listStrings, wx.wxLB_SINGLE )
- mainSizer:Add( showListBox, 1, wx.wxGROW+wx.wxALIGN_CENTER_HORIZONTAL+wx.wxALL, 5 )
- panel:SetSizer( mainSizer )
- for n = 1, showListBox:GetCount() do
- showListBox:Check(n-1, sudokuGUI.IsCheckedMenuItem(listBoxValues[n]))
- end
- panel:Connect(ID_LISTBOX, wx.wxEVT_COMMAND_CHECKLISTBOX_TOGGLED,
- function (event)
- local sel = event:GetSelection()
- local checked = showListBox:IsChecked(sel)
- local id = listBoxValues[sel+1]
- if id == sudokuGUI.ID_ELIMINATE_NAKED then
- sudokuGUI.CheckListBoxCheck(showListBox, 2, 4, checked)
- elseif id == sudokuGUI.ID_ELIMINATE_HIDDEN then
- sudokuGUI.CheckListBoxCheck(showListBox, 5, 7, checked)
- else
- showListBox:Check(0, sudokuGUI.CheckListBoxIsChecked(showListBox, 2, 4))
- showListBox:Check(1, sudokuGUI.CheckListBoxIsChecked(showListBox, 5, 7))
- end
- end)
- function sudokuGUI.PreferencesDialogPageSolve.Apply()
- for n = 1, showListBox:GetCount() do
- sudokuGUI.CheckMenuItem(listBoxValues[n], showListBox:IsChecked(n-1))
- end
- sudokuGUI.UpdateTable()
- end
- function sudokuGUI.PreferencesDialogPageSolve.Destroy()
- end
- return panel
- end
- function sudokuGUI.PreferencesDialog()
- local dialog = wx.wxDialog(sudokuGUI.frame, wx.wxID_ANY,
- "wxLuaSudoku - Preferences",
- wx.wxDefaultPosition, wx.wxDefaultSize,
- wx.wxDEFAULT_DIALOG_STYLE+wx.wxRESIZE_BORDER)
- local panel = wx.wxPanel(dialog, wx.wxID_ANY)
- local notebook = wx.wxNotebook(panel, wx.wxID_ANY)
- local notebookPages = {}
- local page1 = sudokuGUI.PreferencesDialogPageUI.Create(notebook)
- notebook:AddPage(page1, "Fonts and Colors", true)
- table.insert(notebookPages, sudokuGUI.PreferencesDialogPageUI)
- local page2 = sudokuGUI.PreferencesDialogPageShow.Create(notebook)
- notebook:AddPage(page2, "Mark groups", false)
- table.insert(notebookPages, sudokuGUI.PreferencesDialogPageShow)
- local page3 = sudokuGUI.PreferencesDialogPageSolve.Create(notebook)
- notebook:AddPage(page3, "Eliminate groups", false)
- table.insert(notebookPages, sudokuGUI.PreferencesDialogPageSolve)
- local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
- local buttonSizer = wx.wxBoxSizer( wx.wxHORIZONTAL )
- local okButton = wx.wxButton( panel, wx.wxID_OK, "&OK", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
- buttonSizer:Add( okButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
- local cancelButton = wx.wxButton( panel, wx.wxID_CANCEL, "&Cancel", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
- buttonSizer:Add( cancelButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
- local applyButton = wx.wxButton( panel, wx.wxID_APPLY, "&Apply", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
- buttonSizer:Add( applyButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
- mainSizer:Add( notebook, 1, wx.wxGROW+wx.wxALIGN_CENTER, 0 )
- mainSizer:Add( buttonSizer, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
- panel:SetSizer( mainSizer )
- mainSizer:SetSizeHints( dialog )
- dialog:Connect(wx.wxID_APPLY, wx.wxEVT_COMMAND_BUTTON_CLICKED,
- function (event)
- --local sel = notebook:GetSelection()
- --if sel >= 0 then notebookPages[sel+1].Apply() end
- for n = 1, #notebookPages do
- notebookPages[n].Apply()
- end
- end)
- dialog:Connect(wx.wxID_OK, wx.wxEVT_COMMAND_BUTTON_CLICKED,
- function (event)
- for n = 1, #notebookPages do
- notebookPages[n].Apply()
- notebookPages[n].Destroy()
- end
- event:Skip() -- wxDialog will cancel automatically
- end)
- dialog:Connect(wx.wxID_CANCEL, wx.wxEVT_COMMAND_BUTTON_CLICKED,
- function (event)
- for n = 1, #notebookPages do
- notebookPages[n].Destroy()
- end
- event:Skip() -- wxDialog will cancel automatically
- end)
- dialog:ShowModal()
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.ConfigSave(save_prefs)
- if not sudokuGUI.config then
- sudokuGUI.config = wx.wxFileConfig("wxLuaSudoku", "wxLua")
- end
- if not sudokuGUI.config then return end
- -- write the frame position so we can restore it
- local x, y = sudokuGUI.frame:GetPositionXY()
- local w, h = sudokuGUI.frame:GetClientSizeWH()
- local max = booltoint(sudokuGUI.frame:IsMaximized())
- sudokuGUI.config:Write("wxLuaSudoku/Frame", string.format("x:%d y:%d w:%d h:%d maximized:%d", x, y, w, h, max))
- if not save_prefs then return end
- if sudokuGUI.query_save_prefs then
- local ret = wx.wxMessageBox(
- "Preferences are stored in an ini file which you may delete:\n"..
- "MSW : Documents and Settings\\user\\wxLuaSudoku.ini\n"..
- "Unix : /home/user/.wxLuaSudoku",
- "wxLuaSudoku - Save preferences?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxCANCEL then
- return
- end
- sudokuGUI.query_save_prefs = false
- end
- if sudokuGUI.config then
- sudokuGUI.ConfigReadWrite(false, sudokuGUI.config)
- sudokuGUI.config:Flush(true)
- end
- end
- function sudokuGUI.ConfigLoad()
- if not sudokuGUI.config then
- sudokuGUI.config = wx.wxFileConfig("wxLuaSudoku", "wxLua")
- end
- if sudokuGUI.config then
- local dispX, dispY, dispW, dispH = wx.wxClientDisplayRect()
- local _, str = sudokuGUI.config:Read("wxLuaSudoku/Frame")
- local x, y, w, h, max = string.match(str, "x:(%d+) y:(%d+) w:(%d+) h:(%d+) maximized:(%d+)")
- if (x ~= nil) and (y ~= nil) and (w ~= nil) and (h ~= nil) and (max ~= nil) then
- x = tonumber(x); y = tonumber(y); w = tonumber(w); h = tonumber(h)
- max = inttobool(tonumber(max))
- if max then
- sudokuGUI.frame:Maximize(true)
- else
- if x < dispX - 5 then x = 0 end
- if y < dispY - 5 then y = 0 end
- if w > dispW then w = dispW end
- if h > dispH then h = dispH end
- sudokuGUI.frame:Move(x, y)
- sudokuGUI.frame:SetClientSize(w, h)
- end
- end
- sudokuGUI.ConfigReadWrite(true, sudokuGUI.config)
- end
- for winID = 1, 81 do
- if sudokuGUI.IsOddBlockCell(winID) then
- sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
- else
- sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
- end
- end
- local show_toolbar = sudokuGUI.frame:GetMenuBar():IsChecked(sudokuGUI.ID_SHOW_TOOLBAR)
- if sudokuGUI.frame:GetToolBar():IsShown() ~= show_toolbar then
- -- generate fake event to simplify processing
- local evt = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, sudokuGUI.ID_SHOW_TOOLBAR)
- evt:SetInt(booltoint(show_toolbar))
- sudokuGUI.OnMenuEvent(evt)
- end
- local show_toolbar_labels = sudokuGUI.frame:GetMenuBar():IsChecked(sudokuGUI.ID_SHOW_TOOLBAR_LABELS)
- if (bit.band(sudokuGUI.frame:GetToolBar():GetWindowStyleFlag(), wx.wxTB_TEXT) ~= 0) ~= show_toolbar_labels then
- -- generate fake event to simplify processing
- local evt = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, sudokuGUI.ID_SHOW_TOOLBAR_LABELS)
- evt:SetInt(booltoint(show_toolbar_labels))
- sudokuGUI.OnMenuEvent(evt)
- end
- local show_statusbar = sudokuGUI.frame:GetMenuBar():IsChecked(sudokuGUI.ID_SHOW_STATUSBAR)
- if sudokuGUI.frame:GetStatusBar():IsShown() ~= show_statusbar then
- -- generate fake event to simplify processing
- local evt = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, sudokuGUI.ID_SHOW_STATUSBAR)
- evt:SetInt(booltoint(show_statusbar))
- sudokuGUI.OnMenuEvent(evt)
- end
- sudokuGUI.valueFont_cache = {} -- clear cache in case the font has changed
- sudokuGUI.possibleFont_cache = {}
- -- update font size
- local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
- sudokuGUI.GetCellBestSize(width, height)
- -- update for preferences
- sudokuGUI.UpdateTable()
- sudokuGUI.Refresh()
- end
- function sudokuGUI.ConfigReadWrite(read, config)
- local path = "wxLuaSudoku"
- local function ReadWriteColour(key, c)
- if read then
- if config:HasEntry(key) then
- local _, str = config:Read(key)
- local r, g, b = string.match(str, "r:(%d+) g:(%d+) b:(%d+)")
- if (r == nil) or (g == nil) or (b == nil) then return end
- r = tonumber(r); g = tonumber(g); b = tonumber(b)
- if (r < 0) or (r > 255) then return end -- sanity check
- if (g < 0) or (g > 255) then return end
- if (b < 0) or (b > 255) then return end
- c:Set(r, g, b)
- end
- else
- config:Write(key, string.format("r:%d g:%d b:%d", c:Red(), c:Green(), c:Blue()))
- end
- end
- local function ReadWriteFont(key, f)
- if read then
- if config:HasEntry(key) then
- local _, str = config:Read(key)
- local face, family, style, underlined, weight = string.match(str, "face:(\"[%w ]+\") family:(%d+) style:(%d+) underlined:(%d+) weight:(%d+)")
- if (face == nil) or (family == nil) or (style == nil) or (underlined == nil) or (weight == nil) then return end
- family = tonumber(family); style = tonumber(style);
- underlined = inttobool(tonumber(underlined)); weight = tonumber(weight)
- -- remove quotes
- if string.len(face) > 2 then face = string.sub(face, 2, -2) end
- -- test so see if the values are any good
- local ff = wx.wxFont(12, family, style, weight, underlined, face)
- if not ff:Ok() then return end
- local tempF = wx.wxFont(f)
- f:SetFaceName(face)
- f:SetFamily(family)
- f:SetStyle(style)
- f:SetUnderlined(underlined)
- f:SetWeight(weight)
- -- shouldn't happen but we always want a usable font
- if not f:Ok() then
- f:SetFaceName(tempF:GetFaceName())
- f:SetFamily(tempF:GetFamily())
- f:SetStyle(tempF:GetStyle())
- f:SetUnderlined(tempF:GetUnderlined())
- f:SetWeight(tempF:GetWeight())
- end
- end
- else
- config:Write(key, string.format("face:\"%s\" family:%d style:%d underlined:%d weight:%d",
- f:GetFaceName(), f:GetFamily(), f:GetStyle(), booltoint(f:GetUnderlined()), f:GetWeight()))
- end
- end
- if read then
- local _
- if config:HasEntry(path.."/LastOpenedFilepath") then
- _, sudokuGUI.filePath = config:Read(path.."/LastOpenedFilepath", "")
- end
- if config:HasEntry(path.."/LastOpenedFilename") then
- _, sudokuGUI.fileName = config:Read(path.."/LastOpenedFilename", "")
- end
- if config:HasEntry(path.."/GenerateDifficulty") then
- _, sudokuGUI.difficulty = config:Read(path.."/GenerateDifficulty", 0)
- end
- else
- config:Write(path.."/LastOpenedFilepath", sudokuGUI.filePath)
- config:Write(path.."/LastOpenedFilename", sudokuGUI.fileName)
- config:Write(path.."/GenerateDifficulty", sudokuGUI.difficulty)
- end
- ReadWriteColour(path.."/Colours/Value", sudokuGUI.Colours[sudokuGUI.VALUE_COLOUR])
- ReadWriteColour(path.."/Colours/ValueInit", sudokuGUI.Colours[sudokuGUI.INIT_VALUE_COLOUR])
- ReadWriteColour(path.."/Colours/ValuePossible", sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
- ReadWriteColour(path.."/Colours/ValueInvalid", sudokuGUI.Colours[sudokuGUI.INVALID_VALUE_COLOUR])
- ReadWriteColour(path.."/Colours/CellBackground", sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
- ReadWriteColour(path.."/Colours/CellOddBackground", sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
- ReadWriteColour(path.."/Colours/CellFocus", sudokuGUI.Colours[sudokuGUI.FOCUS_CELL_COLOUR])
- ReadWriteColour(path.."/Colours/NakedPairs", sudokuGUI.Colours[sudokuGUI.NAKED_PAIRS_COLOUR])
- ReadWriteColour(path.."/Colours/NakedTriplets", sudokuGUI.Colours[sudokuGUI.NAKED_TRIPLETS_COLOUR])
- ReadWriteColour(path.."/Colours/NakedQuads", sudokuGUI.Colours[sudokuGUI.NAKED_QUADS_COLOUR])
- ReadWriteColour(path.."/Colours/HiddenPairs", sudokuGUI.Colours[sudokuGUI.HIDDEN_PAIRS_COLOUR])
- ReadWriteColour(path.."/Colours/HiddenTriplets", sudokuGUI.Colours[sudokuGUI.HIDDEN_TRIPLETS_COLOUR])
- ReadWriteColour(path.."/Colours/HiddenQuads", sudokuGUI.Colours[sudokuGUI.HIDDEN_QUADS_COLOUR])
- ReadWriteFont(path.."/Fonts/Value", sudokuGUI.valueFont.wxfont)
- ReadWriteFont(path.."/Fonts/ValuePossible", sudokuGUI.possibleFont.wxfont)
- local function ReadWritePref(key, pref)
- if read then
- if config:HasEntry(key) then
- local _, v = config:Read(key, 0)
- sudokuGUI.CheckMenuItem(pref, inttobool(v))
- end
- else
- config:Write(key, booltoint(sudokuGUI.IsCheckedMenuItem(pref)))
- end
- end
- ReadWritePref(path.."/Preferences/SHOW_ERRORS", sudokuGUI.ID_SHOW_ERRORS)
- ReadWritePref(path.."/Preferences/SHOW_MISTAKES", sudokuGUI.ID_SHOW_MISTAKES)
- ReadWritePref(path.."/Preferences/SHOW_TOOLBAR", sudokuGUI.ID_SHOW_TOOLBAR)
- ReadWritePref(path.."/Preferences/SHOW_TOOLBAR_LABELS", sudokuGUI.ID_SHOW_TOOLBAR_LABELS)
- ReadWritePref(path.."/Preferences/SHOW_STATUSBAR", sudokuGUI.ID_SHOW_STATUSBAR)
- ReadWritePref(path.."/Preferences/SHOW_POSSIBLE", sudokuGUI.ID_SHOW_POSSIBLE)
- ReadWritePref(path.."/Preferences/SHOW_USER_POSSIBLE", sudokuGUI.ID_SHOW_USER_POSSIBLE)
- ReadWritePref(path.."/Preferences/SHOW_POSSIBLE_LINE", sudokuGUI.ID_SHOW_POSSIBLE_LINE)
- ReadWritePref(path.."/Preferences/SHOW_NAKED", sudokuGUI.ID_SHOW_NAKED)
- ReadWritePref(path.."/Preferences/SHOW_HIDDEN", sudokuGUI.ID_SHOW_HIDDEN)
- ReadWritePref(path.."/Preferences/SHOW_NAKEDPAIRS", sudokuGUI.ID_SHOW_NAKEDPAIRS)
- ReadWritePref(path.."/Preferences/SHOW_HIDDENPAIRS", sudokuGUI.ID_SHOW_HIDDENPAIRS)
- ReadWritePref(path.."/Preferences/SHOW_NAKEDTRIPLETS", sudokuGUI.ID_SHOW_NAKEDTRIPLETS)
- ReadWritePref(path.."/Preferences/SHOW_HIDDENTRIPLETS", sudokuGUI.ID_SHOW_HIDDENTRIPLETS)
- ReadWritePref(path.."/Preferences/SHOW_NAKEDQUADS", sudokuGUI.ID_SHOW_NAKEDQUADS)
- ReadWritePref(path.."/Preferences/SHOW_HIDDENQUADS", sudokuGUI.ID_SHOW_HIDDENQUADS)
- ReadWritePref(path.."/Preferences/ELIMINATE_NAKED", sudokuGUI.ID_ELIMINATE_NAKED)
- ReadWritePref(path.."/Preferences/ELIMINATE_HIDDEN", sudokuGUI.ID_ELIMINATE_HIDDEN)
- ReadWritePref(path.."/Preferences/ELIMINATE_NAKEDPAIRS", sudokuGUI.ID_ELIMINATE_NAKEDPAIRS)
- ReadWritePref(path.."/Preferences/ELIMINATE_HIDDENPAIRS", sudokuGUI.ID_ELIMINATE_HIDDENPAIRS)
- ReadWritePref(path.."/Preferences/ELIMINATE_NAKEDTRIPLETS", sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS)
- ReadWritePref(path.."/Preferences/ELIMINATE_HIDDENTRIPLETS", sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS)
- ReadWritePref(path.."/Preferences/ELIMINATE_NAKEDQUADS", sudokuGUI.ID_ELIMINATE_NAKEDQUADS)
- ReadWritePref(path.."/Preferences/ELIMINATE_HIDDENQUADS", sudokuGUI.ID_ELIMINATE_HIDDENQUADS)
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.InitFontsAndColours()
- sudokuGUI.Colours =
- {
- [sudokuGUI.VALUE_COLOUR] = wx.wxColour(0, 0, 230),
- [sudokuGUI.INIT_VALUE_COLOUR] = wx.wxColour(0, 0, 0),
- [sudokuGUI.POSS_VALUE_COLOUR] = wx.wxColour(0, 0, 0),
- [sudokuGUI.INVALID_VALUE_COLOUR] = wx.wxColour(255, 0, 0),
- [sudokuGUI.BACKGROUND_COLOUR] = wx.wxColour(255, 255, 255),
- [sudokuGUI.ODD_BACKGROUND_COLOUR] = wx.wxColour(250, 250, 210),
- [sudokuGUI.FOCUS_CELL_COLOUR] = wx.wxColour(200, 220, 250),
- [sudokuGUI.NAKED_PAIRS_COLOUR] = wx.wxColour(255, 0, 0),
- [sudokuGUI.NAKED_TRIPLETS_COLOUR] = wx.wxColour(255, 180, 0),
- [sudokuGUI.NAKED_QUADS_COLOUR] = wx.wxColour(255, 255, 0),
- [sudokuGUI.HIDDEN_PAIRS_COLOUR] = wx.wxColour(0, 220, 0),
- [sudokuGUI.HIDDEN_TRIPLETS_COLOUR] = wx.wxColour(0, 240, 160),
- [sudokuGUI.HIDDEN_QUADS_COLOUR] = wx.wxColour(0, 220, 220)
- }
- sudokuGUI.Colours_ = {}
- for n = 1, sudokuGUI.COLOUR_MAX do
- sudokuGUI.Colours_[n] = wx.wxColour(sudokuGUI.Colours[n])
- end
- -- just use defaults since some XP systems may not even have wxMODERN
- sudokuGUI.possibleFont_wxfont_ = wx.wxFont(wx.wxNORMAL_FONT)
- sudokuGUI.valueFont_wxfont_ = wx.wxFont(wx.wxNORMAL_FONT)
- sudokuGUI.valueFont_wxfont_:SetWeight(wx.wxFONTWEIGHT_BOLD)
- if not sudokuGUI.valueFont_wxfont_:Ok() then
- sudokuGUI.valueFont_wxfont_:Destroy()
- sudokuGUI.valueFont_wxfont_ = wx.wxFont(wx.wxNORMAL_FONT)
- end
- sudokuGUI.possibleFont.wxfont = wx.wxFont(sudokuGUI.possibleFont_wxfont_)
- sudokuGUI.valueFont.wxfont = wx.wxFont(sudokuGUI.valueFont_wxfont_)
- end
- -- ----------------------------------------------------------------------------
- -- Create a table of the menu IDs to use as a "case" type statement
- sudokuGUI.MenuId = {}
- -- ----------------------------------------------------------------------------
- sudokuGUI.MenuId[sudokuGUI.ID_NEW] = function() sudokuGUI.NewPuzzle(true) end
- sudokuGUI.MenuId[sudokuGUI.ID_CREATE] = function(event) sudokuGUI.CreatePuzzle(event:IsChecked()) end
- sudokuGUI.MenuId[sudokuGUI.ID_GENERATE] = function() sudokuGUI.GeneratePuzzle() end
- sudokuGUI.MenuId[sudokuGUI.ID_OPEN] = function() sudokuGUI.OpenPuzzle() end
- sudokuGUI.MenuId[sudokuGUI.ID_SAVEAS] = function() sudokuGUI.SaveAsPuzzle() end
- sudokuGUI.MenuId[sudokuGUI.ID_PAGESETUP] = function() sudokuGUI.PageSetup() end
- sudokuGUI.MenuId[sudokuGUI.ID_PRINTSETUP] = function() sudokuGUI.PrintSetup() end
- sudokuGUI.MenuId[sudokuGUI.ID_PRINTPREVIEW] = function() sudokuGUI.PrintPreview() end
- sudokuGUI.MenuId[sudokuGUI.ID_PRINT] = function() sudokuGUI.Print() end
- sudokuGUI.MenuId[sudokuGUI.ID_EXIT] = function() sudokuGUI.frame:Close() end
- -- ----------------------------------------------------------------------------
- sudokuGUI.MenuId[sudokuGUI.ID_COPY_PUZZLE] =
- function (event)
- local str = sudoku.ToString(sudokuGUI.GetCurrentTable())
- if wx.wxClipboard.Get():Open() then
- wx.wxClipboard.Get():SetData(wx.wxTextDataObject(str))
- wx.wxClipboard.Get():Close()
- end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_RESET] = function() sudokuGUI.ResetPuzzle() end
- sudokuGUI.MenuId[sudokuGUI.ID_UNDO] = function() sudokuGUI.Undo() end
- sudokuGUI.MenuId[sudokuGUI.ID_REDO] = function() sudokuGUI.Redo() end
- sudokuGUI.MenuId[sudokuGUI.ID_PREFERENCES] = function() sudokuGUI.PreferencesDialog() end
- sudokuGUI.MenuId[sudokuGUI.ID_SAVE_PREFERENCES] = function() sudokuGUI.ConfigSave(true) end
- -- ----------------------------------------------------------------------------
- -- Makes sure that menu and tool items are in sync and updates the table
- function sudokuGUI.MenuCheckUpdate(event)
- sudokuGUI.CheckMenuItem(event:GetId(), event:IsChecked())
- sudokuGUI.UpdateTable()
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_ERRORS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_MISTAKES] =
- function (event)
- -- need to solve it ourselves first
- if (not sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE)) and
- (event:IsChecked()) and (not sudokuGUI.sudokuSolnTable) then
- sudokuGUI.sudokuSolnTable = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
- if not sudokuGUI.sudokuSolnTable then
- event:SetInt(0) -- uncheck for MenuCheckUpdate function
- sudokuGUI.frame:GetMenuBar():Check(sudokuGUI.ID_SHOW_MISTAKES, false)
- end
- end
- sudokuGUI.MenuCheckUpdate(event)
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_TOOLBAR] =
- function(event)
- sudokuGUI.frame:GetToolBar():Show(event:IsChecked())
- -- hack to make the wxFrame layout the child panel
- local w, h = sudokuGUI.frame:GetSizeWH()
- sudokuGUI.frame:SetSize(wx.wxSize(w, h+1))
- sudokuGUI.frame:SetSize(wx.wxSize(w, h))
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_TOOLBAR, event:IsChecked())
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_TOOLBAR_LABELS] =
- function(event)
- local style = wx.wxNO_BORDER
- if event:IsChecked() then
- style = style + wx.wxTB_TEXT
- end
- sudokuGUI.frame:GetToolBar():SetWindowStyle(style)
- sudokuGUI.frame:GetToolBar():Realize()
- -- hack to make the wxFrame layout the child panel
- local w, h = sudokuGUI.frame:GetSizeWH()
- sudokuGUI.frame:SetSize(wx.wxSize(w, h+1))
- sudokuGUI.frame:SetSize(wx.wxSize(w, h))
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_TOOLBAR_LABELS, event:IsChecked())
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_STATUSBAR] =
- function(event)
- sudokuGUI.frame:GetStatusBar():Show(event:IsChecked())
- -- hack to make the wxFrame layout the child panel
- local w, h = sudokuGUI.frame:GetSizeWH()
- sudokuGUI.frame:SetSize(wx.wxSize(w, h+1))
- sudokuGUI.frame:SetSize(wx.wxSize(w, h))
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_STATUSBAR, event:IsChecked())
- end
- -- ----------------------------------------------------------------------------
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_POSSIBLE] =
- function (event)
- if event:IsChecked() then
- -- make this act like a radio item that can be unchecked
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE, false)
- end
- sudokuGUI.MenuCheckUpdate(event)
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_USER_POSSIBLE] =
- function (event)
- if event:IsChecked() then
- -- make this act like a radio item that can be unchecked
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_POSSIBLE, false)
- end
- sudokuGUI.MenuCheckUpdate(event)
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_POSSIBLE_LINE] =
- function (event)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE, event:IsChecked())
- local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
- sudokuGUI.GetCellBestSize(width-1, height-1)
- sudokuGUI.Refresh()
- end
- sudokuGUI.MenuId[sudokuGUI.ID_USER_POSSIBLE_CLEAR] =
- function (event)
- local ret = wx.wxMessageBox("Clear all of your pencil marks?",
- "wxLuaSudoku - clear pencil marks?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxOK then
- sudokuGUI.pencilMarks = {}
- for cell = 1, 81 do
- sudokuGUI.pencilMarks[cell] = {}
- end
- sudokuGUI.UpdateTable()
- end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_USER_POSSIBLE_SETALL] =
- function (event)
- local ret = wx.wxMessageBox("Set all values as possible in the pencil marks?",
- "wxLuaSudoku - set all pencil marks?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxOK then
- sudokuGUI.pencilMarks = {}
- for cell = 1, 81 do
- sudokuGUI.pencilMarks[cell] = {}
- for v = 1, 9 do
- sudokuGUI.pencilMarks[cell][v] = v
- end
- end
- sudokuGUI.UpdateTable()
- end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_USER_POSSIBLE_INIT] =
- function (event)
- local ret = wx.wxMessageBox("Initialize the pencil marks to the calculated possible values?",
- "wxLuaSudoku - initialize pencil marks?",
- wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- if ret == wx.wxOK then
- local s = sudokuGUI.GetCurrentTable()
- for cell = 1, 81 do
- sudokuGUI.pencilMarks[cell] = {}
- for v = 1, 9 do
- sudokuGUI.pencilMarks[cell][v] = s.possible[cell][v]
- end
- end
- sudokuGUI.UpdateTable()
- end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKED] =
- function (event)
- local checked = event:IsChecked()
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_NAKEDPAIRS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_NAKEDTRIPLETS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_NAKEDQUADS, checked)
- sudokuGUI.UpdateTable()
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDEN] =
- function (event)
- local checked = event:IsChecked()
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_HIDDENPAIRS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_HIDDENTRIPLETS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_HIDDENQUADS, checked)
- sudokuGUI.UpdateTable()
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKEDPAIRS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDENPAIRS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKEDTRIPLETS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDENTRIPLETS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKEDQUADS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDENQUADS] = sudokuGUI.MenuCheckUpdate
- -- ----------------------------------------------------------------------------
- sudokuGUI.MenuId[sudokuGUI.ID_VERIFY_PUZZLE] =
- function (event)
- local s = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
- if s then
- sudokuGUI.sudokuSolnTable = s
- end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SHOW_SOLUTION] =
- function (event)
- if not sudokuGUI.sudokuSolnTable then
- local s = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
- if s then
- sudokuGUI.sudokuSolnTable = s
- end
- end
- if sudokuGUI.sudokuSolnTable then
- sudokuGUI.AddTable(sudokuGUI.sudokuSolnTable)
- end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKED] =
- function (event)
- local checked = event:IsChecked()
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_NAKEDPAIRS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_NAKEDQUADS, checked)
- sudokuGUI.UpdateTable()
- end
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDEN] =
- function (event)
- local checked = event:IsChecked()
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_HIDDENPAIRS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS, checked)
- sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_HIDDENQUADS, checked)
- sudokuGUI.UpdateTable()
- end
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKEDPAIRS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDENPAIRS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKEDQUADS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDENQUADS] = sudokuGUI.MenuCheckUpdate
- sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANSINGLES] =
- function (event)
- local s = TableCopy(sudokuGUI.GetCurrentTable())
- local changed_cells = sudoku.SolveScanSingles(s)
- if changed_cells then sudokuGUI.AddTable(s) end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANROWS] =
- function (event)
- local s = TableCopy(sudokuGUI.GetCurrentTable())
- local changed_cells = sudoku.SolveScanRows(s)
- if changed_cells then sudokuGUI.AddTable(s) end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANCOLS] =
- function (event)
- local s = TableCopy(sudokuGUI.GetCurrentTable())
- local changed_cells = sudoku.SolveScanCols(s)
- if changed_cells then sudokuGUI.AddTable(s) end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANBLOCKS] =
- function (event)
- local s = TableCopy(sudokuGUI.GetCurrentTable())
- local changed_cells = sudoku.SolveScanBlocks(s)
- if changed_cells then sudokuGUI.AddTable(s) end
- end
- sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANNING] = function (event) sudokuGUI.SolveScanning() end
- sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_BRUTEFORCE] = function (event) sudokuGUI.SolveBruteForce() end
- -- ----------------------------------------------------------------------------
- sudokuGUI.MenuId[sudokuGUI.ID_ABOUT] =
- function (event)
- wx.wxMessageBox("Welcome to wxLuaSudoku!\nWritten by John Labenski\nCopyright 2006.\n"..
- wxlua.wxLUA_VERSION_STRING.." built with "..wx.wxVERSION_STRING,
- "About wxLuaSudoku",
- wx.wxOK + wx.wxICON_INFORMATION,
- sudokuGUI.frame )
- end
- sudokuGUI.MenuId[sudokuGUI.ID_HELP] =
- function (event)
- local helpFrame = wx.wxFrame(sudokuGUI.frame, wx.wxID_ANY, "Help on wxLuaSudoku", wx.wxDefaultPosition, wx.wxSize(600,400))
- local htmlWin = wx.wxHtmlWindow(helpFrame)
- if (htmlWin:SetPage(sudokuGUIhelp)) then
- helpFrame:Centre()
- helpFrame:Show(true)
- else
- helpFrame:Destroy()
- end
- end
- -- ----------------------------------------------------------------------------
- function sudokuGUI.OnMenuEvent(event)
- local id = event:GetId()
- if sudokuGUI.MenuId[id] then
- sudokuGUI.MenuId[id](event)
- return
- end
- end
- -- ----------------------------------------------------------------------------
- -- Unify all checking and unchecking of the menu items and
- -- make sure menu/toolbar are in sync
- function sudokuGUI.CheckMenuItem(id, check)
- sudokuGUI.frame:GetMenuBar():Check(id, check)
- sudokuGUI.frame:GetToolBar():ToggleTool(id, check) -- doesn't care if id doesn't exist
- sudokuGUI.menuCheckIDs[id] = check
- end
- function sudokuGUI.IsCheckedMenuItem(id)
- if sudokuGUI.menuCheckIDs[id] == nil then
- sudokuGUI.menuCheckIDs[id] = sudokuGUI.frame:GetMenuBar():IsChecked(id)
- end
- return sudokuGUI.menuCheckIDs[id]
- end
- -- ----------------------------------------------------------------------------
- function main()
- sudokuGUI.block_refresh = true
- -- initialize the fonts and colours to use (must always exist)
- sudokuGUI.InitFontsAndColours()
- -- initialize the printing defaults
- sudokuGUI.printData:SetPaperId(wx.wxPAPER_LETTER);
- sudokuGUI.pageSetupData:SetMarginTopLeft(wx.wxPoint(25, 25));
- sudokuGUI.pageSetupData:SetMarginBottomRight(wx.wxPoint(25, 25));
- -- Create the main frame for the program
- sudokuGUI.frame = wx.wxFrame(wx.NULL, wx.wxID_ANY, "wxLuaSudoku",
- wx.wxDefaultPosition, wx.wxSize(300,320))
- sudokuGUI.frame:SetSizeHints(300, 300);
- local bitmap = wx.wxBitmap(sudokuGUIxpmdata)
- local icon = wx.wxIcon()
- icon:CopyFromBitmap(bitmap)
- sudokuGUI.frame:SetIcon(icon)
- local function MItem(menu, id, text, help, bmp)
- local m = wx.wxMenuItem(menu, id, text, help)
- m:SetBitmap(bmp)
- bmp:delete()
- return m
- end
- local fileMenu = wx.wxMenu("", 0)
- fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_NEW, "&New...\tCtrl-N", "Clear the current puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_NEW, wx.wxART_TOOLBAR)))
- fileMenu:AppendCheckItem(sudokuGUI.ID_CREATE, "&Create...\tCtrl-T", "Enter the initial values for the puzzle")
- fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_GENERATE, "&Generate...\tCtrl-G", "Generate a new puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_EXECUTABLE_FILE, wx.wxART_TOOLBAR)))
- fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_OPEN, "&Open...\tCtrl-O", "Open a puzzle file", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR)))
- fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_SAVEAS, "&Save as...\tCtrl-S", "Save the current puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_SAVE_AS, wx.wxART_TOOLBAR)))
- fileMenu:AppendSeparator()
- fileMenu:Append(sudokuGUI.ID_PAGESETUP, "Page S&etup...", "Setup the printout page")
- --fileMenu:Append(sudokuGUI.ID_PRINTSETUP, "Print Se&tup...", "Setup the printer")
- fileMenu:Append(sudokuGUI.ID_PRINTPREVIEW, "Print Pre&view...", "Preview the printout")
- fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_PRINT, "&Print...", "Print the puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_PRINT, wx.wxART_TOOLBAR)))
- fileMenu:AppendSeparator()
- fileMenu:Append(sudokuGUI.ID_EXIT, "E&xit\tCtrl-X", "Quit the program")
- local editMenu = wx.wxMenu("", 0)
- editMenu:Append(sudokuGUI.ID_COPY_PUZZLE, "Copy puzzle", "Copy the puzzle to the clipboard")
- editMenu:AppendSeparator()
- editMenu:Append(sudokuGUI.ID_RESET, "Re&set...\tCtrl-R", "Reset the puzzle to the initial state")
- editMenu:AppendSeparator()
- editMenu:Append(MItem(editMenu, sudokuGUI.ID_UNDO, "&Undo\tCtrl-Z", "Undo the last entry", wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR)))
- editMenu:Append(MItem(editMenu, sudokuGUI.ID_REDO, "&Redo\tCtrl-Y", "Redo the last entry", wx.wxArtProvider.GetBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR)))
- editMenu:AppendSeparator()
- editMenu:Append(sudokuGUI.ID_PREFERENCES, "P&references...", "Show the preferences dialog")
- editMenu:AppendSeparator()
- editMenu:Append(sudokuGUI.ID_SAVE_PREFERENCES, "Sa&ve preferences...", "Save the preferences")
- local viewMenu = wx.wxMenu("", 0)
- viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_ERRORS, "Mark &errors\tCtrl-E", "Mark duplicate values in puzzle")
- viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_MISTAKES, "Mark &mistakes\tCtrl-M", "Mark wrong values in puzzle")
- viewMenu:AppendSeparator()
- viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_TOOLBAR, "Show toolbar", "Show the toolbar")
- viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_TOOLBAR_LABELS, "Show toolbar labels", "Show labels on the toolbar")
- viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_STATUSBAR, "Show statusbar", "Show the statusbar")
- viewMenu:Check(sudokuGUI.ID_SHOW_TOOLBAR, true)
- viewMenu:Check(sudokuGUI.ID_SHOW_TOOLBAR_LABELS, true)
- viewMenu:Check(sudokuGUI.ID_SHOW_STATUSBAR, true)
- local possibleMenu = wx.wxMenu("", 0)
- possibleMenu:AppendCheckItem( sudokuGUI.ID_SHOW_POSSIBLE, "Show calculated &possible\tCtrl-P", "Show calculated possible values for the cells")
- possibleMenu:AppendCheckItem( sudokuGUI.ID_SHOW_USER_POSSIBLE, "Show/Edit pencil marks\tCtrl-l", "Show and edit user set possible values for the cells")
- possibleMenu:AppendSeparator()
- possibleMenu:AppendCheckItem( sudokuGUI.ID_SHOW_POSSIBLE_LINE, "Show possible in a &line", "Show possible values for the cells in a line")
- possibleMenu:AppendSeparator()
- local userPossMenu = wx.wxMenu("", 0)
- userPossMenu:Append( sudokuGUI.ID_USER_POSSIBLE_CLEAR, "Clear all...", "Clear all pencil marks")
- userPossMenu:Append( sudokuGUI.ID_USER_POSSIBLE_SETALL, "Set all...", "Set all pencil marks")
- userPossMenu:Append( sudokuGUI.ID_USER_POSSIBLE_INIT, "Calculate...", "Initialize pencil marks to calculated possible")
- possibleMenu:Append(sudokuGUI.ID_USER_POSSIBLE_MENU, "Pencil marks", userPossMenu, "Setup user possible values")
- possibleMenu:AppendSeparator()
- local possViewMenu = wx.wxMenu("", 0)
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKED, "Mark &naked groups", "Mark all naked groups in possible values")
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDEN, "Mark &hidden groups", "Mark all hidden groups in possible values")
- possViewMenu:AppendSeparator()
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKEDPAIRS, "Mark naked pairs", "Mark naked pairs in possible values")
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDENPAIRS, "Mark hidden pairs", "Mark hidden pairs in possible values")
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKEDTRIPLETS, "Mark naked triplets", "Mark naked triplets in possible values")
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDENTRIPLETS, "Mark hidden triplets", "Mark hidden triplets in possible values")
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKEDQUADS, "Mark naked quads", "Mark naked quads in possible values")
- possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDENQUADS, "Mark hidden quads", "Mark hidden quads in possible values")
- possibleMenu:Append(sudokuGUI.ID_SHOW_MENU, "Mark &groups", possViewMenu, "Mark naked/hidden groups")
- local solveMenu = wx.wxMenu("", 0)
- solveMenu:Append(sudokuGUI.ID_VERIFY_PUZZLE, "Verify unique solution...", "Verify that the puzzle has only one solution")
- solveMenu:AppendSeparator()
- solveMenu:Append(sudokuGUI.ID_SHOW_SOLUTION, "Show solution", "Show the solution to the puzzle")
- solveMenu:AppendSeparator()
- local elimSolveMenu = wx.wxMenu("", 0)
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKED, "Eliminate &naked groups", "Eliminate all naked groups from possible values")
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDEN, "Eliminate &hidden groups", "Eliminate all hidden groups from possible values")
- elimSolveMenu:AppendSeparator()
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKEDPAIRS, "Eliminate naked pairs", "Eliminate naked pairs from possible values")
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDENPAIRS, "Eliminate hidden pairs", "Eliminate hidden pairs from possible values")
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS, "Eliminate naked triplets", "Eliminate naked triplets from possible values")
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS, "Eliminate hidden triplets", "Eliminate hidden triplets from possible values")
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKEDQUADS, "Eliminate naked quads", "Eliminate naked quads from possible values")
- elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDENQUADS, "Eliminate hidden quads", "Eliminate hidden quads from possible values")
- solveMenu:Append(sudokuGUI.ID_ELIMINATE_MENU, "&Eliminate groups", elimSolveMenu, "Remove possible values using naked and hidden groups")
- solveMenu:AppendSeparator()
- solveMenu:Append(sudokuGUI.ID_SOLVE_SCANSINGLES, "Solve (scan singles)\tCtrl-1", "Solve all cells with only one possibility")
- solveMenu:Append(sudokuGUI.ID_SOLVE_SCANROWS, "Solve (scan rows)\tCtrl-2", "Solve cells in rows with only one possible value")
- solveMenu:Append(sudokuGUI.ID_SOLVE_SCANCOLS, "Solve (scan cols)\tCtrl-3", "Solve cells in cols with only one possible value")
- solveMenu:Append(sudokuGUI.ID_SOLVE_SCANBLOCKS, "Solve (scan blocks)\tCtrl-4", "Solve cells in blocks with only one possible value")
- solveMenu:AppendSeparator()
- solveMenu:Append(sudokuGUI.ID_SOLVE_SCANNING, "Solve (&scanning)\tCtrl-L", "Solve the puzzle by only scanning")
- solveMenu:Append(sudokuGUI.ID_SOLVE_BRUTEFORCE, "Solve (&brute force)\tCtrl-B", "Solve the puzzle by guessing values")
- local helpMenu = wx.wxMenu("", 0)
- helpMenu:Append(sudokuGUI.ID_ABOUT, "&About...", "About the wxLuaSudoku Application")
- helpMenu:Append(MItem(helpMenu, sudokuGUI.ID_HELP, "&Help...", "Help using the wxLuaSudoku application", wx.wxArtProvider.GetBitmap(wx.wxART_HELP, wx.wxART_TOOLBAR)))
- local menuBar = wx.wxMenuBar()
- menuBar:Append(fileMenu, "&File")
- menuBar:Append(editMenu, "&Edit")
- menuBar:Append(viewMenu, "&View")
- menuBar:Append(possibleMenu, "&Possible")
- menuBar:Append(solveMenu, "&Solve")
- menuBar:Append(helpMenu, "&Help")
- sudokuGUI.frame:SetMenuBar(menuBar)
- local toolBar = sudokuGUI.frame:CreateToolBar(wx.wxNO_BORDER + wx.wxTB_TEXT)
- local tbSize = toolBar:GetToolBitmapSize() -- required to force help icon to right size in MSW
- toolBar:AddTool(sudokuGUI.ID_NEW, "New", wx.wxArtProvider.GetBitmap(wx.wxART_NEW, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "New...", "Clear the current puzzle")
- toolBar:AddCheckTool(sudokuGUI.ID_CREATE, "Create", wx.wxArtProvider.GetBitmap(wx.wxART_ADD_BOOKMARK, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, "Create...", "Enter initial values for the puzzle")
- toolBar:AddTool(sudokuGUI.ID_GENERATE, "Generate", wx.wxArtProvider.GetBitmap(wx.wxART_EXECUTABLE_FILE, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Generate...", "Generate a new puzzle")
- toolBar:AddTool(sudokuGUI.ID_OPEN, "Open", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Open...", "Open a puzzle file")
- toolBar:AddTool(sudokuGUI.ID_SAVEAS, "Save", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_SAVE_AS, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Save as...", "Save the current puzzle")
- toolBar:AddTool(sudokuGUI.ID_PRINT, "Print", wx.wxArtProvider.GetBitmap(wx.wxART_PRINT, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Print...", "Print the puzzle")
- toolBar:AddSeparator()
- toolBar:AddTool(sudokuGUI.ID_UNDO, "Undo", wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Undo", "Undo the last entry")
- toolBar:AddTool(sudokuGUI.ID_REDO, "Redo", wx.wxArtProvider.GetBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Redo", "Redo the last entry")
- toolBar:AddSeparator()
- toolBar:AddTool(sudokuGUI.ID_HELP, "Help", wx.wxArtProvider.GetBitmap(wx.wxART_HELP, wx.wxART_TOOLBAR, tbSize), wx.wxNullBitmap, wx.wxITEM_NORMAL, "Help...", "Help on wxLuaSudoku")
- toolBar:Realize()
- sudokuGUI.frame:CreateStatusBar(2)
- local stat_width = sudokuGUI.frame:GetStatusBar():GetTextExtent("Step : 00000")
- sudokuGUI.frame:SetStatusWidths({-1, stat_width})
- sudokuGUI.frame:SetStatusText("Welcome to wxLuaSudoku.", 0)
- -- ------------------------------------------------------------------------
- -- Use single centralized menu/toolbar event handler
- sudokuGUI.frame:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED,
- sudokuGUI.OnMenuEvent)
- -- ------------------------------------------------------------------------
- local values =
- {
- 5,0,0, 8,0,3, 0,6,0,
- 1,0,6, 0,9,2, 0,8,5,
- 0,0,8, 5,0,7, 0,4,0,
- 0,0,1, 0,3,4, 0,7,0,
- 0,9,0, 0,0,8, 1,3,4,
- 3,0,0, 0,2,0, 5,9,0,
- 0,0,5, 1,0,0, 0,0,3,
- 0,0,0, 0,0,9, 0,0,0,
- 0,0,7, 3,0,0, 4,0,9
- }
- local solution =
- {
- 5,4,9, 8,1,3, 2,6,7,
- 1,7,6, 4,9,2, 3,8,5,
- 2,3,8, 5,6,7, 9,4,1,
- 8,5,1, 9,3,4, 6,7,2,
- 7,9,2, 6,5,8, 1,3,4,
- 3,6,4, 7,2,1, 5,9,8,
- 9,8,5, 1,4,6, 7,2,3,
- 4,1,3, 2,7,9, 8,5,6,
- 6,2,7, 3,8,5, 4,1,9
- }
- local s = sudoku.CreateTable()
- sudoku.SetValues(s, values)
- sudokuGUI.sudokuTables_pos = 1
- sudokuGUI.sudokuTables[1] = s
- sudokuGUI.sudokuSolnTable = sudoku.CreateTable()
- sudoku.SetValues(sudokuGUI.sudokuSolnTable, solution)
- sudokuGUI.panel = wx.wxPanel(sudokuGUI.frame, wx.wxID_ANY)
- --sudokuGUI.panel:SetBackgroundColour(wx.wxColour(0,0,0))
- local gridsizer = wx.wxGridSizer(9, 9, 2, 2)
- for i = 1, 81 do
- local win = sudokuGUI.CreateCellWindow( sudokuGUI.panel, i, size )
- gridsizer:Add(win, 1, wx.wxALL+wx.wxGROW+ wx.wxALIGN_CENTER, 0)
- sudokuGUI.cellWindows[i] = win
- end
- local topsizer = wx.wxBoxSizer(wx.wxVERTICAL)
- topsizer:Add(gridsizer, 1, wx.wxALL+wx.wxGROW+wx.wxALIGN_CENTER, 0)
- sudokuGUI.panel:SetSizer( topsizer )
- --topsizer:Fit(sudokuGUI.frame)
- --topsizer:SetSizeHints( sudokuGUI.frame )
- -- ------------------------------------------------------------------------
- -- After being created - connect the size event to help MSW repaint the
- -- child windows
- sudokuGUI.cellWindows[1]:Connect(wx.wxEVT_SIZE,
- function (event)
- local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
- sudokuGUI.GetCellBestSize(width, height)
- sudokuGUI.Refresh()
- event:Skip(true)
- end )
- -- save the config when closing the frame
- sudokuGUI.frame:Connect(wx.wxEVT_CLOSE_WINDOW,
- function (event)
- event:Skip(true) -- allow it to really exit
- sudokuGUI.ConfigSave(false)
- end )
- local cell_width, cell_height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
- sudokuGUI.GetCellBestSize(cell_width, cell_height)
- --sudokuGUI.UpdateTable()
- sudokuGUI.frame:SetClientSize(300,300)
- sudokuGUI.block_refresh = false
- sudokuGUI.ConfigLoad()
- sudokuGUI.frame:Show(true)
- collectgarbage("collect") -- cleanup any locals
- end
- main()
- if false then
- function ProfileBegin()
- Profile_Counters = {}
- Profile_Names = {}
- local function hook ()
- local f = debug.getinfo(2, "f").func
- if Profile_Counters[f] == nil then -- first time `f' is called?
- Profile_Counters[f] = 1
- Profile_Names[f] = debug.getinfo(2, "Sn")
- --TableDump(Profile_Names[f])
- else -- only increment the counter
- Profile_Counters[f] = Profile_Counters[f] + 1
- end
- end
- debug.sethook(hook, "c") -- turn on the hook
- end
- function ProfileEnd()
- debug.sethook() -- turn off the hook
- function getname (func)
- local n = Profile_Names[func]
- if n.what == "C" then
- return n.name
- end
- local loc = string.format("[%s]:%s", n.short_src, n.linedefined)
- if n.namewhat ~= "" then
- return string.format("%s (%s)", loc, n.name)
- else
- return string.format("%s", loc)
- end
- end
- for func, count in pairs(Profile_Counters) do
- print(getname(func), count)
- end
- end
- s = sudoku.CreateTable()
- s.flags[sudoku.ELIMINATE_HIDDEN_PAIRS] = true
- s.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS] = true
- s.flags[sudoku.ELIMINATE_HIDDEN_QUADS] = true
- t = os.time()
- --ProfileBegin()
- for n = 1, 10 do
- a, b = sudoku.FindAllNakedHiddenGroups(s, true)
- --a, b, c = sudoku.FindPossibleCountRowColBlock(s)
- --a, b, c = sudoku.FindAllPossibleGroups(s)
- end
- --ProfileEnd()
- print(os.time()-t)
- end
- --[[
- for i = 1, 1 do
- local s = sudoku.GeneratePuzzle()
- s = sudoku.GeneratePuzzleDifficulty(s, 35, true)
- sudoku.UpdateTable(s)
- local n, h = sudoku.FindAllNakedHiddenGroups(s, true)
- --TableDump(n)
- --TableDump(h)
- local c = 0
- cnp = TableCount(n.pairs.cells)
- cnt = TableCount(n.triplets.cells)
- cnq = TableCount(n.quads.cells)
- chp = TableCount(h.pairs.cells)
- cht = TableCount(h.triplets.cells)
- chq = TableCount(h.quads.cells)
- print(i, string.format("n %03d %03d %03d h %03d %03d %03d", cnp, cnt, cnq, chp, cht, chq))
- a[string.format("n %03d %03d %03d h %03d %03d %03d", cnp, cnt, cnq, chp, cht, chq)] = TableCopy(s.values)
- if (cnp > 0) and (cnt > 0) and (cnq > 0) and (chp > 0) and (cht > 0) and (chq > 0) then
- break
- end
- end
- ]]
- -- Call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
- -- otherwise the wxLua program will exit immediately.
- -- Does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit since the
- -- MainLoop is already running or will be started by the C++ program.
- wx.wxGetApp():MainLoop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement