Advertisement
Guest User

WxLuaSudoku

a guest
Aug 11th, 2011
235
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 216.91 KB | None | 0 0
  1. -------------------------------------------------------------------------=---
  2. -- Name:      wxluasudoku.wx.lua
  3. -- Purpose:   wxLuaSudoku - a wxLua program to generate/solve/play Sudoku puzzles
  4. -- Author:    John Labenski
  5. -- Created:   2006
  6. -- Copyright: (c) 2006 John Labenski. All rights reserved.
  7. -- Licence:   wxWindows licence
  8. -------------------------------------------------------------------------=---
  9.  
  10. -- Load the wxLua module, does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit
  11. package.cpath = package.cpath..";./?.dll;./?.so;../lib/?.so;../lib/vc_dll/?.dll;../lib/bcc_dll/?.dll;../lib/mingw_dll/?.dll;"
  12. require("wx")
  13.  
  14. -- Coding notes:
  15. -- All non gui sudoku functions are in the "sudoku" table and all gui related
  16. -- functions are in the "sudokuGui" table.
  17.  
  18. -- sudoku.GetXXX()  retrieve precalculated or set values from the sudoku table
  19. -- sudoku.SetXXX()  set values to the sudoku table
  20. -- sudoku.FindXXX() search the table for "stuff", but do not modify it, returns result
  21. -- sudoku.CalcXXX() search the table for "stuff" and store the values to it for GetXXX functions
  22.  
  23. sudoku = sudoku or {} -- the table to hold the sudoku solver functions
  24.  
  25. -- ============================================================================
  26. -- A simple function to implement "cond ? A : B", eg "result = iff(cond, A, B)"
  27. --   note all terms must be able to be evaluated
  28. function iff(cond, A, B) if cond then return A else return B end end
  29.  
  30. -- make the number or bool into a bool or number
  31. function inttobool(n)
  32.     if (n == nil) or (n == 0) then return false end
  33.     return true
  34. end
  35. function booltoint(n)
  36.     if (n == nil) or (n == false) then return 0 end
  37.     return 1
  38. end
  39.  
  40. -- ============================================================================
  41. -- Simple functions to count the total number of elements in a table
  42. -- Notes about speed : using for loop and pairs() in TableCount is 2X faster
  43. --   than using while loop and next(table). However using next() is slightly
  44. --   faster than using for k,v in pairs(t) do return true end in TableIsEmpty.
  45.  
  46. function TableCount(atable)
  47.     local count = 0
  48.     for k, v in pairs(atable) do count = count + 1 end
  49.     return count
  50. end
  51. function TableIsEmpty(atable)
  52.     return next(atable) == nil
  53. end
  54.  
  55. -- Set a value in the table or subtable, first making sure that the subtables
  56. --   are created first. Modifies the input table.
  57. --   TableSetValue(3, atable, "How", "Are", 4, "You") ==> atable.How.Are[4].You = 3
  58. function TableSetValue(value, atable, ...)
  59.     if type(atable) ~= "table" then atable = {} end
  60.     local t = atable -- t moves up levels through atable
  61.     local args = {...}
  62.     for n = 1, #args-1 do
  63.         local a = args[n]
  64.         if not t[a] then t[a] = {} end
  65.         t = t[a]
  66.     end
  67.     t[args[#args]] = value
  68. end
  69. -- Remove a value in the table or subtable, first making sure that the subtables
  70. --   exist. Modifies the input table.
  71. --   TableRemoveValue(atable, false, "How", "Are", 4, "You") ==> atable.How.Are[4].You = nil
  72. function TableRemoveValue(atable, only_if_empty, ...)
  73.     if type(atable) ~= "table" then return end
  74.     local t = atable -- t moves up levels through atable
  75.     local args = {...}
  76.     for n = 1, #args-1 do
  77.         t = t[args[n]]
  78.         if not t then return end -- already gone
  79.     end
  80.     if (not only_if_empty) or ((type(t[args[#args]]) == "table") and TableIsEmpty(t[args[#args]])) then
  81.         t[args[#args]] = nil
  82.     end
  83. end
  84.  
  85. -- ============================================================================
  86. -- completely dump the contents of a table
  87. --   atable is the input table to dump the contents of
  88. --   prefix is a string prefix for debugging purposes
  89. --   tablelevel is tracker for recursive calls to TableDump (do not use initially)
  90. function TableDump(atable, prefix, tablelevel)
  91.     local function print_val(v)
  92.         local t = type(v)
  93.         if t == "number" then
  94.             return tostring(v)
  95.         elseif t == "string" then
  96.             return "\""..v.."\""
  97.         end
  98.         return "'"..tostring(v).."'"
  99.     end
  100.  
  101.     prefix = prefix or ""
  102.     if tablelevel == nil then
  103.         tablelevel = ""
  104.         print(prefix.."-Dumping Table "..tostring(atable))
  105.     end
  106.  
  107.     prefix = prefix.."  "
  108.     local n = 0
  109.  
  110.     for k, v in pairs(atable) do
  111.         n = n + 1
  112.  
  113.         print(string.format("%s %d: %s[%s] = %s", prefix, n, tablelevel, print_val(k), print_val(v)))
  114.  
  115.         if type(v) == "table" then
  116.             TableDump(v, prefix.."  ", tablelevel.."["..print_val(k).."]")
  117.         end
  118.     end
  119. end
  120.  
  121. -- ============================================================================
  122. -- Make a deep copy of a table, including all sub tables, fails on recursive tables.
  123. --   returns a new table
  124. function TableCopy(atable)
  125.     if not atable then return nil end
  126.     local newtable = {}
  127.     for k, v in pairs(atable) do
  128.         if type(v) == "table" then
  129.             newtable[k] = TableCopy(v)
  130.         else
  131.             newtable[k] = v
  132.         end
  133.     end
  134.     return newtable
  135. end
  136.  
  137. -- Merge the two tables together, adding or replacing values in original_table
  138. --  with those in new_table, returns a new table and doesn't modify inputs
  139. --  fails on recursive tables, returns the new table
  140. function TableMerge(new_table, original_table)
  141.     new_table       = new_table or {}
  142.     local out_table = TableCopy(original_table or {})
  143.  
  144.     for k, v in pairs(new_table) do
  145.         if type(v) == "table" then
  146.             if out_table[k] and (type(out_table[k]) == "table") then
  147.                 out_table[k] = TableMerge(v, out_table[k])
  148.             elseif out_table[k] then
  149.                 local ov = out_table[k]
  150.                 out_table[k] = TableCopy(v)
  151.                 table.insert(out_table[k], ov)
  152.             else
  153.                 out_table[k] = TableCopy(v)
  154.             end
  155.         elseif out_table[k] and (type(out_table[k]) == "table") then
  156.             table.insert(out_table[k], v)
  157.         else
  158.             out_table[k] = v
  159.         end
  160.     end
  161.  
  162.     return out_table
  163. end
  164.  
  165. -- ============================================================================
  166. -- Flags for the sudokuTable.flags[flag] = true/false/nil
  167.  
  168. sudoku.ELIMINATE_NAKED_PAIRS     = 1 -- for sudoku.CalcAllPossible(sudokuTable)
  169. sudoku.ELIMINATE_HIDDEN_PAIRS    = 2 --   set to true to have it run
  170. sudoku.ELIMINATE_NAKED_TRIPLETS  = 3 -- for sudoku.CalcAllPossible(sudokuTable)
  171. sudoku.ELIMINATE_HIDDEN_TRIPLETS = 4 --   set to true to have it run
  172. sudoku.ELIMINATE_NAKED_QUADS     = 5 -- for sudoku.CalcAllPossible(sudokuTable)
  173. sudoku.ELIMINATE_HIDDEN_QUADS    = 6 --   set to true to have it run
  174. sudoku.FILENAME                  = 7 -- store the fileName from Open/Save functions
  175.  
  176. sudoku.ELIMINATE_FLAG_MIN        = 1 -- for iterating the ELIMINATE flags
  177. sudoku.ELIMINATE_FLAG_MAX        = 6
  178.  
  179. -- given the upper left cell of block you can iterate through the block using
  180. --   for n = 1, 9 do cell = n + block_cell + sudoku.LinearBlockCellTable[n] ... end
  181. sudoku.LinearBlockCellTable = { -1, -1, -1, 5, 5, 5, 11, 11, 11 }
  182.  
  183. sudoku.CellToRowTable   = {}
  184. sudoku.CellToColTable   = {}
  185. sudoku.BlockToRowTable  = {}
  186. sudoku.BlockToColTable  = {}
  187. sudoku.CellToBlockTable = {}
  188. sudoku.BlockToCellTable = {}
  189.  
  190. for cell = 1, 81 do
  191.     local row = math.floor(cell/9.1)+1
  192.     local col = cell-(row-1)*9
  193.     sudoku.CellToRowTable[cell] = row
  194.     sudoku.CellToColTable[cell] = col
  195.  
  196.     local block_row = math.floor(row/3.5)+1
  197.     local block_col = math.floor(col/3.5)+1
  198.     sudoku.CellToBlockTable[cell] = (block_row-1)*3 + block_col
  199. end
  200.  
  201. for block = 1, 9 do
  202.     local row = math.floor(block/3.5)*3+1
  203.     local col = math.fmod(block-1,3)*3+1
  204.     sudoku.BlockToRowTable[block] = row
  205.     sudoku.BlockToColTable[block] = col
  206.  
  207.     sudoku.BlockToCellTable[block] = (row-1)*9 + col
  208. end
  209.  
  210. -- ============================================================================
  211. -- Create a sudoku table to be used with the rest of the sudoku functions
  212. function sudoku.CreateTable()
  213.     local sudokuTable =
  214.     {
  215.         values       = {}, -- array (1-81) of values[cell#] = value (0 means unset)
  216.         row_values   = {}, -- array (1-9) of values[row#][value] = { cell1, cell2... }
  217.         col_values   = {}, -- array (1-9) of values[col#][value] = { cell1, cell2... }
  218.         block_values = {}, -- array (1-9) of values[block#][value] = { cell1, cell2... }
  219.         possible     = {}, -- possible values per cell, possible[cell# 1-81] = { val1, val2... }
  220.         invalid      = {}, -- array (1-81) of known invalid[cell#] = true/nil
  221.         flags        = {}  -- extra flags for puzzle, eg. ELIMINATE_NAKED_PAIRS
  222.     }
  223.  
  224.     for i = 1, 81 do
  225.         sudokuTable.values[i] = 0    -- initialize to unknown
  226.         sudokuTable.possible[i] = {} -- initialize to empty
  227.     end
  228.     for i = 1, 9 do
  229.         sudokuTable.row_values[i]   = {}
  230.         sudokuTable.col_values[i]   = {}
  231.         sudokuTable.block_values[i] = {}
  232.     end
  233.  
  234.     sudoku.UpdateTable(sudokuTable)
  235.  
  236.     return sudokuTable
  237. end
  238.  
  239. -- Update all the values in the table using only the cell values, modifies input sudokuTable.
  240. function sudoku.UpdateTable(sudokuTable)
  241.     sudoku.CalcRowColBlockValues(sudokuTable)
  242.     sudoku.CalcInvalidCells(sudokuTable)
  243.     sudoku.CalcAllPossible(sudokuTable)
  244. end
  245.  
  246. -- Set the values table in the sudokuTable and update everything, modifies input sudokuTable.
  247. function sudoku.SetValues(sudokuTable, values)
  248.     sudokuTable.values = values
  249.     sudoku.UpdateTable(sudokuTable)
  250. end
  251.  
  252. -- Open a sudoku table from a file, the file should be formatted as 9x9 numbers
  253. --  with 9 numbers per row and 9 columns.
  254. --  returns a sudoku.CreateTable() with the values set and "" on success
  255. --    or nil, error_message on failure
  256. function sudoku.Open(fileName)
  257.     local values      = {}
  258.     local value_count = 0 -- number of cols in line
  259.     local row_count   = 0 -- number of rows read
  260.     local line_n      = 0 -- actual line number in file
  261.     for line in io.lines(fileName) do
  262.         line_n = line_n + 1
  263.         local col_count = 0
  264.         for k, v in string.gmatch(line, "%d") do
  265.             k = tonumber(k)
  266.             if (k >= 0) and (k <= 9) then
  267.                 table.insert(values, k)
  268.                 col_count = col_count + 1
  269.                 value_count = value_count + 1
  270.             else
  271.                 return nil, string.format("Error loading sudoku file : '%s' invalid number '%d' on line %d.", fileName, k, line_n)
  272.             end
  273.         end
  274.  
  275.         if col_count == 9 then
  276.             row_count = row_count + 1
  277.         elseif (col_count ~= 0) and (col_count ~= 9) then
  278.             return nil, string.format("Error loading sudoku file : '%s' on line %d.\nExpecting 9 columns, found %d.", fileName, line_n, col_count)
  279.         end
  280.     end
  281.  
  282.     if line_n == 0 then
  283.         return nil, string.format("Error opening sudoku file : '%s'.", fileName)
  284.     elseif row_count ~= 9 then
  285.         return nil, string.format("Error loading sudoku file : '%s', expected 9 rows, found %d.", fileName, row_count)
  286.     elseif value_count ~= 81 then
  287.         return nil, string.format("Error loading sudoku file : '%s', expected 81 numbers, found %d.", fileName, value_count)
  288.     end
  289.  
  290.     local s = sudoku.CreateTable()
  291.     s.flags[sudoku.FILENAME] = fileName
  292.     sudoku.SetValues(s, values)
  293.  
  294.     return s, ""
  295. end
  296.  
  297. -- Save a sudoku grid as a 9x9 comma separated table to a file, returns success
  298. function sudoku.Save(sudokuTable, fileName)
  299.     local f = io.open(fileName, "w+")
  300.     if not f then return false end
  301.  
  302.     local str = sudoku.ToString(sudokuTable)
  303.     f:write(str)
  304.     io.close(f)
  305.  
  306.     sudokuTable.flags[sudoku.FILENAME] = fileName
  307.  
  308.     return true
  309. end
  310.  
  311. -- ============================================================================
  312. -- Neatly print the sudoku grid
  313. function sudoku.PrintGrid(sudokuTable)
  314.     local str = string.rep("-", 13).."\n"
  315.     for r = 1, 9 do
  316.         str = str.."|"
  317.         for c = 1, 9 do
  318.             local v = " "
  319.             if sudoku.HasValue(sudokuTable, r, c) then
  320.                 v = tostring(sudoku.GetValue(sudokuTable, r, c))
  321.             end
  322.             str = str..v
  323.             if math.fmod(c, 3) == 0 then str = str.."|" end
  324.         end
  325.         str = str.."\n"
  326.         if math.fmod(r, 3) == 0 then str = str..string.rep("-", 13).."\n" end
  327.     end
  328.  
  329.     print(str)
  330. end
  331.  
  332. -- Write the grid itself to a string as 9x9 with a space/line separating blocks
  333. function sudoku.ToString(sudokuTable)
  334.     local str = ""
  335.     for r = 1, 9 do
  336.         for c = 1, 9 do
  337.             local v = "0"
  338.             if sudoku.HasValue(sudokuTable, r, c) then
  339.                 v = tostring(sudoku.GetValue(sudokuTable, r, c))
  340.             end
  341.             str = str..v..","
  342.             if math.fmod(c, 3) == 0 then str = str.." " end
  343.         end
  344.         str = str.."\n"
  345.         if (r < 9) and (math.fmod(r, 3) == 0) then str = str.."\n" end
  346.     end
  347.  
  348.     return str
  349. end
  350.  
  351. -- Neatly print the possible values for each cell (you must calculate it first)
  352. function sudoku.PrintPossible(sudokuTable)
  353.     local str = string.rep("-", 103).."\n"
  354.     for r = 1, 9 do
  355.         str = str.."|"
  356.         for c = 1, 9 do
  357.             local has_value = sudoku.HasValue(sudokuTable, r, c)
  358.             if has_value then str = str.."<" else str = str.."[" end
  359.             local p = sudoku.GetPossible(sudokuTable, r, c)
  360.             for i = 1, 9 do
  361.                 str = str..(p[i] or " ")
  362.             end
  363.             if has_value then str = str..">" else str = str.."]" end
  364.             if math.fmod(c, 3) == 0 then str = str.."|" end
  365.         end
  366.         str = str.."\n"
  367.         if math.fmod(r, 3) == 0 then str = str..string.rep("-", 103).."\n" end
  368.     end
  369.  
  370.     print(str)
  371. end
  372.  
  373. -- ============================================================================
  374. -- Convert a row, col cell index (1-9) into a linear position in the grid (1-81)
  375. function sudoku.RowColToCell(row, col)
  376.     return (row-1)*9 + col
  377. end
  378. -- Convert a linear cell index (1-81) into a row, col cell index (1-9)
  379. function sudoku.CellToRowCol(cell)
  380.     return sudoku.CellToRowTable[cell], sudoku.CellToColTable[cell]
  381. end
  382. function sudoku.CellToRow(cell)
  383.     return sudoku.CellToRowTable[cell]
  384. end
  385. function sudoku.CellToCol(cell)
  386.     return sudoku.CellToColTable[cell]
  387. end
  388.  
  389. -- ============================================================================
  390. -- Check the validity of rows, cols, cells, blocks, values
  391. function sudoku.IsValidValueN(value)
  392.     return (value >= 1) and (value <= 9)
  393. end
  394.  
  395. -- ============================================================================
  396. -- Convert a row, col cell index (1-9) into the linear block number (1-9)
  397. function sudoku.RowColToBlock(row, col)
  398.     return sudoku.CellToBlockTable[sudoku.RowColToCell(row, col)]
  399. end
  400. -- Get the block (1-9) that this cell (1-81) is in
  401. function sudoku.CellToBlock(cell)
  402.     return sudoku.CellToBlockTable[cell]
  403. end
  404. -- Get the upper left cell of this block
  405. function sudoku.BlockToCell(block)
  406.     return sudoku.BlockToCellTable[block]
  407. end
  408. -- Convert a linear block index (1-9) into upper left row, col cell index (1-9)
  409. function sudoku.BlockToRowCol(block)
  410.     return sudoku.BlockToRowTable[block], sudoku.BlockToColTable[block]
  411. end
  412. function sudoku.BlockToRow(block)
  413.     return sudoku.BlockToRowTable[block]
  414. end
  415. function sudoku.BlockToCol(block)
  416.     return sudoku.BlockToColTable[block]
  417. end
  418. -- Get the upper left row, col cell of the block given by row, col
  419. function sudoku.RowColToBlockRowCol(row, col)
  420.     local block = sudoku.RowColToBlock(row, col)
  421.     return sudoku.BlockToRowTable[block], sudoku.BlockToColTable[block]
  422. end
  423.  
  424.  
  425. -- Generate a table of {[cell] = {hash table of cells that are in the row, col, block of this cell}}
  426. sudoku.cellToRowColBlockCellsTable = {}
  427. for cell = 1, 81 do
  428.     local row, col = sudoku.CellToRowCol(cell)
  429.     local block_cell = sudoku.BlockToCell(sudoku.CellToBlock(cell))
  430.  
  431.     sudoku.cellToRowColBlockCellsTable[cell] = {}
  432.  
  433.     for rcb = 1, 9 do
  434.         local c = sudoku.RowColToCell(rcb, col)
  435.         sudoku.cellToRowColBlockCellsTable[cell][c] = true
  436.         c = sudoku.RowColToCell(row, rcb)
  437.         sudoku.cellToRowColBlockCellsTable[cell][c] = true
  438.         c = rcb + block_cell + sudoku.LinearBlockCellTable[rcb]
  439.         sudoku.cellToRowColBlockCellsTable[cell][c] = true
  440.     end
  441. end
  442.  
  443. -- Generate a table of {[cell] = {array of cells that are in the row, col, block of this cell}}
  444. sudoku.cellToRowColBlockCellsArray = {}
  445. for cell = 1, 81 do
  446.     sudoku.cellToRowColBlockCellsArray[cell] = {}
  447.     for k, v in pairs(sudoku.cellToRowColBlockCellsTable[cell]) do
  448.         table.insert(sudoku.cellToRowColBlockCellsArray[cell], k)
  449.     end
  450. end
  451.  
  452. sudoku.RowCellTable = {}
  453. sudoku.ColCellTable = {}
  454. sudoku.BlockCellTable = {}
  455. sudoku.BlockCellShiftTable = {0, 3, 6, 27, 30, 33, 54, 57, 60}
  456.  
  457. for n = 1, 9 do
  458.     local nn = (n-1)*9
  459.     sudoku.RowCellTable[n] = {1+nn, 2+nn, 3+nn, 4+nn, 5+nn, 6+nn, 7+nn, 8+nn, 9+nn}
  460.  
  461.     nn = n - 1
  462.     sudoku.ColCellTable[n] = {1+nn, 10+nn, 19+nn, 28+nn, 37+nn, 46+nn, 55+nn, 64+nn, 73+nn}
  463.  
  464.     nn = sudoku.BlockCellShiftTable[n]
  465.     sudoku.BlockCellTable[n] = {1+nn, 2+nn, 3+nn, 10+nn, 11+nn, 12+nn, 19+nn, 20+nn, 21+nn}
  466. end
  467.  
  468. -- ============================================================================
  469. -- Get the cell value at a specific row, col
  470. function sudoku.GetValue(sudokuTable, row, col)
  471.     return sudoku.GetCellValue(sudokuTable, sudoku.RowColToCell(row, col))
  472. end
  473. -- Set the cell value at a specific row, col, modifies input sudokuTable.
  474. function sudoku.SetValue(sudokuTable, row, col, value)
  475.     local cell      = sudoku.RowColToCell(row, col)
  476.     local block     = sudoku.CellToBlock(cell)
  477.     local old_value = sudokuTable.values[cell]
  478.  
  479.     if not sudoku.IsValidValueN(value) then value = 0 end
  480.     sudokuTable.values[cell] = value
  481.  
  482.     --remove the old_value from the row, col, block values
  483.     if sudoku.IsValidValueN(old_value) then
  484.         if sudokuTable.row_values[row] and sudokuTable.row_values[row][old_value] then
  485.             sudokuTable.row_values[row][old_value][cell] = nil
  486.             if TableIsEmpty(sudokuTable.row_values[row][old_value]) then sudokuTable.row_values[row][old_value] = nil end
  487.         end
  488.         if sudokuTable.col_values[col] and sudokuTable.col_values[col][old_value] then
  489.             sudokuTable.col_values[col][old_value][cell] = nil
  490.             if TableIsEmpty(sudokuTable.col_values[col][old_value]) then sudokuTable.col_values[col][old_value] = nil end
  491.         end
  492.         if sudokuTable.block_values[block] and sudokuTable.block_values[block][old_value] then
  493.             sudokuTable.block_values[block][old_value][cell] = nil
  494.             if TableIsEmpty(sudokuTable.block_values[block][old_value]) then sudokuTable.block_values[block][old_value] = nil end
  495.         end
  496.     end
  497.     --add new value to the row, col, block values
  498.     if value ~= 0 then
  499.         if not sudokuTable.row_values[row] then
  500.             sudokuTable.row_values[row] = {[value] = {[cell] = cell}}
  501.         elseif not sudokuTable.row_values[row][value] then
  502.             sudokuTable.row_values[row][value] = {[cell] = cell}
  503.         else
  504.             sudokuTable.row_values[row][value][cell] = cell
  505.         end
  506.  
  507.         if not sudokuTable.col_values[col] then
  508.             sudokuTable.col_values[col] = {[value] = {[cell] = cell}}
  509.         elseif not sudokuTable.col_values[col][value] then
  510.             sudokuTable.col_values[col][value] = {[cell] = cell}
  511.         else
  512.             sudokuTable.col_values[col][value][cell] = cell
  513.         end
  514.  
  515.         if not sudokuTable.block_values[block] then
  516.             sudokuTable.block_values[block] = {[value] = {[cell] = cell}}
  517.         elseif not sudokuTable.block_values[block][value] then
  518.             sudokuTable.block_values[block][value] = {[cell] = cell}
  519.         else
  520.             sudokuTable.block_values[block][value][cell] = cell
  521.         end
  522.     end
  523. end
  524. -- Does the cell have a value at a specific row, col
  525. function sudoku.HasValue(sudokuTable, row, col)
  526.     return sudoku.HasCellValue(sudokuTable, sudoku.RowColToCell(row, col))
  527. end
  528.  
  529. -- Set the cell value at a specific cell
  530. function sudoku.GetCellValue(sudokuTable, cell)
  531.     return sudokuTable.values[cell]
  532. end
  533. -- Set the cell value at a specific cell, modifies input sudokuTable.
  534. function sudoku.SetCellValue(sudokuTable, cell, value)
  535.     local row, col = sudoku.CellToRowCol(cell)
  536.     sudoku.SetValue(sudokuTable, row, col, value)
  537. end
  538. -- Does the cell have a value at a specific cell
  539. function sudoku.HasCellValue(sudokuTable, cell)
  540.     return sudoku.IsValidValueN(sudokuTable.values[cell])
  541. end
  542.  
  543. -- ============================================================================
  544. -- Set the row_values, col_values, block_values tables of the input sudokuTable
  545. -- eg. row values table is row_values[row#][value][cell#] = cell#
  546. --     if no value then row_values[row#][value] = nil
  547. function sudoku.CalcRowColBlockValues(sudokuTable)
  548.     sudokuTable.row_values   = {}
  549.     sudokuTable.col_values   = {}
  550.     sudokuTable.block_values = {}
  551.  
  552.     for cell = 1, 81 do
  553.         local row, col = sudoku.CellToRowCol(cell)
  554.         local block    = sudoku.CellToBlock(cell)
  555.  
  556.         if not sudokuTable.row_values[row] then sudokuTable.row_values[row] = {} end
  557.         if not sudokuTable.col_values[col] then sudokuTable.col_values[col] = {} end
  558.         if not sudokuTable.block_values[block] then sudokuTable.block_values[block] = {} end
  559.  
  560.         local value = sudoku.GetCellValue(sudokuTable, cell)
  561.  
  562.         if sudoku.IsValidValueN(value) then
  563.             if not sudokuTable.row_values[row][value] then
  564.                 sudokuTable.row_values[row][value] = {[cell] = cell}
  565.             else
  566.                 sudokuTable.row_values[row][value][cell] = cell
  567.             end
  568.             if not sudokuTable.col_values[col][value] then
  569.                 sudokuTable.col_values[col][value] = {[cell] = cell}
  570.             else
  571.                 sudokuTable.col_values[col][value][cell] = cell
  572.             end
  573.             if not sudokuTable.block_values[block][value] then
  574.                 sudokuTable.block_values[block][value] = {[cell] = cell}
  575.             else
  576.                 sudokuTable.block_values[block][value][cell] =  cell
  577.             end
  578.         end
  579.     end
  580. end
  581.  
  582. -- ============================================================================
  583. -- Can this value be put into this cell given the other existing values?
  584. function sudoku.IsValidValue(sudokuTable, row, col, value)
  585.     if sudokuTable.row_values[row][value] or
  586.        sudokuTable.col_values[col][value] or
  587.        sudokuTable.block_values[sudoku.RowColToBlock(row, col)][value] then
  588.         return false
  589.     end
  590.  
  591.     return true
  592. end
  593.  
  594. -- Find all the invalid cells by looking for duplicates, modifies input sudokuTable.
  595. --   fills sudokuTable.invalid table with the values
  596. function sudoku.CalcInvalidCells(sudokuTable)
  597.     sudokuTable.invalid = {} -- reset to all good
  598.  
  599.     for n = 1, 9 do
  600.         for i, cell_table in pairs(sudokuTable.row_values[n]) do
  601.             if TableCount(cell_table) > 1 then
  602.                 for j, cell in pairs(cell_table) do
  603.                     sudokuTable.invalid[cell] = true
  604.                 end
  605.             end
  606.         end
  607.         for i, cell_table in pairs(sudokuTable.col_values[n]) do
  608.             if TableCount(cell_table) > 1 then
  609.                 for j, cell in pairs(cell_table) do
  610.                     sudokuTable.invalid[cell] = true
  611.                 end
  612.             end
  613.         end
  614.         for i, cell_table in pairs(sudokuTable.block_values[n]) do
  615.             if TableCount(cell_table) > 1 then
  616.                 for j, cell in pairs(cell_table) do
  617.                     sudokuTable.invalid[cell] = true
  618.                 end
  619.             end
  620.         end
  621.     end
  622. end
  623.  
  624. -- ============================================================================
  625. -- Get the possible values at a specific row, col cell
  626. --  Must be previously set from sudoku.CalcAllPossible
  627. function sudoku.GetPossible(sudokuTable, row, col)
  628.     return sudokuTable.possible[sudoku.RowColToCell(row, col)]
  629. end
  630. function sudoku.GetCellPossible(sudokuTable, cell)
  631.     return sudokuTable.possible[cell]
  632. end
  633.  
  634. -- Set the possible values at a specific row, col cell. Modifies input sudokuTable.
  635. function sudoku.SetPossible(sudokuTable, row, col, possibleTable)
  636.     sudokuTable.possible[sudoku.RowColToCell(row, col)] = possibleTable
  637. end
  638. function sudoku.SetCellPossible(sudokuTable, cell, possibleTable)
  639.     sudokuTable.possible[cell] = possibleTable
  640. end
  641.  
  642. -- Remove a possible value at a specific row, col cell only. Modifies input sudokuTable.
  643. function sudoku.RemovePossible(sudokuTable, row, col, value)
  644.     return sudoku.RemoveCellPossible(sudokuTable, sudoku.RowColToCell(row, col), value)
  645. end
  646. function sudoku.RemoveCellPossible(sudokuTable, cell, value)
  647.     sudokuTable.possible[cell][value] = nil
  648. end
  649.  
  650. -- Remove a possible values from the row, col, block. Modifies input sudokuTable.
  651. --   if exceptTable then don't remove it from exceptTable[cell#] = true
  652. function sudoku.RemovePossibleAll(sudokuTable, cell, value, exceptTable, break_if_empty)
  653.     exceptTable = exceptTable or {}
  654.     break_if_empty = break_if_empty or false
  655.  
  656.     for i, c in ipairs(sudoku.cellToRowColBlockCellsArray[cell]) do
  657.         if (not exceptTable[c]) and sudokuTable.possible[c][value] then
  658.             sudokuTable.possible[c][value] = nil
  659.             if break_if_empty and (not sudoku.HasCellValue(sudokuTable, c)) and TableIsEmpty(sudokuTable.possible[c]) then
  660.                 return
  661.             end
  662.         end
  663.     end
  664. end
  665. -- Remove a possible values from the row. Modifies input sudokuTable.
  666. --   if exceptTable then don't remove it from exceptTable[cell#] = true
  667. function sudoku.RemovePossibleRow(sudokuTable, row, value, exceptTable)
  668.     exceptTable = exceptTable or {}
  669.     for col = 1, 9 do
  670.         local cell = sudoku.RowColToCell(row, col)
  671.         if (not exceptTable[cell]) and sudokuTable.possible[cell][value] then
  672.             sudokuTable.possible[cell][value] = nil
  673.         end
  674.     end
  675. end
  676. -- Remove a possible values from the col. Modifies input sudokuTable.
  677. --   if exceptTable then don't remove it from exceptTable[cell#] = true
  678. function sudoku.RemovePossibleCol(sudokuTable, col, value, exceptTable)
  679.     exceptTable = exceptTable or {}
  680.     for row = 1, 9 do
  681.         local cell = sudoku.RowColToCell(row, col)
  682.         if (not exceptTable[cell]) and sudokuTable.possible[cell][value] then
  683.             sudokuTable.possible[cell][value] = nil
  684.         end
  685.     end
  686. end
  687. -- Remove a possible values from the block. Modifies input sudokuTable.
  688. --   if exceptTable then don't remove it from exceptTable[cell#] = true
  689. function sudoku.RemovePossibleBlock(sudokuTable, block, value, exceptTable)
  690.     exceptTable = exceptTable or {}
  691.     local block_cell = sudoku.BlockToCell(block)
  692.     for n = 1, 9 do
  693.         local cell = n + block_cell + sudoku.LinearBlockCellTable[n]
  694.         if (not exceptTable[cell]) and sudokuTable.possible[cell][value] then
  695.             sudokuTable.possible[cell][value] = nil
  696.         end
  697.     end
  698. end
  699.  
  700. -- Get the count of all possible values for rows, cols, and blocks
  701. --   returns 3 tables row_possible[row#][value] = #times possible value occurs in row
  702. --   and the same for col_possible, block_possible
  703. --   if no possible values (all values set) then row_possible[row#] = nil
  704. function sudoku.FindPossibleCountRowColBlock(sudokuTable)
  705.     local row_possible   = {}
  706.     local col_possible   = {}
  707.     local block_possible = {}
  708.  
  709.     for cell = 1, 81 do
  710.         local row, col = sudoku.CellToRowCol(cell)
  711.         local block    = sudoku.CellToBlock(cell)
  712.         local cell_possible = sudoku.GetCellPossible(sudokuTable, cell)
  713.  
  714.         for pvalue, is_possible in pairs(cell_possible) do
  715.             if not row_possible[row]     then row_possible[row] = {} end
  716.             if not col_possible[col]     then col_possible[col] = {} end
  717.             if not block_possible[block] then block_possible[block] = {} end
  718.  
  719.             row_possible[row][pvalue]     = (row_possible[row][pvalue] or 0) + 1
  720.             col_possible[col][pvalue]     = (col_possible[col][pvalue] or 0) + 1
  721.             block_possible[block][pvalue] = (block_possible[block][pvalue] or 0) + 1
  722.         end
  723.     end
  724.  
  725.     return row_possible, col_possible, block_possible
  726. end
  727.  
  728. -- Find all the possible values for row, col cell
  729. --  returns a table of possible[value] = true
  730. function sudoku.FindPossibleCell(sudokuTable, row, col)
  731.     local possible = {}
  732.  
  733.     -- gather up all the set values in row, col, and block
  734.     local rowValues   = sudokuTable.row_values[row]
  735.     local colValues   = sudokuTable.col_values[col]
  736.     local blockValues = sudokuTable.block_values[sudoku.RowColToBlock(row, col)]
  737.     -- remove the set values from the possible values
  738.     for v = 1, 9 do
  739.         if (rowValues[v] == nil) and (colValues[v] == nil) and (blockValues[v] == nil) then
  740.             possible[v] = v
  741.         end
  742.     end
  743.  
  744.     return possible
  745. end
  746.  
  747. -- Find all the possible values for the whole table by filling out the
  748. --  possible table in the input sudokuTable. Modifies input sudokuTable.
  749. function sudoku.CalcAllPossible(sudokuTable)
  750.     for cell = 1, 81 do
  751.         local row, col = sudoku.CellToRowCol(cell)
  752.         local possible = {}
  753.  
  754.         if not sudoku.HasCellValue(sudokuTable, cell) then
  755.             local block = sudoku.CellToBlock(cell)
  756.  
  757.             for v = 1, 9 do
  758.                 if (sudokuTable.row_values[row][v] == nil) and
  759.                    (sudokuTable.col_values[col][v] == nil) and
  760.                    (sudokuTable.block_values[block][v] == nil) then
  761.                     possible[v] = v
  762.                 end
  763.             end
  764.  
  765.         end
  766.  
  767.         sudoku.SetCellPossible(sudokuTable, cell, possible)
  768.     end
  769.  
  770.     -- this function checks flags to see if it should run
  771.     sudoku.RemovePossibleGroups(sudokuTable)
  772. end
  773.  
  774. -- Find all the possible pairs, triplets, quads in the table
  775. --  must run CalcAllPossible first, does not eliminate any.
  776. --  returns 3 tables, possible_pairs.rows[row#][key] = { cell1, cell2... },
  777. --                    possible_pairs.cols[col#][key] = { cell1, cell2... },
  778. --                    possible_pairs.blocks[block#][key] = { cell1, cell2... },
  779. --  and the same for possible_triplets, possible_quads
  780. --  key is constructed from the number group as string.char(val1, val2...)
  781. sudoku.FindAllPossibleGroups_Cache = {}
  782.  
  783. function sudoku.FindAllPossibleGroups(sudokuTable)
  784.     local possible_pairs    = {rows = {}, cols = {}, blocks = {}}
  785.     local possible_triplets = {rows = {}, cols = {}, blocks = {}}
  786.     local possible_quads    = {rows = {}, cols = {}, blocks = {}}
  787.     local char0 = string.byte("0")
  788.  
  789.     local cache_key_flags = 1*booltoint(sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_PAIRS]) +
  790.                             2*booltoint(sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS]) +
  791.                             4*booltoint(sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_QUADS])
  792.  
  793.     local cache_keys = { 10^1, 10^2, 10^3, 10^4, 10^5, 10^6, 10^7, 10^8, 10^9 }
  794.  
  795.     local function add_possible(atable, rcb_key, key, cell)
  796.         local a = atable[rcb_key]
  797.         if not a then
  798.             atable[rcb_key] = { [key] = {cell} }
  799.         elseif not a[key] then
  800.             a[key] = {cell}
  801.         else
  802.             a[key][#a[key]+1] = cell
  803.         end
  804.     end
  805.  
  806.     for cell = 1, 81 do
  807.         local row, col = sudoku.CellToRowCol(cell)
  808.         local block    = sudoku.CellToBlock(cell)
  809.  
  810.         local cell_possible = sudoku.GetCellPossible(sudokuTable, cell)
  811.         local cell_possible_table = {}
  812.         local cache_key = cache_key_flags
  813.  
  814.         -- convert key, value table to indexed table and a key for the cache
  815.         local count = 0
  816.         for n = 1, 9 do
  817.             if cell_possible[n] then
  818.                 cell_possible_table[#cell_possible_table+1] = char0+n
  819.                 cache_key = cache_key + cache_keys[n]
  820.                 count = count + 1
  821.             end
  822.         end
  823.  
  824.         local possible_pairs_keys    = {}
  825.         local possible_triplets_keys = {}
  826.         local possible_quads_keys    = {}
  827.  
  828.         -- either use the cached key table or create a new key table for the possible
  829.         -- Note: cache cuts time for 100 calls to this fn w/ empty puzzle from 8 to 1 sec
  830.  
  831.         if (count > 1) and sudoku.FindAllPossibleGroups_Cache[cache_key] then
  832.             possible_pairs_keys    = sudoku.FindAllPossibleGroups_Cache[cache_key].possible_pairs
  833.             possible_triplets_keys = sudoku.FindAllPossibleGroups_Cache[cache_key].possible_triplets
  834.             possible_quads_keys    = sudoku.FindAllPossibleGroups_Cache[cache_key].possible_quads
  835.         elseif (count > 1) then
  836.  
  837.             local elim_pairs    = (count == 2) or sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_PAIRS]
  838.             local elim_triplets = (count == 3) or sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS]
  839.             local elim_quads    = (count == 4) or sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_QUADS]
  840.  
  841.             for i = 1, count do
  842.                 for j = i+1, count do
  843.                     local pkey = string.char(cell_possible_table[i], cell_possible_table[j])
  844.                     if elim_pairs then
  845.                         possible_pairs_keys[#possible_pairs_keys+1] = pkey
  846.                     end
  847.  
  848.                     for k = j+1, count do
  849.                         local tkey = pkey..string.char(cell_possible_table[k])
  850.                         if elim_triplets then
  851.                             possible_triplets_keys[#possible_triplets_keys+1] = tkey
  852.                         end
  853.  
  854.                         if elim_quads then
  855.                             for l = k+1, count do
  856.                                 local qkey = tkey..string.char(cell_possible_table[l])
  857.                                 possible_quads_keys[#possible_quads_keys+1] = qkey
  858.                             end
  859.                         end
  860.                     end
  861.                 end
  862.             end
  863.  
  864.             sudoku.FindAllPossibleGroups_Cache[cache_key] = {}
  865.             sudoku.FindAllPossibleGroups_Cache[cache_key].possible_pairs    = possible_pairs_keys
  866.             sudoku.FindAllPossibleGroups_Cache[cache_key].possible_triplets = possible_triplets_keys
  867.             sudoku.FindAllPossibleGroups_Cache[cache_key].possible_quads    = possible_quads_keys
  868.         end
  869.  
  870.         for k, key in pairs(possible_pairs_keys) do
  871.             add_possible(possible_pairs.rows,   row,   key, cell)
  872.             add_possible(possible_pairs.cols,   col,   key, cell)
  873.             add_possible(possible_pairs.blocks, block, key, cell)
  874.         end
  875.         for k, key in pairs(possible_triplets_keys) do
  876.             add_possible(possible_triplets.rows,   row,   key, cell)
  877.             add_possible(possible_triplets.cols,   col,   key, cell)
  878.             add_possible(possible_triplets.blocks, block, key, cell)
  879.         end
  880.         for k, key in pairs(possible_quads_keys) do
  881.             add_possible(possible_quads.rows,   row,   key, cell)
  882.             add_possible(possible_quads.cols,   col,   key, cell)
  883.             add_possible(possible_quads.blocks, block, key, cell)
  884.         end
  885.     end
  886.  
  887.     return possible_pairs, possible_triplets, possible_quads
  888. end
  889.  
  890. -- Find all the naked and hidden pairs, triplets, quads in the table
  891. --  must run CalcAllPossible first, does not eliminate any.
  892. --  returns 2 tables, naked.rows[row#][key] = { cell1, cell2... },
  893. --                    naked.cols[col#][key] = { cell1, cell2... },
  894. --                    naked.blocks[block#][key] = { cell1, cell2... },
  895. --  and the same for hidden
  896. --  key is constructed from the number group as string.char(val1, val2...)
  897. function sudoku.FindAllNakedHiddenGroups(sudokuTable, find_all)
  898.     local flags = sudokuTable.flags
  899.  
  900.     if find_all == true then
  901.         sudokuTable.flags = TableCopy(flags) -- unref the table
  902.         -- turn all ELIMINATE_XXX on
  903.         for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
  904.             sudokuTable.flags[n] = true
  905.         end
  906.     end
  907.  
  908.     local row_possible,   col_possible,      block_possible = sudoku.FindPossibleCountRowColBlock(sudokuTable)
  909.     local possible_pairs, possible_triplets, possible_quads = sudoku.FindAllPossibleGroups(sudokuTable)
  910.     local char0 = string.byte("0")
  911.     local all_groups = { [2] = 36, [3] = 84, [4] = 126 } -- eg. 9!/(2! * (9-2)!)
  912.  
  913.     if find_all == true then
  914.         sudokuTable.flags = flags -- put the flags back to how they were
  915.     end
  916.  
  917.     local naked =
  918.     {
  919.         pairs    = {rows = {}, cols = {}, blocks = {}, cells = {}},
  920.         triplets = {rows = {}, cols = {}, blocks = {}, cells = {}},
  921.         quads    = {rows = {}, cols = {}, blocks = {}, cells = {}}
  922.     }
  923.     local hidden =
  924.     {
  925.         pairs    = {rows = {}, cols = {}, blocks = {}, cells = {}},
  926.         triplets = {rows = {}, cols = {}, blocks = {}, cells = {}},
  927.         quads    = {rows = {}, cols = {}, blocks = {}, cells = {}}
  928.     }
  929.  
  930.     -- cache all the cell possible value counts
  931.     local cell_possible_count = {}
  932.     for n = 1, 81 do
  933.         cell_possible_count[n] = TableCount(sudoku.GetCellPossible(sudokuTable, n) or {})
  934.     end
  935.  
  936.     local function dofind(rcb_table, num, key, cell_table_, rcb, rcb_possible)
  937.         local naked_cell_table  = {}
  938.         local naked_cell_count  = 0
  939.         local hidden_cell_table = {}
  940.         local hidden_cell_count = 0
  941.         local is_hidden = true
  942.  
  943.         -- can only be exactly as many nums in key as in rcb for hidden
  944.         for n = 1, num do
  945.             if rcb_possible[string.byte(key, n)-char0] ~= num then
  946.                 is_hidden = false
  947.                 break
  948.             end
  949.         end
  950.  
  951.         for n, cell in ipairs(cell_table_) do
  952.             if cell_possible_count[cell] == num then
  953.                 naked_cell_table[#naked_cell_table+1] = cell
  954.                 naked_cell_count = naked_cell_count + 1
  955.             end
  956.  
  957.             if is_hidden then
  958.                 hidden_cell_table[#hidden_cell_table+1] = cell
  959.                 hidden_cell_count = hidden_cell_count + 1
  960.             end
  961.         end
  962.  
  963.         -- has to be at least the same cell_count as num, if more then error, but...
  964.         if (naked_cell_count >= num) then
  965.             if not rcb_table.naked_table[rcb] then rcb_table.naked_table[rcb] = {} end
  966.             rcb_table.naked_table[rcb][key] = naked_cell_table
  967.  
  968.             local cell_table = rcb_table.naked_table_base.cells
  969.             for n, cell in pairs(naked_cell_table) do
  970.                 if not cell_table[cell] then cell_table[cell] = {} end
  971.                 table.insert(cell_table[cell], key)
  972.             end
  973.         end
  974.         -- has to be at least the same cell_count as num, if more then error, but...
  975.         if is_hidden and (hidden_cell_count >= num) then
  976.             if not rcb_table.hidden_table[rcb] then rcb_table.hidden_table[rcb] = {} end
  977.             rcb_table.hidden_table[rcb][key] = hidden_cell_table
  978.  
  979.             local cell_table = rcb_table.hidden_table_base.cells
  980.             for n, cell in pairs(hidden_cell_table) do
  981.                 if not cell_table[cell] then cell_table[cell] = {} end
  982.                 table.insert(cell_table[cell], key)
  983.             end
  984.         end
  985.     end
  986.  
  987.     local function find(naked_table, hidden_table, possible_table, num)
  988.         local rcb_table = {}
  989.         rcb_table.naked_table_base  = naked_table
  990.         rcb_table.hidden_table_base = hidden_table
  991.  
  992.         rcb_table.naked_table  = naked_table.rows
  993.         rcb_table.hidden_table = hidden_table.rows
  994.         for row, key_table in pairs(possible_table.rows) do
  995.             for key, cell_table in pairs(key_table) do
  996.                 dofind(rcb_table, num, key, cell_table, row, row_possible[row])
  997.             end
  998.         end
  999.  
  1000.         rcb_table.naked_table  = naked_table.cols
  1001.         rcb_table.hidden_table = hidden_table.cols
  1002.         for col, key_table in pairs(possible_table.cols) do
  1003.             for key, cell_table in pairs(key_table) do
  1004.                 dofind(rcb_table, num, key, cell_table, col, col_possible[col])
  1005.             end
  1006.         end
  1007.  
  1008.         rcb_table.naked_table  = naked_table.blocks
  1009.         rcb_table.hidden_table = hidden_table.blocks
  1010.         for block, key_table in pairs(possible_table.blocks) do
  1011.             for key, cell_table in pairs(key_table) do
  1012.                 dofind(rcb_table, num, key, cell_table, block, block_possible[block])
  1013.             end
  1014.         end
  1015.  
  1016.         return naked_table, hidden_table
  1017.     end
  1018.  
  1019.     naked.pairs,    hidden.pairs    = find(naked.pairs,    hidden.pairs,    possible_pairs,    2)
  1020.     naked.triplets, hidden.triplets = find(naked.triplets, hidden.triplets, possible_triplets, 3)
  1021.     naked.quads,    hidden.quads    = find(naked.quads,    hidden.quads,    possible_quads,    4)
  1022.  
  1023.     return naked, hidden
  1024. end
  1025.  
  1026. -- ============================================================================
  1027. -- Find all pairs, triplets, quads of values in rows and reset the possible
  1028. --   values for the row to exclude these values. Modifies input sudokuTable.
  1029. function sudoku.RemovePossibleGroups(sudokuTable)
  1030.     -- must have at least one flag set
  1031.     local has_elim_flags = false
  1032.     for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
  1033.         if sudokuTable.flags[n] == true then
  1034.             has_elim_flags = true
  1035.             break
  1036.         end
  1037.     end
  1038.     if has_elim_flags == false then
  1039.         return
  1040.     end
  1041.  
  1042.     local naked, hidden = sudoku.FindAllNakedHiddenGroups(sudokuTable, false)
  1043.     local char0 = string.byte("0")
  1044.  
  1045.     local function clear_possible(group_table, num, remove_fn)
  1046.         for n = 1, 9 do
  1047.             if group_table[n] then
  1048.                 for key, cell_table in pairs(group_table[n]) do
  1049.  
  1050.                     local exceptTable = {}
  1051.                     for k, v in pairs(cell_table) do
  1052.                         exceptTable[v] = v
  1053.                     end
  1054.  
  1055.                     for k = 1, num do
  1056.                         local val = string.byte(key, k)-char0
  1057.                         remove_fn(sudokuTable, n, val, exceptTable)
  1058.                     end
  1059.                 end
  1060.             end
  1061.         end
  1062.     end
  1063.  
  1064.     if (sudokuTable.flags[sudoku.ELIMINATE_NAKED_PAIRS] == true) then
  1065.         clear_possible(naked.pairs.rows,   2, sudoku.RemovePossibleRow)
  1066.         clear_possible(naked.pairs.cols,   2, sudoku.RemovePossibleCol)
  1067.         clear_possible(naked.pairs.blocks, 2, sudoku.RemovePossibleBlock)
  1068.     end
  1069.     if (sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_PAIRS] == true) then
  1070.         clear_possible(hidden.pairs.rows,   2, sudoku.RemovePossibleRow)
  1071.         clear_possible(hidden.pairs.cols,   2, sudoku.RemovePossibleCol)
  1072.         clear_possible(hidden.pairs.blocks, 2, sudoku.RemovePossibleBlock)
  1073.     end
  1074.  
  1075.     if (sudokuTable.flags[sudoku.ELIMINATE_NAKED_TRIPLETS] == true) then
  1076.         clear_possible(naked.triplets.rows,   3, sudoku.RemovePossibleRow)
  1077.         clear_possible(naked.triplets.cols,   3, sudoku.RemovePossibleCol)
  1078.         clear_possible(naked.triplets.blocks, 3, sudoku.RemovePossibleBlock)
  1079.     end
  1080.     if (sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS] == true) then
  1081.         clear_possible(hidden.triplets.rows,   3, sudoku.RemovePossibleRow)
  1082.         clear_possible(hidden.triplets.cols,   3, sudoku.RemovePossibleCol)
  1083.         clear_possible(hidden.triplets.blocks, 3, sudoku.RemovePossibleBlock)
  1084.     end
  1085.  
  1086.     if (sudokuTable.flags[sudoku.ELIMINATE_NAKED_QUADS] == true) then
  1087.         clear_possible(naked.quads.rows,   4, sudoku.RemovePossibleRow)
  1088.         clear_possible(naked.quads.cols,   4, sudoku.RemovePossibleCol)
  1089.         clear_possible(naked.quads.blocks, 4, sudoku.RemovePossibleBlock)
  1090.     end
  1091.     if (sudokuTable.flags[sudoku.ELIMINATE_HIDDEN_QUADS] == true) then
  1092.         clear_possible(hidden.quads.rows,   4, sudoku.RemovePossibleRow)
  1093.         clear_possible(hidden.quads.cols,   4, sudoku.RemovePossibleCol)
  1094.         clear_possible(hidden.quads.blocks, 4, sudoku.RemovePossibleBlock)
  1095.     end
  1096. end
  1097.  
  1098. -- ============================================================================
  1099. -- Find all the cells that only have a single possible value and set it.
  1100. --   Modifies input sudokuTable.
  1101. function sudoku.SolveScanSingles(sudokuTable)
  1102.     sudoku.CalcAllPossible(sudokuTable)
  1103.     local changed_cells = {}
  1104.  
  1105.     for row = 1, 9 do
  1106.         for col = 1, 9 do
  1107.             if not sudoku.HasValue(sudokuTable, row, col) then
  1108.                 local possible = sudoku.GetPossible(sudokuTable, row, col)
  1109.                 local count = 0
  1110.                 local value = nil
  1111.                 for pvalue, is_possible in pairs(possible) do -- count possible values
  1112.                     count = count + 1
  1113.                     value = pvalue
  1114.                 end
  1115.                 if count == 1 then
  1116.                     local cell = sudoku.RowColToCell(row, col)
  1117.                     sudoku.SetValue(sudokuTable, row, col, value)
  1118.                     sudoku.RemovePossibleAll(sudokuTable, cell, value)
  1119.                     changed_cells[cell] = value
  1120.                 end
  1121.             end
  1122.         end
  1123.     end
  1124.  
  1125.     if TableIsEmpty(changed_cells) then changed_cells = nil end -- reset if not used
  1126.  
  1127.     return changed_cells
  1128. end
  1129.  
  1130. -- Find all the cells that only have a single possible value per row and set it.
  1131. --   Modifies input sudokuTable.
  1132. function sudoku.SolveScanRows(sudokuTable)
  1133.     sudoku.SolveScanRowsCols(sudokuTable, true)
  1134. end
  1135. -- Find all the cells that only have a single possible value per col and set it
  1136. --   Modifies input sudokuTable.
  1137. function sudoku.SolveScanCols(sudokuTable)
  1138.     sudoku.SolveScanRowsCols(sudokuTable, false)
  1139. end
  1140. function sudoku.SolveScanRowsCols(sudokuTable, scan_rows)
  1141.     sudoku.CalcAllPossible(sudokuTable)
  1142.     local changed_cells = {}
  1143.  
  1144.     for row_col1 = 1, 9 do
  1145.         local row = nil -- set row or col depending on scan_rows
  1146.         local col = nil
  1147.         if scan_rows then row = row_col1 else col = row_col1 end
  1148.  
  1149.         local possible = {} -- all the possible values along the row or col
  1150.         for i = 1, 9 do possible[i] = {} end
  1151.  
  1152.         -- fill possible[pvalue] = { cell1, cell2... } along row or col
  1153.         for row_col2 = 1, 9 do
  1154.             if scan_rows then col = row_col2 else row = row_col2 end
  1155.  
  1156.             if not sudoku.HasValue(sudokuTable, row, col) then
  1157.                 local cell_possible = sudoku.GetPossible(sudokuTable, row, col)
  1158.                 for pvalue, is_possible in pairs(cell_possible) do
  1159.                     table.insert(possible[pvalue], sudoku.RowColToCell(row, col))
  1160.                 end
  1161.             end
  1162.         end
  1163.  
  1164.         -- iterate through the values and if only one possibility set it
  1165.         for value = 1, 9 do
  1166.             if TableCount(possible[value]) == 1 then
  1167.                 local cell = possible[value][1]
  1168.                 sudoku.SetCellValue(sudokuTable, cell, value)
  1169.                 sudoku.RemovePossibleAll(sudokuTable, cell, value)
  1170.                 changed_cells[cell] = value
  1171.             end
  1172.         end
  1173.     end
  1174.  
  1175.     if TableIsEmpty(changed_cells) then changed_cells = nil end -- reset if not used
  1176.  
  1177.     return changed_cells
  1178. end
  1179.  
  1180. -- Find all the cells that only have a single possible value per block and set it
  1181. --   Modifies input sudokuTable.
  1182. function sudoku.SolveScanBlocks(sudokuTable)
  1183.     sudoku.CalcAllPossible(sudokuTable)
  1184.     local changed_cells = {}
  1185.  
  1186.     for block = 1, 9 do
  1187.         local block_row, block_col = sudoku.BlockToRowCol(block)
  1188.  
  1189.         local possible = {}
  1190.         for i = 1, 9 do possible[i] = {} end
  1191.  
  1192.         -- fill possible[pvalue] = { cell1, cell2... } for whole block
  1193.         for row = block_row, block_row+2 do
  1194.             for col = block_col, block_col+2 do
  1195.                 if not sudoku.HasValue(sudokuTable, row, col) then
  1196.                     local cell_possible = sudoku.GetPossible(sudokuTable, row, col)
  1197.                     for pvalue, is_possible in pairs(cell_possible) do
  1198.                         table.insert(possible[pvalue], sudoku.RowColToCell(row, col))
  1199.                     end
  1200.                 end
  1201.             end
  1202.         end
  1203.  
  1204.         -- iterate through the values and if only one possibility set it
  1205.         for value = 1, 9 do
  1206.             if TableCount(possible[value]) == 1 then
  1207.                 local cell = possible[value][1]
  1208.                 sudoku.SetCellValue(sudokuTable, cell, value)
  1209.                 sudoku.RemovePossibleAll(sudokuTable, cell, value)
  1210.                 changed_cells[cell] = value
  1211.             end
  1212.         end
  1213.     end
  1214.  
  1215.     if TableIsEmpty(changed_cells) then changed_cells = nil end -- reset if not used
  1216.  
  1217.     return changed_cells
  1218. end
  1219.  
  1220. -- Find all the cells that only have a single possible value per row, col, block and set it
  1221. --   Modifies input sudokuTable.
  1222. function sudoku.SolveScan(sudokuTable)
  1223.     local changed_single = {}
  1224.     local changed_rows   = {}
  1225.     local changed_cols   = {}
  1226.     local changed_blocks = {}
  1227.     local changed_cells  = {} -- total cells changed
  1228.     local count = 0
  1229.  
  1230.     local function add_changed(changed_table, changed_cells)
  1231.         if changed_table then
  1232.             changed_cells = TableMerge(changed_table, changed_cells)
  1233.         end
  1234.         return changed_cells
  1235.     end
  1236.  
  1237.     while (count < 10000) and (changed_single or changed_rows or changed_cols or changed_blocks) do
  1238.         changed_single = sudoku.SolveScanSingles(sudokuTable)
  1239.         changed_rows   = sudoku.SolveScanRows(sudokuTable)
  1240.         changed_cols   = sudoku.SolveScanCols(sudokuTable)
  1241.         changed_blocks = sudoku.SolveScanBlocks(sudokuTable)
  1242.  
  1243.         changed_cells = add_changed(changed_single, changed_cells)
  1244.         changed_cells = add_changed(changed_rows,   changed_cells)
  1245.         changed_cells = add_changed(changed_cols,   changed_cells)
  1246.         changed_cells = add_changed(changed_blocks, changed_cells)
  1247.  
  1248.         count = count + 1
  1249.     end
  1250.  
  1251.     if TableIsEmpty(changed_cells) then changed_cells = nil end -- nothing done
  1252.  
  1253.     return count, changed_cells
  1254. end
  1255.  
  1256. -- Brute force recursive solver, returns a new table and does not the input sudokuTable.
  1257. --   (call with only the SudokuTable, don't enter other parameters)
  1258. function sudoku.SolveBruteForce(sudokuTable, backwards)
  1259.     -- first time through find possible to limit choices, subsequent calls ok
  1260.     local s = sudoku.CreateTable()
  1261.  
  1262.     -- finding all the possibilities is slow, but at least do solve scan
  1263.     --s.flags[sudoku.ELIMINATE_NAKED_PAIRS]     = true
  1264.     --s.flags[sudoku.ELIMINATE_HIDDEN_PAIRS]    = true
  1265.     --s.flags[sudoku.ELIMINATE_NAKED_TRIPLETS]  = true
  1266.     --s.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS] = true
  1267.     --s.flags[sudoku.ELIMINATE_NAKED_QUADS]     = true
  1268.     --s.flags[sudoku.ELIMINATE_HIDDEN_QUADS]    = true
  1269.  
  1270.     s.values = TableCopy(sudokuTable.values)
  1271.     sudoku.CalcRowColBlockValues(s)
  1272.     sudoku.CalcAllPossible(s)
  1273.     sudoku.SolveScan(s)
  1274.  
  1275.     -- table consists of guesses[cell] = #num
  1276.     -- guesses.current is current guess #
  1277.     local guesses = { current = 0 }
  1278.     for n = 1, 81 do guesses[n] = 0 end
  1279.     -- we don't need these for this and they just slow TableCopy down
  1280.     --  they're recreated at the end using UpdateTable
  1281.     s.row_values   = nil
  1282.     s.col_values   = nil
  1283.     s.block_values = nil
  1284.     s.invalid      = nil
  1285.     s.flags        = nil
  1286.  
  1287.     return sudoku.DoSolveBruteForce(sudokuTable, backwards, s, guesses, 1)
  1288. end
  1289.  
  1290. function sudoku.DoSolveBruteForce(sudokuTable, backwards, simpleTable, guesses, cell)
  1291.     local s = simpleTable
  1292.     local g, empty_possible
  1293.  
  1294.     if sudoku.SolveBruteForceHook then
  1295.         if not sudoku.SolveBruteForceHook(guesses, cell) then
  1296.             return nil, guesses, cell
  1297.         end
  1298.     end
  1299.  
  1300.     while cell <= 81 do
  1301.         if not sudoku.HasCellValue(s, cell) then
  1302.             local possible = sudoku.GetCellPossible(s, cell)
  1303.  
  1304.             --for k, v in pairs(possible) do -- use for loop to ensure direction
  1305.            
  1306.             local start_n = iff(backwards, 9,  1)
  1307.             local end_n   = iff(backwards, 1,  9)
  1308.             local dir_n   = iff(backwards, -1, 1)
  1309.            
  1310.             for n = start_n, end_n, dir_n do
  1311.                 if possible[n] then
  1312.                     -- try a number and remove it as a possibility
  1313.                     sudoku.RemoveCellPossible(s, cell, n)
  1314.  
  1315.                     -- start a new table and test out this guess
  1316.                     local s1 = TableCopy(s)
  1317.                     -- don't use SetValue since we only care about possible
  1318.                     s1.values[cell] = n --sudoku.SetValue(s1, row, col, n)
  1319.                     sudoku.RemovePossibleAll(s1, cell, n, nil, true)
  1320.  
  1321.                     guesses[cell]   = guesses[cell] + 1
  1322.                     guesses.current = guesses.current + 1
  1323.  
  1324.                     -- check for nil return from RemovePossibleAll for break_if_empty
  1325.                     if s1 then
  1326.                         s1, g = sudoku.DoSolveBruteForce(sudokuTable, backwards, s1, guesses, cell+1)
  1327.                         -- if s1 then success! we're all done
  1328.                         if s1 then
  1329.                             -- copy all original data back and just set the values
  1330.                             local s2 = TableCopy(sudokuTable)
  1331.                             sudoku.SetValues(s2, s1.values)
  1332.                             return s2, g
  1333.                         end
  1334.                     end
  1335.                 end
  1336.             end
  1337.  
  1338.             return nil, guesses -- tried all values for cell with no solution
  1339.         end
  1340.  
  1341.         cell = cell + 1
  1342.     end
  1343.  
  1344.     local s2 = TableCopy(sudokuTable)
  1345.     sudoku.SetValues(s2, s.values)
  1346.     return s2, guesses
  1347. end
  1348.  
  1349. -- Does this puzzle have a unique solution. It works by trying the brute force
  1350. -- solution method iterating from low to high numbers and then from high to low.
  1351. -- This should always find if there are at least two solutions.
  1352. -- returns nil if no solution or [s1, s2] if at least two solutions, else
  1353. -- just the single unique solution.
  1354. -- Returns a new solved table or nil on failure, doesn't modify input sudokuTable.
  1355. function sudoku.IsUniquePuzzle(sudokuTable)
  1356.  
  1357.     local s1, g1 = sudoku.SolveBruteForce(sudokuTable, false)
  1358.     if not s1 then return nil end
  1359.  
  1360.     local s2, g2 = sudoku.SolveBruteForce(sudokuTable, true)
  1361.     if not s2 then return nil end
  1362.  
  1363.     if not sudoku.IsSamePuzzle(s1, s2) then return s1, s2 end
  1364.  
  1365.     return s1
  1366. end
  1367.  
  1368. -- Do these two puzzles have the same cell values? Returns true/false.
  1369. function sudoku.IsSamePuzzle(s1, s2)
  1370.     for cell = 1, 81 do
  1371.         if sudoku.GetCellValue(s1, cell) ~= sudoku.GetCellValue(s2, cell) then
  1372.             return false
  1373.         end
  1374.     end
  1375.  
  1376.     return true
  1377. end
  1378.  
  1379. -- ============================================================================
  1380. -- Create a full puzzle with all the values
  1381. --  returns sudokuTable, count where count is the number of iterations
  1382. function sudoku.GeneratePuzzle()
  1383.     local cell = 0
  1384.     local count = 0
  1385.     local stuck = {} -- count how many times we've backtracked stuck[cell/9] = count
  1386.  
  1387.     math.randomseed(os.time())
  1388.     local sudokuTable = sudoku.CreateTable()
  1389.  
  1390.     while cell < 81 do
  1391.         cell = cell + 1
  1392.         count = count + 1
  1393.  
  1394.         if sudoku.GeneratePuzzleHook then
  1395.             if not sudoku.GeneratePuzzleHook(count, cell) then
  1396.                 return nil, count
  1397.             end
  1398.         end
  1399.  
  1400.         local value = math.random(9)
  1401.         local row, col = sudoku.CellToRowCol(cell)
  1402.  
  1403.         if sudoku.IsValidValue(sudokuTable, row, col, value) then
  1404.             sudoku.SetCellValue(sudokuTable, cell, value)
  1405.             sudoku.RemovePossibleAll(sudokuTable, cell, value)
  1406.         else
  1407.             -- try other values starting at value+1 and wrapping around
  1408.             local set_value = false
  1409.             local i = value + 1
  1410.             if i > 9 then i = 1 end
  1411.             while i ~= value do
  1412.                 if sudoku.IsValidValue(sudokuTable, row, col, i) then
  1413.                     sudoku.SetCellValue(sudokuTable, cell, i)
  1414.                     sudoku.RemovePossibleAll(sudokuTable, cell, i)
  1415.                     set_value = true
  1416.                     break
  1417.                 end
  1418.                 i = i + 1
  1419.                 if i > 9 then i = 1 end
  1420.             end
  1421.  
  1422.             -- whoops, go back a row or more and start over just to be sure
  1423.             if not set_value then
  1424.                 local block = math.floor(cell/9) + 1
  1425.                 stuck[block] = (stuck[block] or 0) + 1
  1426.                 local goback = 9
  1427.  
  1428.                 if stuck[block] and (stuck[block] > 5) then
  1429.                     goback = 2 * stuck[block] + 1
  1430.                 end
  1431.  
  1432.                 local cell_start = cell - goback
  1433.                 if cell_start < 1 then
  1434.                     cell_start = 1
  1435.                 elseif cell_start < 10 then
  1436.                     stuck = {} -- really start all over
  1437.                 end
  1438.  
  1439.                 for i = cell_start, cell do
  1440.                     sudoku.SetCellValue(sudokuTable, i, 0)
  1441.                 end
  1442.  
  1443.                 cell = cell_start - 1
  1444.             end
  1445.         end
  1446.     end
  1447.  
  1448.     return sudokuTable, count
  1449. end
  1450.  
  1451. function sudoku.GeneratePuzzleDifficulty(sudokuTable, num_cells_to_keep, ensure_unique)
  1452.     if num_cells_to_keep < 1 then
  1453.         return sudoku.CreateTable()
  1454.     end
  1455.  
  1456.     if ensure_unique == nil then ensure_unique = true end
  1457.     math.randomseed(os.time()+1)
  1458.     local trial = 1
  1459.     local i = 0
  1460.     local count = 0
  1461.     local soln = TableCopy(sudokuTable)
  1462.     local cellTable = {}
  1463.     for n = 1, 81 do cellTable[n] = n end
  1464.  
  1465.     local cell_count, cell_n, cell
  1466.  
  1467.     while i < 81 - num_cells_to_keep do
  1468.         cell_count = #cellTable
  1469.  
  1470.         -- restart this function if we run out of cells to try
  1471.         if (cell_count == 0) or (cell_count + i < num_cells_to_keep) then
  1472.             trial = trial + 1
  1473.             i = 0
  1474.             count = 0
  1475.             cellTable = {}
  1476.             for n = 1, 81 do cellTable[n] = n end
  1477.             cell_count = #cellTable
  1478.             cell_n = math.random(cell_count)
  1479.         elseif cell_count == 1 then
  1480.             cell_n = 1
  1481.         else
  1482.             cell_n = math.random(cell_count)
  1483.         end
  1484.  
  1485.         cell = cellTable[cell_n]
  1486.         count = count + 1
  1487.  
  1488.         if sudoku.GeneratePuzzleDifficultyHook then
  1489.             if not sudoku.GeneratePuzzleDifficultyHook(count, i, cell, cell_count, trial) then
  1490.                 return nil, count
  1491.             end
  1492.         end
  1493.  
  1494.         if ensure_unique == true then
  1495.             -- test if soln going forward is same as soln backwards and original
  1496.             local s = TableCopy(sudokuTable)
  1497.             sudoku.SetCellValue(s, cell, 0)
  1498.             local soln1 = sudoku.SolveBruteForce(s, false)
  1499.             if not soln1 then return nil, count end
  1500.  
  1501.             if not sudoku.IsSamePuzzle(soln, soln1) then
  1502.                 table.remove(cellTable, cell_n)
  1503.             else
  1504.                 local soln2 = sudoku.SolveBruteForce(s, true)
  1505.                 if not soln2 then return nil, count end
  1506.  
  1507.                 if not sudoku.IsSamePuzzle(soln, soln2) then
  1508.                     table.remove(cellTable, cell_n)
  1509.                 else
  1510.                     table.remove(cellTable, cell_n)
  1511.                     sudoku.SetCellValue(sudokuTable, cell, 0)
  1512.                     i = i + 1
  1513.                 end
  1514.             end
  1515.         else
  1516.             table.remove(cellTable, cell_n)
  1517.             sudoku.SetCellValue(sudokuTable, cell, 0)
  1518.             i = i + 1
  1519.         end
  1520.     end
  1521.  
  1522.     return sudokuTable, count
  1523. end
  1524.  
  1525. -- ============================================================================
  1526. -- ============================================================================
  1527.  
  1528. sudokuGUIxpmdata =
  1529. {
  1530.     "16 15 7 1",
  1531.     "  c None",
  1532.     "a c Black",
  1533.     "b c #808080",
  1534.     "c c #FFFF00",
  1535.     "d c #FF0000",
  1536.     "e c #0000FF",
  1537.     "g c #00FF00",
  1538.     " aaaaaaaaaaaaa  ",
  1539.     " addaggaeeaccab ",
  1540.     " addaggaeeaccab ",
  1541.     " aaaaaaaaaaaaab ",
  1542.     " accaddaggaeeab ",
  1543.     " accaddaggaeeab ",
  1544.     " aaaaaaaaaaaaab ",
  1545.     " aeeaccaddaggab ",
  1546.     " aeeaccaddaggab ",
  1547.     " aaaaaaaaaaaaab ",
  1548.     " aggaeeaccaddab ",
  1549.     " aggaeeaccaddab ",
  1550.     " aaaaaaaaaaaaab ",
  1551.     "  bbbbbbbbbbbbb ",
  1552.     "                "
  1553. }
  1554.  
  1555. -- NOTE: HTML generated using NVU and "tidy -wrap 79 -i -omit -o l.html input.html"
  1556.  
  1557. sudokuGUIhelp =
  1558. [[
  1559. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  1560.  
  1561. <meta name="generator" content=
  1562. "HTML Tidy for Linux/x86 (vers 1st December 2004), see www.w3.org">
  1563.  
  1564.   <title>wxLuaSudoku</title>
  1565.   <meta content="John Labenski" name="author">
  1566.   <meta content="Documentation for the wxLuaSudoku program" name=
  1567.   "description">
  1568.  
  1569. <body bgcolor="#FFFFCC">
  1570.   <h1>wxLuaSudoku</h1>Copyright : John Labenski, 2006<br>
  1571.   License : wxWindows license.<br>
  1572.   <br>
  1573.   <i>This program is free software; you can redistribute it and/or modify it
  1574.   under the terms of the wxWindows License; either version 3 of the License,
  1575.   or (at your option) any later version.<br>
  1576.   <br>
  1577.   This program is distributed in the hope that it will be useful, but WITHOUT
  1578.   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  1579.   FITNESS FOR A PARTICULAR PURPOSE. See the wxWindows License for more
  1580.   details.</i><br>
  1581.   <br>
  1582.   If you use this program and find any bugs or have added any features that
  1583.   you feel may be generally useful, please feel free to contact me by e-mail
  1584.   at jrl1[at]sourceforge[dot]net or on the wxlua-users mailing list.<br>
  1585.  
  1586.   <h2>Purpose</h2>The purpose of this program is to demonstrate programming in
  1587.   wxLua. It may not be the best, fastest, or most complete sudoku solver
  1588.   program available, but the code should be easily understandable with a
  1589.   straightforward implementation. However, you may find that it is capable
  1590.   enough and since it's free, you got what you paid for.<br>
  1591.  <br>
  1592.  wxLua is a binding of the wxWidgets cross-platform GUI library to the Lua
  1593.  programming language. It is available as an application, library, or source
  1594.  code for MSW, Unix like systems, and OSX. More information about wxLua can
  1595.  be found on <a href=
  1596.  "http://wxlua.sourceforge.net">wxlua.sourceforge.net</a>. Information about
  1597.  the wxWidgets library can be found on <a href=
  1598.  "http://www.wxwidgets.org">www.wxwidgets.org</a> and the Lua programming
  1599.  language on <a href="http://www.lua.org">www.lua.org</a>.<br>
  1600.  
  1601.  <h2>Program features</h2>
  1602.  
  1603.  <ul>
  1604.    <li>Enter your own initial values for a puzzle to solve
  1605.  
  1606.    <li>Generate random unique new puzzles
  1607.  
  1608.    <li>Load/Save puzzles to files
  1609.  
  1610.    <li>Print the puzzle
  1611.  
  1612.    <li>Undo/Redo of the steps you've taken
  1613.  
  1614.     <li>Mark errors and/or mistakes
  1615.  
  1616.     <li>Show calculated possible values for each cell
  1617.  
  1618.     <li>Pencil marks, possible values for cells that you have determined
  1619.  
  1620.     <li>Automatic marking of hidden/naked pairs, triplets, and quads of
  1621.     possible values
  1622.  
  1623.     <li>Eliminate possible values by finding hidden/naked pairs, triplets, and
  1624.     quads of possible values
  1625.  
  1626.     <li>Solve the puzzle by scanning for values, can use eliminate
  1627.     naked/hidden groups to do better
  1628.  
  1629.     <li>Solve the puzzle by brute force, by guessing values from the possible
  1630.     values
  1631.  
  1632.     <li>No installer, registry entries, or other nonsense. Merely delete the
  1633.     program when you're done with it.
  1634.  </ul>
  1635.  
  1636.  <h2>How to play</h2>Sudoku is a puzzle game of choosing where to place the
  1637.  numbers 1 to 9 in a 9x9 grid of cells. There are many varieties of this
  1638.  game, but wxLuaSudoku only works with the 9x9 grid. The grid of 81 cells has
  1639.  9 rows, 9 columns, and 9 blocks that consist of smaller 3x3 grids of cells.
  1640.  Each cell in the grid can take a value of 1 to 9, but there are limitations.
  1641.  Each row, column, and block must have the full array of numbers from 1 to 9.
  1642.  Therefore, there can be no duplicate values in any row, column, or block.
  1643.  For a well formed puzzle there's just one possible solution for the initial
  1644.   seed of values. There are numerous tutorials on the internet, for example
  1645.   <a href=
  1646.   "http://en.wikipedia.org/wiki/Sudoku">http://en.wikipedia.org/wiki/Sudoku</a>,
  1647.   so lets move on...<br>
  1648.  
  1649.   <h2><a name="Some_Sudoku_terms" id="Some_Sudoku_terms"></a>Some Sudoku
  1650.   terms</h2>
  1651.  
  1652.   <ul>
  1653.     <li><b>Initial values :</b> These are values for the cells that the puzzle
  1654.     starts with. The minimum number of initial values that a puzzle can start
  1655.     with is thought to be 17 in order for the puzzle to have a single unique
  1656.     solution.
  1657.  
  1658.     <li><b>Possible values :</b> These are values that an empty cell might
  1659.     take given the existing values in the row, col, and block that the cell is
  1660.     in.
  1661.  
  1662.     <li>
  1663.       <b>Naked pairs, triplets, quads :</b> When looking though the possible
  1664.       values for the cells you may find that two or more cells have the
  1665.       identical possible values and only those values. Those would be called
  1666.       naked because they're immediately visible.
  1667.  
  1668.      <ul>
  1669.        <li>Pair example: If two cells in a row have the possible values of 2
  1670.        and 4 then either 2 or 4 has to be in those two cells. You can
  1671.        therefore eliminate 2 and 4 from the possible values in the rest of
  1672.        the row and if they're both in the same block, the block too.
  1673.       </ul>
  1674.  
  1675.     <li>
  1676.       <b>Hidden pairs, triplets, quads :</b> A hidden group is defined as a
  1677.       set of numbers that appear in as many cells as the size of the group.
  1678.       They are similar to naked groups, but there may also be some other
  1679.       possible values that make them a little harder to find.
  1680.  
  1681.       <ul>
  1682.         <li>Pair example: If two cells in a row have the possible values 1, 2,
  1683.         4 and 1, 2, 4, 5, 6 respectively and the numbers 2 and 4 do not appear
  1684.         in any other cell in the row you know that either 2 or 4 must go into
  1685.         either cell and eliminate the other possible values from those two
  1686.         cells.
  1687.       </ul>
  1688.     </ul>
  1689.  
  1690.   <h2>Entering values</h2>Enter values into the cells by clicking on a cell to
  1691.   highlight it and then type a number. Use 0, space, or any non number to
  1692.   clear the cell's value. The cells that the puzzle is initialized with are
  1693.  read-only, meaning that you cannot change their values unless you are
  1694.  actually creating a puzzle using the menu item <i>File-&gt;Create</i>.
  1695.  Navigate through the cells with the cursor keys or mouse.<br>
  1696.  
  1697.  <h2><a name="Pencil_marks" id="Pencil_marks"></a>Pencil marks</h2>You can
  1698.  annotate the empty cells with a list of possible values that may work by
  1699.  checking the menu item <i>Possible-&gt;Show/Edit pencil marks</i>. If you
  1700.  want initialize the pencil marks to be all selected, none selected, or set
  1701.  to the calculated values use the menu items under <i>Possible-&gt;Pencil
  1702.  marks</i>. In order to toggle the values, hold down the shift key and all of
  1703.  the possible values will be shown. Click on a value to either select or
  1704.  deselect it. Alternatively, you can use shift-Number to toggle the selection
  1705.  state of the pencil marks.<br>
  1706.  <br>
  1707.  Note that this is different than the menu item <i>Possible-&gt;Show
  1708.  calculated possible</i> in that, obviously, this will show a list of
  1709.  calculated possible values that could go into the cells based on the current
  1710.  state of the puzzle. You can switch between the two views or not show the
  1711.  possible values altogether by unchecking both of them.
  1712.  
  1713.  <h2><a name="Creating_a_puzzle" id="Creating_a_puzzle"></a>Creating a
  1714.  puzzle</h2>To create a new puzzle by hand check the menu item
  1715.  <i>File-&gt;Create</i>. You've now put the program in a state where any
  1716.   values that you enter will be the initial values of the puzzle. Error
  1717.   checking of invalid values, values that appear more than once in the same
  1718.   row, column, or block, is automatically turned on since it's fairly
  1719.  pointless to create puzzles that cannot be solved, though this program
  1720.  generously allows you to do so. Enter the values as described above and when
  1721.  done be sure to uncheck the <i>File-&gt;Create</i> item. The program will
  1722.  then try to find a solution to the puzzle and verify that there is only one
  1723.  unique solution.<br>
  1724.  <br>
  1725.  If you want to check the state of the puzzle during creation to confirm that
  1726.  it has a unique solution, use the <i>Solve-&gt;Verify unique solution</i>
  1727.  menu item. If you're determined to create an invalid sudoku puzzle this
  1728.   program will not stop you and you can merely cancel out of the warning
  1729.   dialogs to continue and you're on your own. However, showing mistakes will
  1730.  be disabled and "solving" using the scanning technique may yield strange
  1731.  results that are mathematically consistent with the rules of the game, but
  1732.  bring you no closer to the nonexistent solution. If at some later point you
  1733.  wish to have mistakes shown using <i>View-&gt;Mark mistakes</i>, the program
  1734.  will try to solve the initial puzzle again, with the same result, no
  1735.  solution.<br>
  1736.  <br>
  1737.  If you want to start with a completely empty grid use <i>File-&gt;New</i>.
  1738.  If on the other hand, you want to start with a completely filled grid of
  1739.  random values, use <i>File-&gt;Generate</i> with the number of cell values
  1740.  to show set to 81, all shown.
  1741.  
  1742.  <h2>Cell markings</h2>The colors and fonts for the cells can be changed with
  1743.  the preferences dialog from the menu item <i>View-&gt;Preferences</i>. The
  1744.  values in the cells have one color if it's an initial value and a different
  1745.   color if you've entered the value into the cell during play. The possible
  1746.  values that could go into a particular cell use a smaller font and can be
  1747.  drawn as a 3x3 grid or in a single line which may be useful if you want to
  1748.  print it and work it out by hand.<br>
  1749.  <br>
  1750.  Possible groups of values for the cells may be set to be marked if they are
  1751.  naked and or hidden. Note: It may happen that, for example, there's a
  1752.   triplet of possible values in a row and some of the values are shared by a
  1753.   pair in a column. In that case the pair values get the appropriate markings
  1754.   overriding the triplet markings since it's usually best to try to work with
  1755.  them first. Also, if you get really off track the program will happily mark
  1756.  groups that would obviously make no sense if there weren't any mistakes.
  1757.   This is by design since some people apparently find it interesting to do
  1758.   whatever it is that they want to do.
  1759.  
  1760.   <h2>Menu item descriptions</h2>All items followed by ... denote that a
  1761.   dialog will be shown that can be canceled before any action is taken.
  1762.  
  1763.   <ul>
  1764.     <li>
  1765.       <b>File</b>
  1766.  
  1767.       <ul>
  1768.         <li><b>New...</b> : Clear the whole puzzle to an uninitialized state so
  1769.         that you can then "Create" it from scratch.
  1770.  
  1771.         <li>
  1772.           <b>Create...</b> : Check this item to enter the initial values,
  1773.           uncheck when done to play.
  1774.  
  1775.           <ul>
  1776.             <li>See the section on <a href="#Creating_a_puzzle">Creating a
  1777.             puzzle</a>.
  1778.           </ul>
  1779.  
  1780.         <li>
  1781.           <b>Generate...</b> : Have the program create a new random unique
  1782.           puzzle for you.
  1783.  
  1784.           <ul>
  1785.             <li>Set the number of cell values to show, the more that are
  1786.             visible, the easier it'll probably be.
  1787.  
  1788.            <li>This program does not try to categorize how difficult the
  1789.            generated puzzle is.
  1790.  
  1791.            <li>Note: If you have less than 17 cells shown there will not be a
  1792.            unique solution.&nbsp;
  1793.  
  1794.            <li>The less cells you have shown the longer it will take to find
  1795.            a unique puzzle, but the program will try for as long as it takes.
  1796.            If you get impatient, just cancel the generation and you will be
  1797.            asked if you want the program to just randomly remove cell values
  1798.            which may or may not yield a unique solution.
  1799.          </ul>
  1800.  
  1801.        <li>
  1802.          <b>Open...</b> : Open a puzzle from a file, the puzzle should be a
  1803.          9x9 grid of numbers separated by spaces or commas.
  1804.  
  1805.          <ul>
  1806.            <li>After opening, the program will try to solve the puzzle and let
  1807.            you know if there is a problem, such as a non unique solution. You
  1808.            can just cancel out if you'd like to skip this check.
  1809.           </ul>
  1810.  
  1811.         <li><b>Save as...</b> : Save the current puzzle to disk.
  1812.  
  1813.         <li><b>Page Setup...</b> : Adjust the paper and margins for printing.
  1814.  
  1815.         <li><b>Print Preview...</b> : Preview what the printout will look
  1816.         like.
  1817.  
  1818.         <li><b>Print...</b> : Print the current puzzle, WYSIWYG, what you see
  1819.         is what you get.
  1820.  
  1821.         <li><b>Exit</b> : Exit the program.
  1822.       </ul>
  1823.  
  1824.     <li>
  1825.       <b>Edit</b>
  1826.  
  1827.       <ul>
  1828.         <li><b>Copy puzzle</b> : Copy the puzzle to the clipboard to paste
  1829.         into other programs.
  1830.  
  1831.         <li><b>Reset...</b> : Clear all the values you've entered to return
  1832.        back to the initial state. This is the same as just undoing back to
  1833.        the beginning.
  1834.  
  1835.        <li><b>Undo/Redo</b> : You can undo and redo values that you've
  1836.         entered into the puzzle. If you undo a couple steps and enter a new
  1837.         value you cannot then redo back to where you were.
  1838.  
  1839.         <li><b>Preferences</b> : Set the colors to use and whatnot in a
  1840.         dialog. See the <a href="#Preferences_dialog">preferences dialog</a>
  1841.         section.
  1842.  
  1843.         <li>
  1844.           <b>Save preferences</b> : Save the current preferences, including
  1845.           what you've set to show and exclude.
  1846.  
  1847.          <ul>
  1848.            <li>This program does not automatically save the preferences on
  1849.            exit, but rather only when you explicitly request it to do so.
  1850.  
  1851.            <li>MSW : preferences are stored in "Documents and
  1852.            Settings\username\wxLuaSudoku.ini"
  1853.  
  1854.            <li>Unix : preferences are stored in "~/.wxLuaSudoku.ini"
  1855.  
  1856.            <li>You may delete these files if you want to reset the program to
  1857.            the defaults or are done using it.
  1858.          </ul>
  1859.        </ul>
  1860.  
  1861.    <li>
  1862.      <b>View</b>
  1863.  
  1864.      <ul>
  1865.        <li><b>Mark errors</b> : Mark the cells that have obviously invalid
  1866.        values, not invalid from the standpoint of it being the "correct"
  1867.        value, but whether or not it's in conflict with another value that has
  1868.         already exists in same row, column, or block in the puzzle.
  1869.  
  1870.         <li>
  1871.           <b>Mark mistakes</b> : Mark the cells that have "wrong" values that
  1872.           will not lead to a solution by comparing the values to a
  1873.           precalculated solution.
  1874.  
  1875.           <ul>
  1876.             <li>If the solution has not been already found, perhaps you've
  1877.            canceled the initial check, the program will have to work it out.
  1878.            If you cancel this process and then want any mistakes shown again,
  1879.            it'll have to try to solve it again. In the case that the program
  1880.             cannot find a solution or there isn't a unique solution, a warning
  1881.            dialog will be shown and marking the mistakes will be
  1882.            automatically unchecked.
  1883.          </ul>
  1884.  
  1885.        <li><b>Show toolbar</b> : Show or hide the toolbar at the top of the
  1886.        program.
  1887.  
  1888.        <li><b>Show statusbar</b> : Show or hide the statusbar at the bottom
  1889.        of the program.
  1890.      </ul>
  1891.  
  1892.    <li>
  1893.      <b>Possible</b>
  1894.  
  1895.      <ul>
  1896.        <li>
  1897.          <b>Show calculated possible</b> : Show the possible values that the
  1898.          program has calculated for the empty cells.
  1899.  
  1900.          <ul>
  1901.            <li>Note that this does not take into account any invalid or
  1902.            erroneous values you may have entered, it just shows you possible
  1903.            values given the current state of the puzzle.
  1904.          </ul>
  1905.  
  1906.        <li>
  1907.          <b>Show/Edit pencil marks</b> : Allows you to enter the possible
  1908.          values for the cells by hand.
  1909.  
  1910.          <ul>
  1911.            <li>See the section above about <a href="#Pencil_marks">entering
  1912.            pencil marks</a>.
  1913.  
  1914.            <li>You can show the calculated possible values or the pencil
  1915.            marks or neither, but not both at the same time.
  1916.          </ul>
  1917.  
  1918.        <li><b>Show possible line</b> : Show the possible values in a single
  1919.        line, useful if you want to print it out to play later.
  1920.  
  1921.        <li>
  1922.          <b>Pencil marks</b>
  1923.  
  1924.          <ul>
  1925.            <li><b>Clear all...</b> : Clear all the pencil marks for the whole
  1926.            puzzle.
  1927.  
  1928.            <li><b>Set all...</b> : Set all values for the pencil marks for
  1929.            the whole puzzle.
  1930.  
  1931.            <li><b>Calculate...</b> : Set all the pencil marks to the same
  1932.            values as the calculated possible values.
  1933.          </ul>
  1934.  
  1935.        <li>
  1936.          <b>Mark groups</b>
  1937.  
  1938.          <ul>
  1939.            <li><b>Mark naked groups</b> : Mark all naked groups of cells;
  1940.            pairs, triplets, and quads.
  1941.  
  1942.            <li><b>Mark hidden groups</b> : Mark all hidden groups of cells;
  1943.            pairs, triplets, and quads.
  1944.  
  1945.            <li>... mark each group individually
  1946.  
  1947.            <li><i>Note: See Solve-&gt;Eliminate to remove possible values
  1948.            that can be excluded once the groups have been found. You do not
  1949.            have to show the groups to eliminate values or vice versa.</i>
  1950.          </ul>
  1951.        </ul>
  1952.  
  1953.    <li>
  1954.      <b>Solve</b>
  1955.  
  1956.      <ul>
  1957.        <li>
  1958.          <b>Verify unique solution...</b> : Use the brute force solver to
  1959.          find if there is more than one solution for the initial values of
  1960.          the puzzle or if there is a solution at all.
  1961.  
  1962.          <ul>
  1963.            <li>Typically this is not necessary since the program
  1964.            automatically tries to verify the puzzle after opening or creating
  1965.            a puzzle, but if you had canceled the check you can verify it by
  1966.            hand using this.
  1967.          </ul>
  1968.  
  1969.        <li>
  1970.          <b>Show solution</b> : Show the solution to the puzzle that should
  1971.          have been automatically found after opening, creating, or generating
  1972.          a puzzle.
  1973.  
  1974.          <ul>
  1975.            <li>If you had canceled out of the puzzle verification procedure
  1976.            the program will have to solve it using the initial values in the
  1977.            puzzle.
  1978.  
  1979.            <li>Use undo to return back to playing the game if you wish to
  1980.            just take a peek at some answers.
  1981.          </ul>
  1982.  
  1983.        <li>
  1984.          <b>Eliminate groups</b>
  1985.  
  1986.          <ul>
  1987.            <li><b>Eliminate naked groups</b> : Remove all calculated possible
  1988.            values that can be excluded by evaluating the naked groups of
  1989.            pairs, triplets, and quads. This does not work on the pencil
  1990.            marks. See the <a href="#Some_Sudoku_terms">Sudoku terms</a>
  1991.            section about naked groups.
  1992.  
  1993.            <li><b>Eliminate hidden groups</b> : Remove all calculated
  1994.            possible values that can be excluded by evaluating the hidden
  1995.            groups of pairs, triplets, and quads. This does not work on the
  1996.            pencil marks. See the <a href="#Some_Sudoku_terms">Sudoku
  1997.            terms</a> section about hidden groups.
  1998.  
  1999.            <li>... eliminate each group individually
  2000.  
  2001.            <li><i>Note: This updates the View-&gt;Show calculated possible
  2002.            and also for solving using Solve (scan ...) since the more
  2003.            possible values you remove the easier it is to narrow down the
  2004.            correct values.</i>
  2005.          </ul>
  2006.  
  2007.        <li><i><u>Note on solving</u>: Solving works on the current puzzle
  2008.        state which means that if you have inadvertently entered a wrong value
  2009.        the solver stops at the point where no new values can be placed. The
  2010.        values it finds will be logically correct based on the rules of the
  2011.        game, but probably wrong. Use mark mistakes if you want to check how
  2012.        you're doing.</i>
  2013.  
  2014.         <li><b>Solve (scan singles)</b> : Fill in the values for all cells
  2015.         that can only take one value.
  2016.  
  2017.         <li><b>Solve (scan rows)</b> : Fill in the values of cells that are
  2018.         the only ones in the row to have a particular possible value.
  2019.  
  2020.         <li><b>Solve (scan cols)</b> : Same for columns
  2021.  
  2022.         <li><b>Solve (scan blocks)</b> : Same for blocks
  2023.  
  2024.         <li><i><u>Note on solve scanning</u> : You can try iterating through
  2025.         scanning singles, rows, columns, blocks to see which new values can be
  2026.         found after other values have been set.</i>
  2027.  
  2028.         <li>
  2029.           <b>Solve (scanning)</b> : Try solving the puzzle by scanning over
  2030.           and over until no new cell values can be found.
  2031.  
  2032.           <ul>
  2033.             <li>Check different eliminate groups to allow the solver to take
  2034.             advantage of the reduced number of possible values.
  2035.           </ul>
  2036.  
  2037.         <li>
  2038.           <b>Solve (brute force)</b> : Solve the puzzle by guessing values
  2039.           from the possible values using the current puzzle values.
  2040.  
  2041.           <ul>
  2042.             <li>This will fail if the puzzle cannot be solved (maybe you've
  2043.            made a mistake?)
  2044.          </ul>
  2045.        </ul>
  2046.  
  2047.    <li>
  2048.      <b>Help</b>
  2049.  
  2050.      <ul>
  2051.        <li><b>About...</b> : A simple about this program dialog.
  2052.  
  2053.        <li><b>Help...</b> : Show this document.
  2054.      </ul>
  2055.    </ul>
  2056.  
  2057.  <h2><a name="Preferences_dialog" id="Preferences_dialog"></a>Preferences
  2058.  dialog</h2>In the preferences dialog you can adjust the colors to use for
  2059.  the different elements in a cell. A sample cell is shown to give you an idea
  2060.  of what it'll look like when done. Note that some fonts you may choose will
  2061.   not render properly or may be badly placed in the cells. This is a problem
  2062.   with the font itself, just pick a different font. Also, bitmapped fonts only
  2063.   come in a limited number of sizes and cannot be scaled to arbitrary sizes,
  2064.   again, pick another font that works better on your system.<br>
  2065.   <br>
  2066.   Additionally, a simpler interface to the menu items <i>Possible-&gt;Mark
  2067.   groups</i> and <i>Solve-&gt;Eliminate groups</i> is provided to make it
  2068.   easier to check and uncheck multiple items.
  2069. ]]
  2070.  
  2071. -- Simple way to generate unique window or menu ids and ensure they're in order
  2072. function NewID()
  2073.     if not sudokuGUI_ID_New then sudokuGUI_ID_New = wx.wxID_HIGHEST end
  2074.     sudokuGUI_ID_New = sudokuGUI_ID_New + 1
  2075.     return sudokuGUI_ID_New
  2076. end
  2077.  
  2078. sudokuGUI =
  2079. {
  2080.     frame             = nil, -- The wxFrame of the program
  2081.     panel             = nil, -- The main wxPanel child of the wxFrame
  2082.     cellWindows       = {},  -- The 81 grid cell wxWindows, children of panel
  2083.     cellTextCtrl      = nil, -- Single wxTextCtrl editor for entering cell values
  2084.  
  2085.     focused_cell_id   = 0,   -- window id of the currently focused cell, 0 for none
  2086.  
  2087.     block_refresh     = false, -- temporarily block refreshing when true
  2088.  
  2089.     filePath          = "",  -- last opened filePath
  2090.     fileName          = "",  -- last opened fileName
  2091.  
  2092.     printData         = wx.wxPrintData(),
  2093.     pageSetupData     = wx.wxPageSetupDialogData(),
  2094.  
  2095.     config            = nil, -- the wxFileConfig for saving preferences
  2096.  
  2097.     Colours                = {}, -- table of wxColours to use, indexes below
  2098.     VALUE_COLOUR           = 1,
  2099.     INIT_VALUE_COLOUR      = 2,
  2100.     POSS_VALUE_COLOUR      = 3,
  2101.     INVALID_VALUE_COLOUR   = 4,
  2102.     BACKGROUND_COLOUR      = 5,
  2103.     ODD_BACKGROUND_COLOUR  = 6,
  2104.     FOCUS_CELL_COLOUR      = 7,
  2105.  
  2106.     NAKED_PAIRS_COLOUR     = 8,
  2107.     NAKED_TRIPLETS_COLOUR  = 9,
  2108.     NAKED_QUADS_COLOUR     = 10,
  2109.     HIDDEN_PAIRS_COLOUR    = 11,
  2110.     HIDDEN_TRIPLETS_COLOUR = 12,
  2111.     HIDDEN_QUADS_COLOUR    = 13,
  2112.     COLOUR_MAX             = 13,
  2113.  
  2114.                            -- A large wxFont for the cell values
  2115.     valueFont            = { wxfont = nil, size = 8, width = 0, height = 0 },
  2116.                            -- A smallish wxFont for the possible values
  2117.     possibleFont         = { wxfont = nil, size = 6, width = 0, height = 0 },
  2118.  
  2119.     valueFont_cache      = {}, -- valueFont_cache[size]    = { width, height }
  2120.     possibleFont_cache   = {}, -- possibleFont_cache[size] = { width, height }
  2121.  
  2122.                                -- calc positions once in PaintCell
  2123.                                -- recalc if any of these parameters change
  2124.                                -- pos[value].x and .y are upper left corners
  2125.     possiblePosCache     = { pos = {}, width = 1, height = 1, line = false, cell_width = 1, cell_height = 1 },
  2126.  
  2127.     query_save_prefs     = true, -- tell user that prefs are stored as fileconfig
  2128.  
  2129.     ID_NEW               = NewID(),
  2130.     ID_CREATE            = NewID(),
  2131.     ID_GENERATE          = NewID(),
  2132.     ID_OPEN              = NewID(),
  2133.     ID_SAVEAS            = NewID(),
  2134.     ID_PAGESETUP         = NewID(),
  2135.     ID_PRINTSETUP        = NewID(),
  2136.     ID_PRINTPREVIEW      = NewID(),
  2137.     ID_PRINT             = NewID(),
  2138.     ID_EXIT              = wx.wxID_EXIT,
  2139.  
  2140.     ID_COPY_PUZZLE       = NewID(),
  2141.     ID_RESET             = NewID(),
  2142.     ID_UNDO              = NewID(),
  2143.     ID_REDO              = NewID(),
  2144.     ID_PREFERENCES       = NewID(), -- show preferences dialog
  2145.     ID_SAVE_PREFERENCES  = NewID(), -- save preferences
  2146.  
  2147.     ID_SHOW_TOOLBAR        = NewID(),
  2148.     ID_SHOW_TOOLBAR_LABELS = NewID(),
  2149.     ID_SHOW_STATUSBAR      = NewID(),
  2150.     ID_SHOW_ERRORS         = NewID(), -- Show errors in the grid
  2151.     ID_SHOW_MISTAKES       = NewID(), -- Show mistakes in the grid
  2152.  
  2153.     ID_SHOW_POSSIBLE       = NewID(), -- Show the possible values
  2154.     ID_SHOW_USER_POSSIBLE  = NewID(), -- Show the user's possible values
  2155.     ID_SHOW_POSSIBLE_LINE  = NewID(), -- Show the possible values in a line
  2156.  
  2157.     ID_USER_POSSIBLE_MENU     = NewID(),
  2158.       ID_USER_POSSIBLE_CLEAR  = NewID(),
  2159.       ID_USER_POSSIBLE_SETALL = NewID(),
  2160.       ID_USER_POSSIBLE_INIT   = NewID(),
  2161.  
  2162.     ID_SHOW_MENU             = NewID(),
  2163.       ID_SHOW_NAKED          = NewID(), -- mark naked groups
  2164.       ID_SHOW_HIDDEN         = NewID(), -- mark hidden groups
  2165.       ID_SHOW_NAKEDPAIRS     = NewID(), -- mark naked pairs
  2166.       ID_SHOW_HIDDENPAIRS    = NewID(), -- mark hidden pairs
  2167.       ID_SHOW_NAKEDTRIPLETS  = NewID(), -- mark naked triplets
  2168.       ID_SHOW_HIDDENTRIPLETS = NewID(), -- mark hidden triplets
  2169.       ID_SHOW_NAKEDQUADS     = NewID(), -- mark naked quads
  2170.       ID_SHOW_HIDDENQUADS    = NewID(), -- mark hidden quads
  2171.  
  2172.     ID_VERIFY_PUZZLE              = NewID(),
  2173.     ID_SHOW_SOLUTION              = NewID(), -- Show the solution to the puzzle
  2174.     ID_ELIMINATE_MENU             = NewID(),
  2175.       ID_ELIMINATE_NAKED          = NewID(), -- eliminate naked groups
  2176.       ID_ELIMINATE_HIDDEN         = NewID(), -- eliminate hidden groups
  2177.       ID_ELIMINATE_NAKEDPAIRS     = NewID(), -- eliminate naked pairs
  2178.       ID_ELIMINATE_HIDDENPAIRS    = NewID(), -- eliminate hidden pairs
  2179.       ID_ELIMINATE_NAKEDTRIPLETS  = NewID(), -- eliminate naked triplets
  2180.       ID_ELIMINATE_HIDDENTRIPLETS = NewID(), -- eliminate hidden triplets
  2181.       ID_ELIMINATE_NAKEDQUADS     = NewID(), -- eliminate naked quads
  2182.       ID_ELIMINATE_HIDDENQUADS    = NewID(), -- eliminate hidden quads
  2183.     ID_SOLVE_SCANSINGLES   = NewID(), -- Solve for single lone values
  2184.     ID_SOLVE_SCANROWS      = NewID(), -- Solve for single values in rows
  2185.     ID_SOLVE_SCANCOLS      = NewID(), -- Solve for single values in cols
  2186.     ID_SOLVE_SCANBLOCKS    = NewID(), -- Solve for single values in blocks
  2187.     ID_SOLVE_SCANNING      = NewID(), -- Solve the puzzle by scanning
  2188.     ID_SOLVE_BRUTEFORCE    = NewID(), -- Solve the puzzle by brute force
  2189.  
  2190.     ID_ABOUT               = NewID(),
  2191.     ID_HELP                = NewID(),
  2192.  
  2193.     menuCheckIDs        = {}, -- the current state of the check menu items
  2194.                               --  GTK doesn't like to access them in EVT_PAINT
  2195.                               --  we don't have to set them since nil == false
  2196.  
  2197.     sudokuTables        = {}, -- A table of the sudoku tables for undoing, should always be 1
  2198.     sudokuTables_pos    = 0,  -- Current position in the tables
  2199.  
  2200.     sudokuSolnTable       = nil, -- solution to the current puzzle
  2201.     nonunique_init_puzzle = nil, -- nil for don't know, is true/false once verified
  2202.  
  2203.     possNakedTable         = nil,
  2204.     possHiddenTable        = nil,
  2205.  
  2206.     pencilMarks            = {},  -- pencilMarks[cell][value] = true/nil
  2207.     pencilMarksNakedTable  = nil,
  2208.     pencilMarksHiddenTable = nil,
  2209.  
  2210.     difficulty             = 30, -- number of cells to keep for new puzzle
  2211.  
  2212.     sayings_n = 0,               -- number of sayings, calculated later
  2213.     sayings = {
  2214.         "Pondering...     ",
  2215.         "Musing...        ",
  2216.         "Meditating...    ",
  2217.         "Contemplating... ",
  2218.         "Reflecting...    ",
  2219.         "Mulling...       ",
  2220.         "Considering...   ",
  2221.         "Deliberating...  ",
  2222.         "Thinking...      ",
  2223.         "Working...       ",
  2224.         "Grinding...      ",
  2225.         "Brooding...      ",
  2226.         "Languishing?     ",
  2227.         "Despairing...    ",
  2228.  
  2229.         "To be, or not to be that is the question ",
  2230.         "Whether 'tis nobler in the mind to suffer ",
  2231.         "The slings and arrows of outrageous fortune, ",
  2232.         "Or to take arms against a sea of troubles, ",
  2233.         "And by opposing end them? ",
  2234.         "To die: to sleep; ",
  2235.         "No more; and by a sleep to say we end ",
  2236.         "The heart-ache and the thousand natural shocks",
  2237.         "That flesh is heir to, 'tis a consummation",
  2238.         "Devoutly to be wish'd. To die, to sleep;",
  2239.         "To sleep: perchance to dream:",
  2240.         "ay, there's the rub;",
  2241.         "For in that sleep of death what dreams may come",
  2242.         "When we have shuffled off this mortal coil,",
  2243.         "Must give us pause: there's the respect",
  2244.         "That makes calamity of so long life;",
  2245.         "For who would bear the whips and scorns of time,",
  2246.         "The oppressor's wrong, the proud man's contumely,",
  2247.         "The pangs of despised love, the law's delay,",
  2248.         "The insolence of office and the spurns",
  2249.         "That patient merit of the unworthy takes,",
  2250.         "When he himself might his quietus make",
  2251.         "With a bare bodkin? who would fardels bear,",
  2252.         "To grunt and sweat under a weary life,",
  2253.         "But that the dread of something after death,",
  2254.         "The undiscover'd country from whose bourn",
  2255.         "No traveller returns, puzzles the will",
  2256.         "And makes us rather bear those ills we have",
  2257.         "Than fly to others that we know not of?",
  2258.         "Thus conscience does make cowards of us all;",
  2259.         "And thus the native hue of resolution",
  2260.         "Is sicklied o'er with the pale cast of thought,",
  2261.         "And enterprises of great pitch and moment",
  2262.         "With this regard their currents turn awry,",
  2263.         "And lose the name of action.-- Soft you now!",
  2264.         "The fair Ophelia! Nymph, in thy orisons",
  2265.         "Be all my sins remember'd..."
  2266.     }
  2267. }
  2268.  
  2269. sudokuGUI.sayings_n = #sudokuGUI.sayings -- calc number of sayings
  2270.  
  2271. for cell = 1, 81 do
  2272.     sudokuGUI.pencilMarks[cell] = {}
  2273. end
  2274.  
  2275. -- ----------------------------------------------------------------------------
  2276. -- Run this function once to find the best font sizes to use and store them
  2277. --   if height/width = nil then use a default size,
  2278. --   else try to fit a font size to given height/width
  2279. function sudokuGUI.GetCellBestSize(cell_width, cell_height)
  2280.     local dc = wx.wxClientDC(sudokuGUI.frame)
  2281.  
  2282.     local size = sudokuGUI.DoGetCellBestSize(dc, cell_width, cell_height,
  2283.                         sudokuGUI.valueFont, sudokuGUI.possibleFont,
  2284.                         sudokuGUI.valueFont_cache, sudokuGUI.possibleFont_cache)
  2285.  
  2286.     dc:delete() -- ALWAYS delete() any wxDCs created when done
  2287.     return size
  2288. end
  2289.  
  2290. function sudokuGUI.DoGetCellBestSize(dc, cell_width, cell_height,
  2291.                                      valueFont, possibleFont,
  2292.                                      valueFont_cache, possibleFont_cache) -- cache are optional
  2293.  
  2294.     local function GetBestFontSize(dc, width, height, fontTable, font_cache)
  2295.  
  2296.         local function DoGetBestFontSize(step, largest)
  2297.             for s = fontTable.size, largest, step do
  2298.                 fontTable.size = s
  2299.  
  2300.                 if font_cache[fontTable.size] then
  2301.                     fontTable.width  = font_cache[fontTable.size].width
  2302.                     fontTable.height = font_cache[fontTable.size].height
  2303.                 else
  2304.                     fontTable.wxfont:SetPointSize(fontTable.size)
  2305.                     dc:SetFont(fontTable.wxfont)
  2306.                     fontTable.width, fontTable.height = dc:GetTextExtent("5")
  2307.  
  2308.                     font_cache[fontTable.size] = {}
  2309.                     font_cache[fontTable.size].width  = fontTable.width
  2310.                     font_cache[fontTable.size].height = fontTable.height
  2311.                 end
  2312.  
  2313.                 if (fontTable.height > height) or (fontTable.width > width) then
  2314.                     break
  2315.                 end
  2316.             end
  2317.         end
  2318.  
  2319.         fontTable.size = 2
  2320.         if not font_cache then font_cache = {} end -- use local font cache
  2321.  
  2322.         -- skip font sizes by 4 to get a rough estimate of the size
  2323.         DoGetBestFontSize(4, 1000)
  2324.         local largest = fontTable.size
  2325.         fontTable.size = iff(fontTable.size-3 > 1, fontTable.size-3, 2)
  2326.         -- get the best size to use
  2327.         DoGetBestFontSize(1, largest)
  2328.  
  2329.         -- use next smaller value that actually fits
  2330.         fontTable.size = iff(fontTable.size-1 > 1, fontTable.size-1, 2)
  2331.         fontTable.wxfont:SetPointSize(fontTable.size)
  2332.         fontTable.width  = font_cache[fontTable.size].width
  2333.         fontTable.height = font_cache[fontTable.size].height
  2334.  
  2335.         return fontTable
  2336.     end
  2337.  
  2338.     if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE) then
  2339.         possibleFont = GetBestFontSize(dc, (cell_width-2)/10, (cell_height-2), possibleFont, possibleFont_cache)
  2340.     else
  2341.         possibleFont = GetBestFontSize(dc, (cell_width-2)/3, (cell_height-2)/3, possibleFont, possibleFont_cache)
  2342.     end
  2343.  
  2344.     valueFont = GetBestFontSize(dc, cell_width-4, cell_height-4, valueFont, valueFont_cache)
  2345.  
  2346.     return wx.wxSize(valueFont.height+4, valueFont.height+4)
  2347. end
  2348.  
  2349. -- ----------------------------------------------------------------------------
  2350. -- Is this cell in an "odd" block for colouring the blocks
  2351. function sudokuGUI.IsOddBlockCell(cell)
  2352.     return math.fmod(sudoku.CellToBlock(cell), 2) ~= 0
  2353. end
  2354.  
  2355. -- ----------------------------------------------------------------------------
  2356. -- Create one of the 81 cell windows and connect the events to it
  2357. function sudokuGUI.CreateCellWindow(parent, winID, size)
  2358.     local win = wx.wxWindow(parent, winID,
  2359.                             wx.wxDefaultPosition, wx.wxDefaultSize,
  2360.                             wx.wxWANTS_CHARS+wx.wxSIMPLE_BORDER) --wxSUNKEN_BORDER)
  2361.  
  2362.     -- set the background colour to reduce flicker
  2363.     if sudokuGUI.IsOddBlockCell(winID) then
  2364.         win:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
  2365.     else
  2366.         win:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
  2367.     end
  2368.  
  2369.     win:Connect(wx.wxEVT_ERASE_BACKGROUND, function(event) end)
  2370.     win:Connect(wx.wxEVT_PAINT,       sudokuGUI.OnPaintCellWindow)
  2371.     win:Connect(wx.wxEVT_KEY_DOWN,    sudokuGUI.OnKeyDownCellWindow )
  2372.     win:Connect(wx.wxEVT_KEY_UP,      sudokuGUI.OnKeyUpCellWindow )
  2373.     win:Connect(wx.wxEVT_LEFT_DOWN,   sudokuGUI.OnLeftClickCellWindow )
  2374.     win:Connect(wx.wxEVT_LEFT_DCLICK, sudokuGUI.OnLeftDClickCellWindow )
  2375.     return win
  2376. end
  2377.  
  2378. -- ----------------------------------------------------------------------------
  2379. -- wxPaintEvent handler for all of the cell windows
  2380. function sudokuGUI.OnPaintCellWindow(event)
  2381.     local win = event:GetEventObject():DynamicCast("wxWindow")
  2382.  
  2383.     -- ALWAYS create a wxPaintDC in a wxEVT_PAINT handler, even if unused
  2384.     local dc = wx.wxPaintDC(win)
  2385.     if not sudokuGUI.block_refresh then
  2386.         local cell = win:GetId()
  2387.         local width, height = win:GetClientSizeWH()
  2388.         sudokuGUI.PaintCell(dc, cell, width, height, sudokuGUI.valueFont, sudokuGUI.possibleFont)
  2389.     end
  2390.     dc:delete() -- ALWAYS delete() any wxDCs created when done
  2391. end
  2392.  
  2393. function sudokuGUI.PaintCell(dc, cell, width, height, valueFont, possibleFont)
  2394.     -- clear the window before drawing
  2395.     dc:SetPen(wx.wxTRANSPARENT_PEN)
  2396.     local bgColour
  2397.     if sudokuGUI.focused_cell_id ~= cell then
  2398.         if sudokuGUI.IsOddBlockCell(cell) then
  2399.             bgColour = sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR]
  2400.         else
  2401.             bgColour = sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR]
  2402.         end
  2403.     else
  2404.         bgColour = sudokuGUI.Colours[sudokuGUI.FOCUS_CELL_COLOUR]
  2405.     end
  2406.     local brush = wx.wxBrush(bgColour, wx.wxSOLID)
  2407.     dc:SetBrush(brush)
  2408.     dc:DrawRectangle(0, 0, width, height)
  2409.     brush:delete()
  2410.  
  2411.     local sudokuTable = sudokuGUI.GetCurrentTable()
  2412.     local value_str, is_init = sudokuGUI.GetCellValueString(cell)
  2413.     local has_cell_value = string.len(value_str) ~= 0
  2414.  
  2415.     -- Draw the possible values
  2416.     local show_possible      = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE)
  2417.     local show_possible_user = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE)
  2418.     local show_possible_line = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE)
  2419.  
  2420.     if (show_possible or show_possible_user) and (not has_cell_value) then
  2421.         local possible = sudoku.GetCellPossible(sudokuTable, cell)
  2422.         if show_possible_user then
  2423.             possible = sudokuGUI.pencilMarks[cell]
  2424.         end
  2425.  
  2426.         dc:SetTextForeground(sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
  2427.         dc:SetFont(possibleFont.wxfont)
  2428.  
  2429.         -- find the positions of each possible value
  2430.         local pos = sudokuGUI.CalcPossiblePositions(width, height, possibleFont)
  2431.  
  2432.         local show_possible_user_all = false
  2433.         if show_possible_user and (cell == sudokuGUI.focused_cell_id) then
  2434.             show_possible_user_all = wx.wxGetKeyState(wx.WXK_SHIFT)
  2435.         end
  2436.  
  2437.         -- draw each one separately, even for line to ensure monospace
  2438.         for i = 1, 9 do
  2439.             if possible and possible[i] then
  2440.                 dc:DrawText(tostring(i), pos[i].x, pos[i].y)
  2441.             elseif show_possible_user_all then
  2442.                 dc:SetBackgroundMode(wx.wxSOLID)
  2443.                 dc:SetTextForeground(bgColour)
  2444.                 dc:SetTextBackground(sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
  2445.                 dc:DrawText(tostring(i), pos[i].x, pos[i].y)
  2446.                 dc:SetTextForeground(sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
  2447.                 dc:SetTextBackground(bgColour)
  2448.                 dc:SetBackgroundMode(wx.wxTRANSPARENT)
  2449.             end
  2450.         end
  2451.  
  2452.         local nakedTable  = sudokuGUI.possNakedTable
  2453.         local hiddenTable = sudokuGUI.possHiddenTable
  2454.         if show_possible_user then
  2455.             nakedTable  = sudokuGUI.pencilMarksNakedTable
  2456.             hiddenTable = sudokuGUI.pencilMarksHiddenTable
  2457.         end
  2458.  
  2459.         if nakedTable and hiddenTable then
  2460.             dc:SetBrush(wx.wxTRANSPARENT_BRUSH)
  2461.             local char0 = string.byte("0")
  2462.  
  2463.             local function draw_nakedhidden(colourID, num, key_table, hidden)
  2464.                 if (not key_table) or (#key_table < 1) then return end
  2465.  
  2466.                 local pen = wx.wxPen(sudokuGUI.Colours[colourID], 1, wx.wxSOLID)
  2467.                 dc:SetPen(pen)
  2468.  
  2469.                 for k = 1, #key_table do
  2470.                     for n = 1, num do
  2471.                         local val = string.byte(key_table[k], n)-char0
  2472.                         if hidden ~= true then
  2473.                             dc:DrawRectangle(pos[val].x, pos[val].y,
  2474.                                              possibleFont.width, possibleFont.height)
  2475.                         else
  2476.                             dc:DrawRoundedRectangle(pos[val].x, pos[val].y,
  2477.                                                     possibleFont.width, possibleFont.height,
  2478.                                                     20)
  2479.                         end
  2480.                     end
  2481.                 end
  2482.  
  2483.                 pen:delete()
  2484.             end
  2485.  
  2486.             -- draw pair marker last so it's on top of the others
  2487.             if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_HIDDENQUADS) then
  2488.                 draw_nakedhidden(sudokuGUI.HIDDEN_QUADS_COLOUR, 4, hiddenTable.quads.cells[cell], true)
  2489.             end
  2490.             if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_NAKEDQUADS) then
  2491.                 draw_nakedhidden(sudokuGUI.NAKED_QUADS_COLOUR, 4, nakedTable.quads.cells[cell])
  2492.             end
  2493.  
  2494.             if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_HIDDENTRIPLETS) then
  2495.                 draw_nakedhidden(sudokuGUI.HIDDEN_TRIPLETS_COLOUR, 3, hiddenTable.triplets.cells[cell], true)
  2496.             end
  2497.             if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_NAKEDTRIPLETS) then
  2498.                 draw_nakedhidden(sudokuGUI.NAKED_TRIPLETS_COLOUR, 3, nakedTable.triplets.cells[cell])
  2499.             end
  2500.  
  2501.             if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_HIDDENPAIRS) then
  2502.                 draw_nakedhidden(sudokuGUI.HIDDEN_PAIRS_COLOUR, 2, hiddenTable.pairs.cells[cell], true)
  2503.             end
  2504.             if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_NAKEDPAIRS) then
  2505.                 draw_nakedhidden(sudokuGUI.NAKED_PAIRS_COLOUR, 2, nakedTable.pairs.cells[cell])
  2506.             end
  2507.         end
  2508.     end
  2509.  
  2510.     -- mark invalid cells, always mark invalid if creating
  2511.     local show_errors = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_ERRORS)
  2512.     if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE) then show_errors = true end
  2513.     if show_errors then show_errors = sudokuGUI.GetCurrentTable().invalid[cell] end
  2514.  
  2515.     local show_mistakes = sudokuGUI.sudokuSolnTable and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES)
  2516.  
  2517.     if show_errors then
  2518.         dc:SetPen(wx.wxPen(sudokuGUI.Colours[sudokuGUI.INVALID_VALUE_COLOUR], 1, wx.wxSOLID))
  2519.         dc:DrawLine(0, 0, width, height)
  2520.         dc:DrawLine(width, 0, 0, height)
  2521.     elseif show_mistakes then
  2522.         if sudoku.HasCellValue(sudokuTable, cell) and
  2523.             (sudoku.GetCellValue(sudokuGUI.sudokuSolnTable, cell) ~=
  2524.              sudoku.GetCellValue(sudokuTable, cell)) then
  2525.             local pen = wx.wxPen(sudokuGUI.Colours[sudokuGUI.INVALID_VALUE_COLOUR], 1, wx.wxSOLID)
  2526.             dc:SetPen(pen)
  2527.             pen:delete()
  2528.             dc:DrawLine(0, 0, width, height)
  2529.         end
  2530.     end
  2531.  
  2532.     -- Draw the set value, if any
  2533.     if has_cell_value then
  2534.         local fgColour = sudokuGUI.Colours[sudokuGUI.VALUE_COLOUR]
  2535.         if is_init then
  2536.             fgColour = sudokuGUI.Colours[sudokuGUI.INIT_VALUE_COLOUR]
  2537.         end
  2538.  
  2539.         dc:SetTextForeground(fgColour)
  2540.  
  2541.         dc:SetFont(valueFont.wxfont)
  2542.         dc:DrawText(value_str, width/2  - valueFont.width/2,
  2543.                                height/2 - valueFont.height/2)
  2544.     end
  2545. end
  2546.  
  2547. -- ----------------------------------------------------------------------------
  2548.  
  2549. function sudokuGUI.CalcPossiblePositions(width, height, possibleFont)
  2550.     local pos = {}
  2551.     local show_possible_line = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE)
  2552.  
  2553.     if (sudokuGUI.possiblePosCache.pos and
  2554.         (sudokuGUI.possiblePosCache.cell_width  == width) and
  2555.         (sudokuGUI.possiblePosCache.cell_height == height) and
  2556.         (sudokuGUI.possiblePosCache.width  == possibleFont.width) and
  2557.         (sudokuGUI.possiblePosCache.height == possibleFont.height) and
  2558.         (sudokuGUI.possiblePosCache.line   == show_possible_line)) then
  2559.         pos = sudokuGUI.possiblePosCache.pos
  2560.     else
  2561.         if show_possible_line then
  2562.             for i = 1, 9 do
  2563.                 pos[i] = { x = (1+ (i-1)*possibleFont.width), y = 1 }
  2564.             end
  2565.         else
  2566.             local c = 0
  2567.             local horiz_space = (width  - 3*possibleFont.width)/4
  2568.             local vert_space  = (height - 3*possibleFont.height)/4
  2569.             local h_space = horiz_space
  2570.             local v_space = vert_space
  2571.  
  2572.             for j = 1, 3 do
  2573.                 -- try to center it a little better
  2574.                 if (j == 1) and (vert_space - math.floor(vert_space) > .3) then
  2575.                     v_space = vert_space+1
  2576.                 else
  2577.                     v_space = vert_space
  2578.                 end
  2579.  
  2580.                 for i = 1, 3 do
  2581.                     c = c + 1
  2582.                     if (i == 1) and (horiz_space - math.floor(horiz_space) > .3) then
  2583.                         h_space = horiz_space+1
  2584.                     else
  2585.                         h_space = horiz_space
  2586.                     end
  2587.                     pos[c] = { x = i*h_space + (i-1)*possibleFont.width,
  2588.                                y = j*v_space + (j-1)*possibleFont.height }
  2589.                 end
  2590.             end
  2591.         end
  2592.  
  2593.         -- cache these values for next cell
  2594.         sudokuGUI.possiblePosCache.pos         = pos
  2595.         sudokuGUI.possiblePosCache.cell_width  = width
  2596.         sudokuGUI.possiblePosCache.cell_height = height
  2597.         sudokuGUI.possiblePosCache.width       = possibleFont.width
  2598.         sudokuGUI.possiblePosCache.height      = possibleFont.height
  2599.         sudokuGUI.possiblePosCache.line        = show_possible_line
  2600.     end
  2601.  
  2602.     return pos
  2603. end
  2604.  
  2605. -- ----------------------------------------------------------------------------
  2606.  
  2607. function sudokuGUI.ConnectPrintEvents(printOut)
  2608.     printOut:SetPageInfo(1, 1, 1, 1)
  2609.     printOut.HasPage = function(self, pageNum) return pageNum == 1 end
  2610.     --printOut.GetPageInfo = function(self) return 1, 1, 1, 1 end
  2611.  
  2612.     printOut.OnPrintPage = function(self, pageNum)
  2613.         local dc = self:GetDC()
  2614.  
  2615.         local ppiScr_width, ppiScr_height = self:GetPPIScreen()
  2616.         local ppiPrn_width, ppiPrn_height = self:GetPPIPrinter()
  2617.         local ppi_scale_x = ppiPrn_width/ppiScr_width
  2618.         local ppi_scale_y = ppiPrn_height/ppiScr_height
  2619.  
  2620.         -- Get the size of DC in pixels and the number of pixels in the page
  2621.         local dc_width, dc_height = dc:GetSize()
  2622.         local pagepix_width, pagepix_height = self:GetPageSizePixels()
  2623.  
  2624.         local dc_pagepix_scale_x = dc_width/pagepix_width
  2625.         local dc_pagepix_scale_y = dc_height/pagepix_height
  2626.  
  2627.         -- If printer pageWidth == current DC width, then this doesn't
  2628.         -- change. But w might be the preview bitmap width, so scale down.
  2629.         local dc_scale_x = ppi_scale_x * dc_pagepix_scale_x
  2630.         local dc_scale_y = ppi_scale_y * dc_pagepix_scale_y
  2631.  
  2632.         -- calculate the pixels / mm (25.4 mm = 1 inch)
  2633.         local ppmm_x = ppiScr_width / 25.4
  2634.         local ppmm_y = ppiScr_height / 25.4
  2635.  
  2636.         -- Adjust the page size for the pixels / mm scaling factor
  2637.         local pageMM_width, pageMM_height = self:GetPageSizeMM()
  2638.         local pagerect_x, pagerect_y = 0, 0
  2639.         local pagerect_w, pagerect_h = pageMM_width * ppmm_x, pageMM_height * ppmm_y
  2640.  
  2641.         -- get margins informations and convert to printer pixels
  2642.         local topLeft     = sudokuGUI.pageSetupData:GetMarginTopLeft()
  2643.         local bottomRight = sudokuGUI.pageSetupData:GetMarginBottomRight()
  2644.  
  2645.         local top    = topLeft:GetY()     * ppmm_y
  2646.         local bottom = bottomRight:GetY() * ppmm_y
  2647.         local left   = topLeft:GetX()     * ppmm_x
  2648.         local right  = bottomRight:GetX() * ppmm_x
  2649.  
  2650.         local printrect_x, printrect_y = left, top
  2651.         local printrect_w, printrect_h = pagerect_w-(left+right), pagerect_h-(top+bottom)
  2652.  
  2653.         dc:SetUserScale(dc_scale_x, dc_scale_y);
  2654.  
  2655.         local cell_width  = (printrect_w)/11
  2656.         local cell_height = (printrect_h)/11
  2657.         if cell_width < cell_height then cell_height = cell_width end
  2658.         if cell_width > cell_height then cell_width  = cell_height end
  2659.  
  2660.         -- calculate font sizes for the printout, copy font since we'll recalc the size
  2661.         local valueFont    = { wxfont = wx.wxFont(sudokuGUI.valueFont.wxfont),    size = 8, width = 0, height = 0 }
  2662.         local possibleFont = { wxfont = wx.wxFont(sudokuGUI.possibleFont.wxfont), size = 6, width = 0, height = 0 }
  2663.         sudokuGUI.DoGetCellBestSize(dc, cell_width, cell_height,
  2664.                                     valueFont, possibleFont)
  2665.  
  2666.         local function RowOrigin(row) return printrect_x + row*cell_height + row end
  2667.         local function ColOrigin(col) return printrect_y + col*cell_width + col end
  2668.  
  2669.         local old_focused_cell_id = sudokuGUI.focused_cell_id -- clear focus
  2670.         sudokuGUI.focused_cell_id = 0
  2671.  
  2672.         for row = 1, 9 do
  2673.             for col = 1, 9 do
  2674.                 local x = ColOrigin(col)
  2675.                 local y = RowOrigin(row)
  2676.                 dc:SetDeviceOrigin(x*dc_scale_x, y*dc_scale_x)
  2677.                 local cell = sudoku.RowColToCell(row, col)
  2678.                 sudokuGUI.PaintCell(dc, cell, cell_width, cell_height, valueFont, possibleFont)
  2679.             end
  2680.         end
  2681.  
  2682.         valueFont.wxfont:delete()
  2683.         possibleFont.wxfont:delete()
  2684.  
  2685.         sudokuGUI.focused_cell_id = old_focused_cell_id -- restore focus
  2686.  
  2687.         dc:SetDeviceOrigin(0, 0)
  2688.         local borders = { [1]=true, [4]=true, [7]=true, [10]=true }
  2689.         for i = 1, 10 do
  2690.             local pen = wx.wxPen(wx.wxBLACK, iff(borders[i], 4, 2), wx.wxSOLID)
  2691.             dc:SetPen(pen)
  2692.             pen:delete()
  2693.             dc:DrawLine(ColOrigin(1), RowOrigin(i), ColOrigin(10), RowOrigin(i))
  2694.             dc:DrawLine(ColOrigin(i), RowOrigin(1), ColOrigin(i),  RowOrigin(10))
  2695.         end
  2696.  
  2697.         return true
  2698.    end
  2699. end
  2700.  
  2701. function sudokuGUI.Print()
  2702.     local printDialogData = wx.wxPrintDialogData(sudokuGUI.printData)
  2703.     local printer = wx.wxPrinter(printDialogData)
  2704.  
  2705.     local luaPrintout = wx.wxLuaPrintout("wxLuaSudoku Printout")
  2706.     sudokuGUI.ConnectPrintEvents(luaPrintout)
  2707.  
  2708.     if printer:Print(sudokuGUI.frame, luaPrintout, true) == false then
  2709.         if printer:GetLastError() == wx.wxPRINTER_ERROR then
  2710.             wx.wxMessageBox("There was a problem printing.\n"..
  2711.                             "Perhaps your current printer is not setup correctly?",
  2712.                             "wxLuaSudoku Printout",
  2713.                             wx.wxOK, sudokuGUI.frame)
  2714.         end
  2715.     else
  2716.         sudokuGUI.printData = printer:GetPrintDialogData():GetPrintData():Copy()
  2717.     end
  2718. end
  2719.  
  2720. function sudokuGUI.PrintPreview()
  2721.     luaPrintout      = wx.wxLuaPrintout("wxLuaSudoku Print Preview")
  2722.     luaPrintPrintout = wx.wxLuaPrintout("wxLuaSudoku Printout")
  2723.     sudokuGUI.ConnectPrintEvents(luaPrintout)
  2724.     sudokuGUI.ConnectPrintEvents(luaPrintPrintout)
  2725.  
  2726.     local printDialogData = wx.wxPrintDialogData(sudokuGUI.printData):GetPrintData()
  2727.     local preview         = wx.wxPrintPreview(luaPrintout, luaPrintPrintout, printDialogData)
  2728.  
  2729.     local result = preview:Ok()
  2730.     if result == false then
  2731.         wx.wxMessageBox("There was a problem previewing.\n"..
  2732.                         "Perhaps your current printer is not setup correctly?",
  2733.                         "wxLuaSudoku print preview error",
  2734.                         wx.wxOK, sudokuGUI.frame)
  2735.     else
  2736.         local previewFrame = wx.wxPreviewFrame(preview, sudokuGUI.frame,
  2737.                                                "wxLuaSudoku print preview",
  2738.                                                wx.wxDefaultPosition, wx.wxSize(600, 600))
  2739.  
  2740.         previewFrame:Connect(wx.wxEVT_CLOSE_WINDOW,
  2741.                 function (event)
  2742.                     previewFrame:Destroy()
  2743.                     event:Skip()
  2744.                 end )
  2745.  
  2746.         previewFrame:Centre(wx.wxBOTH)
  2747.         previewFrame:Initialize()
  2748.         previewFrame:Show(true)
  2749.     end
  2750. end
  2751.  
  2752. function sudokuGUI.PrintSetup() -- FIXME DEPRICATED IN WXWIDGETS?
  2753.     local printDialogData = wx.wxPrintDialogDataFromPrintData(sudokuGUI.printData)
  2754.     local printerDialog   = wx.wxPrintDialog(sudokuGUI.frame, printDialogData)
  2755.     printerDialog:GetPrintDialogData():SetSetupDialog(true)
  2756.     printerDialog:ShowModal()
  2757.     sudokuGUI.printData = printerDialog:GetPrintDialogData():GetPrintData():Copy()
  2758.     --printerDialog:Destroy()
  2759. end
  2760.  
  2761. function sudokuGUI.PageSetup()
  2762.     sudokuGUI.printData = sudokuGUI.pageSetupData:GetPrintData():Copy()
  2763.     local pageSetupDialog = wx.wxPageSetupDialog(sudokuGUI.frame, sudokuGUI.pageSetupData)
  2764.     pageSetupDialog:ShowModal()
  2765.     sudokuGUI.printData     = pageSetupDialog:GetPageSetupDialogData():GetPrintData():Copy()
  2766.     sudokuGUI.pageSetupData = pageSetupDialog:GetPageSetupDialogData():Copy()
  2767.     --pageSetupDialog:Destroy()
  2768. end
  2769.  
  2770. -- ----------------------------------------------------------------------------
  2771. -- Set the currently focused window, refresh new and old
  2772. function sudokuGUI.SetFocusWindow(cell)
  2773.     local last_id = sudokuGUI.focused_cell_id
  2774.     sudokuGUI.focused_cell_id = iff((cell>=1) and (cell<=81), cell, 0)
  2775.  
  2776.     if sudokuGUI.cellWindows[last_id] then
  2777.         sudokuGUI.cellWindows[last_id]:Refresh()
  2778.     end
  2779.     if sudokuGUI.cellWindows[cell] then
  2780.         sudokuGUI.cellWindows[cell]:Refresh()
  2781.     end
  2782. end
  2783.  
  2784. -- ----------------------------------------------------------------------------
  2785.  
  2786. function sudokuGUI.OnKeyUpCellWindow(event)
  2787.     event:Skip()
  2788.     -- we don't care who actually got this event, just use the "focused cell"
  2789.     if (sudokuGUI.focused_cell_id < 1) then return end
  2790.  
  2791.     local key = event:GetKeyCode()
  2792.  
  2793.     if (sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) == true) and
  2794.         (key == wx.WXK_SHIFT) then
  2795.         sudokuGUI.cellWindows[sudokuGUI.focused_cell_id]:Refresh(false)
  2796.         return
  2797.     end
  2798.  
  2799. end
  2800.  
  2801. -- Left down click handler for the cell windows, hide the cell editor
  2802. function sudokuGUI.OnKeyDownCellWindow(event)
  2803.     event:Skip()
  2804.     -- we don't care who actually got this event, just use the "focused cell"
  2805.     if (sudokuGUI.focused_cell_id < 1) then return end
  2806.  
  2807.     local key = event:GetKeyCode()
  2808.  
  2809.     if (sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) == true) and
  2810.         (key == wx.WXK_SHIFT) then
  2811.         sudokuGUI.cellWindows[sudokuGUI.focused_cell_id]:Refresh(false)
  2812.     end
  2813.  
  2814.     if event:HasModifiers() or event:AltDown() or event:ControlDown() then
  2815.         sudokuGUI.cellWindows[sudokuGUI.focused_cell_id]:Refresh(false)
  2816.         return
  2817.     end
  2818.     -- clear the current focused window
  2819.     if (key == wx.WXK_ESCAPE) then
  2820.         sudokuGUI.SetFocusWindow(0)
  2821.         return
  2822.     end
  2823.  
  2824.     -- the cursor keys move the focus cell
  2825.     local movefocus =
  2826.     {
  2827.         [wx.WXK_LEFT]       = -1, [wx.WXK_NUMPAD_LEFT]  = -1,
  2828.         [wx.WXK_UP]         = -9, [wx.WXK_NUMPAD_UP]    = -9,
  2829.         [wx.WXK_RIGHT]      = 1,  [wx.WXK_NUMPAD_RIGHT] = 1,
  2830.         [wx.WXK_DOWN]       = 9,  [wx.WXK_NUMPAD_DOWN]  = 9,
  2831.  
  2832.         [wx.WXK_PAGEUP]     = -9, [wx.WXK_PRIOR] = -9,
  2833.         [wx.WXK_PAGEDOWN]   = 9,  [wx.WXK_NEXT]  = 9,
  2834.  
  2835.         [wx.WXK_NUMPAD_HOME]     = -10,
  2836.         [wx.WXK_NUMPAD_PAGEUP]   = -8, [wx.WXK_NUMPAD_PRIOR] = -8,
  2837.         [wx.WXK_NUMPAD_END]      = 8,
  2838.         [wx.WXK_NUMPAD_PAGEDOWN] = 10, [wx.WXK_NUMPAD_NEXT]  = 10,
  2839.  
  2840.         [wx.WXK_TAB]          = 1,
  2841.         [wx.WXK_RETURN]       = 1,
  2842.         [wx.WXK_NUMPAD_ENTER] = 1
  2843.     }
  2844.  
  2845.     if (key == wx.WXK_HOME) then
  2846.         sudokuGUI.SetFocusWindow(1)
  2847.         return
  2848.     elseif (key == wx.WXK_END) then
  2849.         sudokuGUI.SetFocusWindow(81)
  2850.         return
  2851.     elseif movefocus[key] then
  2852.         local cell = sudokuGUI.focused_cell_id + movefocus[key]
  2853.         if (cell >= 1) and (cell <= 81) then
  2854.             sudokuGUI.SetFocusWindow(cell)
  2855.         end
  2856.         return
  2857.     end
  2858.  
  2859.     -- translate number pad keys to numbers
  2860.     local numpad =
  2861.     {
  2862.         [wx.WXK_NUMPAD0] = 0,
  2863.         [wx.WXK_NUMPAD1] = 1,
  2864.         [wx.WXK_NUMPAD2] = 2,
  2865.         [wx.WXK_NUMPAD3] = 3,
  2866.         [wx.WXK_NUMPAD4] = 4,
  2867.         [wx.WXK_NUMPAD5] = 5,
  2868.         [wx.WXK_NUMPAD6] = 6,
  2869.         [wx.WXK_NUMPAD7] = 7,
  2870.         [wx.WXK_NUMPAD8] = 8,
  2871.         [wx.WXK_NUMPAD9] = 9,
  2872.  
  2873.         [wx.WXK_DELETE]         = 0,
  2874.         [wx.WXK_BACK]           = 0,
  2875.         [wx.WXK_SPACE]          = 0,
  2876.         [wx.WXK_NUMPAD_INSERT]  = 0,
  2877.         [wx.WXK_NUMPAD_DECIMAL] = 0,
  2878.         [wx.WXK_NUMPAD_DELETE]  = 0,
  2879.     }
  2880.  
  2881.     local zero = string.byte("0")
  2882.     if numpad[key] then key = zero + numpad[key] end
  2883.  
  2884.     if (key < 32) or (key > 127) then return end -- normal ASCII key
  2885.  
  2886.     local one  = string.byte("1")
  2887.     local nine = string.byte("9")
  2888.  
  2889.     if (key >= one) and (key <= nine) then
  2890.         key = key - one + 1
  2891.     else
  2892.         key = 0
  2893.     end
  2894.  
  2895.     if event:ShiftDown() and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) then
  2896.         if (key >= 1) and (key <= 9) then
  2897.             sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][key] = iff(sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][key], nil, key)
  2898.             sudokuGUI.UpdateTable()
  2899.         end
  2900.     else
  2901.         sudokuGUI.SetCellValue(sudokuGUI.focused_cell_id, key)
  2902.     end
  2903. end
  2904.  
  2905. -- ----------------------------------------------------------------------------
  2906. -- Test to see if one of the possible values has been clicked on
  2907. function sudokuGUI.HitTestPossibleValue(mx, my)
  2908.     local cell = sudokuGUI.focused_cell_id
  2909.     if (cell < 1) or (cell > 81) then return nil end
  2910.  
  2911.     local w = sudokuGUI.possiblePosCache.width
  2912.     local h = sudokuGUI.possiblePosCache.height
  2913.     local rect = wx.wxRect(0, 0, w, h)
  2914.  
  2915.     for n = 1, 9 do
  2916.         rect.X = sudokuGUI.possiblePosCache.pos[n].x
  2917.         rect.Y = sudokuGUI.possiblePosCache.pos[n].y
  2918.         if rect:Inside(mx, my) then
  2919.             return n
  2920.         end
  2921.     end
  2922.  
  2923.     return nil
  2924. end
  2925.  
  2926. -- ----------------------------------------------------------------------------
  2927. -- Left down click handler for the cell windows, hide the cell editor
  2928. function sudokuGUI.OnLeftClickCellWindow(event)
  2929.     event:Skip()
  2930.  
  2931.     local win = event:GetEventObject():DynamicCast("wxWindow")
  2932.     local winId = win:GetId()
  2933.  
  2934.     if sudokuGUI.cellTextCtrl then
  2935.         sudokuGUI.SaveCellTextCtrlValue()
  2936.         sudokuGUI.DestroyCellTextCtrl()
  2937.     end
  2938.  
  2939.     sudokuGUI.SetFocusWindow(winId)
  2940.  
  2941.     if event:ShiftDown() and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) then
  2942.         local p = sudokuGUI.HitTestPossibleValue(event:GetX(), event:GetY())
  2943.         if p then
  2944.             sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][p] = iff(sudokuGUI.pencilMarks[sudokuGUI.focused_cell_id][p], nil, p)
  2945.             sudokuGUI.cellWindows[winId]:Refresh(false)
  2946.         end
  2947.     end
  2948.  
  2949. end
  2950. -- Left double click handler for the cell windows, hide old, show new cell editor
  2951. function sudokuGUI.OnLeftDClickCellWindow(event)
  2952.     event:Skip()
  2953.     local win = event:GetEventObject():DynamicCast("wxWindow")
  2954.     local winId = win:GetId()
  2955.     local winWidth, winHeight = win:GetSizeWH()
  2956.  
  2957.     if event:ShiftDown() then return end
  2958.  
  2959.     if sudokuGUI.cellTextCtrl then
  2960.         if sudokuGUI.cellTextCtrl:GetId() == winId then
  2961.             sudokuGUI.cellTextCtrl:Show(true)
  2962.             return
  2963.         end
  2964.         sudokuGUI.SaveCellTextCtrlValue()
  2965.         sudokuGUI.DestroyCellTextCtrl()
  2966.     end
  2967.  
  2968.     local is_creating = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE)
  2969.     local value, is_init = sudokuGUI.GetCellValueString(winId)
  2970.     if is_init and (not is_creating) then return end
  2971.  
  2972.     sudokuGUI.cellTextCtrl = wx.wxTextCtrl(win, winId, value,
  2973.                             wx.wxPoint(0, 0), wx.wxSize(winWidth, winHeight),
  2974.                             wx.wxTE_PROCESS_ENTER+wx.wxTE_CENTRE)
  2975.     sudokuGUI.cellTextCtrl:SetFont(sudokuGUI.valueFont.wxfont)
  2976.     sudokuGUI.cellTextCtrl:SetMaxLength(1)
  2977.     --local valid = wx.wxTextValidator(wx.wxFILTER_INCLUDE_LIST)
  2978.     --valid:SetIncludeList(
  2979.     --cellTextCtrl:SetValidator(valid)
  2980.  
  2981.     sudokuGUI.cellTextCtrl:Connect(winId, wx.wxEVT_COMMAND_TEXT_ENTER,
  2982.             function (event)
  2983.                 local win = event:GetEventObject():DynamicCast("wxWindow")
  2984.                 sudokuGUI.SaveCellTextCtrlValue()
  2985.                 win:Show(false) -- just hide it, we'll destroy it later
  2986.             end)
  2987.  
  2988.     sudokuGUI.cellTextCtrl:Connect(wx.wxEVT_CHAR,
  2989.             function (event)
  2990.                 if (event:GetKeyCode() == wx.WXK_ESCAPE) then
  2991.                     sudokuGUI.cellTextCtrl:Show(false)
  2992.                     sudokuGUI.cellTextCtrl:SetValue("")
  2993.                 end
  2994.                 event:Skip()
  2995.             end)
  2996. end
  2997.  
  2998. function sudokuGUI.DestroyCellTextCtrl()
  2999.     if sudokuGUI.cellTextCtrl then
  3000.         sudokuGUI.cellTextCtrl:Show(false)
  3001.         sudokuGUI.cellTextCtrl:Destroy()
  3002.         sudokuGUI.cellTextCtrl = nil
  3003.     end
  3004. end
  3005.  
  3006. -- Save the value of the text editor back to the grid
  3007. function sudokuGUI.SaveCellTextCtrlValue()
  3008.     if not sudokuGUI.cellTextCtrl then return end
  3009.     local value = sudokuGUI.cellTextCtrl:GetValue()
  3010.     local cell = sudokuGUI.cellTextCtrl:GetId()
  3011.     sudokuGUI.SetCellValue(cell, value)
  3012. end
  3013.  
  3014. -- ----------------------------------------------------------------------------
  3015. -- Set the value of the cell from the value string
  3016. function sudokuGUI.SetCellValue(cell, value)
  3017.     -- fix the value to something reasonable
  3018.     if type(value) == "string" then
  3019.         if (value == "") or (value == " ") or (value == "0") then
  3020.             value = 0
  3021.         elseif (string.len(value) ~= 1) or (not string.find("123456789", value)) then
  3022.             return
  3023.         else
  3024.             value = tonumber(value)
  3025.         end
  3026.     end
  3027.  
  3028.     -- if value is still bad, just exit
  3029.     if not ((value == 0) or sudoku.IsValidValueN(value)) then return end
  3030.  
  3031.     local is_creating = sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE)
  3032.     local row, col = sudoku.CellToRowCol(cell)
  3033.     local is_init = sudoku.HasCellValue(sudokuGUI.sudokuTables[1], cell)
  3034.  
  3035.     if is_creating then
  3036.         if sudoku.GetCellValue(sudokuGUI.sudokuTables[1], cell) ~= value then
  3037.             -- add the value to all the tables since it's an init value
  3038.             for n = 1, TableCount(sudokuGUI.sudokuTables) do
  3039.                 sudoku.SetValue(sudokuGUI.sudokuTables[n], row, col, value)
  3040.                 sudoku.UpdateTable(sudokuGUI.sudokuTables[n])
  3041.             end
  3042.             -- refresh all in case it's invalid also if not at 1st table update possible
  3043.             sudokuGUI.UpdateTable(true)
  3044.             sudokuGUI.sudokuSolnTable = nil -- don't know anymore
  3045.         end
  3046.     else
  3047.         local s = sudokuGUI.GetCurrentTable()
  3048.         if (not is_init) and (sudoku.GetCellValue(s, cell) ~= value) then
  3049.             local s = TableCopy(s)
  3050.             sudoku.SetValue(s, row, col, value)
  3051.             sudokuGUI.AddTable(s)
  3052.         end
  3053.     end
  3054.  
  3055.     sudokuGUI.UpdateGUI()
  3056. end
  3057.  
  3058. -- ----------------------------------------------------------------------------
  3059. -- Get the initial sudoku table
  3060. function sudokuGUI.GetInitTable()
  3061.     return sudokuGUI.sudokuTables[1]
  3062. end
  3063. -- Set the initial sudoku table, clearing all others
  3064. function sudokuGUI.SetInitTable(sudokuTable, solnTable)
  3065.     sudokuGUI.sudokuSolnTable = solnTable
  3066.     sudokuGUI.sudokuTables_pos = 1
  3067.     sudokuGUI.sudokuTables = {}
  3068.     table.insert(sudokuGUI.sudokuTables, sudokuTable)
  3069.     sudokuGUI.UpdateTable() -- resets possible and refreshes too
  3070. end
  3071.  
  3072. -- ----------------------------------------------------------------------------
  3073. -- Get the current sudoku table to use
  3074. function sudokuGUI.GetCurrentTable()
  3075.     return sudokuGUI.sudokuTables[sudokuGUI.sudokuTables_pos]
  3076. end
  3077. -- Set the current sudoku table to this table
  3078. function sudokuGUI.SetCurrentTable(sudokuTable)
  3079.     sudokuGUI.sudokuTables[sudokuGUI.sudokuTables_pos] = sudokuTable
  3080. end
  3081.  
  3082. -- ----------------------------------------------------------------------------
  3083. -- Add a sudoku table to the list of tables, removing any past the current position,
  3084. --   find possible, and refresh
  3085. function sudokuGUI.AddTable(sudokuTable)
  3086.     while TableCount(sudokuGUI.sudokuTables) > sudokuGUI.sudokuTables_pos do
  3087.         table.remove(sudokuGUI.sudokuTables)
  3088.     end
  3089.  
  3090.     -- clear calculated values to save memory
  3091.     for n = 2, sudokuGUI.sudokuTables_pos do
  3092.         sudokuGUI.sudokuTables[n].row_values   = {}
  3093.         sudokuGUI.sudokuTables[n].col_values   = {}
  3094.         sudokuGUI.sudokuTables[n].block_values = {}
  3095.         sudokuGUI.sudokuTables[n].possible = {}
  3096.         sudokuGUI.sudokuTables[n].invalid  = {}
  3097.     end
  3098.  
  3099.     table.insert(sudokuGUI.sudokuTables, sudokuTable)
  3100.     sudokuGUI.sudokuTables_pos = sudokuGUI.sudokuTables_pos + 1
  3101.  
  3102.     sudokuGUI.UpdateTable()
  3103. end
  3104.  
  3105. -- ----------------------------------------------------------------------------
  3106. -- Get the value of the cell as a printable string
  3107. function sudokuGUI.GetCellValueString(cell)
  3108.     local value = ""
  3109.  
  3110.     if sudoku.HasCellValue(sudokuGUI.sudokuTables[1], cell) then
  3111.         return tostring(sudoku.GetCellValue(sudokuGUI.sudokuTables[1], cell)), true
  3112.     elseif sudoku.HasCellValue(sudokuGUI.GetCurrentTable(), cell) then
  3113.         value = tostring(sudoku.GetCellValue(sudokuGUI.GetCurrentTable(), cell))
  3114.     end
  3115.  
  3116.     return value, false
  3117. end
  3118.  
  3119. -- ----------------------------------------------------------------------------
  3120. -- refresh all the grid cells
  3121. function sudokuGUI.Refresh()
  3122.     for i = 1, 81 do
  3123.         if sudokuGUI.cellWindows[i] then
  3124.             sudokuGUI.cellWindows[i]:Refresh(false)
  3125.         end
  3126.     end
  3127.  
  3128.     sudokuGUI.UpdateGUI()
  3129. end
  3130.  
  3131. -- ----------------------------------------------------------------------------
  3132. -- Create a new empty puzzle
  3133. function sudokuGUI.NewPuzzle()
  3134.     local ret = wx.wxMessageBox("Clear all the values in the current puzzle and start anew?\n"..
  3135.                                 "Use 'Create' to enter the initial values.",
  3136.                                 "wxLuaSudoku - New puzzle?",
  3137.                                 wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  3138.                                 sudokuGUI.frame )
  3139.  
  3140.     if ret == wx.wxOK then
  3141.         sudokuGUI.SetInitTable(sudoku.CreateTable(), nil)
  3142.     end
  3143. end
  3144.  
  3145. -- ----------------------------------------------------------------------------
  3146. -- Create a puzzle by hand
  3147. function sudokuGUI.CreatePuzzle(init)
  3148.     local enableIds =
  3149.     {
  3150.         sudokuGUI.ID_GENERATE,
  3151.         sudokuGUI.ID_OPEN,
  3152.         sudokuGUI.ID_SAVEAS,
  3153.  
  3154.         sudokuGUI.ID_RESET,
  3155.         sudokuGUI.ID_UNDO,
  3156.         sudokuGUI.ID_REDO,
  3157.  
  3158.         sudokuGUI.ID_SOLVE_SCANSINGLES,
  3159.         sudokuGUI.ID_SOLVE_SCANROWS,
  3160.         sudokuGUI.ID_SOLVE_SCANCOLS,
  3161.         sudokuGUI.ID_SOLVE_SCANBLOCKS,
  3162.         sudokuGUI.ID_SOLVE_SCANNING,
  3163.         sudokuGUI.ID_SOLVE_BRUTEFORCE
  3164.     }
  3165.  
  3166.     sudokuGUI.CheckMenuItem(sudokuGUI.ID_CREATE, init)
  3167.  
  3168.     if init then
  3169.         local ret = wx.wxMessageBox(
  3170.             "Enter values in the cells to initialize the puzzle with.\n"..
  3171.             "Previous cell values will be overwritten.\n"..
  3172.             "Don't forget to uncheck 'Create' before playing.",
  3173.             "wxLuaSudoku - Initialize puzzle?",
  3174.             wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  3175.             sudokuGUI.frame )
  3176.  
  3177.         if ret == wx.wxCANCEL then
  3178.             sudokuGUI.CheckMenuItem(sudokuGUI.ID_CREATE, false)
  3179.             return
  3180.         end
  3181.     else
  3182.         sudokuGUI.sudokuSolnTable = nil -- reset to unknown
  3183.  
  3184.         if not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid) then
  3185.             -- try to make them correct the puzzle
  3186.             local ret = wx.wxMessageBox(
  3187.                 "The initial puzzle you've created has invalid values.\n"..
  3188.                 "Press 'Ok' to correct them before continuing.\n"..
  3189.                 "If you press 'Cancel' showing mistakes will be disabled and "..
  3190.                 "don't blame me if things don't work out for you.",
  3191.                 "wxLuaSudoku - Invalid initial puzzle!",
  3192.                 wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
  3193.                 sudokuGUI.frame )
  3194.  
  3195.             if ret == wx.wxOK then
  3196.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_CREATE, true)
  3197.                 init = true
  3198.             end
  3199.         else --if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES) then
  3200.             sudokuGUI.sudokuSolnTable = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
  3201.         end
  3202.  
  3203.         if (not sudokuGUI.sudokuSolnTable) and sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES) then
  3204.             sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_MISTAKES, false)
  3205.         end
  3206.     end
  3207.  
  3208.     for n, id in pairs(enableIds) do
  3209.         sudokuGUI.frame:GetMenuBar():Enable(id, not init)
  3210.         sudokuGUI.frame:GetToolBar():EnableTool(id, not init)
  3211.     end
  3212.  
  3213.     sudokuGUI.UpdateTable()
  3214. end
  3215.  
  3216. -- ----------------------------------------------------------------------------
  3217. -- Generate a new puzzle automatically
  3218. function sudokuGUI.GeneratePuzzle()
  3219.     local keep = wx.wxGetNumberFromUser("Set the difficulty of the new puzzle by clearing cells.\n"..
  3220.                                         "Note: The minimum number of cells to show for a unique puzzle is 17.",
  3221.                                         "Number of cell values to show",
  3222.                                         "wxLuaSudoku - Generate puzzle?",
  3223.                                         sudokuGUI.difficulty, 1, 81,
  3224.                                         sudokuGUI.frame)
  3225.     if keep < 1 then return end -- canceled
  3226.  
  3227.     sudokuGUI.difficulty = keep
  3228.  
  3229.     local solve_progress = 0
  3230.     local start_time     = os.time()
  3231.     local last_time      = start_time
  3232.     local solve_ok       = true
  3233.     local msg_idx        = 1
  3234.  
  3235.     local progressDialog = wx.wxProgressDialog("wxLuaSudoku - Generating...",
  3236.                            string.format("%s\nIteration # %d, current cell %d            ", sudokuGUI.sayings[1], 0, 0),
  3237.                            1000, sudokuGUI.frame,
  3238.                            wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
  3239.  
  3240.     -- define handler function here so it'll work w/o gui
  3241.     function sudoku.GeneratePuzzleHook(count, cell)
  3242.         if solve_ok == false then return false end -- canceled
  3243.         solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
  3244.         if solve_progress%10 ~= 0 then return true end
  3245.         if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
  3246.             msg_idx = msg_idx + 1
  3247.             last_time = os.time()
  3248.         end
  3249.         local msg = string.format("%s\nIteration # %d, current cell %d            ", sudokuGUI.sayings[msg_idx], count, cell)
  3250.         solve_ok = progressDialog:Update(solve_progress, msg)
  3251.         return solve_ok
  3252.     end
  3253.  
  3254.     local s, count = sudoku.GeneratePuzzle()
  3255.     progressDialog:Destroy()
  3256.     if not s then return end
  3257.  
  3258.     -- have complete puzzle, now remove cells
  3259.  
  3260.     local diff_count = 0
  3261.     local diff_i     = 0
  3262.     local diff_cell  = 0
  3263.     local diff_open  = 0
  3264.     local diff_trial = 0
  3265.  
  3266.     -- define handler function here so it'll work w/o gui
  3267.     function sudoku.GeneratePuzzleDifficultyHook(count, i, cell, open_cells, trial)
  3268.         diff_count     = count
  3269.         diff_i         = i
  3270.         diff_cell      = cell
  3271.         diff_open      = open_cells
  3272.         diff_trial     = trial
  3273.         if solve_ok == false then return false end -- canceled
  3274.         if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
  3275.             msg_idx = msg_idx + 1
  3276.             last_time = os.time()
  3277.         end
  3278.         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)
  3279.         solve_ok = progressDialog:Update(i, msg)
  3280.         return solve_ok
  3281.     end
  3282.  
  3283.     -- hook into brute force solver to update the generate puzzle progress dialog
  3284.     function sudoku.SolveBruteForceHook(guesses, cell)
  3285.         solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
  3286.         if solve_progress%10 ~= 0 then return true end
  3287.         return sudoku.GeneratePuzzleDifficultyHook(diff_count, diff_i, diff_cell, diff_open, diff_trial)
  3288.     end
  3289.  
  3290.     local ensure_unique = true
  3291.  
  3292.     while 1 do
  3293.         diff_count = 0
  3294.         diff_i     = 0
  3295.         diff_cell  = 0
  3296.         diff_open  = 0
  3297.         diff_trial = 0
  3298.  
  3299.         solve_progress = 0
  3300.         start_time     = os.time()
  3301.         last_time      = start_time
  3302.         solve_ok       = true
  3303.         msg_idx        = 1
  3304.  
  3305.         local caption = "wxLuaSudoku - Ensuring unique solution..."
  3306.         if ensure_unique == false then
  3307.             caption = "wxLuaSudoku - Removing values randomly..."
  3308.         end
  3309.  
  3310.         progressDialog = wx.wxProgressDialog(caption,
  3311.                             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),
  3312.                             81 - sudokuGUI.difficulty + 1, sudokuGUI.frame,
  3313.                             wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
  3314.  
  3315.         local s1 = sudoku.GeneratePuzzleDifficulty(TableCopy(s), sudokuGUI.difficulty, ensure_unique)
  3316.         progressDialog:Destroy()
  3317.  
  3318.         if s1 then
  3319.             if ensure_unique then
  3320.                 sudokuGUI.SetInitTable(s1, TableCopy(s))
  3321.             else
  3322.                 -- verify the puzzle anyway to let them know the status
  3323.                 local s2 = sudokuGUI.VerifyUniquePuzzle(s1)
  3324.                 sudokuGUI.SetInitTable(s1, s2)
  3325.             end
  3326.             break
  3327.         else
  3328.             local ret = wx.wxMessageBox("The puzzle was not fully generated. "..
  3329.                                         "Press 'Ok' to randomly remove cell values which may or may not "..
  3330.                                         "yield a unique puzzle or 'Cancel' to abort",
  3331.                                         "wxLuaSudoku - Unfinished generation",
  3332.                                         wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
  3333.                                         sudokuGUI.frame)
  3334.  
  3335.             if ret == wx.wxOK then
  3336.                 ensure_unique = false
  3337.             else
  3338.                 break
  3339.             end
  3340.         end
  3341.     end
  3342. end
  3343.  
  3344. -- ----------------------------------------------------------------------------
  3345. -- Open a puzzle from a file
  3346. function sudokuGUI.OpenPuzzle()
  3347.     local fileDialog = wx.wxFileDialog(sudokuGUI.frame, "Open file",
  3348.                                        sudokuGUI.filePath, sudokuGUI.fileName,
  3349.                                        "wxLuaSudoku files (*.sudoku)|*.sudoku|All files (*)|*",
  3350.                                        wx.wxOPEN + wx.wxFILE_MUST_EXIST)
  3351.     if fileDialog:ShowModal() == wx.wxID_OK then
  3352.         local fileName = fileDialog:GetPath()
  3353.         local fn = wx.wxFileName(fileName)
  3354.         sudokuGUI.filePath = fn:GetPath()
  3355.         sudokuGUI.fileName = fn:GetFullName()
  3356.  
  3357.         local s, msg = sudoku.Open(fileName)
  3358.         if s then
  3359.             sudokuGUI.frame:SetTitle("wxLuaSudoku - "..sudokuGUI.fileName)
  3360.  
  3361.             sudokuGUI.SetInitTable(s, nil)
  3362.  
  3363.             if not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid) then
  3364.                 -- make them correct the puzzle
  3365.                 local ret = wx.wxMessageBox(
  3366.                     "The puzzle you've opened has invalid values.\n"..
  3367.                     "Press 'Ok' to correct them using 'Create' before continuing "..
  3368.                     "otherwise 'Cancel' to ignore them.",
  3369.                     "wxLuaSudoku - Invalid puzzle",
  3370.                     wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
  3371.                     sudokuGUI.frame )
  3372.  
  3373.                 if ret == wx.wxOK then
  3374.                     sudokuGUI.CreatePuzzle(true)
  3375.                 end
  3376.             else --if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_MISTAKES) then
  3377.                 sudokuGUI.sudokuSolnTable = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
  3378.             end
  3379.         else
  3380.             wx.wxMessageBox( msg,
  3381.                              "wxLuaSudoku - Open file error",
  3382.                              wx.wxOK + wx.wxICON_ERROR,
  3383.                              sudokuGUI.frame )
  3384.         end
  3385.     end
  3386.     fileDialog:Destroy()
  3387. end
  3388.  
  3389. -- ----------------------------------------------------------------------------
  3390. -- Save the puzzle to a file
  3391. function sudokuGUI.SaveAsPuzzle()
  3392.     local fileDialog = wx.wxFileDialog(sudokuGUI.frame, "Save puzzle",
  3393.                                        sudokuGUI.filePath, sudokuGUI.fileName,
  3394.                                        "wxLuaSudoku files (*.sudoku)|*.sudoku|All files (*)|*",
  3395.                                        wx.wxSAVE + wx.wxOVERWRITE_PROMPT)
  3396.     local result = false
  3397.     if fileDialog:ShowModal() == wx.wxID_OK then
  3398.         local fileName = fileDialog:GetPath()
  3399.         local fn = wx.wxFileName(fileName)
  3400.         sudokuGUI.filePath = fn:GetPath()
  3401.         sudokuGUI.fileName = fn:GetFullName()
  3402.  
  3403.         result = sudoku.Save(sudokuGUI.GetCurrentTable(), fileName)
  3404.         if result then
  3405.             sudokuGUI.frame:SetTitle("wxLuaSudoku - "..sudokuGUI.fileName)
  3406.         else
  3407.             wx.wxMessageBox( "Unable to save file\n'"..fileName.."'",
  3408.                              "wxLuaSudoku - Save file error",
  3409.                              wx.wxOK + wx.wxICON_ERROR,
  3410.                              sudokuGUI.frame )
  3411.  
  3412.         end
  3413.     end
  3414.     fileDialog:Destroy()
  3415.     return result
  3416. end
  3417.  
  3418. -- ----------------------------------------------------------------------------
  3419.  
  3420. function sudokuGUI.Undo()
  3421.     if sudokuGUI.sudokuTables_pos > 1 then
  3422.         sudokuGUI.sudokuTables_pos = sudokuGUI.sudokuTables_pos - 1
  3423.         sudokuGUI.UpdateTable(true)
  3424.     end
  3425. end
  3426. function sudokuGUI.Redo()
  3427.     if sudokuGUI.sudokuTables_pos < TableCount(sudokuGUI.sudokuTables) then
  3428.         sudokuGUI.sudokuTables_pos = sudokuGUI.sudokuTables_pos + 1
  3429.         sudokuGUI.UpdateTable(true)
  3430.     end
  3431. end
  3432.  
  3433. -- ----------------------------------------------------------------------------
  3434. -- Try to fix the invalid cells if possible
  3435. function sudokuGUI.FixInvalid(sudokuTable, show_dialog)
  3436.     local s = TableCopy(sudokuTable)
  3437.     local solnTable = sudokuGUI.sudokuSolnTable
  3438.     if solnTable and TableIsEmpty(solnTable.invalid) then
  3439.         for cell = 1, 81 do
  3440.             if sudoku.HasCellValue(s, cell) then
  3441.                 local current_value = sudoku.GetCellValue(s, cell)
  3442.                 local correct_value = sudoku.GetCellValue(solnTable, cell)
  3443.                 if current_value ~= correct_value then
  3444.                     sudoku.SetCellValue(s, cell, correct_value)
  3445.                 end
  3446.             end
  3447.         end
  3448.         sudoku.UpdateTable(s)
  3449.         return s
  3450.     else
  3451.         if show_dialog then
  3452.             local msg = "The initial puzzle must be solved first.\n"..
  3453.                         "Would you like me to try to solve it?"
  3454.             local flags = wx.wxYES_NO
  3455.  
  3456.             local invalid = true
  3457.             if solnTable and not TableIsEmpty(solnTable.invalid) then
  3458.                 invalid = true
  3459.             end
  3460.             if invalid then
  3461.                 msg = "The initial puzzle has invalid values.\n"..
  3462.                       "Please correct them first using Create."
  3463.                 flags = wx.wxOK
  3464.             end
  3465.             local ret = wx.wxMessageBox(msg,
  3466.                                         "wxLuaSudoku - Invalid puzzle",
  3467.                                         flags + wx.wxICON_INFORMATION,
  3468.                                         sudokuGUI.frame )
  3469.  
  3470.             if not invalid and (ret == wx.wxOK) then
  3471.                 s = sudokuGUI.SolveBruteForce(sudokuGUI.sudokuTables[1])
  3472.                 if not s then
  3473.                     wx.wxMessageBox("Unable to solve or or solving was aborted, giving up.",
  3474.                                     "wxLuaSudoku - Invalid puzzle",
  3475.                                     wx.wxOK + wx.wxICON_INFORMATION,
  3476.                                     sudokuGUI.frame )
  3477.                     return nil
  3478.                 end
  3479.                 sudokuGUI.sudokuSolnTable = s
  3480.                 sudoku.UpdateTable(sudokuGUI.sudokuSolnTable)
  3481.                 return sudokuGUI.FixInvalid(sudokuTable, show_dialog)
  3482.             else
  3483.                 return nil
  3484.             end
  3485.         else
  3486.             return nil
  3487.         end
  3488.     end
  3489. end
  3490.  
  3491. -- ----------------------------------------------------------------------------
  3492.  
  3493. function sudokuGUI.VerifyUniquePuzzle(sudokuTable)
  3494.     sudoku.CalcInvalidCells(sudokuTable)
  3495.     local invalid_count = TableCount(sudokuTable.invalid)
  3496.  
  3497.     if invalid_count > 0 then
  3498.         local ret = wx.wxMessageBox(
  3499.                 string.format("The initial values of the puzzle are invalid.\n"..
  3500.                               "There are %d cells with duplicate values.\n"..
  3501.                               "Please select Create and fix them before trying to solve.\n", invalid_count),
  3502.                               "wxLuaSudoku - Invalid puzzle",
  3503.                               wx.wxOK + wx.wxICON_ERROR,
  3504.                               sudokuGUI.frame )
  3505.         return
  3506.     end
  3507.  
  3508.     local solve_progress = 0
  3509.     local start_time     = os.time()
  3510.     local last_time      = start_time
  3511.     local solve_ok       = true
  3512.     local msg_idx        = 1
  3513.  
  3514.     -- define handler function here so it'll work w/o gui
  3515.     function sudoku.SolveBruteForceHook(guesses, cell)
  3516.         if solve_ok == false then return false end -- canceled
  3517.         solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
  3518.         if (solve_progress-1)%10 ~= 0 then return true end
  3519.         if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
  3520.             msg_idx = msg_idx + 1
  3521.             last_time = os.time()
  3522.         end
  3523.         local msg = string.format("%s\nIteration # %d, current cell %d            ", sudokuGUI.sayings[msg_idx], guesses.current, cell)
  3524.         solve_ok = progressDialog:Update(solve_progress, msg)
  3525.         return solve_ok
  3526.     end
  3527.  
  3528.     local ret = wx.wxOK
  3529.     while ret == wx.wxOK do
  3530.         solve_progress = 0
  3531.         start_time     = os.time()
  3532.         last_time      = start_time
  3533.         solve_ok       = true
  3534.         msg_idx        = 1
  3535.  
  3536.         progressDialog = wx.wxProgressDialog("wxLuaSudoku - Verifying puzzle...",
  3537.                             string.format("%s\nIteration # %d, current cell %d            ", sudokuGUI.sayings[1], 0, 0),
  3538.                             1000, sudokuGUI.frame,
  3539.                             wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
  3540.  
  3541.         local s1, s2 = sudoku.IsUniquePuzzle(sudokuTable)
  3542.         progressDialog:Destroy()
  3543.  
  3544.         if s1 and (s2 == nil) then
  3545.             return s1
  3546.         elseif solve_ok == false then
  3547.             ret = wx.wxMessageBox("The puzzle was not fully verified and therefore may not have a unique solution or a solution at all.\n"..
  3548.                                   "Press 'Ok' to restart checking or 'Cancel' to quit.",
  3549.                                   "wxLuaSudoku - Unfinished check",
  3550.                                   wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
  3551.                                   sudokuGUI.frame)
  3552.         elseif s1 and s2 then
  3553.             wx.wxMessageBox("The puzzle does not have a unique solution.\n"..
  3554.                             "Use 'Create' to fix the problem, showing mistakes will be disabled.",
  3555.                             "wxLuaSudoku - Nonunique puzzle",
  3556.                             wx.wxOK + wx.wxICON_ERROR,
  3557.                             sudokuGUI.frame)
  3558.  
  3559.             sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_MISTAKES, false)
  3560.             return nil
  3561.         else
  3562.             wx.wxMessageBox("The puzzle does not have a solution.\n"..
  3563.                             "Use 'Create' to fix the problem, showing mistakes will be disabled.",
  3564.                             "wxLuaSudoku - Unsolvable puzzle",
  3565.                             wx.wxOK + wx.wxICON_ERROR,
  3566.                             sudokuGUI.frame)
  3567.  
  3568.             sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_MISTAKES, false)
  3569.             return nil
  3570.         end
  3571.     end
  3572.  
  3573.     return nil
  3574. end
  3575.  
  3576. -- ----------------------------------------------------------------------------
  3577. -- Use the scanning method to solve it
  3578. function sudokuGUI.SolveScanning()
  3579.     local s = TableCopy(sudokuGUI.GetCurrentTable())
  3580. --[[
  3581.     local invalid = not TableIsEmpty(s.invalid)
  3582.     if invalid then
  3583.         if not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid) then
  3584.             local ret = wx.wxMessageBox("The initial values in the puzzle are invalid.\n"..
  3585.                                         "Please select Create and fix them before trying to solve.\n"..
  3586.                                         "Press 'Cancel' to try to solve it anyway.",
  3587.                                         "wxLuaSudoku - Invalid puzzle",
  3588.                                         wx.wxOK + wx.wxCANCEL + wx.wxICON_ERROR,
  3589.                                         sudokuGUI.frame )
  3590.  
  3591.             if ret == wx.wxOK then return end
  3592.         else
  3593.             local ret = wx.wxMessageBox("The current puzzle has invalid cell values.\n"..
  3594.                                         "Would you like me to try to fix those cells or press cancel to abort solving.",
  3595.                                         "wxLuaSudoku - Invalid puzzle",
  3596.                                         wx.wxYES_NO + wx.wxCANCEL + wx.wxICON_INFORMATION,
  3597.                                         sudokuGUI.frame )
  3598.  
  3599.             if ret == wx.wxCANCEL then
  3600.                 return
  3601.             elseif ret == wx.wxYES then
  3602.                 local fixedS = sudokuGUI.FixInvalid(s, true)
  3603.                 if fixedS then
  3604.                     sudokuGUI.AddTable(fixedS)
  3605.                     s = TableCopy(sudokuGUI.GetCurrentTable())
  3606.                 else
  3607.                     return
  3608.                 end
  3609.             end
  3610.         end
  3611.     end
  3612. ]]
  3613.     local count, changed_cells = sudoku.SolveScan(s)
  3614.     local changed_count = 0
  3615.     if changed_cells then
  3616.         sudokuGUI.AddTable(s)
  3617.         changed_count = TableCount(changed_cells)
  3618.     end
  3619.  
  3620.     local msg = string.format("Scanned rows, cols, and blocks %d times.\n"..
  3621.                               "Found %d new values.\n"..
  3622.                               "You may be able to do better using 'Eliminate groups'", count, changed_count)
  3623.     wx.wxMessageBox( msg,
  3624.                      "wxLuaSudoku - Finished scanning",
  3625.                      wx.wxOK + wx.wxICON_INFORMATION,
  3626.                      sudokuGUI.frame )
  3627. end
  3628.  
  3629. -- ----------------------------------------------------------------------------
  3630. -- Use the brute force method to solve it
  3631. function sudokuGUI.SolveBruteForce(sudokuTable)
  3632.  
  3633.     local s
  3634.     if sudokuTable then
  3635.         s = TableCopy(sudokuTable)
  3636.     else
  3637.         s = TableCopy(sudokuGUI.GetCurrentTable())
  3638.     end
  3639. --[[
  3640.     local invalid = not TableIsEmpty(s.invalid)
  3641.     if invalid then
  3642.         if (sudokuTable == nil) and (not TableIsEmpty(sudokuGUI.sudokuTables[1].invalid)) then
  3643.             wx.wxMessageBox("The initial values in the puzzle are invalid.\n"..
  3644.                             "Please select Create and fix them before trying to solve.",
  3645.                             "wxLuaSudoku - Invalid puzzle",
  3646.                             wx.wxOK + wx.wxICON_ERROR,
  3647.                             sudokuGUI.frame )
  3648.  
  3649.             return
  3650.         end
  3651.  
  3652.         local ret = wx.wxMessageBox("The current puzzle has invalid cell values.\n"..
  3653.                                     "Would you like me to try to fix those cells or press cancel to abort solving.",
  3654.                                     "wxLuaSudoku - Invalid puzzle",
  3655.                                     wx.wxYES_NO + wx.wxCANCEL + wx.wxICON_INFORMATION,
  3656.                                     sudokuGUI.frame )
  3657.  
  3658.         if ret == wx.wxCANCEL then
  3659.             return
  3660.         elseif ret == wx.wxYES then
  3661.             local fixedS = sudokuGUI.FixInvalid(s, true)
  3662.             if fixedS then
  3663.                 sudokuGUI.AddTable(fixedS)
  3664.                 s = TableCopy(sudokuGUI.GetCurrentTable())
  3665.             else
  3666.                 return
  3667.             end
  3668.         end
  3669.     end
  3670. ]]
  3671.     local progressDialog = wx.wxProgressDialog("wxLuaSudoku - Solving...",
  3672.                            string.format("%s\nIteration # %d, current cell %d            ", sudokuGUI.sayings[1], 0, 0),
  3673.                            1000, sudokuGUI.frame,
  3674.                            wx.wxPD_AUTO_HIDE+wx.wxPD_CAN_ABORT+wx.wxPD_ELAPSED_TIME)
  3675.  
  3676.     local solve_progress = 0
  3677.     local start_time     = os.time()
  3678.     local last_time      = start_time
  3679.     local solve_ok       = true
  3680.     local msg_idx        = 1
  3681.  
  3682.     -- define handler function here so it'll work w/o gui
  3683.     function sudoku.SolveBruteForceHook(guesses, cell)
  3684.         if solve_ok == false then return false end -- canceled
  3685.         solve_progress = iff(solve_progress+1 >= 1000, 0, solve_progress + 1)
  3686.         if (solve_progress-1)%10 ~= 0 then return true end
  3687.         if (msg_idx < sudokuGUI.sayings_n) and (os.time() - last_time > 4) then
  3688.             msg_idx = msg_idx + 1
  3689.             last_time = os.time()
  3690.         end
  3691.         local msg = string.format("%s\nIteration # %d, current cell %d            ", sudokuGUI.sayings[msg_idx], guesses.current, cell)
  3692.         solve_ok = progressDialog:Update(solve_progress, msg)
  3693.         return solve_ok
  3694.     end
  3695.  
  3696.     -- "cheat" a little by using SolveScan to get easy to find values
  3697.     --local flags = TableCopy(s.flags)
  3698.     --for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
  3699.     --    s.flags[n] = true
  3700.     --end
  3701.  
  3702.     --local count, changed_cells = sudoku.SolveScan(s)
  3703.     local s, g = sudoku.SolveBruteForce(s)
  3704.  
  3705.     progressDialog:Destroy()
  3706.  
  3707.     if not s then
  3708.         if solve_ok then
  3709.             wx.wxMessageBox("Sorry, no solutions found!",
  3710.                             "wxLuaSudoku - error",
  3711.                             wx.wxOK + wx.wxICON_INFORMATION,
  3712.                             sudokuGUI.frame )
  3713.         end
  3714.     elseif not sudokuTable then
  3715.         --s.flags = flags         -- restore flags
  3716.         sudokuGUI.AddTable(s)    -- we solved the current grid
  3717.     else
  3718.         --s.flags = flags         -- restore flags
  3719.         return s                -- we solved the input grid
  3720.     end
  3721. end
  3722.  
  3723. -- ----------------------------------------------------------------------------
  3724. -- Reset the grid to the original values
  3725. function sudokuGUI.ResetPuzzle(dont_query_user)
  3726.     dont_query_user = dont_query_user or false
  3727.     local ret = wx.wxOK
  3728.     if not dont_query_user then
  3729.         ret = wx.wxMessageBox("Reset the puzzle to the initial state?",
  3730.                               "wxLuaSudoku - reset puzzle?",
  3731.                                wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  3732.                                sudokuGUI.frame )
  3733.     end
  3734.  
  3735.     if ret == wx.wxCANCEL then
  3736.         return
  3737.     else
  3738.         sudokuGUI.sudokuTables_pos = 1
  3739.         while TableCount(sudokuGUI.sudokuTables) > 1 do
  3740.             table.remove(sudokuGUI.sudokuTables, 2)
  3741.         end
  3742.     end
  3743.  
  3744.     sudokuGUI.UpdateTable() -- redo it anyway
  3745. end
  3746.  
  3747. -- ----------------------------------------------------------------------------
  3748.  
  3749. function sudokuGUI.UpdateTable(refresh)
  3750.     if refresh == nil then refresh = true end
  3751.  
  3752.     local sudokuTable = sudokuGUI.GetCurrentTable()
  3753.  
  3754.     sudokuGUI.block_refresh = true
  3755.  
  3756.     local has_show_flag = false
  3757.     for n = sudoku.ELIMINATE_FLAG_MIN, sudoku.ELIMINATE_FLAG_MAX do
  3758.         local id = n + sudokuGUI.ID_ELIMINATE_NAKEDPAIRS - sudoku.ELIMINATE_FLAG_MIN
  3759.         sudokuTable.flags[n] = sudokuGUI.IsCheckedMenuItem(id)
  3760.  
  3761.         local show_id = n + sudokuGUI.ID_SHOW_NAKEDPAIRS - sudoku.ELIMINATE_FLAG_MIN
  3762.         if (not has_show_flag) and (sudokuGUI.IsCheckedMenuItem(show_id) == true) then
  3763.             has_show_flag = true
  3764.         end
  3765.     end
  3766.  
  3767.     sudoku.UpdateTable(sudokuTable)
  3768.  
  3769.     if has_show_flag == true then
  3770.         -- swap out the possible table temporarily to calc pencil marks
  3771.         if sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE) then
  3772.             local p = sudokuTable.possible
  3773.             sudokuTable.possible = sudokuGUI.pencilMarks
  3774.             sudokuGUI.pencilMarksNakedTable, sudokuGUI.pencilMarksHiddenTable = sudoku.FindAllNakedHiddenGroups(sudokuTable, true)
  3775.             sudokuTable.possible = p
  3776.         else
  3777.             sudokuGUI.possNakedTable, sudokuGUI.possHiddenTable = sudoku.FindAllNakedHiddenGroups(sudokuTable, true)
  3778.         end
  3779.     end
  3780.  
  3781.     sudokuGUI.SetCurrentTable(sudokuTable)
  3782.  
  3783.     sudokuGUI.block_refresh = false
  3784.  
  3785.     if (refresh == true) then
  3786.         sudokuGUI.Refresh()
  3787.     end
  3788.  
  3789.     sudokuGUI.UpdateGUI()
  3790. end
  3791.  
  3792. -- ----------------------------------------------------------------------------
  3793.  
  3794. function sudokuGUI.UpdateGUI()
  3795.     local table_count = #sudokuGUI.sudokuTables
  3796.     local table_pos   = sudokuGUI.sudokuTables_pos
  3797.     sudokuGUI.frame:GetMenuBar():Enable(sudokuGUI.ID_UNDO, table_pos > 1)
  3798.     sudokuGUI.frame:GetMenuBar():Enable(sudokuGUI.ID_REDO, table_pos < table_count)
  3799.     sudokuGUI.frame:GetToolBar():EnableTool(sudokuGUI.ID_UNDO, table_pos > 1)
  3800.     sudokuGUI.frame:GetToolBar():EnableTool(sudokuGUI.ID_REDO, table_pos < table_count)
  3801.  
  3802.     sudokuGUI.frame:SetStatusText(string.format("Step : %d", table_pos), 1)
  3803. end
  3804.  
  3805. -- ----------------------------------------------------------------------------
  3806. -- The preference pages are in a table so they can be accessed easily
  3807.  
  3808. sudokuGUI.PreferencesDialogPageUI = {}
  3809.  
  3810. function sudokuGUI.PreferencesDialogPageUI.Create(parent)
  3811.     local panel = wx.wxPanel(parent, wx.wxID_ANY)
  3812.  
  3813.     local ID_LISTBOX        = 10
  3814.     local ID_SAMPLE_TEXT    = 11
  3815.     local ID_FONT_BUTTON    = 12
  3816.     local ID_COLOUR_BUTTON  = 13
  3817.     local ID_RESET_BUTTON   = 14
  3818.  
  3819.     local listStrings = -- in same order as the colours
  3820.     {
  3821.         "Values",
  3822.         "Initial values",
  3823.         "Possible values",
  3824.         "Invalid values",
  3825.         "Background",
  3826.         "Odd background",
  3827.         "Focused cell",
  3828.         "Naked pairs",
  3829.         "Naked triplets",
  3830.         "Naked quads",
  3831.         "Hidden pairs",
  3832.         "Hidden triplets",
  3833.         "Hidden quads"
  3834.     }
  3835.  
  3836.     local listBoxValues = {}
  3837.     for n = 1, sudokuGUI.COLOUR_MAX do
  3838.         table.insert(listBoxValues, {colour = wx.wxColour(sudokuGUI.Colours[n])})
  3839.     end
  3840.     listBoxValues[sudokuGUI.VALUE_COLOUR].font      = wx.wxFont(sudokuGUI.valueFont.wxfont)
  3841.     listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font = wx.wxFont(sudokuGUI.possibleFont.wxfont)
  3842.  
  3843.     local reset_fonts = true
  3844.  
  3845.     -- Create the dialog ------------------------------------------------------
  3846.  
  3847.     local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
  3848.  
  3849.     local fcFlexSizer = wx.wxFlexGridSizer( 1, 2, 0, 0 )
  3850.     fcFlexSizer:AddGrowableCol( 0 )
  3851.     fcFlexSizer:AddGrowableRow( 0 )
  3852.  
  3853.     local fcListBox = wx.wxListBox( panel, ID_LISTBOX, wx.wxDefaultPosition, wx.wxSize(80,100), listStrings, wx.wxLB_SINGLE )
  3854.     fcListBox:SetSelection(0)
  3855.     fcFlexSizer:Add( fcListBox, 0, wx.wxGROW+wx.wxALIGN_CENTER_HORIZONTAL+wx.wxALL, 5 )
  3856.  
  3857.     local fcBoxSizer = wx.wxBoxSizer( wx.wxVERTICAL )
  3858.  
  3859.     local sampleWin = wx.wxWindow(panel, ID_SAMPLE_TEXT, wx.wxDefaultPosition, wx.wxSize(140,140))
  3860.     fcBoxSizer:Add( sampleWin, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 );
  3861.  
  3862.     local fontButton = wx.wxButton( panel, ID_FONT_BUTTON, "Choose Font", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
  3863.     fcBoxSizer:Add( fontButton, 0, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL+wx.wxALL, 5 )
  3864.     local colourButton = wx.wxButton( panel, ID_COLOUR_BUTTON, "Choose Color", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
  3865.     fcBoxSizer:Add( colourButton, 0, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL+wx.wxALL, 5 )
  3866.     local resetButton = wx.wxButton( panel, ID_RESET_BUTTON, "Reset Value...", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
  3867.     fcBoxSizer:Add( resetButton, 0, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL+wx.wxALL, 5 )
  3868.  
  3869.     fcFlexSizer:Add( fcBoxSizer, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
  3870.  
  3871.     mainSizer:Add( fcFlexSizer, 1, wx.wxGROW+wx.wxALIGN_CENTER_VERTICAL, 5 )
  3872.     panel:SetSizer( mainSizer )
  3873.  
  3874.     sampleWin:Connect(wx.wxEVT_PAINT,
  3875.         function (event)
  3876.             local win = event:GetEventObject():DynamicCast("wxWindow")
  3877.             local sel = fcListBox:GetSelection() + 1
  3878.             local width, height = win:GetClientSizeWH()
  3879.             local dc = wx.wxPaintDC(win)
  3880.  
  3881.             local function SetFontSize(size, width, height, font)
  3882.                 -- alternate way, but it fails for fonts that can't scale large enough
  3883.                 --local f = wx.wxNullFont:NewSize(wx.wxSize(width, height), font:GetFamily(), font:GetStyle(), font:GetWeight(), font:GetUnderlined(), font:GetFaceName())
  3884.                 --font:SetPointSize(f:GetPointSize())
  3885.  
  3886.                 local font_width = 0
  3887.                 local font_height = 0
  3888.                 while (font_width < width) and (font_height < height) do
  3889.                     font:SetPointSize(size)
  3890.                     dc:SetFont(font)
  3891.                     font_width, font_height = dc:GetTextExtent("5")
  3892.                     size = size + 2
  3893.                     if size > 200 then break end -- oops bad font?
  3894.                 end
  3895.                 font:SetPointSize(size-1)
  3896.             end
  3897.  
  3898.             -- clear background
  3899.             local c = listBoxValues[sudokuGUI.BACKGROUND_COLOUR].colour
  3900.             if (sel == sudokuGUI.ODD_BACKGROUND_COLOUR) or (sel == sudokuGUI.FOCUS_CELL_COLOUR) then
  3901.                 c = listBoxValues[sel].colour
  3902.             end
  3903.             local brush = wx.wxBrush(c, wx.wxSOLID)
  3904.             dc:SetBrush(brush)
  3905.             brush:delete()
  3906.             dc:DrawRectangle(0, 0, width, height)
  3907.  
  3908.             -- draw possible values
  3909.             dc:SetTextForeground(listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].colour)
  3910.             local font = listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font
  3911.             if reset_fonts then SetFontSize(4, width/4, height/4, font) end
  3912.             dc:SetFont(font)
  3913.             local font_width, font_height = dc:GetTextExtent("5")
  3914.  
  3915.             local pos =
  3916.             {
  3917.                 [1] = { x = 2,                  y = 2 },
  3918.                 [3] = { x = width-font_width-2, y = 2 },
  3919.                 [4] = { x = 2,                  y = (height-font_height)/2-2 },
  3920.                 [6] = { x = width-font_width-2, y = (height-font_height)/2-2 },
  3921.                 [7] = { x = 2,                  y = height-font_height-2 },
  3922.                 [9] = { x = width-font_width-2, y = height-font_height-2 }
  3923.             }
  3924.  
  3925.             dc:SetBrush(wx.wxTRANSPARENT_BRUSH)
  3926.  
  3927.             local function DrawPossible(idx, n, value, hidden)
  3928.                 dc:DrawText(value, pos[n].x, pos[n].y)
  3929.                 local pen = wx.wxPen(listBoxValues[idx].colour, 1, wx.wxSOLID)
  3930.                 dc:SetPen(pen); pen:delete()
  3931.                 if hidden ~= true then
  3932.                     dc:DrawRectangle(pos[n].x, pos[n].y, font_width, font_height)
  3933.                 else
  3934.                     dc:DrawRoundedRectangle(pos[n].x, pos[n].y, font_width, font_height, 20)
  3935.                 end
  3936.             end
  3937.  
  3938.             DrawPossible(sudokuGUI.NAKED_PAIRS_COLOUR,     1, "2")
  3939.             DrawPossible(sudokuGUI.NAKED_TRIPLETS_COLOUR,  4, "3")
  3940.             DrawPossible(sudokuGUI.NAKED_QUADS_COLOUR,     7, "4")
  3941.             DrawPossible(sudokuGUI.HIDDEN_PAIRS_COLOUR,    3, "2", true)
  3942.             DrawPossible(sudokuGUI.HIDDEN_TRIPLETS_COLOUR, 6, "3", true)
  3943.             DrawPossible(sudokuGUI.HIDDEN_QUADS_COLOUR,    9, "4", true)
  3944.  
  3945.             -- draw invalid marker
  3946.             local pen = wx.wxPen(listBoxValues[sudokuGUI.INVALID_VALUE_COLOUR].colour, 1, wx.wxSOLID)
  3947.             dc:SetPen(pen); pen:delete()
  3948.             dc:DrawLine(0, 0, width, height)
  3949.  
  3950.             -- draw value
  3951.             if (sel == sudokuGUI.INIT_VALUE_COLOUR) then
  3952.                 dc:SetTextForeground(listBoxValues[sudokuGUI.INIT_VALUE_COLOUR].colour)
  3953.             else
  3954.                 dc:SetTextForeground(listBoxValues[sudokuGUI.VALUE_COLOUR].colour)
  3955.             end
  3956.  
  3957.             local old_font = font
  3958.             local font = listBoxValues[sudokuGUI.VALUE_COLOUR].font
  3959.             if reset_fonts then SetFontSize(old_font:GetPointSize(), width-2, height-2, font) end
  3960.             dc:SetFont(font)
  3961.             local font_width, font_height = dc:GetTextExtent("9")
  3962.             dc:DrawText("9", (width-font_width)/2, (height-font_height)/2)
  3963.  
  3964.             reset_fonts = false
  3965.             dc:delete()
  3966.         end)
  3967.  
  3968.     panel:Connect(ID_LISTBOX, wx.wxEVT_COMMAND_LISTBOX_SELECTED,
  3969.         function (event)
  3970.             local sel = event:GetSelection() + 1
  3971.             panel:FindWindow(ID_FONT_BUTTON):Enable(listBoxValues[sel].font ~= nil)
  3972.             colourButton:SetForegroundColour(listBoxValues[sel].colour)
  3973.             sampleWin:Refresh(false)
  3974.         end)
  3975.  
  3976.     panel:Connect(ID_FONT_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED,
  3977.         function (event)
  3978.             local sel = fcListBox:GetSelection() + 1
  3979.             local f = listBoxValues[sel].font
  3980.             f = wx.wxGetFontFromUser(panel, f)
  3981.             if f:Ok() then
  3982.                 listBoxValues[sel].font:delete()
  3983.                 listBoxValues[sel].font = f
  3984.                 reset_fonts = true
  3985.             else
  3986.                 f:delete()
  3987.             end
  3988.             sampleWin:Refresh(false)
  3989.         end)
  3990.     panel:Connect(ID_COLOUR_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED,
  3991.         function (event)
  3992.             local sel = fcListBox:GetSelection() + 1
  3993.             local c = listBoxValues[sel].colour
  3994.             c = wx.wxGetColourFromUser(panel, c)
  3995.             if c:Ok() then
  3996.                 listBoxValues[sel].colour:delete()
  3997.                 listBoxValues[sel].colour = c
  3998.                 colourButton:SetForegroundColour(c)
  3999.             else
  4000.                 c:delete()
  4001.             end
  4002.             sampleWin:Refresh(false)
  4003.         end)
  4004.     panel:Connect(ID_RESET_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED,
  4005.         function (event)
  4006.             local sel = fcListBox:GetSelection() + 1
  4007.  
  4008.             local ret = wx.wxMessageBox(
  4009.                 "Press 'Yes' to reset all the colors and fonts or 'No' to reset only just the selected item.",
  4010.                 "wxLuaSudoku - Reset colors or fonts?",
  4011.                 wx.wxYES_NO + wx.wxCANCEL + wx.wxICON_INFORMATION,
  4012.                 panel )
  4013.  
  4014.             if ret == wx.wxYES then
  4015.                 for n = 1, sudokuGUI.COLOUR_MAX do
  4016.                     listBoxValues[n].colour:delete()
  4017.                     listBoxValues[n].colour = wx.wxColour(sudokuGUI.Colours_[n])
  4018.                 end
  4019.  
  4020.                 listBoxValues[sudokuGUI.VALUE_COLOUR].font:delete()
  4021.                 listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font:delete()
  4022.                 listBoxValues[sudokuGUI.VALUE_COLOUR].font      = wx.wxFont(sudokuGUI.valueFont_wxfont_)
  4023.                 listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font = wx.wxFont(sudokuGUI.possibleFont_wxfont_)
  4024.             elseif ret == wx.wxNO then
  4025.                 listBoxValues[sel].colour:delete()
  4026.                 listBoxValues[sel].colour = wx.wxColour(sudokuGUI.Colours_[sel])
  4027.  
  4028.                 if (sel == sudokuGUI.VALUE_COLOUR) then
  4029.                     listBoxValues[sel].font:delete()
  4030.                     listBoxValues[sel].font = wx.wxFont(sudokuGUI.valueFont_wxfont_)
  4031.                 elseif (sel == sudokuGUI.POSS_VALUE_COLOUR) then
  4032.                     listBoxValues[sel].font:delete()
  4033.                     listBoxValues[sel].font = wx.wxFont(sudokuGUI.possibleFont_wxfont_)
  4034.                 end
  4035.             end
  4036.  
  4037.             colourButton:SetForegroundColour(listBoxValues[sel].colour)
  4038.             reset_fonts = true
  4039.             sampleWin:Refresh(false)
  4040.         end)
  4041.  
  4042.     function sudokuGUI.PreferencesDialogPageUI.Apply()
  4043.         for n = 1, sudokuGUI.COLOUR_MAX do
  4044.             sudokuGUI.Colours[n]:delete()
  4045.             sudokuGUI.Colours[n] = wx.wxColour(listBoxValues[n].colour)
  4046.         end
  4047.  
  4048.         -- copy the fonts since when applied their size will change
  4049.         sudokuGUI.valueFont.wxfont:delete()
  4050.         sudokuGUI.valueFont.wxfont    = wx.wxFont(listBoxValues[sudokuGUI.VALUE_COLOUR].font)
  4051.         sudokuGUI.possibleFont.wxfont:delete()
  4052.         sudokuGUI.possibleFont.wxfont = wx.wxFont(listBoxValues[sudokuGUI.POSS_VALUE_COLOUR].font)
  4053.         sudokuGUI.valueFont_cache    = {} -- clear cache so GetCellBestSize recreates it
  4054.         sudokuGUI.possibleFont_cache = {}
  4055.  
  4056.         for winID = 1, 81 do
  4057.             if sudokuGUI.IsOddBlockCell(winID) then
  4058.                 sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
  4059.             else
  4060.                 sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
  4061.             end
  4062.         end
  4063.  
  4064.         local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
  4065.         sudokuGUI.GetCellBestSize(width, height)
  4066.         sudokuGUI.Refresh()
  4067.     end
  4068.  
  4069.     function sudokuGUI.PreferencesDialogPageUI.Destroy()
  4070.         for n = 1, sudokuGUI.COLOUR_MAX do
  4071.             listBoxValues[n].colour:delete()
  4072.             if listBoxValues[n].font then listBoxValues[n].font:delete() end
  4073.         end
  4074.     end
  4075.  
  4076.     return panel
  4077. end
  4078.  
  4079.  
  4080. function sudokuGUI.CheckListBoxCheck(clBox, n_start, n_end, check)
  4081.     for n = n_start, n_end do
  4082.         clBox:Check(n, check)
  4083.     end
  4084. end
  4085. function sudokuGUI.CheckListBoxIsChecked(clBox, n_start, n_end)
  4086.     for n = n_start, n_end do
  4087.         if not clBox:IsChecked(n) then return false end
  4088.     end
  4089.     return true
  4090. end
  4091.  
  4092. sudokuGUI.PreferencesDialogPageShow = {}
  4093.  
  4094. function sudokuGUI.PreferencesDialogPageShow.Create(parent)
  4095.     local panel = wx.wxPanel(parent, wx.wxID_ANY)
  4096.  
  4097.     local ID_LISTBOX  = 10
  4098.  
  4099.     local listStrings =
  4100.     {
  4101.         "All naked groups",
  4102.         "All hidden groups",
  4103.         "Naked pairs",
  4104.         "Naked triplets",
  4105.         "Naked quads",
  4106.         "Hidden pairs",
  4107.         "Hidden triplets",
  4108.         "Hidden quads"
  4109.     }
  4110.  
  4111.     local listBoxValues =
  4112.     {
  4113.         sudokuGUI.ID_SHOW_NAKED,
  4114.         sudokuGUI.ID_SHOW_HIDDEN,
  4115.         sudokuGUI.ID_SHOW_NAKEDPAIRS,
  4116.         sudokuGUI.ID_SHOW_NAKEDTRIPLETS,
  4117.         sudokuGUI.ID_SHOW_NAKEDQUADS,
  4118.         sudokuGUI.ID_SHOW_HIDDENPAIRS,
  4119.         sudokuGUI.ID_SHOW_HIDDENTRIPLETS,
  4120.         sudokuGUI.ID_SHOW_HIDDENQUADS
  4121.     }
  4122.  
  4123.     -- Create the dialog ------------------------------------------------------
  4124.  
  4125.     local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
  4126.     local showListBox = wx.wxCheckListBox( panel, ID_LISTBOX, wx.wxDefaultPosition, wx.wxSize(80,100), listStrings, wx.wxLB_SINGLE )
  4127.     mainSizer:Add( showListBox, 1, wx.wxGROW+wx.wxALIGN_CENTER_HORIZONTAL+wx.wxALL, 5 )
  4128.     panel:SetSizer( mainSizer )
  4129.  
  4130.     for n = 1, showListBox:GetCount() do
  4131.         showListBox:Check(n-1, sudokuGUI.IsCheckedMenuItem(listBoxValues[n]))
  4132.     end
  4133.  
  4134.     panel:Connect(ID_LISTBOX, wx.wxEVT_COMMAND_CHECKLISTBOX_TOGGLED,
  4135.         function (event)
  4136.             local sel = event:GetSelection()
  4137.             local checked = showListBox:IsChecked(sel)
  4138.             local id = listBoxValues[sel+1]
  4139.             if id == sudokuGUI.ID_SHOW_NAKED then
  4140.                 sudokuGUI.CheckListBoxCheck(showListBox, 2, 4, checked)
  4141.             elseif id == sudokuGUI.ID_SHOW_HIDDEN then
  4142.                 sudokuGUI.CheckListBoxCheck(showListBox, 5, 7, checked)
  4143.             else
  4144.                 showListBox:Check(0, sudokuGUI.CheckListBoxIsChecked(showListBox, 2, 4))
  4145.                 showListBox:Check(1, sudokuGUI.CheckListBoxIsChecked(showListBox, 5, 7))
  4146.             end
  4147.         end)
  4148.  
  4149.     function sudokuGUI.PreferencesDialogPageShow.Apply()
  4150.         for n = 1, showListBox:GetCount() do
  4151.             sudokuGUI.CheckMenuItem(listBoxValues[n], showListBox:IsChecked(n-1))
  4152.         end
  4153.         sudokuGUI.UpdateTable()
  4154.     end
  4155.  
  4156.     function sudokuGUI.PreferencesDialogPageShow.Destroy()
  4157.     end
  4158.  
  4159.     return panel
  4160. end
  4161.  
  4162. sudokuGUI.PreferencesDialogPageSolve = {}
  4163.  
  4164. function sudokuGUI.PreferencesDialogPageSolve.Create(parent)
  4165.     local panel = wx.wxPanel(parent, wx.wxID_ANY)
  4166.  
  4167.     local ID_LISTBOX  = 10
  4168.  
  4169.     local listStrings =
  4170.     {
  4171.         "All naked groups",
  4172.         "All hidden groups",
  4173.         "Naked pairs",
  4174.         "Naked triplets",
  4175.         "Naked quads",
  4176.         "Hidden pairs",
  4177.         "Hidden triplets",
  4178.         "Hidden quads"
  4179.     }
  4180.  
  4181.     local listBoxValues =
  4182.     {
  4183.         sudokuGUI.ID_ELIMINATE_NAKED,
  4184.         sudokuGUI.ID_ELIMINATE_HIDDEN,
  4185.         sudokuGUI.ID_ELIMINATE_NAKEDPAIRS,
  4186.         sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS,
  4187.         sudokuGUI.ID_ELIMINATE_NAKEDQUADS,
  4188.         sudokuGUI.ID_ELIMINATE_HIDDENPAIRS,
  4189.         sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS,
  4190.         sudokuGUI.ID_ELIMINATE_HIDDENQUADS
  4191.     }
  4192.  
  4193.     -- Create the dialog ------------------------------------------------------
  4194.  
  4195.     local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
  4196.     local showListBox = wx.wxCheckListBox( panel, ID_LISTBOX, wx.wxDefaultPosition, wx.wxSize(80,100), listStrings, wx.wxLB_SINGLE )
  4197.     mainSizer:Add( showListBox, 1, wx.wxGROW+wx.wxALIGN_CENTER_HORIZONTAL+wx.wxALL, 5 )
  4198.     panel:SetSizer( mainSizer )
  4199.  
  4200.     for n = 1, showListBox:GetCount() do
  4201.         showListBox:Check(n-1, sudokuGUI.IsCheckedMenuItem(listBoxValues[n]))
  4202.     end
  4203.  
  4204.     panel:Connect(ID_LISTBOX, wx.wxEVT_COMMAND_CHECKLISTBOX_TOGGLED,
  4205.         function (event)
  4206.             local sel = event:GetSelection()
  4207.             local checked = showListBox:IsChecked(sel)
  4208.             local id = listBoxValues[sel+1]
  4209.             if id == sudokuGUI.ID_ELIMINATE_NAKED then
  4210.                 sudokuGUI.CheckListBoxCheck(showListBox, 2, 4, checked)
  4211.             elseif id == sudokuGUI.ID_ELIMINATE_HIDDEN then
  4212.                 sudokuGUI.CheckListBoxCheck(showListBox, 5, 7, checked)
  4213.             else
  4214.                 showListBox:Check(0, sudokuGUI.CheckListBoxIsChecked(showListBox, 2, 4))
  4215.                 showListBox:Check(1, sudokuGUI.CheckListBoxIsChecked(showListBox, 5, 7))
  4216.             end
  4217.         end)
  4218.  
  4219.     function sudokuGUI.PreferencesDialogPageSolve.Apply()
  4220.         for n = 1, showListBox:GetCount() do
  4221.             sudokuGUI.CheckMenuItem(listBoxValues[n], showListBox:IsChecked(n-1))
  4222.         end
  4223.         sudokuGUI.UpdateTable()
  4224.     end
  4225.  
  4226.     function sudokuGUI.PreferencesDialogPageSolve.Destroy()
  4227.     end
  4228.  
  4229.     return panel
  4230. end
  4231.  
  4232. function sudokuGUI.PreferencesDialog()
  4233.     local dialog = wx.wxDialog(sudokuGUI.frame, wx.wxID_ANY,
  4234.                                "wxLuaSudoku - Preferences",
  4235.                                wx.wxDefaultPosition, wx.wxDefaultSize,
  4236.                                wx.wxDEFAULT_DIALOG_STYLE+wx.wxRESIZE_BORDER)
  4237.  
  4238.     local panel = wx.wxPanel(dialog, wx.wxID_ANY)
  4239.     local notebook = wx.wxNotebook(panel, wx.wxID_ANY)
  4240.  
  4241.     local notebookPages = {}
  4242.  
  4243.     local page1 = sudokuGUI.PreferencesDialogPageUI.Create(notebook)
  4244.     notebook:AddPage(page1, "Fonts and Colors", true)
  4245.     table.insert(notebookPages, sudokuGUI.PreferencesDialogPageUI)
  4246.  
  4247.     local page2 = sudokuGUI.PreferencesDialogPageShow.Create(notebook)
  4248.     notebook:AddPage(page2, "Mark groups", false)
  4249.     table.insert(notebookPages, sudokuGUI.PreferencesDialogPageShow)
  4250.  
  4251.     local page3 = sudokuGUI.PreferencesDialogPageSolve.Create(notebook)
  4252.     notebook:AddPage(page3, "Eliminate groups", false)
  4253.     table.insert(notebookPages, sudokuGUI.PreferencesDialogPageSolve)
  4254.  
  4255.     local mainSizer = wx.wxBoxSizer( wx.wxVERTICAL )
  4256.  
  4257.     local buttonSizer = wx.wxBoxSizer( wx.wxHORIZONTAL )
  4258.     local okButton = wx.wxButton( panel, wx.wxID_OK, "&OK", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
  4259.     buttonSizer:Add( okButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
  4260.     local cancelButton = wx.wxButton( panel, wx.wxID_CANCEL, "&Cancel", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
  4261.     buttonSizer:Add( cancelButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
  4262.     local applyButton = wx.wxButton( panel, wx.wxID_APPLY, "&Apply", wx.wxDefaultPosition, wx.wxDefaultSize, 0 )
  4263.     buttonSizer:Add( applyButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
  4264.  
  4265.     mainSizer:Add( notebook, 1, wx.wxGROW+wx.wxALIGN_CENTER, 0 )
  4266.     mainSizer:Add( buttonSizer, 0, wx.wxALIGN_CENTER+wx.wxALL, 5 )
  4267.     panel:SetSizer( mainSizer )
  4268.     mainSizer:SetSizeHints( dialog )
  4269.  
  4270.     dialog:Connect(wx.wxID_APPLY, wx.wxEVT_COMMAND_BUTTON_CLICKED,
  4271.         function (event)
  4272.             --local sel = notebook:GetSelection()
  4273.             --if sel >= 0 then notebookPages[sel+1].Apply() end
  4274.             for n = 1, #notebookPages do
  4275.                 notebookPages[n].Apply()
  4276.             end
  4277.  
  4278.         end)
  4279.     dialog:Connect(wx.wxID_OK, wx.wxEVT_COMMAND_BUTTON_CLICKED,
  4280.         function (event)
  4281.             for n = 1, #notebookPages do
  4282.                 notebookPages[n].Apply()
  4283.                 notebookPages[n].Destroy()
  4284.             end
  4285.  
  4286.             event:Skip() -- wxDialog will cancel automatically
  4287.         end)
  4288.     dialog:Connect(wx.wxID_CANCEL, wx.wxEVT_COMMAND_BUTTON_CLICKED,
  4289.         function (event)
  4290.             for n = 1, #notebookPages do
  4291.                 notebookPages[n].Destroy()
  4292.             end
  4293.  
  4294.             event:Skip() -- wxDialog will cancel automatically
  4295.         end)
  4296.  
  4297.     dialog:ShowModal()
  4298. end
  4299.  
  4300. -- ----------------------------------------------------------------------------
  4301.  
  4302. function sudokuGUI.ConfigSave(save_prefs)
  4303.     if not sudokuGUI.config then
  4304.         sudokuGUI.config = wx.wxFileConfig("wxLuaSudoku", "wxLua")
  4305.     end
  4306.  
  4307.     if not sudokuGUI.config then return end
  4308.  
  4309.     -- write the frame position so we can restore it
  4310.     local x, y = sudokuGUI.frame:GetPositionXY()
  4311.     local w, h = sudokuGUI.frame:GetClientSizeWH()
  4312.     local max  = booltoint(sudokuGUI.frame:IsMaximized())
  4313.     sudokuGUI.config:Write("wxLuaSudoku/Frame", string.format("x:%d y:%d w:%d h:%d maximized:%d", x, y, w, h, max))
  4314.  
  4315.     if not save_prefs then return end
  4316.  
  4317.     if sudokuGUI.query_save_prefs then
  4318.         local ret = wx.wxMessageBox(
  4319.             "Preferences are stored in an ini file which you may delete:\n"..
  4320.             "MSW : Documents and Settings\\user\\wxLuaSudoku.ini\n"..
  4321.             "Unix : /home/user/.wxLuaSudoku",
  4322.             "wxLuaSudoku - Save preferences?",
  4323.             wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  4324.             sudokuGUI.frame )
  4325.  
  4326.         if ret == wx.wxCANCEL then
  4327.             return
  4328.         end
  4329.  
  4330.         sudokuGUI.query_save_prefs = false
  4331.     end
  4332.  
  4333.     if sudokuGUI.config then
  4334.         sudokuGUI.ConfigReadWrite(false, sudokuGUI.config)
  4335.         sudokuGUI.config:Flush(true)
  4336.     end
  4337. end
  4338.  
  4339. function sudokuGUI.ConfigLoad()
  4340.     if not sudokuGUI.config then
  4341.         sudokuGUI.config = wx.wxFileConfig("wxLuaSudoku", "wxLua")
  4342.     end
  4343.  
  4344.     if sudokuGUI.config then
  4345.         local dispX, dispY, dispW, dispH = wx.wxClientDisplayRect()
  4346.         local _, str = sudokuGUI.config:Read("wxLuaSudoku/Frame")
  4347.         local x, y, w, h, max = string.match(str, "x:(%d+) y:(%d+) w:(%d+) h:(%d+) maximized:(%d+)")
  4348.         if (x ~= nil) and (y ~= nil) and (w ~= nil) and (h ~= nil) and (max ~= nil) then
  4349.             x = tonumber(x); y = tonumber(y); w = tonumber(w); h = tonumber(h)
  4350.             max = inttobool(tonumber(max))
  4351.             if max then
  4352.                 sudokuGUI.frame:Maximize(true)
  4353.             else
  4354.                 if x < dispX - 5 then x = 0 end
  4355.                 if y < dispY - 5 then y = 0 end
  4356.                 if w > dispW then w = dispW end
  4357.                 if h > dispH then h = dispH end
  4358.  
  4359.                 sudokuGUI.frame:Move(x, y)
  4360.                 sudokuGUI.frame:SetClientSize(w, h)
  4361.             end
  4362.         end
  4363.  
  4364.         sudokuGUI.ConfigReadWrite(true, sudokuGUI.config)
  4365.     end
  4366.  
  4367.     for winID = 1, 81 do
  4368.         if sudokuGUI.IsOddBlockCell(winID) then
  4369.             sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
  4370.         else
  4371.             sudokuGUI.cellWindows[winID]:SetBackgroundColour(sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
  4372.         end
  4373.     end
  4374.  
  4375.     local show_toolbar = sudokuGUI.frame:GetMenuBar():IsChecked(sudokuGUI.ID_SHOW_TOOLBAR)
  4376.     if sudokuGUI.frame:GetToolBar():IsShown() ~= show_toolbar then
  4377.         -- generate fake event to simplify processing
  4378.         local evt = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, sudokuGUI.ID_SHOW_TOOLBAR)
  4379.         evt:SetInt(booltoint(show_toolbar))
  4380.         sudokuGUI.OnMenuEvent(evt)
  4381.     end
  4382.  
  4383.     local show_toolbar_labels = sudokuGUI.frame:GetMenuBar():IsChecked(sudokuGUI.ID_SHOW_TOOLBAR_LABELS)
  4384.     if (bit.band(sudokuGUI.frame:GetToolBar():GetWindowStyleFlag(), wx.wxTB_TEXT) ~= 0) ~= show_toolbar_labels then
  4385.         -- generate fake event to simplify processing
  4386.         local evt = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, sudokuGUI.ID_SHOW_TOOLBAR_LABELS)
  4387.         evt:SetInt(booltoint(show_toolbar_labels))
  4388.         sudokuGUI.OnMenuEvent(evt)
  4389.     end
  4390.  
  4391.     local show_statusbar = sudokuGUI.frame:GetMenuBar():IsChecked(sudokuGUI.ID_SHOW_STATUSBAR)
  4392.     if sudokuGUI.frame:GetStatusBar():IsShown() ~= show_statusbar then
  4393.         -- generate fake event to simplify processing
  4394.         local evt = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, sudokuGUI.ID_SHOW_STATUSBAR)
  4395.         evt:SetInt(booltoint(show_statusbar))
  4396.         sudokuGUI.OnMenuEvent(evt)
  4397.     end
  4398.  
  4399.     sudokuGUI.valueFont_cache    = {} -- clear cache in case the font has changed
  4400.     sudokuGUI.possibleFont_cache = {}
  4401.  
  4402.     -- update font size
  4403.     local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
  4404.     sudokuGUI.GetCellBestSize(width, height)
  4405.     -- update for preferences
  4406.     sudokuGUI.UpdateTable()
  4407.     sudokuGUI.Refresh()
  4408. end
  4409.  
  4410. function sudokuGUI.ConfigReadWrite(read, config)
  4411.     local path = "wxLuaSudoku"
  4412.  
  4413.     local function ReadWriteColour(key, c)
  4414.         if read then
  4415.             if config:HasEntry(key) then
  4416.                 local _, str = config:Read(key)
  4417.                 local r, g, b = string.match(str, "r:(%d+) g:(%d+) b:(%d+)")
  4418.                 if (r == nil) or (g == nil) or (b == nil) then return end
  4419.                 r = tonumber(r); g = tonumber(g); b = tonumber(b)
  4420.                 if (r < 0) or (r > 255) then return end -- sanity check
  4421.                 if (g < 0) or (g > 255) then return end
  4422.                 if (b < 0) or (b > 255) then return end
  4423.                 c:Set(r, g, b)
  4424.             end
  4425.         else
  4426.             config:Write(key, string.format("r:%d g:%d b:%d", c:Red(), c:Green(), c:Blue()))
  4427.         end
  4428.     end
  4429.  
  4430.     local function ReadWriteFont(key, f)
  4431.         if read then
  4432.             if config:HasEntry(key) then
  4433.                 local _, str = config:Read(key)
  4434.                 local face, family, style, underlined, weight = string.match(str, "face:(\"[%w ]+\") family:(%d+) style:(%d+) underlined:(%d+) weight:(%d+)")
  4435.                 if (face == nil) or (family == nil) or (style == nil) or (underlined == nil) or (weight == nil) then return end
  4436.                 family = tonumber(family); style = tonumber(style);
  4437.                 underlined = inttobool(tonumber(underlined)); weight = tonumber(weight)
  4438.                 -- remove quotes
  4439.                 if string.len(face) > 2 then face = string.sub(face, 2, -2) end
  4440.  
  4441.                 -- test so see if the values are any good
  4442.                 local ff = wx.wxFont(12, family, style, weight, underlined, face)
  4443.                 if not ff:Ok() then return end
  4444.  
  4445.                 local tempF = wx.wxFont(f)
  4446.                 f:SetFaceName(face)
  4447.                 f:SetFamily(family)
  4448.                 f:SetStyle(style)
  4449.                 f:SetUnderlined(underlined)
  4450.                 f:SetWeight(weight)
  4451.  
  4452.                 -- shouldn't happen but we always want a usable font
  4453.                 if not f:Ok() then
  4454.                     f:SetFaceName(tempF:GetFaceName())
  4455.                     f:SetFamily(tempF:GetFamily())
  4456.                     f:SetStyle(tempF:GetStyle())
  4457.                     f:SetUnderlined(tempF:GetUnderlined())
  4458.                     f:SetWeight(tempF:GetWeight())
  4459.                 end
  4460.             end
  4461.         else
  4462.             config:Write(key, string.format("face:\"%s\" family:%d style:%d underlined:%d weight:%d",
  4463.                 f:GetFaceName(), f:GetFamily(), f:GetStyle(), booltoint(f:GetUnderlined()), f:GetWeight()))
  4464.         end
  4465.     end
  4466.  
  4467.     if read then
  4468.         local _
  4469.         if config:HasEntry(path.."/LastOpenedFilepath") then
  4470.             _, sudokuGUI.filePath   = config:Read(path.."/LastOpenedFilepath", "")
  4471.         end
  4472.         if config:HasEntry(path.."/LastOpenedFilename") then
  4473.             _, sudokuGUI.fileName   = config:Read(path.."/LastOpenedFilename", "")
  4474.         end
  4475.         if config:HasEntry(path.."/GenerateDifficulty") then
  4476.             _, sudokuGUI.difficulty = config:Read(path.."/GenerateDifficulty", 0)
  4477.         end
  4478.     else
  4479.         config:Write(path.."/LastOpenedFilepath", sudokuGUI.filePath)
  4480.         config:Write(path.."/LastOpenedFilename", sudokuGUI.fileName)
  4481.         config:Write(path.."/GenerateDifficulty", sudokuGUI.difficulty)
  4482.     end
  4483.  
  4484.     ReadWriteColour(path.."/Colours/Value",             sudokuGUI.Colours[sudokuGUI.VALUE_COLOUR])
  4485.     ReadWriteColour(path.."/Colours/ValueInit",         sudokuGUI.Colours[sudokuGUI.INIT_VALUE_COLOUR])
  4486.     ReadWriteColour(path.."/Colours/ValuePossible",     sudokuGUI.Colours[sudokuGUI.POSS_VALUE_COLOUR])
  4487.     ReadWriteColour(path.."/Colours/ValueInvalid",      sudokuGUI.Colours[sudokuGUI.INVALID_VALUE_COLOUR])
  4488.     ReadWriteColour(path.."/Colours/CellBackground",    sudokuGUI.Colours[sudokuGUI.BACKGROUND_COLOUR])
  4489.     ReadWriteColour(path.."/Colours/CellOddBackground", sudokuGUI.Colours[sudokuGUI.ODD_BACKGROUND_COLOUR])
  4490.     ReadWriteColour(path.."/Colours/CellFocus",         sudokuGUI.Colours[sudokuGUI.FOCUS_CELL_COLOUR])
  4491.  
  4492.     ReadWriteColour(path.."/Colours/NakedPairs",     sudokuGUI.Colours[sudokuGUI.NAKED_PAIRS_COLOUR])
  4493.     ReadWriteColour(path.."/Colours/NakedTriplets",  sudokuGUI.Colours[sudokuGUI.NAKED_TRIPLETS_COLOUR])
  4494.     ReadWriteColour(path.."/Colours/NakedQuads",     sudokuGUI.Colours[sudokuGUI.NAKED_QUADS_COLOUR])
  4495.     ReadWriteColour(path.."/Colours/HiddenPairs",    sudokuGUI.Colours[sudokuGUI.HIDDEN_PAIRS_COLOUR])
  4496.     ReadWriteColour(path.."/Colours/HiddenTriplets", sudokuGUI.Colours[sudokuGUI.HIDDEN_TRIPLETS_COLOUR])
  4497.     ReadWriteColour(path.."/Colours/HiddenQuads",    sudokuGUI.Colours[sudokuGUI.HIDDEN_QUADS_COLOUR])
  4498.  
  4499.     ReadWriteFont(path.."/Fonts/Value",         sudokuGUI.valueFont.wxfont)
  4500.     ReadWriteFont(path.."/Fonts/ValuePossible", sudokuGUI.possibleFont.wxfont)
  4501.  
  4502.     local function ReadWritePref(key, pref)
  4503.         if read then
  4504.             if config:HasEntry(key) then
  4505.                 local _, v = config:Read(key, 0)
  4506.                 sudokuGUI.CheckMenuItem(pref, inttobool(v))
  4507.             end
  4508.         else
  4509.             config:Write(key, booltoint(sudokuGUI.IsCheckedMenuItem(pref)))
  4510.         end
  4511.     end
  4512.  
  4513.     ReadWritePref(path.."/Preferences/SHOW_ERRORS",          sudokuGUI.ID_SHOW_ERRORS)
  4514.     ReadWritePref(path.."/Preferences/SHOW_MISTAKES",        sudokuGUI.ID_SHOW_MISTAKES)
  4515.     ReadWritePref(path.."/Preferences/SHOW_TOOLBAR",         sudokuGUI.ID_SHOW_TOOLBAR)
  4516.     ReadWritePref(path.."/Preferences/SHOW_TOOLBAR_LABELS",  sudokuGUI.ID_SHOW_TOOLBAR_LABELS)
  4517.     ReadWritePref(path.."/Preferences/SHOW_STATUSBAR",       sudokuGUI.ID_SHOW_STATUSBAR)
  4518.  
  4519.     ReadWritePref(path.."/Preferences/SHOW_POSSIBLE",        sudokuGUI.ID_SHOW_POSSIBLE)
  4520.     ReadWritePref(path.."/Preferences/SHOW_USER_POSSIBLE",   sudokuGUI.ID_SHOW_USER_POSSIBLE)
  4521.     ReadWritePref(path.."/Preferences/SHOW_POSSIBLE_LINE",   sudokuGUI.ID_SHOW_POSSIBLE_LINE)
  4522.  
  4523.     ReadWritePref(path.."/Preferences/SHOW_NAKED",               sudokuGUI.ID_SHOW_NAKED)
  4524.     ReadWritePref(path.."/Preferences/SHOW_HIDDEN",              sudokuGUI.ID_SHOW_HIDDEN)
  4525.     ReadWritePref(path.."/Preferences/SHOW_NAKEDPAIRS",          sudokuGUI.ID_SHOW_NAKEDPAIRS)
  4526.     ReadWritePref(path.."/Preferences/SHOW_HIDDENPAIRS",         sudokuGUI.ID_SHOW_HIDDENPAIRS)
  4527.     ReadWritePref(path.."/Preferences/SHOW_NAKEDTRIPLETS",       sudokuGUI.ID_SHOW_NAKEDTRIPLETS)
  4528.     ReadWritePref(path.."/Preferences/SHOW_HIDDENTRIPLETS",      sudokuGUI.ID_SHOW_HIDDENTRIPLETS)
  4529.     ReadWritePref(path.."/Preferences/SHOW_NAKEDQUADS",          sudokuGUI.ID_SHOW_NAKEDQUADS)
  4530.     ReadWritePref(path.."/Preferences/SHOW_HIDDENQUADS",         sudokuGUI.ID_SHOW_HIDDENQUADS)
  4531.  
  4532.     ReadWritePref(path.."/Preferences/ELIMINATE_NAKED",          sudokuGUI.ID_ELIMINATE_NAKED)
  4533.     ReadWritePref(path.."/Preferences/ELIMINATE_HIDDEN",         sudokuGUI.ID_ELIMINATE_HIDDEN)
  4534.     ReadWritePref(path.."/Preferences/ELIMINATE_NAKEDPAIRS",     sudokuGUI.ID_ELIMINATE_NAKEDPAIRS)
  4535.     ReadWritePref(path.."/Preferences/ELIMINATE_HIDDENPAIRS",    sudokuGUI.ID_ELIMINATE_HIDDENPAIRS)
  4536.     ReadWritePref(path.."/Preferences/ELIMINATE_NAKEDTRIPLETS",  sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS)
  4537.     ReadWritePref(path.."/Preferences/ELIMINATE_HIDDENTRIPLETS", sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS)
  4538.     ReadWritePref(path.."/Preferences/ELIMINATE_NAKEDQUADS",     sudokuGUI.ID_ELIMINATE_NAKEDQUADS)
  4539.     ReadWritePref(path.."/Preferences/ELIMINATE_HIDDENQUADS",    sudokuGUI.ID_ELIMINATE_HIDDENQUADS)
  4540. end
  4541.  
  4542. -- ----------------------------------------------------------------------------
  4543.  
  4544. function sudokuGUI.InitFontsAndColours()
  4545.     sudokuGUI.Colours =
  4546.     {
  4547.         [sudokuGUI.VALUE_COLOUR]           = wx.wxColour(0, 0, 230),
  4548.         [sudokuGUI.INIT_VALUE_COLOUR]      = wx.wxColour(0, 0, 0),
  4549.         [sudokuGUI.POSS_VALUE_COLOUR]      = wx.wxColour(0, 0, 0),
  4550.         [sudokuGUI.INVALID_VALUE_COLOUR]   = wx.wxColour(255, 0, 0),
  4551.         [sudokuGUI.BACKGROUND_COLOUR]      = wx.wxColour(255, 255, 255),
  4552.         [sudokuGUI.ODD_BACKGROUND_COLOUR]  = wx.wxColour(250, 250, 210),
  4553.         [sudokuGUI.FOCUS_CELL_COLOUR]      = wx.wxColour(200, 220, 250),
  4554.  
  4555.         [sudokuGUI.NAKED_PAIRS_COLOUR]     = wx.wxColour(255, 0, 0),
  4556.         [sudokuGUI.NAKED_TRIPLETS_COLOUR]  = wx.wxColour(255, 180, 0),
  4557.         [sudokuGUI.NAKED_QUADS_COLOUR]     = wx.wxColour(255, 255, 0),
  4558.         [sudokuGUI.HIDDEN_PAIRS_COLOUR]    = wx.wxColour(0, 220, 0),
  4559.         [sudokuGUI.HIDDEN_TRIPLETS_COLOUR] = wx.wxColour(0, 240, 160),
  4560.         [sudokuGUI.HIDDEN_QUADS_COLOUR]    = wx.wxColour(0, 220, 220)
  4561.     }
  4562.  
  4563.     sudokuGUI.Colours_ = {}
  4564.     for n = 1, sudokuGUI.COLOUR_MAX do
  4565.         sudokuGUI.Colours_[n] = wx.wxColour(sudokuGUI.Colours[n])
  4566.     end
  4567.  
  4568.     --   just use defaults since some XP systems may not even have wxMODERN
  4569.     sudokuGUI.possibleFont_wxfont_ = wx.wxFont(wx.wxNORMAL_FONT)
  4570.     sudokuGUI.valueFont_wxfont_    = wx.wxFont(wx.wxNORMAL_FONT)
  4571.     sudokuGUI.valueFont_wxfont_:SetWeight(wx.wxFONTWEIGHT_BOLD)
  4572.     if not sudokuGUI.valueFont_wxfont_:Ok() then
  4573.         sudokuGUI.valueFont_wxfont_:Destroy()
  4574.         sudokuGUI.valueFont_wxfont_ = wx.wxFont(wx.wxNORMAL_FONT)
  4575.     end
  4576.  
  4577.     sudokuGUI.possibleFont.wxfont = wx.wxFont(sudokuGUI.possibleFont_wxfont_)
  4578.     sudokuGUI.valueFont.wxfont    = wx.wxFont(sudokuGUI.valueFont_wxfont_)
  4579. end
  4580.  
  4581. -- ----------------------------------------------------------------------------
  4582. -- Create a table of the menu IDs to use as a "case" type statement
  4583. sudokuGUI.MenuId = {}
  4584. -- ----------------------------------------------------------------------------
  4585. sudokuGUI.MenuId[sudokuGUI.ID_NEW]          = function() sudokuGUI.NewPuzzle(true) end
  4586. sudokuGUI.MenuId[sudokuGUI.ID_CREATE]       = function(event) sudokuGUI.CreatePuzzle(event:IsChecked()) end
  4587. sudokuGUI.MenuId[sudokuGUI.ID_GENERATE]     = function() sudokuGUI.GeneratePuzzle() end
  4588. sudokuGUI.MenuId[sudokuGUI.ID_OPEN]         = function() sudokuGUI.OpenPuzzle() end
  4589. sudokuGUI.MenuId[sudokuGUI.ID_SAVEAS]       = function() sudokuGUI.SaveAsPuzzle() end
  4590. sudokuGUI.MenuId[sudokuGUI.ID_PAGESETUP]    = function() sudokuGUI.PageSetup() end
  4591. sudokuGUI.MenuId[sudokuGUI.ID_PRINTSETUP]   = function() sudokuGUI.PrintSetup() end
  4592. sudokuGUI.MenuId[sudokuGUI.ID_PRINTPREVIEW] = function() sudokuGUI.PrintPreview() end
  4593. sudokuGUI.MenuId[sudokuGUI.ID_PRINT]        = function() sudokuGUI.Print() end
  4594. sudokuGUI.MenuId[sudokuGUI.ID_EXIT]         = function() sudokuGUI.frame:Close() end
  4595. -- ----------------------------------------------------------------------------
  4596. sudokuGUI.MenuId[sudokuGUI.ID_COPY_PUZZLE] =
  4597.             function (event)
  4598.                 local str = sudoku.ToString(sudokuGUI.GetCurrentTable())
  4599.                 if wx.wxClipboard.Get():Open() then
  4600.                     wx.wxClipboard.Get():SetData(wx.wxTextDataObject(str))
  4601.                     wx.wxClipboard.Get():Close()
  4602.                 end
  4603.             end
  4604. sudokuGUI.MenuId[sudokuGUI.ID_RESET] = function() sudokuGUI.ResetPuzzle() end
  4605. sudokuGUI.MenuId[sudokuGUI.ID_UNDO]  = function() sudokuGUI.Undo() end
  4606. sudokuGUI.MenuId[sudokuGUI.ID_REDO]  = function() sudokuGUI.Redo() end
  4607. sudokuGUI.MenuId[sudokuGUI.ID_PREFERENCES]      = function() sudokuGUI.PreferencesDialog() end
  4608. sudokuGUI.MenuId[sudokuGUI.ID_SAVE_PREFERENCES] = function() sudokuGUI.ConfigSave(true) end
  4609. -- ----------------------------------------------------------------------------
  4610. -- Makes sure that menu and tool items are in sync and updates the table
  4611. function sudokuGUI.MenuCheckUpdate(event)
  4612.     sudokuGUI.CheckMenuItem(event:GetId(), event:IsChecked())
  4613.     sudokuGUI.UpdateTable()
  4614. end
  4615.  
  4616. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_ERRORS]   = sudokuGUI.MenuCheckUpdate
  4617. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_MISTAKES] =
  4618.             function (event)
  4619.                 -- need to solve it ourselves first
  4620.                 if (not sudokuGUI.IsCheckedMenuItem(sudokuGUI.ID_CREATE)) and
  4621.                     (event:IsChecked()) and (not sudokuGUI.sudokuSolnTable) then
  4622.                     sudokuGUI.sudokuSolnTable = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
  4623.  
  4624.                     if not sudokuGUI.sudokuSolnTable then
  4625.                         event:SetInt(0) -- uncheck for MenuCheckUpdate function
  4626.                         sudokuGUI.frame:GetMenuBar():Check(sudokuGUI.ID_SHOW_MISTAKES, false)
  4627.                     end
  4628.                 end
  4629.                 sudokuGUI.MenuCheckUpdate(event)
  4630.             end
  4631. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_TOOLBAR]  =
  4632.             function(event)
  4633.                 sudokuGUI.frame:GetToolBar():Show(event:IsChecked())
  4634.                 -- hack to make the wxFrame layout the child panel
  4635.                 local w, h = sudokuGUI.frame:GetSizeWH()
  4636.                 sudokuGUI.frame:SetSize(wx.wxSize(w, h+1))
  4637.                 sudokuGUI.frame:SetSize(wx.wxSize(w, h))
  4638.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_TOOLBAR, event:IsChecked())
  4639.             end
  4640. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_TOOLBAR_LABELS]  =
  4641.             function(event)
  4642.                 local style = wx.wxNO_BORDER
  4643.                 if event:IsChecked() then
  4644.                     style = style + wx.wxTB_TEXT
  4645.                 end
  4646.  
  4647.                 sudokuGUI.frame:GetToolBar():SetWindowStyle(style)
  4648.                 sudokuGUI.frame:GetToolBar():Realize()
  4649.                 -- hack to make the wxFrame layout the child panel
  4650.                 local w, h = sudokuGUI.frame:GetSizeWH()
  4651.                 sudokuGUI.frame:SetSize(wx.wxSize(w, h+1))
  4652.                 sudokuGUI.frame:SetSize(wx.wxSize(w, h))
  4653.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_TOOLBAR_LABELS, event:IsChecked())
  4654.             end
  4655. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_STATUSBAR]  =
  4656.             function(event)
  4657.                 sudokuGUI.frame:GetStatusBar():Show(event:IsChecked())
  4658.                 -- hack to make the wxFrame layout the child panel
  4659.                 local w, h = sudokuGUI.frame:GetSizeWH()
  4660.                 sudokuGUI.frame:SetSize(wx.wxSize(w, h+1))
  4661.                 sudokuGUI.frame:SetSize(wx.wxSize(w, h))
  4662.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_STATUSBAR, event:IsChecked())
  4663.             end
  4664. -- ----------------------------------------------------------------------------
  4665. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_POSSIBLE] =
  4666.             function (event)
  4667.                 if event:IsChecked() then
  4668.                     -- make this act like a radio item that can be unchecked
  4669.                     sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_USER_POSSIBLE, false)
  4670.                 end
  4671.                 sudokuGUI.MenuCheckUpdate(event)
  4672.             end
  4673. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_USER_POSSIBLE] =
  4674.             function (event)
  4675.                 if event:IsChecked() then
  4676.                     -- make this act like a radio item that can be unchecked
  4677.                     sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_POSSIBLE, false)
  4678.                 end
  4679.                 sudokuGUI.MenuCheckUpdate(event)
  4680.             end
  4681. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_POSSIBLE_LINE] =
  4682.             function (event)
  4683.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_POSSIBLE_LINE, event:IsChecked())
  4684.                 local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
  4685.                 sudokuGUI.GetCellBestSize(width-1, height-1)
  4686.                 sudokuGUI.Refresh()
  4687.             end
  4688. sudokuGUI.MenuId[sudokuGUI.ID_USER_POSSIBLE_CLEAR] =
  4689.             function (event)
  4690.                 local ret = wx.wxMessageBox("Clear all of your pencil marks?",
  4691.                                             "wxLuaSudoku - clear pencil marks?",
  4692.                                             wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  4693.                                             sudokuGUI.frame )
  4694.                 if ret == wx.wxOK then
  4695.                     sudokuGUI.pencilMarks = {}
  4696.                     for cell = 1, 81 do
  4697.                         sudokuGUI.pencilMarks[cell] = {}
  4698.                     end
  4699.                     sudokuGUI.UpdateTable()
  4700.                 end
  4701.             end
  4702. sudokuGUI.MenuId[sudokuGUI.ID_USER_POSSIBLE_SETALL] =
  4703.             function (event)
  4704.                 local ret = wx.wxMessageBox("Set all values as possible in the pencil marks?",
  4705.                                             "wxLuaSudoku - set all pencil marks?",
  4706.                                             wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  4707.                                             sudokuGUI.frame )
  4708.                 if ret == wx.wxOK then
  4709.                     sudokuGUI.pencilMarks = {}
  4710.                     for cell = 1, 81 do
  4711.                         sudokuGUI.pencilMarks[cell] = {}
  4712.                         for v = 1, 9 do
  4713.                             sudokuGUI.pencilMarks[cell][v] = v
  4714.                         end
  4715.                     end
  4716.                     sudokuGUI.UpdateTable()
  4717.                 end
  4718.             end
  4719. sudokuGUI.MenuId[sudokuGUI.ID_USER_POSSIBLE_INIT] =
  4720.             function (event)
  4721.                 local ret = wx.wxMessageBox("Initialize the pencil marks to the calculated possible values?",
  4722.                                             "wxLuaSudoku - initialize pencil marks?",
  4723.                                             wx.wxOK + wx.wxCANCEL + wx.wxICON_INFORMATION,
  4724.                                             sudokuGUI.frame )
  4725.                 if ret == wx.wxOK then
  4726.                     local s  = sudokuGUI.GetCurrentTable()
  4727.                     for cell = 1, 81 do
  4728.                         sudokuGUI.pencilMarks[cell] = {}
  4729.                         for v = 1, 9 do
  4730.                             sudokuGUI.pencilMarks[cell][v] = s.possible[cell][v]
  4731.                         end
  4732.                     end
  4733.                     sudokuGUI.UpdateTable()
  4734.                 end
  4735.             end
  4736. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKED] =
  4737.             function (event)
  4738.                 local checked = event:IsChecked()
  4739.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_NAKEDPAIRS, checked)
  4740.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_NAKEDTRIPLETS, checked)
  4741.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_NAKEDQUADS, checked)
  4742.                 sudokuGUI.UpdateTable()
  4743.             end
  4744. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDEN] =
  4745.             function (event)
  4746.                 local checked = event:IsChecked()
  4747.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_HIDDENPAIRS, checked)
  4748.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_HIDDENTRIPLETS, checked)
  4749.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_SHOW_HIDDENQUADS, checked)
  4750.                 sudokuGUI.UpdateTable()
  4751.             end
  4752. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKEDPAIRS]     = sudokuGUI.MenuCheckUpdate
  4753. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDENPAIRS]    = sudokuGUI.MenuCheckUpdate
  4754. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKEDTRIPLETS]  = sudokuGUI.MenuCheckUpdate
  4755. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDENTRIPLETS] = sudokuGUI.MenuCheckUpdate
  4756. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_NAKEDQUADS]     = sudokuGUI.MenuCheckUpdate
  4757. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_HIDDENQUADS]    = sudokuGUI.MenuCheckUpdate
  4758. -- ----------------------------------------------------------------------------
  4759. sudokuGUI.MenuId[sudokuGUI.ID_VERIFY_PUZZLE] =
  4760.             function (event)
  4761.                 local s = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
  4762.                 if s then
  4763.                     sudokuGUI.sudokuSolnTable = s
  4764.                 end
  4765.             end
  4766. sudokuGUI.MenuId[sudokuGUI.ID_SHOW_SOLUTION] =
  4767.             function (event)
  4768.                 if not sudokuGUI.sudokuSolnTable then
  4769.                     local s = sudokuGUI.VerifyUniquePuzzle(sudokuGUI.GetInitTable())
  4770.                     if s then
  4771.                         sudokuGUI.sudokuSolnTable = s
  4772.                     end
  4773.                 end
  4774.  
  4775.                 if sudokuGUI.sudokuSolnTable then
  4776.                     sudokuGUI.AddTable(sudokuGUI.sudokuSolnTable)
  4777.                 end
  4778.             end
  4779.  
  4780. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKED] =
  4781.             function (event)
  4782.                 local checked = event:IsChecked()
  4783.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_NAKEDPAIRS, checked)
  4784.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS, checked)
  4785.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_NAKEDQUADS, checked)
  4786.                 sudokuGUI.UpdateTable()
  4787.             end
  4788. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDEN] =
  4789.             function (event)
  4790.                 local checked = event:IsChecked()
  4791.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_HIDDENPAIRS, checked)
  4792.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS, checked)
  4793.                 sudokuGUI.CheckMenuItem(sudokuGUI.ID_ELIMINATE_HIDDENQUADS, checked)
  4794.                 sudokuGUI.UpdateTable()
  4795.             end
  4796. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKEDPAIRS]     = sudokuGUI.MenuCheckUpdate
  4797. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDENPAIRS]    = sudokuGUI.MenuCheckUpdate
  4798. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS]  = sudokuGUI.MenuCheckUpdate
  4799. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS] = sudokuGUI.MenuCheckUpdate
  4800. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_NAKEDQUADS]     = sudokuGUI.MenuCheckUpdate
  4801. sudokuGUI.MenuId[sudokuGUI.ID_ELIMINATE_HIDDENQUADS]    = sudokuGUI.MenuCheckUpdate
  4802.  
  4803. sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANSINGLES] =
  4804.             function (event)
  4805.                 local s = TableCopy(sudokuGUI.GetCurrentTable())
  4806.                 local changed_cells = sudoku.SolveScanSingles(s)
  4807.                 if changed_cells then sudokuGUI.AddTable(s) end
  4808.             end
  4809. sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANROWS] =
  4810.             function (event)
  4811.                 local s = TableCopy(sudokuGUI.GetCurrentTable())
  4812.                 local changed_cells = sudoku.SolveScanRows(s)
  4813.                 if changed_cells then sudokuGUI.AddTable(s) end
  4814.             end
  4815. sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANCOLS] =
  4816.             function (event)
  4817.                 local s = TableCopy(sudokuGUI.GetCurrentTable())
  4818.                 local changed_cells = sudoku.SolveScanCols(s)
  4819.                 if changed_cells then sudokuGUI.AddTable(s) end
  4820.             end
  4821. sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANBLOCKS] =
  4822.             function (event)
  4823.                 local s = TableCopy(sudokuGUI.GetCurrentTable())
  4824.                 local changed_cells = sudoku.SolveScanBlocks(s)
  4825.                 if changed_cells then sudokuGUI.AddTable(s) end
  4826.             end
  4827.  
  4828. sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_SCANNING]   = function (event) sudokuGUI.SolveScanning() end
  4829. sudokuGUI.MenuId[sudokuGUI.ID_SOLVE_BRUTEFORCE] = function (event) sudokuGUI.SolveBruteForce() end
  4830. -- ----------------------------------------------------------------------------
  4831. sudokuGUI.MenuId[sudokuGUI.ID_ABOUT] =
  4832.             function (event)
  4833.                 wx.wxMessageBox("Welcome to wxLuaSudoku!\nWritten by John Labenski\nCopyright 2006.\n"..
  4834.                                 wxlua.wxLUA_VERSION_STRING.." built with "..wx.wxVERSION_STRING,
  4835.                                 "About wxLuaSudoku",
  4836.                                 wx.wxOK + wx.wxICON_INFORMATION,
  4837.                                 sudokuGUI.frame )
  4838.             end
  4839. sudokuGUI.MenuId[sudokuGUI.ID_HELP] =
  4840.             function (event)
  4841.                 local helpFrame = wx.wxFrame(sudokuGUI.frame, wx.wxID_ANY, "Help on wxLuaSudoku", wx.wxDefaultPosition, wx.wxSize(600,400))
  4842.                 local htmlWin = wx.wxHtmlWindow(helpFrame)
  4843.                 if (htmlWin:SetPage(sudokuGUIhelp)) then
  4844.                     helpFrame:Centre()
  4845.                     helpFrame:Show(true)
  4846.                 else
  4847.                     helpFrame:Destroy()
  4848.                 end
  4849.             end
  4850.  
  4851. -- ----------------------------------------------------------------------------
  4852.  
  4853. function sudokuGUI.OnMenuEvent(event)
  4854.     local id = event:GetId()
  4855.  
  4856.     if sudokuGUI.MenuId[id] then
  4857.         sudokuGUI.MenuId[id](event)
  4858.         return
  4859.     end
  4860. end
  4861.  
  4862. -- ----------------------------------------------------------------------------
  4863. -- Unify all checking and unchecking of the menu items and
  4864. --   make sure menu/toolbar are in sync
  4865.  
  4866. function sudokuGUI.CheckMenuItem(id, check)
  4867.     sudokuGUI.frame:GetMenuBar():Check(id, check)
  4868.     sudokuGUI.frame:GetToolBar():ToggleTool(id, check) -- doesn't care if id doesn't exist
  4869.     sudokuGUI.menuCheckIDs[id] = check
  4870. end
  4871. function sudokuGUI.IsCheckedMenuItem(id)
  4872.     if sudokuGUI.menuCheckIDs[id] == nil then
  4873.         sudokuGUI.menuCheckIDs[id] = sudokuGUI.frame:GetMenuBar():IsChecked(id)
  4874.     end
  4875.  
  4876.     return sudokuGUI.menuCheckIDs[id]
  4877. end
  4878.  
  4879. -- ----------------------------------------------------------------------------
  4880.  
  4881. function main()
  4882.  
  4883.     sudokuGUI.block_refresh = true
  4884.  
  4885.     -- initialize the fonts and colours to use (must always exist)
  4886.     sudokuGUI.InitFontsAndColours()
  4887.  
  4888.     -- initialize the printing defaults
  4889.     sudokuGUI.printData:SetPaperId(wx.wxPAPER_LETTER);
  4890.     sudokuGUI.pageSetupData:SetMarginTopLeft(wx.wxPoint(25, 25));
  4891.     sudokuGUI.pageSetupData:SetMarginBottomRight(wx.wxPoint(25, 25));
  4892.  
  4893.     -- Create the main frame for the program
  4894.     sudokuGUI.frame = wx.wxFrame(wx.NULL, wx.wxID_ANY, "wxLuaSudoku",
  4895.                                  wx.wxDefaultPosition, wx.wxSize(300,320))
  4896.  
  4897.     sudokuGUI.frame:SetSizeHints(300, 300);
  4898.     local bitmap = wx.wxBitmap(sudokuGUIxpmdata)
  4899.     local icon = wx.wxIcon()
  4900.     icon:CopyFromBitmap(bitmap)
  4901.     sudokuGUI.frame:SetIcon(icon)
  4902.  
  4903.     local function MItem(menu, id, text, help, bmp)
  4904.         local m = wx.wxMenuItem(menu, id, text, help)
  4905.         m:SetBitmap(bmp)
  4906.         bmp:delete()
  4907.         return m
  4908.     end
  4909.  
  4910.     local fileMenu = wx.wxMenu("", 0)
  4911.     fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_NEW,      "&New...\tCtrl-N",      "Clear the current puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_NEW, wx.wxART_TOOLBAR)))
  4912.     fileMenu:AppendCheckItem(sudokuGUI.ID_CREATE,              "&Create...\tCtrl-T",   "Enter the initial values for the puzzle")
  4913.     fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_GENERATE, "&Generate...\tCtrl-G", "Generate a new puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_EXECUTABLE_FILE, wx.wxART_TOOLBAR)))
  4914.     fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_OPEN,     "&Open...\tCtrl-O",     "Open a puzzle file", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR)))
  4915.     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)))
  4916.     fileMenu:AppendSeparator()
  4917.     fileMenu:Append(sudokuGUI.ID_PAGESETUP,    "Page S&etup...",    "Setup the printout page")
  4918.     --fileMenu:Append(sudokuGUI.ID_PRINTSETUP, "Print Se&tup...",   "Setup the printer")
  4919.     fileMenu:Append(sudokuGUI.ID_PRINTPREVIEW, "Print Pre&view...", "Preview the printout")
  4920.     fileMenu:Append(MItem(fileMenu, sudokuGUI.ID_PRINT,        "&Print...",         "Print the puzzle", wx.wxArtProvider.GetBitmap(wx.wxART_PRINT, wx.wxART_TOOLBAR)))
  4921.     fileMenu:AppendSeparator()
  4922.     fileMenu:Append(sudokuGUI.ID_EXIT, "E&xit\tCtrl-X", "Quit the program")
  4923.  
  4924.     local editMenu = wx.wxMenu("", 0)
  4925.     editMenu:Append(sudokuGUI.ID_COPY_PUZZLE, "Copy puzzle", "Copy the puzzle to the clipboard")
  4926.     editMenu:AppendSeparator()
  4927.     editMenu:Append(sudokuGUI.ID_RESET, "Re&set...\tCtrl-R", "Reset the puzzle to the initial state")
  4928.     editMenu:AppendSeparator()
  4929.     editMenu:Append(MItem(editMenu, sudokuGUI.ID_UNDO, "&Undo\tCtrl-Z", "Undo the last entry", wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR)))
  4930.     editMenu:Append(MItem(editMenu, sudokuGUI.ID_REDO, "&Redo\tCtrl-Y", "Redo the last entry", wx.wxArtProvider.GetBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR)))
  4931.     editMenu:AppendSeparator()
  4932.     editMenu:Append(sudokuGUI.ID_PREFERENCES, "P&references...", "Show the preferences dialog")
  4933.     editMenu:AppendSeparator()
  4934.     editMenu:Append(sudokuGUI.ID_SAVE_PREFERENCES, "Sa&ve preferences...", "Save the preferences")
  4935.  
  4936.     local viewMenu = wx.wxMenu("", 0)
  4937.     viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_ERRORS,   "Mark &errors\tCtrl-E",   "Mark duplicate values in puzzle")
  4938.     viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_MISTAKES, "Mark &mistakes\tCtrl-M", "Mark wrong values in puzzle")
  4939.     viewMenu:AppendSeparator()
  4940.     viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_TOOLBAR,        "Show toolbar",        "Show the toolbar")
  4941.     viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_TOOLBAR_LABELS, "Show toolbar labels", "Show labels on the toolbar")
  4942.     viewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_STATUSBAR,      "Show statusbar",      "Show the statusbar")
  4943.     viewMenu:Check(sudokuGUI.ID_SHOW_TOOLBAR, true)
  4944.     viewMenu:Check(sudokuGUI.ID_SHOW_TOOLBAR_LABELS, true)
  4945.     viewMenu:Check(sudokuGUI.ID_SHOW_STATUSBAR, true)
  4946.  
  4947.     local possibleMenu = wx.wxMenu("", 0)
  4948.     possibleMenu:AppendCheckItem( sudokuGUI.ID_SHOW_POSSIBLE,      "Show calculated &possible\tCtrl-P", "Show calculated possible values for the cells")
  4949.     possibleMenu:AppendCheckItem( sudokuGUI.ID_SHOW_USER_POSSIBLE, "Show/Edit pencil marks\tCtrl-l", "Show and edit user set possible values for the cells")
  4950.     possibleMenu:AppendSeparator()
  4951.     possibleMenu:AppendCheckItem( sudokuGUI.ID_SHOW_POSSIBLE_LINE, "Show possible in a &line",    "Show possible values for the cells in a line")
  4952.     possibleMenu:AppendSeparator()
  4953.     local userPossMenu = wx.wxMenu("", 0)
  4954.       userPossMenu:Append( sudokuGUI.ID_USER_POSSIBLE_CLEAR,  "Clear all...",  "Clear all pencil marks")
  4955.       userPossMenu:Append( sudokuGUI.ID_USER_POSSIBLE_SETALL, "Set all...",    "Set all pencil marks")
  4956.       userPossMenu:Append( sudokuGUI.ID_USER_POSSIBLE_INIT,   "Calculate...", "Initialize pencil marks to calculated possible")
  4957.     possibleMenu:Append(sudokuGUI.ID_USER_POSSIBLE_MENU, "Pencil marks", userPossMenu, "Setup user possible values")
  4958.  
  4959.     possibleMenu:AppendSeparator()
  4960.     local possViewMenu = wx.wxMenu("", 0)
  4961.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKED,  "Mark &naked groups",  "Mark all naked groups in possible values")
  4962.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDEN, "Mark &hidden groups", "Mark all hidden groups in possible values")
  4963.       possViewMenu:AppendSeparator()
  4964.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKEDPAIRS,     "Mark naked pairs",     "Mark naked pairs in possible values")
  4965.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDENPAIRS,    "Mark hidden pairs",    "Mark hidden pairs in possible values")
  4966.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKEDTRIPLETS,  "Mark naked triplets",  "Mark naked triplets in possible values")
  4967.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDENTRIPLETS, "Mark hidden triplets", "Mark hidden triplets in possible values")
  4968.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_NAKEDQUADS,     "Mark naked quads",     "Mark naked quads in possible values")
  4969.       possViewMenu:AppendCheckItem( sudokuGUI.ID_SHOW_HIDDENQUADS,    "Mark hidden quads",    "Mark hidden quads in possible values")
  4970.     possibleMenu:Append(sudokuGUI.ID_SHOW_MENU, "Mark &groups", possViewMenu, "Mark naked/hidden groups")
  4971.  
  4972.     local solveMenu = wx.wxMenu("", 0)
  4973.     solveMenu:Append(sudokuGUI.ID_VERIFY_PUZZLE, "Verify unique solution...", "Verify that the puzzle has only one solution")
  4974.     solveMenu:AppendSeparator()
  4975.     solveMenu:Append(sudokuGUI.ID_SHOW_SOLUTION, "Show solution", "Show the solution to the puzzle")
  4976.     solveMenu:AppendSeparator()
  4977.     local elimSolveMenu = wx.wxMenu("", 0)
  4978.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKED, "Eliminate &naked groups", "Eliminate all naked groups from possible values")
  4979.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDEN, "Eliminate &hidden groups", "Eliminate all hidden groups from possible values")
  4980.       elimSolveMenu:AppendSeparator()
  4981.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKEDPAIRS, "Eliminate naked pairs", "Eliminate naked pairs from possible values")
  4982.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDENPAIRS, "Eliminate hidden pairs", "Eliminate hidden pairs from possible values")
  4983.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKEDTRIPLETS, "Eliminate naked triplets", "Eliminate naked triplets from possible values")
  4984.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDENTRIPLETS, "Eliminate hidden triplets", "Eliminate hidden triplets from possible values")
  4985.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_NAKEDQUADS, "Eliminate naked quads", "Eliminate naked quads from possible values")
  4986.       elimSolveMenu:AppendCheckItem(sudokuGUI.ID_ELIMINATE_HIDDENQUADS, "Eliminate hidden quads", "Eliminate hidden quads from possible values")
  4987.     solveMenu:Append(sudokuGUI.ID_ELIMINATE_MENU, "&Eliminate groups", elimSolveMenu, "Remove possible values using naked and hidden groups")
  4988.     solveMenu:AppendSeparator()
  4989.     solveMenu:Append(sudokuGUI.ID_SOLVE_SCANSINGLES, "Solve (scan singles)\tCtrl-1", "Solve all cells with only one possibility")
  4990.     solveMenu:Append(sudokuGUI.ID_SOLVE_SCANROWS, "Solve (scan rows)\tCtrl-2", "Solve cells in rows with only one possible value")
  4991.     solveMenu:Append(sudokuGUI.ID_SOLVE_SCANCOLS, "Solve (scan cols)\tCtrl-3", "Solve cells in cols with only one possible value")
  4992.     solveMenu:Append(sudokuGUI.ID_SOLVE_SCANBLOCKS, "Solve (scan blocks)\tCtrl-4", "Solve cells in blocks with only one possible value")
  4993.     solveMenu:AppendSeparator()
  4994.     solveMenu:Append(sudokuGUI.ID_SOLVE_SCANNING, "Solve (&scanning)\tCtrl-L", "Solve the puzzle by only scanning")
  4995.     solveMenu:Append(sudokuGUI.ID_SOLVE_BRUTEFORCE, "Solve (&brute force)\tCtrl-B", "Solve the puzzle by guessing values")
  4996.  
  4997.     local helpMenu = wx.wxMenu("", 0)
  4998.     helpMenu:Append(sudokuGUI.ID_ABOUT, "&About...", "About the wxLuaSudoku Application")
  4999.     helpMenu:Append(MItem(helpMenu, sudokuGUI.ID_HELP, "&Help...", "Help using the wxLuaSudoku application", wx.wxArtProvider.GetBitmap(wx.wxART_HELP, wx.wxART_TOOLBAR)))
  5000.  
  5001.     local menuBar = wx.wxMenuBar()
  5002.     menuBar:Append(fileMenu,     "&File")
  5003.     menuBar:Append(editMenu,     "&Edit")
  5004.     menuBar:Append(viewMenu,     "&View")
  5005.     menuBar:Append(possibleMenu, "&Possible")
  5006.     menuBar:Append(solveMenu,    "&Solve")
  5007.     menuBar:Append(helpMenu,     "&Help")
  5008.  
  5009.     sudokuGUI.frame:SetMenuBar(menuBar)
  5010.  
  5011.     local toolBar = sudokuGUI.frame:CreateToolBar(wx.wxNO_BORDER + wx.wxTB_TEXT)
  5012.     local tbSize = toolBar:GetToolBitmapSize() -- required to force help icon to right size in MSW
  5013.     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")
  5014.     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")
  5015.     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")
  5016.     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")
  5017.     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")
  5018.     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")
  5019.     toolBar:AddSeparator()
  5020.     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")
  5021.     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")
  5022.     toolBar:AddSeparator()
  5023.     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")
  5024.     toolBar:Realize()
  5025.  
  5026.     sudokuGUI.frame:CreateStatusBar(2)
  5027.     local stat_width = sudokuGUI.frame:GetStatusBar():GetTextExtent("Step : 00000")
  5028.     sudokuGUI.frame:SetStatusWidths({-1, stat_width})
  5029.     sudokuGUI.frame:SetStatusText("Welcome to wxLuaSudoku.", 0)
  5030.  
  5031.     -- ------------------------------------------------------------------------
  5032.     -- Use single centralized menu/toolbar event handler
  5033.     sudokuGUI.frame:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED,
  5034.                             sudokuGUI.OnMenuEvent)
  5035.  
  5036.     -- ------------------------------------------------------------------------
  5037.  
  5038.     local values =
  5039.     {
  5040.         5,0,0, 8,0,3, 0,6,0,
  5041.         1,0,6, 0,9,2, 0,8,5,
  5042.         0,0,8, 5,0,7, 0,4,0,
  5043.  
  5044.         0,0,1, 0,3,4, 0,7,0,
  5045.         0,9,0, 0,0,8, 1,3,4,
  5046.         3,0,0, 0,2,0, 5,9,0,
  5047.  
  5048.         0,0,5, 1,0,0, 0,0,3,
  5049.         0,0,0, 0,0,9, 0,0,0,
  5050.         0,0,7, 3,0,0, 4,0,9
  5051.     }
  5052.  
  5053.     local solution =
  5054.     {
  5055.         5,4,9, 8,1,3, 2,6,7,
  5056.         1,7,6, 4,9,2, 3,8,5,
  5057.         2,3,8, 5,6,7, 9,4,1,
  5058.  
  5059.         8,5,1, 9,3,4, 6,7,2,
  5060.         7,9,2, 6,5,8, 1,3,4,
  5061.         3,6,4, 7,2,1, 5,9,8,
  5062.  
  5063.         9,8,5, 1,4,6, 7,2,3,
  5064.         4,1,3, 2,7,9, 8,5,6,
  5065.         6,2,7, 3,8,5, 4,1,9
  5066.     }
  5067.  
  5068.     local s = sudoku.CreateTable()
  5069.     sudoku.SetValues(s, values)
  5070.     sudokuGUI.sudokuTables_pos = 1
  5071.     sudokuGUI.sudokuTables[1] = s
  5072.  
  5073.     sudokuGUI.sudokuSolnTable = sudoku.CreateTable()
  5074.     sudoku.SetValues(sudokuGUI.sudokuSolnTable, solution)
  5075.  
  5076.     sudokuGUI.panel = wx.wxPanel(sudokuGUI.frame, wx.wxID_ANY)
  5077.     --sudokuGUI.panel:SetBackgroundColour(wx.wxColour(0,0,0))
  5078.     local gridsizer = wx.wxGridSizer(9, 9, 2, 2)
  5079.  
  5080.     for i = 1, 81 do
  5081.         local win = sudokuGUI.CreateCellWindow( sudokuGUI.panel, i, size )
  5082.         gridsizer:Add(win, 1, wx.wxALL+wx.wxGROW+ wx.wxALIGN_CENTER, 0)
  5083.         sudokuGUI.cellWindows[i] = win
  5084.     end
  5085.  
  5086.     local topsizer = wx.wxBoxSizer(wx.wxVERTICAL)
  5087.     topsizer:Add(gridsizer, 1, wx.wxALL+wx.wxGROW+wx.wxALIGN_CENTER, 0)
  5088.     sudokuGUI.panel:SetSizer( topsizer )
  5089.     --topsizer:Fit(sudokuGUI.frame)
  5090.     --topsizer:SetSizeHints( sudokuGUI.frame )
  5091.  
  5092.     -- ------------------------------------------------------------------------
  5093.     -- After being created - connect the size event to help MSW repaint the
  5094.     --  child windows
  5095.     sudokuGUI.cellWindows[1]:Connect(wx.wxEVT_SIZE,
  5096.             function (event)
  5097.                 local width, height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
  5098.                 sudokuGUI.GetCellBestSize(width, height)
  5099.                 sudokuGUI.Refresh()
  5100.                 event:Skip(true)
  5101.             end )
  5102.  
  5103.     -- save the config when closing the frame
  5104.     sudokuGUI.frame:Connect(wx.wxEVT_CLOSE_WINDOW,
  5105.             function (event)
  5106.                 event:Skip(true) -- allow it to really exit
  5107.                 sudokuGUI.ConfigSave(false)
  5108.             end )
  5109.  
  5110.     local cell_width, cell_height = sudokuGUI.cellWindows[1]:GetClientSizeWH()
  5111.     sudokuGUI.GetCellBestSize(cell_width, cell_height)
  5112.     --sudokuGUI.UpdateTable()
  5113.  
  5114.     sudokuGUI.frame:SetClientSize(300,300)
  5115.     sudokuGUI.block_refresh = false
  5116.     sudokuGUI.ConfigLoad()
  5117.     sudokuGUI.frame:Show(true)
  5118.  
  5119.     collectgarbage("collect") -- cleanup any locals
  5120. end
  5121.  
  5122. main()
  5123.  
  5124.  
  5125.  
  5126. if false then
  5127. function ProfileBegin()
  5128.     Profile_Counters = {}
  5129.     Profile_Names = {}
  5130.     local function hook ()
  5131.       local f = debug.getinfo(2, "f").func
  5132.       if Profile_Counters[f] == nil then    -- first time `f' is called?
  5133.         Profile_Counters[f] = 1
  5134.         Profile_Names[f] = debug.getinfo(2, "Sn")
  5135.         --TableDump(Profile_Names[f])
  5136.       else  -- only increment the counter
  5137.         Profile_Counters[f] = Profile_Counters[f] + 1
  5138.       end
  5139.     end
  5140.  
  5141.     debug.sethook(hook, "c")  -- turn on the hook
  5142. end
  5143.  
  5144. function ProfileEnd()
  5145.     debug.sethook()   -- turn off the hook
  5146.     function getname (func)
  5147.       local n = Profile_Names[func]
  5148.       if n.what == "C" then
  5149.         return n.name
  5150.       end
  5151.       local loc = string.format("[%s]:%s", n.short_src, n.linedefined)
  5152.       if n.namewhat ~= "" then
  5153.         return string.format("%s (%s)", loc, n.name)
  5154.       else
  5155.         return string.format("%s", loc)
  5156.       end
  5157.     end
  5158.     for func, count in pairs(Profile_Counters) do
  5159.       print(getname(func), count)
  5160.     end
  5161. end
  5162.  
  5163. s = sudoku.CreateTable()
  5164. s.flags[sudoku.ELIMINATE_HIDDEN_PAIRS] = true
  5165. s.flags[sudoku.ELIMINATE_HIDDEN_TRIPLETS] = true
  5166. s.flags[sudoku.ELIMINATE_HIDDEN_QUADS] = true
  5167.  
  5168. t = os.time()
  5169.  
  5170. --ProfileBegin()
  5171. for n = 1, 10 do
  5172.     a, b = sudoku.FindAllNakedHiddenGroups(s, true)
  5173.     --a, b, c = sudoku.FindPossibleCountRowColBlock(s)
  5174.     --a, b, c = sudoku.FindAllPossibleGroups(s)
  5175. end
  5176. --ProfileEnd()
  5177.  
  5178. print(os.time()-t)
  5179. end
  5180.  
  5181.  
  5182. --[[
  5183. for i = 1, 1 do
  5184.     local s = sudoku.GeneratePuzzle()
  5185.     s = sudoku.GeneratePuzzleDifficulty(s, 35, true)
  5186.     sudoku.UpdateTable(s)
  5187.     local n, h = sudoku.FindAllNakedHiddenGroups(s, true)
  5188.  
  5189.     --TableDump(n)
  5190.     --TableDump(h)
  5191.     local c = 0
  5192.     cnp = TableCount(n.pairs.cells)
  5193.     cnt = TableCount(n.triplets.cells)
  5194.     cnq = TableCount(n.quads.cells)
  5195.  
  5196.     chp = TableCount(h.pairs.cells)
  5197.     cht = TableCount(h.triplets.cells)
  5198.     chq = TableCount(h.quads.cells)
  5199.  
  5200.  
  5201.     print(i, string.format("n %03d %03d %03d h %03d %03d %03d", cnp, cnt, cnq, chp, cht, chq))
  5202.     a[string.format("n %03d %03d %03d h %03d %03d %03d", cnp, cnt, cnq, chp, cht, chq)] = TableCopy(s.values)
  5203.  
  5204.     if (cnp > 0) and (cnt > 0) and (cnq > 0) and (chp > 0) and (cht > 0) and (chq > 0) then
  5205.         break
  5206.     end
  5207. end
  5208. ]]
  5209.  
  5210. -- Call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
  5211. -- otherwise the wxLua program will exit immediately.
  5212. -- Does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit since the
  5213. -- MainLoop is already running or will be started by the C++ program.
  5214. wx.wxGetApp():MainLoop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement