Sudoku Master, make/solve sudoku puzzles

Applications, Games, Tools, User libs and useful stuff coded in PureBasic
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Sudoku Master, make/solve sudoku puzzles

Post by BasicallyPure »

Here is an image of Sudoku Master with a self generated puzzle.
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
Last edited by BasicallyPure on Sat Mar 22, 2025 6:14 am, edited 7 times in total.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
miso
Enthusiast
Enthusiast
Posts: 406
Joined: Sat Oct 21, 2023 4:06 pm
Location: Hungary

Re: Sudoku Master, make/solve sudoku puzzles

Post by miso »

Thanks, I love this.
User avatar
moulder61
Enthusiast
Enthusiast
Posts: 188
Joined: Sun Sep 19, 2021 6:16 pm
Location: U.K.

Re: Sudoku Master, make/solve sudoku puzzles

Post by moulder61 »

@BasicallyPure

Nice job. :D

I only use Linux, but I like to try any programs/snippets on here regardless. If they work on Linux, that's good, but if not, sometimes I try to fix them. :!:
I literally only just tried it out, so haven't looked into it much yet, but the keyboard shortcuts from lines 434-438 stop it working for me in Linux.
I'm sure one of the more proficient programmers on here can tell us why that is? :wink:
Failing that, I'll mess about with it to see what I can find. :?:
Commenting those lines out doesn't seem too detrimental so far.

Moulder.
"If it ain't broke, fix it until it is!

This message is brought to you thanks to SenselessComments.com

My PB stuff for Linux: "https://u.pcloud.link/publink/show?code ... z3MR0T3jyV
User avatar
blueb
Addict
Addict
Posts: 1111
Joined: Sat Apr 26, 2003 2:15 pm
Location: Cuernavaca, Mexico

Re: Sudoku Master, make/solve sudoku puzzles

Post by blueb »

Wow, thanks BasicallyPure... another great source for me to play with.
I haven't seen you around for years... nice to see you back. :D
- It was too lonely at the top.

System : PB 6.21(x64) and Win 11 Pro (x64)
Hardware: AMD Ryzen 9 5900X w/64 gigs Ram, AMD RX 6950 XT Graphics w/16gigs Mem
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Sudoku Master, make/solve sudoku puzzles

Post by Kwai chang caine »

Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
idle
Always Here
Always Here
Posts: 5834
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Sudoku Master, make/solve sudoku puzzles

Post by idle »

wow love it. Thanks for sharing.
I find sudoku really hard being dyslexic. The numbers literally swim all over the place, but this made it easy.
ps Nice to see you back. :D
User avatar
moulder61
Enthusiast
Enthusiast
Posts: 188
Joined: Sun Sep 19, 2021 6:16 pm
Location: U.K.

Re: Sudoku Master, make/solve sudoku puzzles

Post by moulder61 »

@BasicallyPure

If you are interested, or anyone using Linux is interested, I managed to work out how to get the arrow keys working. Not sure how this affects other systems?
Anyway, lines 440 - 444 now look like this:

Code: Select all

      
      ;AddKeyboardShortcut(0,#PB_Shortcut_Return,#PB_Key_Return)
      AddKeyboardShortcut(0,#PB_Shortcut_Up, #PB_Key_Up)
      AddKeyboardShortcut(0,#PB_Shortcut_Down,#PB_Key_Down)
      AddKeyboardShortcut(0,#PB_Shortcut_Left,#PB_Key_Left)
      AddKeyboardShortcut(0,#PB_Shortcut_Right,#PB_Key_Right)
I commented out the return key line because there's no other reference to it in the code and it does nothing, as far as I can see.

The lines in the main loop need editing to reflect the changes too.

i.e Case #PB_Key_Up instead of Case #PB_Shortcut_Up etc.

Moulder.
"If it ain't broke, fix it until it is!

This message is brought to you thanks to SenselessComments.com

My PB stuff for Linux: "https://u.pcloud.link/publink/show?code ... z3MR0T3jyV
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Sudoku Master, make/solve sudoku puzzles

Post by BasicallyPure »

@Moulder61

Thank you for finding the solution to the problem. :)

The fix has been implemented in the code.

I also got rid of the return key shortcut.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
moulder61
Enthusiast
Enthusiast
Posts: 188
Joined: Sun Sep 19, 2021 6:16 pm
Location: U.K.

Re: Sudoku Master, make/solve sudoku puzzles

Post by moulder61 »

@BasicallyPure

I enjoyed trying to solve the problem. I learn best by modifying someone else's code. :D

Hopefully the changes won't affect it working on Windows or Mac? I have no way of testing that out.

Thanks again for sharing the code. It's a nice game. :wink:

Moulder.
"If it ain't broke, fix it until it is!

This message is brought to you thanks to SenselessComments.com

My PB stuff for Linux: "https://u.pcloud.link/publink/show?code ... z3MR0T3jyV
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: Sudoku Master, make/solve sudoku puzzles

Post by ChrisR »

I'm not much into games now, but I tried it out and even finished level 1, on my own :D
Great work :)
Karellen
User
User
Posts: 83
Joined: Fri Aug 16, 2013 2:52 pm
Location: Germany

Re: Sudoku Master, make/solve sudoku puzzles

Post by Karellen »

Awesome, thanks a lot for sharing! :D
Stanley decided to go to the meeting room...
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Sudoku Master, make/solve sudoku puzzles

Post by BasicallyPure »

@moulder61

I did verify that your fix also works on windows.
I think that it was just luck that my original code worked on windows.

When I first started this project, I just wanted something that would solve or help solve sudoku puzzles.
I had some success with simpler ways of coding logic to help solve a puzzle.
Unfortunately, there are some very difficult puzzles you can find that require extremely complex logic to solve them.
There are videos to be found that explain in great detail how to solve some of these difficult puzzles.
I couldn't even fully understand the explanations, so writing code for those methods was not an option.

I shifted my focus away from solving the puzzles to generating new puzzles with code.
I started by relying on patterns of puzzles I found online and just switched the numbers around.
This method worked, but it was not very satisfying as it relied totally on patterns from puzzles I got from existing puzzles.

I realized I needed to solve two problems to make a puzzle from scratch.
Problem 1: Make a completed puzzle that was random, not relying on any existing patterns.
Problem 2: Remove some of the numbers but ensure the puzzle could still be solved with logic.

Problem 1 is solved by the procedure: BUILD_PUZZLE().
Problem 2 is solved by the procedure: PREPARE_PUZZLE() which is called from within the BUILD_PUZZLE() procedure.

I worked on this thing for several months before I finally got it working.
Sometimes I wondered, why am I torturing myself like this, but in the end the satisfaction was worth it.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
moulder61
Enthusiast
Enthusiast
Posts: 188
Joined: Sun Sep 19, 2021 6:16 pm
Location: U.K.

Re: Sudoku Master, make/solve sudoku puzzles

Post by moulder61 »

@BasicallyPure

I feel your pain!

Not being a real programmer, i.e. never having been taught how to program or even having computers or mobile phones at school way back when I was a lad, maybe calculators were just becoming a thing, I'm literally just using my engineering/autistic brain to try to work stuff out.

I thought I would be good at it, but I don't think I am at all, which is disappointing. :cry:

I wouldn't even know where to start creating something like your Sudoku program?

Having said that, I like taking things apart and trying to figure out how they work, so that's what I try to do. :lol:

Moulder.
"If it ain't broke, fix it until it is!

This message is brought to you thanks to SenselessComments.com

My PB stuff for Linux: "https://u.pcloud.link/publink/show?code ... z3MR0T3jyV
Post Reply