https://1drv.ms/i/c/12ca5f14b603afc5/ET ... Q?e=sD0Jao
This code will generate sudoku puzzles of various difficulties.
You can try and solve them yourself. If you get stuck, ask it for a clue.
Sudoku Master can automatically solve all of its self generated puzzles using its logic procedures.
It is also possible to manually enter puzzles from an external source and try to solve them.
It will not be able to completely solve some of the more difficult puzzles. If you are cleaver, you
can first lock in all of the number you believe to be correct then enter a guess and have it try
to solve the puzzle using your guess.
I developed this application using PureBasic 6.20 x64
Attention: If you compile the code and the puzzle grid appears too small for the fonts try this.
Go to PureBasic's compiler options and uncheck the "Enable DPI aware executable" box.
This may be caused by your computer's system display scale settings set to something other than 100%.
This was the case on my laptop computer using Windows 11.
Edit: Fixed problem when using code with Linux OS
Code: Select all
; Sudoku Master by BasicallyPure 03/15/2025"
Global Title.s = "Sudoku Master"
EnableExplicit
;{ Procedure declarations
Declare APPLY_LEVEL_1_LOGIC() ;use this to help solve the puzzle
Declare APPLY_LEVEL_2_LOGIC() ;use this to help solve the puzzle
Declare BUILD_PUZZLE() ;generate a new puzzle
Declare CHECK_REGION(region.i) ;checks a specified region for duplicates
Declare CHECK_ROW(row.i) ;checks a specified row for duplicates
Declare CHECK_COLUMN(column.i) ;checks a specified column for duplicates
Declare CLEAR() ;erases all boxes in the puzzle
Declare CLEAR_UNLOCKED() ;erase the contents of all unlocked boxes
Declare CREATE_GUI() ;creates the application window, (user interface)
Declare DISABLE_BUTTONS(state) ;disable or enable all buttons
Declare DISPLAY_ALL_BOXES() ;redraw the entire puzzle
Declare DRAW_GRID() ;draw puzzle grid
Declare EDIT() ;handles 'edit' check box toggle event
Declare GET_MOUSE_CLICK_BOX() ;returns the puzzle box that was clicked in
Declare LEVEL_1_LOGIC(column,row) ;try to solve puzzle box using level 1 logic
Declare LEVEL_2_LOGIC(column,row) ;try to solve puzzle box using level 2 logic
Declare LOCK_CURRENT_VALUES() ;locks all filled boxes so they cannot be edited
Declare MAIN_EVENT_LOOP() ;this controls the program flow based on user inputs
Declare STAY_ON_TOP() ;toggle if window is on top or not
Declare PREPARE_PUZZLE() ;hide some boxes of a complete puzzle
Declare CLUE() ;show possibles for the current box only
Declare SCAN_ROW_FOR_SINGLES(c,r) ;this is used to help generate a new filled puzzle
Declare SET_DIFFICULTY() ;determine difficulty of self generated puzzles
Declare SHOW_ALL_POSSIBLES() ;draw in all possible values for unsolved boxes
Declare SOLVE() ;try to solve puzzle using level_1 and level_2 logic
Declare UNLOCK_ALL() ;unlock all puzzle boxes so they can be edited
Declare UPDATE_ALL_POSSIBLES() ;scan entire puzzle and update possibles for eDach box
Declare UPDATE_BOX(x,y,color) ;determine how to display the specified puzzle box
Declare VERIFY() ;check if any of the filled boxes violate the sudoku rules
;}
;{ define global constants
Enumeration
#KEY_0=0:#KEY_1 : #key_2 : #KEY_3 : #KEY_4
#KEY_5 :#KEY_6 : #KEY_7 : #KEY_8 : #KEY_9
EndEnumeration
Enumeration ;control gadgets
#Build : #ClrUnlk : #Diff : #Solve : #Edit : #Top
#Clear : #Lock : #Unlock : #Clue : #AllClues
EndEnumeration
#MainWin = 0
#HiColor = $77B85E ;hilight color
#Base = $C8CED3 ;background color
#Locked = $0D1C1B ;locked boxes color
#Unlkd = $3333BF ;unlocked boxes color
#Grid_1 = $898949 ;grid normal color
#Grid_2 = $103030 ;grid bold color
#Logic_1 = $77A85E ;hilight color for logic_1
#Logic_2 = $A68462 ;hilight color for logic_2
#ErrColor = $EF00EF ;color to flag an invalid entry
#BtnWidth = 114 ;width of control buttons
#BtnHeight= 30 ;height of control buttons
#Bdr = 20 ;puzzle border size
#Fast = 0 ;solve puzzle speed
#Slow = 150 ;solve puzzle speed
;}
;{ define global variables
Global PS = 555 ;default puzzle size, 465 or 555
Global AppWidth = PS+#BtnWidth+(#Bdr*2) ;applicaion width
Global AppHeight = PS+100 ;application height
Global canvas, Font1, Font2, error, Box_X=5, Box_Y=5
Global drawing ;used to tell if StartDrawing() is active
Global NoHilight = #False ;if #True then HILIGHT_BOX() will not erase last highlight
Global PuzzleAltered = #False ;set to #True if the puzzle has been edited
Global AllFilled.i ;used to flag that all boxes are assigned a number
Global difficulty = 3 ;default puzzle difficulty 0 to 5
Global SolveSpeed = #Slow ;use to control puzzle solve speed
Global DisableUpdateBox = #False ;enable(#False) or disable(#True) UPDATE_BOX() procedure
; create array to hold sudoku puzzle
Global Dim Puzzle.b(9,9,9) ;the z dimension holds possible x,y puzzle square values
Global Dim Locked.b(9,9) ;set x,y to #true if cell cannot be changed
;}
;{ set up program environment
Define x, y, z
; fill the puzzle array with starting values
For x = 1 To 9
For y = 1 To 9
Puzzle(x,y,0) = #False ;final puzzle box value not defined yet
For z = 1 To 9
Puzzle(x,y,z) = #True ;all possible values for each square to start
Next z
Next y
Next x
If PS = 465 ;smaller puzzle size
Font1 = LoadFont(#PB_Any, "Arial",20, #PB_Font_Bold) ;large font for 465 puzzle size
Font2 = LoadFont(#PB_Any, "Arial",10, #PB_Font_Bold) ;used for gadget (buttons) text
Else ;larger puzzle size
Font1 = LoadFont(#PB_Any, "Arial",26, #PB_Font_Bold) ;large font for 555 puzzle size
Font2 = LoadFont(#PB_Any, "Arial",12, #PB_Font_Bold) ;used for gadget (buttons) text
EndIf
;}
;{ ---> MAIN PROGRAM LOOP <---
If CREATE_GUI() ;create the application window
DISABLE_BUTTONS(#True)
DisableGadget(#Build,#False)
DisableGadget(#Diff,#False)
MAIN_EVENT_LOOP() ;loop until user closes application
EndIf
End ;terminate program
;} ---> END PROGRAM LOOP <---
Procedure APPLY_LEVEL_1_LOGIC() ;helps solve the puzzle
Protected c, r ;column and row variables
Repeat
PuzzleAltered = #False
For c = 1 To 9 : For r = 1 To 9 ;loop thru all boxes and apply level 1 logic if box is empty
;set PuzzleAtlered To #True If value is determined
If puzzle(c,r,0) = #False : LEVEL_1_LOGIC(c,r) : EndIf ;apply level 1 logic
While WindowEvent() : Wend ;make sure event buffer is empty
Next r : Next c
If PuzzleAltered = #True : DisableGadget(#ClrUnlk,#False) : EndIf ;enable 'clear unlocked boxes' button
Until PuzzleAltered = #False ;no more updates possible using level 1 logic
UPDATE_ALL_POSSIBLES()
EndProcedure
Procedure APPLY_LEVEL_2_LOGIC() ;help solve the puzzle
Protected c, r ;column and row
Repeat
PuzzleAltered = #False
For c = 1 To 9 : For r = 1 To 9 ;loop thru all boxes using level 2 logic
;if box is empty and value is determined then PuzzleAltered will be set to #True
If puzzle(c,r,0) = 0 : LEVEL_2_LOGIC(c,r) : EndIf ;apply level 2 logic
While WindowEvent() : Wend ;make sure event buffer is empty
Next r : Next c
If PuzzleAltered = #True : DisableGadget(#ClrUnlk,#False) : EndIf
Until PuzzleAltered = #False
UPDATE_ALL_POSSIBLES()
EndProcedure
Procedure BUILD_PUZZLE() ;generate a random filled puzzle
Protected c, r, v, p, tries, possible, complete
Dim ValueArray.b(9)
SetGadgetState(#AllClues,#False) ;disable the "All Clues" button
SolveSpeed = #Fast
Repeat
CLEAR()
;perform random fill of row 1
For c = 1 To 9 : ValueArray(c) = c : Next c
RandomizeArray(ValueArray(),1,9)
For c = 1 To 9 ;insert the random values in row 1
Puzzle(c,1,0) = ValueArray(c)
Next c ; random fill of row 1 is complete
Repeat ;this loop fills the remainder of the puzzle
;clear puzzle except for first row
For r = 2 To 9 : For c = 1 To 9 : Puzzle(c,r,0) = 0 : Next c : Next r
UPDATE_ALL_POSSIBLES()
tries = 0
For r = 2 To 9 ;fill rows 2 to 9
c = 1 ;start at column 1
;find and assign any possible singles for the row
While SCAN_ROW_FOR_SINGLES(c,r) = #True : Wend ;fill all single boxes
While tries < 25 : tries + 1 ;abort and go back to row 2 after 25 fails
Repeat ;start filling row r
While Puzzle(c,r,0) <> 0 : c + 1 : Wend ;skip over filled boxes
possible = #False
For p = 1 To 9 ;determine if any possibles exist
If Puzzle(c,r,p) = #True : possible = #True : EndIf
Next p
If possible = #True ;choose random possible and assign to box
While Puzzle(c,r,0) = 0
;pick one of the possible values at random
Repeat : v = Random(9,1) : Until Puzzle(c,r,v) = #True
Puzzle(c,r,0) = v : UPDATE_ALL_POSSIBLES()
While SCAN_ROW_FOR_SINGLES(c,r) = #True : Wend ;fill all single boxes
Wend
Else ;clear current row & repeat row
For c = 1 To 9 : Puzzle(c,r,0)=0 : Next c
UPDATE_ALL_POSSIBLES()
c = 1 : r - 1
Break 2 ;abort and start over on the current row
EndIf
c + 1 ;move to the next column
If c > 8 And r > 8 : complete = #True : EndIf ; puzzle is filled
Until c > 9 : tries = 0 : Break ;row finished
Wend ;keep trying to finish row r
Next r ;start the next row
Until complete = #True
Until PREPARE_PUZZLE() = #True ;hide some boxes & repeat if puzzle is not valid
SolveSpeed = #Slow
DISPLAY_ALL_BOXES() ;show the new puzzle
Box_X = 5 : Box_Y = 5 ;set focus on center square
If difficulty <> 0 : UPDATE_BOX(Box_X,Box_Y,#HiColor) : DisableGadget(#Clue,#False)
Else : DisableGadget(#Build,#False)
EndIf
EndProcedure
Procedure CHECK_REGION(region) ;check acuracy of each 3x3 region
Protected x, y, xs, ys, n, error ;error returned as #true if error is detected
Protected Dim contents(9) ;an array to count how many times a number was found
Select region ;determine column and row of box to start the scan
Case 1,2,3
xs = region*3-2 : ys = 1
Case 4,5,6
xs = (region-3)*3-2 : ys = 4
Case 7,8,9
xs = (region-6)*3-2 : ys = 7
EndSelect
;determine number of occurances of each number in region
For y = ys To ys + 2 ;check 3 rows
For x = xs To xs + 2 ;check 3 columns
n = Puzzle(x,y,0) ;get value of puzzle box x,y
If n > 0 ;puzzle box contained a value
contents(n)+1 ;increment how many times this number was present
UPDATE_BOX(x,y,#Base)
Else
AllFilled = #False ;an empty box was found
EndIf
Next x
Next y
;hilight any boxes that contained duplicate numbers in the region
For y = ys To ys + 2 ;check 3 rows
For x = xs To xs + 2 ;check 3 columns
n = Puzzle(x,y,0) ;get contents of specified box
If contents(n) > 1 ;a duplicate has been detected
UPDATE_BOX(x,y,#ErrColor) ;alert the user with visual indication
error = #True ;error has been detected
EndIf
Next x
Next y
If drawing = #True : StopDrawing() : drawing = #False : EndIf
NoHilight = #True ;a global variable used in UPDATE_BOX() procedure
ProcedureReturn error
EndProcedure
Procedure CHECK_ROW(row.i) ;check the specified row for errors
Protected column, n, error = #False
Protected Dim contents(9)
For column = 1 To 9 ;track number of occurances of each number in row
n = Puzzle(column,row,0) ;get box contents
If n > 0 ;the box has an assigned value
contents(n)+1 ;tally occurences of number 'n'
UPDATE_BOX(column,row,#Base)
Else
AllFilled = #False ;all puzzle boxes are not filled
EndIf
Next column
For column = 1 To 9 ;see if any number occured more than once
n = Puzzle(column,row,0)
If contents(n) > 1 ;a number has more than one occurance
UPDATE_BOX(column,row,#ErrColor) ;hilight the box With error
error = #True ;error has been detected
EndIf
Next column
If drawing = #True : StopDrawing() : drawing = #False : EndIf
NoHilight = #True ;a Global variable used in UPDATE_BOX() procedure
ProcedureReturn error
EndProcedure
Procedure CHECK_COLUMN(column.i) ;check the specified column for errors
Protected row, n, error = #False
Protected Dim contents(9)
For row = 1 To 9 ;track num of occurances of each num in column
n = Puzzle(column,row,0)
If n > 0 : contents(n)+1
UPDATE_BOX(column,row,#Base)
Else : AllFilled = #False : EndIf
Next row
For row = 1 To 9
n = Puzzle(column,row,0)
If contents(n) > 1 ;hilight the box With error
UPDATE_BOX(column,row,#ErrColor)
error = #True ;error has been detected
EndIf
Next row
If drawing = #True : StopDrawing() : drawing = #False : EndIf
NoHilight = #True ;a global variable used in UPDATE_BOX() procedure
ProcedureReturn error
EndProcedure
Procedure CLEAR() ;erase all box contents
Protected x, y, z
For y = 1 To 9
For x = 1 To 9
Puzzle(x,y,0) = #False ;final value is not defined
Locked(x,y) = #False ;box is not locked
For z = 1 To 9
Puzzle(x,y,z) = #True ;all values 1 --> 9 are possible
Next z
UPDATE_BOX(x,y,#Base) ;draw the box blanked
Next x
Next y
Box_X = 5 : Box_Y = 5 ;set edit focus to center square
DISABLE_BUTTONS(#True)
DisableGadget(#Build,#False)
DisableGadget(#Diff,#False)
PuzzleAltered = #False
EndProcedure
Procedure CLEAR_UNLOCKED() ;erase the contents of all unlocked boxes
Protected x, y, z
PuzzleAltered = #False
For y = 1 To 9
For x = 1 To 9
If Locked(x,y) = #False ;erase only if box is unlocked
Puzzle(x,y,0) = #False ;box value is undefined
UPDATE_BOX(x,y,#Base)
For z = 1 To 9 : Puzzle(x,y,z)=#True : Next z ;all values 1 to 9 are possible
PuzzleAltered = #True
EndIf
Next x
Next y
If drawing = #True : StopDrawing() : drawing = #False : EndIf
If PuzzleAltered = #True
UPDATE_ALL_POSSIBLES()
If GetGadgetState(#AllClues) = #True : SHOW_ALL_POSSIBLES() : EndIf
DISABLE_BUTTONS(#True)
SetGadgetState(#Edit,#True)
UPDATE_BOX(Box_X,Box_Y,#HiColor)
DisableGadget(#Clue,#False)
EndIf
EndProcedure
Procedure CREATE_GUI() ;create graphical user interface
; create the application window ---- {G}raphical {U}ser {I}nterface
Static GUI_created = #False
Protected.i flags, n
Protected by = 10 ; set y position of first buttom
Protected bs = #BtnHeight + 10 ; y space between button
Protected bx = AppWidth - #BtnWidth - 15 ; x position of buttons
;don't allow this procedure to be called more than once
If GUI_created = #True : ProcedureReturn #False : EndIf
flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget
If OpenWindow(#MainWin,0,0,AppWidth,AppHeight-80,Title,flags) ; make the window the specified size
SetWindowColor(#MainWin,$C0C5A7) ; window background color
StickyWindow(#MainWin,#False) ; window will not be on top
flags = #PB_Canvas_ClipMouse | #PB_Canvas_Keyboard | #PB_Canvas_Border
canvas = CanvasGadget(#PB_Any,10,10,PS,PS,flags) ;draw the puzzle on this
SetGadgetFont(#PB_Default,FontID(Font2)) ;this font will be used for gadget text
ButtonGadget(#Build,bx,by,#BtnWidth,#BtnHeight,"Build Puzzle")
GadgetToolTip(#Build,"Build a new puzzle") : by + bs
ButtonGadget(#Diff,bx,by,#BtnWidth,#BtnHeight,"Level "+Str(difficulty))
GadgetToolTip(#Diff,"set puzzle difficulty") : by + bs
ButtonGadget(#Lock,bx,by,#BtnWidth,#BtnHeight,"Lock All")
GadgetToolTip(#Lock,"Lock all filled boxes") : by + bs
ButtonGadget(#Unlock,bx,by,#BtnWidth,#BtnHeight,"Unlock All")
GadgetToolTip(#Unlock,"Unlock all boxes for editing") : by + bs
ButtonGadget(#Clear,bx,by,#BtnWidth,#BtnHeight,"Clear All")
GadgetToolTip(#Clear,"Clear all boxes") : by + bs
ButtonGadget(#ClrUnlk,bx,by,#BtnWidth,#BtnHeight,"Clr Unlocked")
GadgetToolTip(#ClrUnlk,"Clear unlocked boxes") : by + bs*2
CheckBoxGadget(#Top,bx,by,#BtnWidth,#BtnHeight,"Stay On Top")
GadgetToolTip(#Top,"window is always on top") : by + bs
CheckBoxGadget(#Edit,bx,by,#BtnWidth,#BtnHeight,"Edit",#PB_CheckBox_Center)
GadgetToolTip(#Edit,"Allow manual input") : by + bs*2
CheckBoxGadget(#AllClues,bx,by,#BtnWidth,#BtnHeight,"All Clues",#PB_CheckBox_Center)
GadgetToolTip(#AllClues,"show possible values") : by + bs
ButtonGadget(#Clue,bx,by,#BtnWidth,#BtnHeight,"Show Clue ")
GadgetToolTip(#Clue,"Show possibles for current box") : by + bs*2
ButtonGadget(#Solve,bx,by,#BtnWidth,#BtnHeight,"Solve")
GadgetToolTip(#Solve,"try to solve with logic")
For n=1 To 9 ;set all number keys as shortcuts
AddKeyboardShortcut(0,$30+n,n) ; number keys
AddKeyboardShortcut(0,$60+n,n) ; num pad keys
Next n
; shortcuts to move active box with arrow keys
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Up,#PB_Key_Up)
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Down,#PB_Key_Down)
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Left,#PB_Key_Left)
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Right,#PB_Key_Right)
; shortcuts to erase box contents
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Space,0)
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Back,0)
AddKeyboardShortcut(#MainWin,#PB_Shortcut_Delete,0)
AddKeyboardShortcut(#MainWin,#PB_Shortcut_0,0)
AddKeyboardShortcut(#MainWin,$60,0) ;NumPad 0
DRAW_GRID()
; application window creation finished
SetActiveGadget(canvas)
GUI_created = #True
ProcedureReturn #True
EndIf
EndProcedure
Procedure CLUE() ;show possibles in the current box
Protected c = Box_X, r = Box_Y
Protected a, b, x, y, z, dc
Protected bx =(PS -(2*#Bdr)-2) / 9, by = (PS -(2*#Bdr)-2) / 9 ;box size
If puzzle(c,r,0) = 0
x=(c-1)*bx+22 : y=(r-1)*by+22
If GetGadgetState(#Edit) = #True ;hilight the active box
dc = #HiColor
Else
dc = #Base
EndIf
If drawing <> #True ; check if StartDrawing is active
StartDrawing(CanvasOutput(canvas)) : drawing = #True
DrawingFont(FontID(Font2)) ;small font
EndIf
Box(x,y,bx-4,by-4,dc)
a = x+3 : b = y
If PS <> 465 : a+3 : b+3 : EndIf ;adjust for 555 size puzzle
UPDATE_ALL_POSSIBLES()
For z = 1 To 9
x=a : y=b
If Puzzle(c,r,z) = #True
Select z
Case 1,2,3 : x+((z-1)*15) : y + 0
Case 4,5,6 : x+((z-4)*15) : y + 13 : If PS <> 465 : y+3 : EndIf
Case 7,8,9 : x+((z-7)*15) : y + 26 : If PS <> 465 : y+6 : EndIf
EndSelect
DrawText(x,y,Str(z),#Black,dc)
EndIf
Next z
EndIf
If drawing = #True : StopDrawing() : drawing = #False : EndIf
EndProcedure
Procedure DISABLE_BUTTONS(state)
;buttons are enabled or disabled as specified by state
DisableGadget(#Build,state)
DisableGadget(#Clear,state)
DisableGadget(#ClrUnlk,state)
DisableGadget(#Lock,state)
DisableGadget(#Unlock,state)
DisableGadget(#Solve,state)
DisableGadget(#Diff,state)
DisableGadget(#Clue,state)
EndProcedure
Procedure DISPLAY_ALL_BOXES()
Protected c, r
For c = 1 To 9 : For r = 1 To 9
UPDATE_BOX(c,r,#Base)
Next r : Next c
StopDrawing() : drawing = #False : Box_X=5 : Box_Y=5
EndProcedure
Procedure DRAW_GRID() ;draw puzzle grid
Protected.i len, color, inc, stop, v1, v2, v3, x, y
StartDrawing(CanvasOutput(canvas)) : drawing = #True
Box(0,0,PS,PS,#Base) ; grid background
;{ draw horizontal grid lines
len = PS-(2*#Bdr)+1 ;horizontal line lengths
inc = (PS - (2*#Bdr)+2) / 9
stop = (inc * 9) + #Bdr
v1 = inc*3 + #Bdr : v2 = inc*6 + #Bdr : v3 = inc*9 + #Bdr
x = #Bdr-1 : y = #Bdr
Repeat
If y=#Bdr Or y=v1 Or y=v2 Or y=v3
color = #Grid_2
Else
color = #Grid_1
EndIf
;3 horizontal parallel lines centered on y value
Line(x,y-1,len,1,color) : Line(x,y,len,1,color) : Line(x,y+1,len,1,Color)
y + inc
Until y > stop
;}
;{ draw vertical grid lines
len = PS-(2*#Bdr)-5 ;vertical line lengths, 6 shorter than horizontal lines
inc = (PS - (2*#Bdr)+2) / 9
stop = (inc * 9) + #Bdr
v1 = inc*3 + #Bdr : v2 = inc*6 + #Bdr : v3 = inc*9 + #Bdr
y = #Bdr+2 : x = #Bdr
Repeat
If x=#Bdr Or x=v1 Or x=v2 Or x=v3
color = #Grid_2
Else
color = #Grid_1
EndIf
;3 vertical parallel lines centered on x value
Line(x-1,y,1,len,color) : Line(x,y,1,len,color) : Line(x+1,y,1,len,Color)
x + inc
Until x > stop
;}
If drawing = #True : StopDrawing() : drawing = #False : EndIf
EndProcedure
Procedure EDIT() ;handle 'edit' check box toggle event
If error = #True ;block until error is corrected
SetGadgetState(#Edit,#True) : ProcedureReturn
EndIf
If GetGadgetState(#Edit) = #PB_Checkbox_Checked
DISABLE_BUTTONS(#True)
DisableGadget(#Clue,#False)
UPDATE_BOX(Box_X,Box_Y,#HiColor)
ElseIf GetGadgetState(#Edit) = #PB_Checkbox_Unchecked
DISABLE_BUTTONS(#False)
DisableGadget(#Clue,#True)
If PuzzleAltered = #True : DisableGadget(#Build,#True) : EndIf
If GetGadgetState(#AllClues) = #True : SHOW_ALL_POSSIBLES() : EndIf
If drawing = #True : StopDrawing() : drawing = #False : EndIf
UPDATE_BOX(Box_X,Box_Y,#Base)
EndIf
EndProcedure
Procedure GET_MOUSE_CLICK_BOX() ;set global variables Box_X and Box_Y
Protected x, y
x = GetGadgetAttribute(canvas,#PB_Canvas_MouseX) - #Bdr
y = GetGadgetAttribute(canvas,#PB_Canvas_MouseY) - #Bdr
;calculate puzzle square clicked
;If x > 0 And y > 0 And x < 440 And y < 440
If x > 0 And y > 0 And x < PS-(#Bdr*2)-3 And y < PS-(#Bdr*2)-3 ;
;x = x/47+1 : y = y/47+1
x = x/((PS -(2*#Bdr)-2) / 9) : x+1 ;: Debug x
y = y/((PS -(2*#Bdr)-2) / 9) : y+1 ;: Debug y
Box_X = x : Box_Y = y ; set global variables
UPDATE_BOX(x,y,#HiColor)
If drawing = #True : StopDrawing() : drawing = #False : EndIf
EndIf
EndProcedure
Procedure LEVEL_1_LOGIC(column,row) ; update possible values for this specific box
Protected n, d, x, y, xs, ys, count
For n = 1 To 9 ;scan the column and row that contains the specified box
d = Puzzle(n,row,0) ;get contents of each box in column n for the specified row
If d > 0 : Puzzle(column,row,d) = #False : EndIf ;remove number d from possibilities
d = puzzle(column,n,0) ;get contents of each box in row n for the specified column
If d > 0 : puzzle(column,row,d) = #False : EndIf ;remove number d from possibilities
Next n
;integer variable math used here to calculate column and row start
;examples: 2/3 = 0 , 5/3 = 1
xs = 1 + ((column-1)/3)*3 ;calculate column start
ys = 1 + ((row -1)/3)*3 ;calculate row start
;scan the 3x3 region that contains the specified box
For x = xs To xs + 2 ;scan 3 columns
For y = ys To ys + 2 ;scan 3 rows
d = Puzzle(x,y,0) ;retreive box contents
If d > 0 : puzzle(column,row,d) = #False : EndIf ;remove from possibilities
Next y
Next x
For n = 1 To 9 ;count how many possible values a box has
If Puzzle(column,row,n) = #True
count = count + 1 : d = n
EndIf
Next n
If count = 1 ;only 1 value for this square is possible
puzzle(column,row,0) = d ;update the puzzle with the new value
For n = 1 To 9 : Puzzle(column,row,n) = #False : Next n ;remove all possibles for this box
If SolveSpeed = #Slow
UPDATE_BOX(column,row,#Logic_1) ;display box value with specified background color
If drawing = #True : StopDrawing() : drawing = #False : EndIf
EndIf
Delay(SolveSpeed) ;slow down so user can see progress
DisableGadget(#Lock,#False)
PuzzleAltered = #True ;level 1 logic is still making progress
EndIf
EndProcedure
Procedure LEVEL_2_LOGIC(column,row)
Protected c, n, r, z, cs, rs, value
Protected Dim possibles(9,2) ; (n,0)= possible values, (n,1)=column, (n,2)=row
;scan all boxes and set possibles for each puzzle array box
UPDATE_ALL_POSSIBLES() ;calling another procedure to perform this function
; <-------------------start check of all boxes in column -------------------------->
;scan possibles of each box in this column
For r = 1 To 9 : For z = 1 To 9
possibles(z,0) + Puzzle(column,r,z) ;tally the count of value z
If Puzzle(column,r,z) = 1 : Puzzle(column,r,z) = z : EndIf
Next z : Next r
value = 0 ;if a value is found only once the box containing that value has been solved
For z = 1 To 9 : If possibles(z,0) = 1 : value = z : Break : EndIf : Next z
;retreive the box location (row) to receive the determined value
For r = 1 To 9 : For z = 1 To 9
If puzzle(column,r,z) = value : possibles(value,2) = r : Break 2 : EndIf
Next z : Next r
Box_X = column : Box_Y = possibles(value,2) ;set the global box location
For r = 1 To 9 : For z = 1 To 9 ;restore puzzle to true/false state
If puzzle(column,r,z) > 0 : puzzle(column,r,z) = 1 : EndIf
Next z : Next r
If value > 0 And Box_Y > 0 : Goto finish : EndIf
; ---------------------- end check of all boxes in column ----------------------------
For n = 1 To 9 ;clear possible values before starting row check
possibles(n,0) = 0 : possibles(n,1) = 0 : possibles(n,2) = 0
Next n
; <------------------------ start check of all boxes in row ------------------------->
;scan possibles of each box in this row
For c = 1 To 9 : For z = 1 To 9
possibles(z,0) + Puzzle(c,row,z) ;tally the count of puzzle(c,row,z)
If Puzzle(c,row,z) = 1 : Puzzle(c,row,z) = z : EndIf
Next z : Next c
value = 0 ;if a value is found only once the box containing that value has been solved
For z = 1 To 9 : If possibles(z,0) = 1 : value = z : Break : EndIf : Next z
;retreive the box location (column) to receive the determined value
For c = 1 To 9 : For z = 1 To 9
If puzzle(c,row,z) = value : possibles(value,1) = c : Break 2 : EndIf
Next z : Next c
Box_X = possibles(value,1) : Box_Y = row ;set the global box location
For c = 1 To 9 : For z = 1 To 9 ;restore puzzle to true/false state
If puzzle(c,row,z) > 0 : puzzle(c,row,z) = 1 : EndIf
Next z : Next c
If value > 0 And Box_X > 0 : Goto finish: : EndIf
; <------------------------ end check of all boxes in row --------------------------->
; <----------------- start check of all boxex for this 3x3 region ------------------->
;clear possible values to start
For n = 1 To 9 : possibles(n,0) = 0 : possibles(n,1) = 0 : possibles(n,2) = 0 : Next n
;integer variable math used here to calculate column and row start
;examples: 2/3 = 0 , 5/3 = 1
rs = 1 + ((row -1)/3)*3 ;calculate row start
cs = 1 + ((column-1)/3)*3 ;calculate column start
For r = rs To rs+2 ;check each row box in this 3 box column
For c = cs To cs+2 ;check each column box in this 3 box row
For z = 1 To 9 ;see which possibles are present in each 3x3 box
possibles(z,0) + puzzle(c,r,z) ;tally the count of puzzle(c,r,z)
If Puzzle(c,r,z) = 1 : Puzzle(c,r,z) = z : EndIf
Next z : Next c : Next r
value = 0 ;if a value is found only once the box containing that value has been solved
For z = 1 To 9 : If possibles(z,0) = 1 : value = z : Break : EndIf : Next z
;retreive the box location (column & row) to receive the determined value
For c = cs To cs + 2 : For r = rs To rs + 2 : For z = 1 To 9
If puzzle(c,r,z) = value : possibles(value,1) = c : possibles(value,2) = r : Break 3 : EndIf
Next z : Next r : Next c
Box_X = possibles(value,1) : Box_Y = possibles(value,2) ;set the global box location
;restore puzzle to true/false state
For c = cs To cs + 2 : For r = rs To rs + 2 : For z = 1 To 9
If puzzle(c,r,z) > 0 : puzzle(c,r,z) = 1 : EndIf
Next z : Next r : Next c
If value > 0 And Box_X > 0 And Box_Y > 0 : Goto finish: : Else : ProcedureReturn : EndIf
; <--------------- End check of all possibles for this 3x3 region ------------------->
finish: ;update puzzle with value
Puzzle(Box_X,Box_Y,0) = value
If SolveSpeed = #Slow
UPDATE_BOX(Box_X,Box_Y,#Logic_2) ;display box value with specified background color
If drawing = #True : StopDrawing() : drawing = #False : EndIf
EndIf
Delay(SolveSpeed) ;slow down so user can see progress
DisableGadget(#Lock,#False)
PuzzleAltered = #True ;indicate progress has been made
EndProcedure
Procedure LOCK_CURRENT_VALUES() ;disable editing of all filled boxes
Protected col,row, AlteredState = PuzzleAltered
For row = 1 To 9
For col = 1 To 9
If Puzzle(col,row,0) <> 0
Locked(col,row) = #True
UPDATE_BOX(col,row,#Base)
EndIf
Next col
Next row
If drawing = #True : StopDrawing() : drawing = #False : EndIf
PuzzleAltered = AlteredState
DISABLE_BUTTONS(#False)
SetGadgetState(#Edit,#True) : UPDATE_BOX(Box_X,Box_Y,#HiColor)
DisableGadget(#Solve,#True)
DisableGadget(#Build,#True)
DisableGadget(#Lock,#True)
DisableGadget(#ClrUnlk,#True)
EndProcedure
Procedure MAIN_EVENT_LOOP()
Protected.i event, x, y, z, r
Protected possibles.s
Repeat ; start the event loop
event = WaitWindowEvent() ;wait for a user action
Select event
Case #PB_Event_CloseWindow ;end program by exiting 'Repeat_ForEver' loop
If PuzzleAltered = #True
r = MessageRequester("EXIT PROGRAM","Are you sure?",#PB_MessageRequester_YesNo|#PB_MessageRequester_Warning)
If r = #PB_MessageRequester_Yes : Break : EndIf
Else
Break
EndIf
Case #PB_Event_Menu ;a shortcut key was pressed
If GetGadgetState(#Edit) = #PB_Checkbox_Checked
Select EventMenu()
Case #key_0 To #KEY_9 ;this is where user inputs puzzle values
If Locked(Box_X,Box_Y) = #False ;box must be unlocked to change value
Puzzle(Box_X,Box_Y,0) = EventMenu() ;box value was manually set
PuzzleAltered = #True
UPDATE_BOX(Box_X,Box_Y,#HiColor)
If VERIFY() ;check rule violations & puzzle complet
SetGadgetState(#Edit,#False)
SetGadgetState(#AllClues,#False)
DISABLE_BUTTONS(#False)
MessageRequester("Success!","This puzzle is correct.",#PB_MessageRequester_Info)
UPDATE_BOX(Box_X,Box_Y,#Base)
EndIf
If PuzzleAltered = #True And Not error
SHOW_ALL_POSSIBLES()
EndIf
EndIf
Case #PB_Key_Up ;move hilight box 1 position up
If error = #False ;skip if error is present
Box_Y - 1
If Box_Y < 1 : Box_Y = 9
Box_X - 1 : If Box_X < 1 : Box_X = 9 : EndIf
EndIf
UPDATE_BOX(Box_X,Box_Y,#HiColor)
EndIf
Case #PB_Key_Down ;move hilight box 1 position down
If error = #False ;skip if error is present
Box_Y + 1
If Box_Y > 9 : Box_Y = 1
Box_X + 1 : If Box_X > 9 : Box_X = 1 : EndIf
EndIf
UPDATE_BOX(Box_X,Box_Y,#HiColor)
EndIf
Case #PB_Key_Left ;move hilight box 1 position left
If error = #False ;skip if error is present
Box_X - 1
If Box_X < 1 : Box_X = 9
Box_Y - 1 : If Box_Y < 1 : Box_Y = 9 : EndIf
EndIf
UPDATE_BOX(Box_X,Box_Y,#HiColor)
EndIf
Case #PB_Key_Right ;move hilight box 1 position right
If error = #False ;skip if error is present
Box_X + 1
If Box_X > 9 : Box_X = 1
Box_Y + 1 : If Box_Y > 9 : Box_Y = 1 : EndIf
EndIf
UPDATE_BOX(Box_X,Box_Y,#HiColor)
EndIf
EndSelect
EndIf
Case #PB_Event_Gadget ;one of the window gadgets has produced an event
Select EventGadget() ;take action based on which gadget produced the event
Case canvas ;the canvas gadget has produced an event
If GetGadgetState(#Edit) = #PB_Checkbox_Checked
If EventType() = #PB_EventType_LeftButtonUp ;respond to mouse click
If error = #False
GET_MOUSE_CLICK_BOX();sets global variables Box_X and Box_Y
EndIf
EndIf
EndIf
Case #Edit : EDIT() ;toggle 'edit' check box
Case #Clear : CLEAR() ;clear all puzzle boxes
Case #ClrUnlk : CLEAR_UNLOCKED() ;clear all unlocked boxes
Case #Build : BUILD_PUZZLE() ;make a new puzzle
Case #Diff : SET_DIFFICULTY()
Case #AllClues : SHOW_ALL_POSSIBLES() ;show possible values for each unsolved box
Case #Lock : LOCK_CURRENT_VALUES() ;prevent all current values from being changed
Case #Solve : SOLVE() ;try to solve the puzzle
Case #Unlock : UNLOCK_ALL() ;allow all boxes to be edited
Case #Clue : CLUE() ;show possible values for the current box
Case #Top : STAY_ON_TOP() ;toggle if window is on top or not
EndSelect
EndSelect
If drawing = #True : StopDrawing() : drawing = #False : EndIf
ForEver
FreeArray(Puzzle())
ProcedureReturn
EndProcedure
Procedure PREPARE_PUZZLE() ;use this procedure to hide some of the puzzle boxes
Protected b, c, r, v
Protected Result = #True ;change to #False if puzzle becomes invalid
If difficulty = 0 ;show a completed puzzle
LOCK_CURRENT_VALUES() : DISPLAY_ALL_BOXES()
ProcedureReturn #True
EndIf
DisableUpdateBox = #True ;disable UPDATE_BOX() procedure
; -------- remove several boxes from the puzzle based upon difficulty level --------
For r = 1 To 9 : For c = 1 To 9 ;lock all boxes
If Puzzle(c,r,0) <> 0 : Locked(c,r) = #True : EndIf
Next c : Next r
For b = 1 To 7*difficulty + 15 ;<------ this determines puzzle difficulty
Repeat : c = Random(9,1) : r = Random(9,1) : Until Puzzle(c,r,0) <> 0
Puzzle(c,r,0) = 0 : Locked(c,r) = #False
Next b
CLEAR_UNLOCKED() : UPDATE_ALL_POSSIBLES()
; -----------------------------------------------------------------------------------
While SOLVE() = #False
Repeat
c = Random(9,1) : r = Random(9,1)
If Puzzle(c,r,0) = #False ;box value has not been assigned
For v = 1 To 9 ;see if any values are possible
If Puzzle(c,r,v) = #True ;a possible value was found
Repeat : v = Random(9,1) : Until Puzzle(c,r,v) = #True ;choose random possible
Puzzle(c,r,0) = v : Locked(c,r) = #True
UPDATE_ALL_POSSIBLES()
Break 2 ;try to solve again
ElseIf v = 9 ;no possibles were found
Result = #False ;puzzle is invalid, generate new puzzle and try again
Break 3 ;exit the procedure
EndIf
Next v
EndIf
ForEver
Wend
CLEAR_UNLOCKED() ;hide the unsolved values
DisableUpdateBox = #False ;enable UPDATE_BOX() procedure
ProcedureReturn(Result)
EndProcedure
Procedure SCAN_ROW_FOR_SINGLES(c,r) ;use when generating new puzzles
;if only one possible value in row is found, update that box
Protected v, n, count, Altered, result
For n = 1 To 9 ;count how many possible values a box has
If Puzzle(c,r,n) = #True
count = count + 1 : v = n ;v saves the possible value found
EndIf
Next n
If count = 1 ;only 1 value for this square is possible
puzzle(c,r,0) = v ;update the puzzle with the new value
UPDATE_ALL_POSSIBLES() ;recalculate possibles for entire puzzle
result = #True ;still making progress
EndIf
ProcedureReturn result
EndProcedure
Procedure SET_DIFFICULTY()
Protected d$, d, event, DifWin, d0, d1, d2, d3, d4, d5, ok
Protected flags = #PB_Window_WindowCentered + #PB_Window_Tool
;Difficulty is a global variable
DifWin = OpenWindow(#PB_Any,0,0,140,100,"Select Difficulty",flags,WindowID(#MainWin))
d0 = OptionGadget(#PB_Any,10,05,30,20,"0") : d1 = OptionGadget(#PB_Any,50,05,30,20,"1")
d2 = OptionGadget(#PB_Any,90,05,30,20,"2") : d3 = OptionGadget(#PB_Any,10,30,30,20,"3")
d4 = OptionGadget(#PB_Any,50,30,30,20,"4") : d5 = OptionGadget(#PB_Any,90,30,30,20,"5")
ok = ButtonGadget(#PB_Any,35,60,70,30,"OK")
Select difficulty
Case 0 : SetGadgetState(d0,#True) : Case 1 : SetGadgetState(d1,#True)
Case 2 : SetGadgetState(d2,#True) : Case 3 : SetGadgetState(d3,#True)
Case 4 : SetGadgetState(d4,#True) : Case 5 : SetGadgetState(d5,#True)
EndSelect
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_Gadget
Select EventGadget()
Case d0 : difficulty = 0 : Case d1 : difficulty = 1 : Case d2 : difficulty = 2
Case d3 : difficulty = 3 : Case d4 : difficulty = 4 : Case d5 : difficulty = 5
Case ok : SetGadgetText(#Diff,"Level "+Str(difficulty))
CloseWindow(DifWin) : Break
EndSelect
EndSelect
ForEver
EndProcedure
Procedure SHOW_ALL_POSSIBLES() ;fill all unsolved boxes with possible values
Protected r, c, x, y, z, a, b
Protected dc ;drawing color
Protected bx=(PS -(2*#Bdr)-2) / 9, by= (PS -(2*#Bdr)-2) / 9 ;box size
If error = #True ;block until error is corrected
SetGadgetState(#AllClues,#True)
ProcedureReturn
EndIf
If drawing <> #True ; check if StartDrawing is active
StartDrawing(CanvasOutput(canvas)) : drawing = #True
EndIf
If GetGadgetState(#AllClues) = #True
DrawingFont(FontID(Font2)) ;small font
UPDATE_ALL_POSSIBLES()
For c = 1 To 9 : For r = 1 To 9
If puzzle(c,r,0) = 0
x=(c-1)*bx+22 : y=(r-1)*by+22
If c = Box_X And r = Box_Y And GetGadgetState(#Edit) = #True ;hilight the active box
dc = #HiColor
Else
dc = #Base
EndIf
Box(x,y,bx-4,by-4,dc)
a=x+3 : b=y
If PS <> 465 : a+3 : b+3 : EndIf ;adjust for 555 size puzzle
For z = 1 To 9
x=a : y=b
If Puzzle(c,r,z) = #True
Select z
Case 1,2,3 : x+((z-1)*15) : y + 0
Case 4,5,6 : x+((z-4)*15) : y + 13 : If PS <> 465 : y+3 : EndIf
Case 7,8,9 : x+((z-7)*15) : y + 26 : If PS <> 465 : y+6 : EndIf
EndSelect
DrawText(x,y,Str(z),#Black,dc)
EndIf
Next z
EndIf
Next r : Next c
Else ;hide all possible values
For c = 1 To 9 : For r = 1 To 9
If puzzle(c,r,0) = 0
UPDATE_BOX(c,r,#Base)
EndIf
Next r : Next c
EndIf
If GetGadgetState(#Edit) = #True
UPDATE_BOX(Box_X,Box_Y,#HiColor)
EndIf
If drawing = #True : StopDrawing() : drawing = #False : EndIf
EndProcedure
Procedure SOLVE() ;attempt to solve the puzzle
Protected logic_1.b, logic_2.b, exit
Protected BeforeSolve = PuzzleAltered ;remember if puzzle had been altered previously
Repeat ;loop until progress has stopped
APPLY_LEVEL_1_LOGIC() : logic_1 = 1-PuzzleAltered
APPLY_LEVEL_2_LOGIC() : logic_2 = 1-PuzzleAltered
exit + (logic_1 & logic_2)
Until exit > 3
;if puzzle had been altered by user action, restore PuzzleAltered to #True
If BeforeSolve = #True : PuzzleAltered = #True : EndIf
If VERIFY()
If SolveSpeed = #Slow
SetGadgetState(#AllClues,#False)
DISABLE_BUTTONS(#False)
MessageRequester("Success!","This puzzle is correct.",#PB_MessageRequester_Info)
UPDATE_BOX(Box_X,Box_Y,#Base)
EndIf
PuzzleAltered = #False
ProcedureReturn(#True)
ElseIf SolveSpeed = #Slow
DISABLE_BUTTONS(#True)
DisableGadget(#ClrUnlk,#False)
SetGadgetState(#Edit,#True)
UPDATE_ALL_POSSIBLES() : SHOW_ALL_POSSIBLES()
ProcedureReturn(#False)
EndIf
EndProcedure
Procedure STAY_ON_TOP() ;toggle if window is on top or not
StickyWindow(#MainWin,GetGadgetState(#Top))
EndProcedure
Procedure UNLOCK_ALL() ;unlock all boxes so they can be edited
Protected x, y, AlteredState = PuzzleAltered
For y = 1 To 9
For x = 1 To 9
Locked(x,y) = #False
UPDATE_BOX(x,y,#Base)
Next x
Next y
If drawing = #True : StopDrawing() : drawing = #False : EndIf
PuzzleAltered = AlteredState
DISABLE_BUTTONS(#True)
SetGadgetState(#Edit,#True)
Box_X=5 : Box_Y=5 : UPDATE_BOX(Box_X,Box_Y,#HiColor)
DisableGadget(#Clue,#False)
DisableGadget(#Lock,#False)
DisableGadget(#Clear,#False)
EndProcedure
Procedure UPDATE_ALL_POSSIBLES()
Protected n, d, x, y, z, xs, ys, region, column, row
;first reset all possibles to #True
For column = 1 To 9 : For row = 1 To 9 ;loop thru all boxes
If Puzzle(column,row,0) = #False ;do only if box is empty
For z = 1 To 9 : Puzzle(column,row,z) = #True : Next z ;all are possible to start
Else
For z = 1 To 9 : Puzzle(column,row,z) = #False : Next z ;none are possible
EndIf
Next row : Next column
For column = 1 To 9 : For row = 1 To 9 ;update every puzzle box
For n = 1 To 9 ;scan the column and row that contains the specified (column,row) box
d = Puzzle(n,row,0) ;get contents of each box in column n for the specified row
If d > 0 : Puzzle(column,row,d) = #False : EndIf ;remove number d from possibilities
d = puzzle(column,n,0) ;get contents of each box in row n for the specified column
If d > 0 : puzzle(column,row,d) = #False : EndIf ;remove number d from possibilities
Next n
region = 1+((column-1)/3) + ((row-1)/3)*3 ;calculate which of nine 3x3 regions the box is in
Select region ;set starting column & row for 3x3 region scan
Case 1,2,3
xs = region*3-2 : ys = 1 ;xs = column start, ys = row start
Case 4,5,6
xs = (region-3)*3-2 : ys = 4
Case 7,8,9
xs = (region-6)*3-2 : ys = 7
EndSelect
;scan the 3x3 region that contains the specified (column,row) box
For x = xs To xs + 2 ;scan 3 columns
For y = ys To ys + 2 ;scan 3 rows
d = Puzzle(x,y,0) ;retreive box contents
If d > 0 : puzzle(column,row,d) = #False : EndIf ;remove from possibilities
Next y
Next x
Next row : Next column
EndProcedure
Procedure UPDATE_BOX(c,r,color) ;update the specified box with the specified color
; displays the box contents if a value has been assigned
; c and r are puzzle box column and row
Static Old_C, Old_R ;use to remember previous active box
Protected x, y ;pixel co-ordinates
Protected a, b, z ;puzzle array indexes
Protected fox=(PS-465)/30, foy=(PS-465)/30 ;font x,y offsets
Protected bx=(PS -(2*#Bdr)-2) / 9, by= (PS -(2*#Bdr)-2) / 9 ;box size
If c<1 Or c>9 Or r<1 Or r>9 : ProcedureReturn : EndIf
If DisableUpdateBox = #True : ProcedureReturn : EndIf
If drawing <> #True ; check if StartDrawing is active
StartDrawing(CanvasOutput(canvas)) : drawing = #True
EndIf
;convert c,r box to pixel x,y values
;puzzle boxes are 43x43 + (box border 2+2) = 47 pixels
;allow for for puzzle borders, x offset=36, y offset=28
x = (c-1)*bx+36 : y = (r-1)*by+28
Box(x-14,y-6,bx-3,by-3,color) ;draw blank square with specified color
DrawingFont(FontID(Font1)) ;large font
If Puzzle(c,r,0) > 0 ;if box value is assigned, draw that value
If Locked(c,r) = #False ;use separate colors for locked and unlocked boxes
DrawText(x+fox, y+foy, Str(Puzzle(c,r,0)), #Unlkd,color)
Else
DrawText(x+fox, y+foy, Str(Puzzle(c,r,0)), #Locked,color)
EndIf
ElseIf GetGadgetState(#AllClues) = #True ;if show all possibles is active
x-14 : y-6 ;small numbers start at different pixel location in box
If GetGadgetState(#Edit) = #True
Box(x,y,bx-4,by-4,#HiColor)
Else
Box(x,y,bx-4,by-4,color)
EndIf
a=x+3 : b=y : If PS <> 465 : a+3 : b+3 : EndIf
DrawingFont(FontID(Font2)) ;use small font
For z = 1 To 9 ;draw all of the possibles for the (c,r) box
x=a : y=b
If Puzzle(c,r,z) = #True
Select z
Case 1,2,3 : x+((z-1)*15) : y + 0
Case 4,5,6 : x+((z-4)*15) : y + 13 : If PS <> 465 : y+3 : EndIf
Case 7,8,9 : x+((z-7)*15) : y + 26 : If PS <> 465 : y+6 : EndIf
EndSelect
DrawText(x,y,Str(z),#Black,color)
EndIf
Next z
Else
Box(x-14,y-6,bx-3,by-3,color) ;draw blank square with specified color
EndIf
x = ((Old_C-1)*bx)+36 : y = ((Old_R-1)*by)+28 ;convert Old_C,Old_R to pixel x,y values
If (c<>Old_C Or r<>Old_R) And color <> #ErrColor And NoHilight = #False
DrawingFont(FontID(Font1)) ;large font
Box(x-14,y-6,bx-3,by-3, #Base) ;erase old hilight
If Puzzle(Old_C,Old_R,0) > 0 ;if box value has been set, draw that value
If Locked(Old_C,Old_R) = #False ;determine text color
DrawText(x+fox, y+foy, Str(Puzzle(Old_C,Old_R,0)),#Unlkd,#Base) ;unlocked
PuzzleAltered = #True
Else
DrawText(x+fox, y+foy, Str(Puzzle(Old_C,Old_R,0)),#Locked,#Base) ;locked
EndIf
ElseIf GetGadgetState(#AllClues) = #True ;show possibles for the empty box
x = ((Old_C-1)*bx)+22 : y = ((Old_R-1)*by)+22
Box(x,y,bx-4,by-4,#Base)
a=x+3 : b=y : If PS <> 465 : a+3 : b+3 : EndIf
DrawingFont(FontID(Font2))
For z = 1 To 9
x=a : y=b
If Puzzle(Old_C,Old_R,z) = #True
Select z
Case 1,2,3 : x+((z-1)*15) : y + 0
Case 4,5,6 : x+((z-4)*15) : y + 13 : If PS <> 465 : y+3 : EndIf
Case 7,8,9 : x+((z-7)*15) : y + 26 : If PS <> 465 : y+6 : EndIf
EndSelect
DrawText(x,y,Str(z),#Black,#Base)
EndIf
Next z
EndIf
Else
NoHilight = #False
EndIf
Old_C = c : Old_R = r
EndProcedure
Procedure VERIFY() ;scan entire puzzle for rule violations
Protected n
AllFilled = #True ;will be set to #False if empty box is found
For n = 1 To 9 ;check all 3x3 regions for errors
error = CHECK_REGION(n)
If error = #True : Break : EndIf
Next n
If error = #False ;proceed if no errors in 3x3 regions
For n = 1 To 9 ;check all columns for errors
error = CHECK_COLUMN(n)
If error = #True : Break : EndIf
Next n
EndIf
If error = #False ;proceed if no errors in columns
For n = 1 To 9 ;check all rows for errors
error = CHECK_ROW(n)
If error = #True : AllFilled = #False : Break : EndIf
Next n
EndIf
If AllFilled = #True And error = #False
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure