Sudoku solver source code listing

Sudoku helper/solver version 2.1A

David E. Dirkse
Castricum
the Netherlands



unit Unit1;
{SUDOKU version 1.2 july 2005
 V2.0 : cut & paste
 V2.1 : group vs row, group vs column checks.
 added reduce-2
 added: autofill numbers different color
 added: backspace deletes all autofill numbers
}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids, StdCtrls, ExtCtrls, davArrayBtn, Buttons, clipbrd;

type
  TForm1 = class(TForm)
    DavArrayBtn1: TDavArrayBtn;
    PaintBox1: TPaintBox;
    StaticText1: TStaticText;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    DavArrayBtn2: TDavArrayBtn;
    Timer1: TTimer;
    SpeedButton1: TSpeedButton;
    procedure FormCreate(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure DavArrayBtn1BtnPaint(sender: TObject; BtnNr: Byte;
      status: TBtnStatus);
    procedure DavArrayBtn1BtnChange(sender: TObject; BtnNr: Byte;
      status: TBtnStatus; button: TMouseButton);
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure DavArrayBtn2BtnPaint(sender: TObject; BtnNr: Byte;
      status: TBtnStatus);
    procedure DavArrayBtn2BtnChange(sender: TObject; BtnNr: Byte;
      status: TBtnStatus; button: TMouseButton);
    procedure FormPaint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure DavArrayBtn1Leave(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type Tbtntype = (btOpen,btSave,btClipboard,btErase,
                 btplace,btBegin,btSearch,btHelp,btNone);
var
  Form1: TForm1;
  square : byte = 50;

implementation

uses helpUnit;

{$R *.DFM}

type
      TSearchBtnType = (sbShow,sbWarn,sbFill,sbFillAll,
                        sbReduce,sbBackspace,sbNone);
      TentryType = (etOrg,etAuto,etManual);//way number was added
      Tnumber = record
                 nr : byte;
                 org : boolean;
                end;
      Tnumber3 = record
                  nr : byte;
                  et : TentryType;
                 end;
      Trc = (rcNone,rcRow,rcColumn);
      Tgamestatus = (gsInit,gsWait,gsPlace,gsSearch,gsEnd);
      TSudoku = array[1..9,1..9] of TNumber;  //ioboard
      Tsudoku3 = array[1..9,1..9] of Tnumber3;//puzzle board
      TMove = record
               col : byte;
               row : byte;
                nr : byte;
              end;
      Tmsg = (msgInfo,msgError,msgHint,msgOpl,msgHelp,msgSaved);
      TTimerControl = (coIdle,coFillSingles,coBackspace,coClearBtn);
      THintResult = (hdNone,hdEmptyField,hdOnly,
                     hd0row,hd0column,hd0group,
                     hd1row,hd1column,hd1group);
      THintData = record
                      row : byte;
                   column : byte;
                    group : byte;
                    digit : byte;//0nly free digit in row/column/group
                   status : THintResult;
                  end;
      Tduplicate = (dupOK,dupRow,dupColumn,dupGroup);

const bgColor = $e0c000;
      markBGcolor = $00b0ff;
      ArrayBtn1colors : TcolorTable =
                      ($d0d0ff,$d0ffd0,$ff0000,$00ff00,$000000);
      ArrayBtn2Colors : TColortable =
                      ($d0d0ff,$d0ffd0,$ff0000,$00ff00,$000000);
      leader = 'SUDOKU - helper : ';
      welcome = 'welcome at the SUDOKU - helper';

var IOboard : TSudoku;
    board : TSudoku3;
    markI : integer = 0;
    markJ : integer = 0;
    gameStatus : Tgamestatus;
    goodnumber : boolean = false;//true if valid number added to field
    Timercontrol : TTimercontrol;
    BGmap : Tbitmap;             //holds background of board
    MarkMap : TBitmap;           //holds marker
    Unmarkmap : Tbitmap;         //holds clear,unmark image
    entryType : TentryType;      //for color of number

//------comp search data

    Xboard   : array[1..9,1..9] of word;
    RowSums  : array[1..9] of word;
    ColSums  : array[1..9] of word;
    groupSums: array[1..9] of word;
    hintflag : boolean = false;        //show hint on fieldchange
    showFlag : boolean = false;        //show choices per field
    addList  : array[1..81] of TMove;//last added numbers
    lastEntry : byte = 0;              //last added entry in addlist
    HintData : THintData;
    Totaldigits : byte = 0;
//
    Pallow : array[1..9] of word;        //for hint reduction
    Psum : array[1..9] of word;
    Xvalue : array[1..9] of word;
    Pmask : array[1..9] of word;

    triple : array[1..3,1..9] of word;   //for hintreduction2

//--------file

    FSodoku : file of Tsudoku;

//-------forward declarations

procedure searchBtnHide; forward;
procedure msg(tt : Tmsg; var s : string); forward;
procedure painthintfield(i,j : byte); forward;
procedure MakeHintfields; forward;
procedure showHintFields; forward;
procedure AnalyzeHints; forward;
procedure reportHintData; forward;
procedure Opengame; forward;
procedure saveGame; forward;
procedure HintReduction; forward;
procedure Hintreduction2; forward;
procedure loadfromP1; forward;
procedure procClipboard; forward;
procedure HintReductionShow; forward;

//---------------general actions (board), conversions

procedure setTotaldigits;
//count nr of digits added in game
//call after initOrg, load from file, pressing search
var i,j : byte;
begin
 totaldigits := 0;
 for j := 1 to 9 do
  for i := 1 to 9 do
   if board[i,j].nr <> 0 then inc(totaldigits);
end;

function IJtoGroupNr(i,j : byte) : byte;
//return group Nr of field [i,j]
var x,y : byte;
begin
 x := (i-1) div 3;
 y := (j-1) div 3;
 result := x + 3*y + 1;
end;

function popcount(bb : word) : byte;
//returns number of bits set in kk
var k : byte;
begin
 result := 0;
 for k := 1 to 9 do
  if (1 shl k) and bb <> 0 then inc(result);
end;

function bitNrToDigit(w : word; n : byte) : byte;
//returns number corresponding to n th bit set in hint word w
//example: if n=3, the 3 rd bit set (from right) is located
//and the corresponding digit is returned
//if no bit found, return 0
var i,count : byte;
begin
 count := 0;
 for i := 1 to 9 do
  begin
   w := w shr 1;
   if (w and 1) <> 0 then
    begin
     inc(count);
     if count = n then
      begin
       result := i;
       exit;          //found
      end;
    end;
  end;
 result := 0;         //not found
end;

function PixelToIJ(x : integer) : byte;
//x,y pixel to field
begin
 result := (x div square) + 1;
end;

function getFieldrect(i,j : byte) : Trect;
begin
 with result do
  begin
   left := (i-1)*square + 1;
   top := (j-1)*square + 1;
   right := i*square;
   bottom := j*square;
  end;
end;

procedure clearAll;
var i,j : byte;
    s : string;
begin
 for i := 1 to 9 do
  for j := 1 to 9 do board[i,j].nr := 0;
//
 with form1 do
  begin
   paintbox1.invalidate;
   caption := leader;
   s := 'board erased';
   msg(msgInfo,s);
  end;
 lastEntry := 0;//clear addlist
 totaldigits := 0;
end;

function getNumFieldrect(i,j : byte) : Trect;
begin
 with result do
  begin
   left := (i-1)*square + 4;
   top := (j-1)*square + 4;
   right := i*square - 4;
   bottom := j*square - 4;
  end;
end;

function CheckDuplicate(ii,jj,nr : byte) : Tduplicate;
//return true if nr already present in row,column,group
var i,j,sum : byte;
    x,y : byte;
begin
 if nr = 0 then
  begin
   if board[ii,jj].nr = 0 then result := dupRow  //no double entry
    else result := dupOk;                        //always erase digit
   exit;
  end;
//                        //nr <> 0
 sum := 0;
 for i := 1 to 9 do                            //check row
  if (board[i,jj].nr = nr) then inc(sum);
 if sum <> 0 then
  begin
   result := dupRow;
   exit;
  end;
 for i := 1 to 9 do                            //check column
  if (board[ii,i].nr = nr) then inc(sum);
 if sum <> 0 then
  begin
   result := dupColumn;
   exit;
  end;
 x := ((ii-1) div 3)*3 + 1;                       //check group
 y := ((jj-1) div 3)*3 + 1;
 for i := 0 to 2 do
  for j := 0 to 2 do
   if board[x+i,y+j].nr = nr then inc(sum);
 if sum <> 0 then
  begin
   result := dupGroup;
   exit;
  end;
 result := dupOK;
end;

//----low level draw procedures

procedure paintmarker(i,j : byte);
//paint marker at field [i,j]
var r : Trect;
begin
 r := getFieldrect(i,j);
 form1.paintbox1.canvas.draw(r.Left,r.top,markmap);
end;

procedure EraseField(i,j : byte);
var r : Trect;
begin
 getfieldrect(i,j);
 with form1.paintbox1.canvas do
  begin
   copymode := cmSRCcopy;
   copyrect(r,BGmap.canvas,r);
  end;
end;

procedure paintUnMark(i,j : byte);
var R : Trect;
begin
 r := getFieldrect(i,j);
 with unmarkmap do with canvas do
  begin
   copymode := cmSRCCopy;
   copyrect(rect(0,0,width,height),BGmap.canvas,r);
   brush.Style := bsSolid;
   brush.Color := $0;
   fillrect(rect(4,4,width-4,height-4));//make transparent
  end;
 form1.paintbox1.canvas.draw(r.Left,r.top,UnMarkmap);
end;

procedure EraseNumberfield(i,j : byte);
var r1,r2 : Trect;
begin
 r1 := getfieldrect(i,j);
 r2 := getNumFieldrect(i,j);
 with UnMarkmap do with canvas do            //prepare unmarkmap
  begin
   brush.style := bsSolid;
   brush.color := 0;
   fillrect(rect(0,0,width,height));
   copymode := cmSRCCopy;
   copyrect(rect(4,4,width-4,height-4),BGmap.canvas,r2);
  end;
 form1.paintbox1.canvas.draw(r1.left,r1.top,UnMarkmap);
end;

procedure paintNumber(i,j : byte; nr : byte; nt : TentryType);
var r : trect;
    x,y : integer;
    ch : string[1];
begin
 r := getNumfieldRect(i,j);
 ch := inttostr(nr);
 eraseNumberField(i,j);
 with form1.PaintBox1.canvas do
  begin
   font.height := trunc(square * 0.8);
   x := (square-8-textwidth(ch)) div 2;
   y := (square-8-textheight(ch)) div 2;
   if nr <> 0 then
    begin
     brush.style := bsClear;
     font.color := $ffffff;
     textout(r.left+1+x,r.top+1+y,ch);
     case nt of
      etOrg : font.color := $d0;
      etAuto : font.color := $b0b0b0;
      etManual : font.color := $0;
     end;
     textout(r.left-1+x,r.top-1+y,ch);
    end;
  end;//with
end;

//-----------marker control

procedure SetMarker(i,j : byte);
begin
 if markI <> 0 then paintUnMark(markI,markJ);
 markI := i; markJ := j;
 paintMarker(i,j);
end;

procedure ClearMarker;
begin
 if markI <> 0 then
  begin
   paintUnmark(markI,markJ);
   markI := 0;
   markJ := 0;
  end;
end;

procedure marknextField;
var key1 : word;
begin
 key1 := VK_RIGHT;
 form1.formkeydown(form1,key1,[]);
end;

procedure writeNumField(i,j : byte);
//write number or hints in field
//called by paintbox1 paint
begin
 with board[i,j] do
  if (nr = 0) and hintflag then painthintfield(i,j)
  else paintNumber(i,j,nr,et);
end;

//-------add original number

procedure AddOrgNumber(nr : byte);
//at marker
label fill;
begin
 goodnumber := false;
 if nr = 0 then goto fill;
 if CheckDuplicate(markI,markJ,nr) <> dupOK then
  begin
   beep;
   exit;
  end
 else board[markI,markJ].et := etOrg;
//
fill:
 goodnumber := true;
 board[markI,markJ].nr := nr;
 paintnumber(markI,markJ,nr,etOrg);
end;

procedure AddToList(ii,jj,n : byte);
//remove previous [col,row] entry
var i,h : byte;
begin
 h := 0;
 for i := 1 to lastEntry do
  with addlist[i] do
   if (col = ii) and (row = jj) then
    begin
     h :=i;
     break;
    end;
 if h <> 0 then                       //previous entry found at h
  begin
   for i := h to lastentry-1 do        //move list down
    begin
     addList[i].col := addlist[i+1].col;
     addlist[i].row := addlist[i+1].row;
     addlist[i].nr  := addlist[i+1].nr;
    end;
   dec(lastEntry);
  end;

 if n <> 0 then inc(lastEntry) else exit;
 with Addlist[lastEntry] do 
  begin
   col := ii;
   row := jj;
   nr := n;
  end;
end;

//----------add number during search

procedure tryAddNumber(n : byte);
//called on keyboard event
const mmm = ' already present in ';
var s : string;
    dupInfo : Tduplicate;
    et : TentryType;
begin
 goodnumber := false;
 if (board[markI,markJ].nr <> 0) and
    (board[markI,markJ].et <> etManual) then
  begin
   beep;
   exit;//can only overwrite manual number
  end;
  dupInfo := CheckDuplicate(markI,markJ,n);
  case dupInfo of
   dupRow : s := inttostr(n)+mmm+'row '+inttostr(markJ);
   dupcolumn : s := inttostr(n)+mmm+'column '+inttostr(markI);
   dupGroup : s := inttostr(n)+mmm+' group '+inttostr(IJtoGroupNr(markI,markJ));
  end;
  if dupInfo <> dupOK then
   begin
    msg(msgError,s);
    beep;
    exit;      //exit if number allready in row/column/group
   end;
 form1.statictext1.caption := '';
 if timercontrol = coFillsingles then et := etAuto else et := etmanual;
 board[markI,markJ].nr := n;
 board[markI,markJ].et := et;
 paintNumber(markI,markJ,n,et);
 goodnumber := true;
 AddToList(markI,markJ,n);
 settotaldigits;             //count digits
 if totaldigits = 81 then
  begin
   s := 'solution found';
   msg(msgOpl,s);
   gamestatus := gsEnd;
  end
 else
  begin
   if showflag then
    begin
     makehintfields;
     showHintFields;
     if hintflag then
      begin
       AnalyzeHints;
       reportHintData;
      end;
    end;//if showflag
  end;//else
end;

procedure moveback;
//take last entry in Addlist back
var c,r : byte;
    manFlag : boolean;
begin
 if lastEntry = 0 then exit; //if no entries
 manFlag := false;
 repeat
  with addList[lastentry] do
   if board[col,row].et = etAuto then
    begin
     board[col,row].nr := 0;
     paintnumber(col,row,0,etmanual);//rewrite,erase field
     dec(lastEntry);
    end
   else manFlag := true;
 until (lastEntry = 0) or manflag;
 if manflag then
  begin
   with AddList[lastEntry] do
    setmarker(col,row);  //mark removing field
   timercontrol := coBackspace;
   form1.timer1.enabled := false;
   form1.timer1.enabled := true;
  end
 else if showflag then        //hint processing
       begin
        makehintfields;
        showHintFields;
        if hintflag then
         begin
          AnalyzeHints;
          reportHintData;
         end;
       end;//if showflag
end;

procedure movebackDelayed;
//called by timer after placing marker
begin
 with addlist[lastEntry] do
  begin
   board[col,row].nr := 0;
   paintnumber(col,row,0,etmanual);//rewrite,erase field
  end;
 dec(lastEntry);
//
 if showflag then        //hint processing
  begin
   makehintfields;
   showHintFields;
   if hintflag then
    begin
     AnalyzeHints;
     reportHintData;
    end;
  end;//if showflag
end;

procedure InitOrg;
//set board to orgBoard
var i,j : byte;
    s : string;
begin
 for i := 1 to 9 do
  for j := 1 to 9 do
   if board[i,j].et <> etOrg then board[i,j].nr := 0;
 form1.paintbox1.Invalidate;
 s := 'restored original puzzle';
 msg(msgInfo,s);
end;

//------------messages--------------------------

procedure msg(tt : Tmsg; var s : string);
//put message in statictext1
const msgcolor : array[msgInfo..msgHelp] of longInt =
                      ($000000,$0000ff,$00d000,$ff0000,$b0b0b0);
      saveText : string = '';
      saveMsg : Tmsg = msgInfo;
begin
 case tt of
  msgInfo,msgerror,msgHint,msgOpl :
   begin
    savetext := s;
    savemsg := tt;
   end;
  msgSaved :
   begin
    tt := saveMsg;
    s := savetext;
   end;
 end;//case
 with form1.statictext1 do
  begin
   font.color := msgColor[tt];
   caption := s;
  end;
end;

//----search support

procedure paintHintField(i,j : byte);
//shows number choice in a field
//acc to xboard[i,j] bits
var sum : word;
    dx,dy : byte;
    nr : byte;
    r : Trect;
    row,col : byte;
begin
 r := getNumFieldrect(i,j);
 with form1.paintbox1.canvas do
  begin
   eraseNumberfield(i,j);
   brush.style := bsclear;
   font.height := trunc(square*0.3);
   font.color := $0;
   dx := trunc(square*0.2);
   dy := textheight('9')-2;
   sum := Xboard[i,j];
   for nr := 1 to 9 do
    begin
     row := (nr-1) div 3;
     col := (nr-1) mod 3+1;
     if ((1 shl nr) and sum) <> 0 then
      textout(r.left+col*dx,r.top+row*dy,inttostr(nr));
    end;
  end;//with
end;

//-------------hint process------------

procedure MakeHintfields;
//make Xboard array 9*9 of word
//each word has bit set for possible digit
//make Row & column sums
var i,j,group,x,y : byte;
begin
 for j := 1 to 9 do
  for i := 1 to 9 do
   if board[i,j].nr = 0 then Xboard[i,j] := 0  //clear Xboard
   else Xboard[i,j] := 1 shl board[i,j].nr;    //set xboard
//
 for i := 1 to 9 do
  begin
   Rowsums[i] := $3fe;                         //init RowSums
   ColSums[i] := $3fe;                         //init Column sums
   GroupSums[i] := $3fe;                       //init group sums
  end;

//make row sums

 for j := 1 to 9 do
  for i := 1 to 9 do RowSums[j] := RowSums[j] xor Xboard[i,j];

//make Column sums

 for i := 1 to 9 do
  for j := 1 to 9 do ColSums[i] := ColSums[i] xor Xboard[i,j];

//make group sums

 for group := 1 to 9 do
  begin
   x := ((group-1) mod 3)*3 + 1;
   y := ((group-1) div 3)*3 + 1; //[x,y] is left top of group
   for j := 0 to 2 do
    for i := 0 to 2 do
     GroupSums[group] := Groupsums[group] xor Xboard[x+i,y+j];
  end;

//combine column-row-group

 for j := 1 to 9 do
  for i := 1 to 9 do
   if board[i,j].nr = 0 then
    Xboard[i,j] := ColSums[i] and Rowsums[j] and GroupSums[IJtoGroupNr(i,j)];
end;

procedure showHintFields;
var i,j : byte;
begin
 for j := 1 to 9 do
  for i := 1 to 9 do
   if board[i,j].nr = 0 then painthintfield(i,j);
end;

procedure ReportHintData;
//called to report hintstatus in statictext1
const ssingle = 'single choice: ';
      s0digit = 'missing number ';
var s : string;
    m : Tmsg;

 function AddS1 : string;
 begin
  with HintData do
   result := 'row '+inttostr(row)+' column '+ inttostr(column);
 end;

begin
 m := msgInfo;
 with HintData do
  case status of
   hdNone : s := '';
   hdEmptyField:
    begin
     m := msgError;
     s := 'empty field: ' + AddS1;
    end;
   hdOnly :
    begin
     m := msghint;
     s := 'single number: '+ '('+inttostr(digit)+') ' + AddS1;
    end;
   hd1row :
    begin
     m := msgHint;
     s := ssingle + '('+inttostr(digit)+') in row '+inttostr(row);
    end;
   hd1column :
    begin
     m := msghint;
     s := ssingle + '('+inttostr(digit)+') in column '+inttostr(column);
    end;
   hd1group :
    begin
     m := msghint;
     s := ssingle+'('+inttostr(digit)+') in group '+inttostr(group);
    end;
   hd0row :
    begin
     m := msgerror;
     s := s0digit + inttostr(digit) + ' in row '+ inttostr(row);
    end;
   hd0column :
    begin
     m := msgerror;
     s := s0digit + inttostr(digit) + ' in column '+ inttostr(column);
    end;
   hd0group :
    begin
     m := msgerror;
     s := s0digit + inttostr(digit) + ' in group '+ inttostr(group);
    end;
  end;//case
 msg(m,s);
 with hintData do
  begin
   if (status = hdOnly) or (status = hd1row) or
      (status = hd1column) or (status = hd1group) then
    setmarker(column,row);
  end;//with
end;

procedure AnalyzeHints;
//make hintdata, no actions
var i,j,p,m,n,q,x,y : byte;
    w : word;
    rowdigits,columndigits,groupdigits : array[1..9,1..9] of byte;
begin
 HintData.status := hdNone;   //reset

//search empty fields

 with HintData do
  begin
   for j := 1 to 9 do
    for i := 1 to 9 do
     if Xboard[i,j] = 0 then
      begin
       row := j;
       column := i;
       status := hdEmptyfield;
       exit;
      end;//with

//setup digit counters

 for i := 1 to 9 do            //clear digit counts
  for j := 1 to 9 do
   begin
    rowdigits[i,j] := 0;       //[row,digitcount]
    columndigits[i,j] := 0;    //[column,digitcount]
    groupdigits[i,j] := 0;     //[group,digitcount]
   end;

  for j := 1 to 9 do            //fill digit counters
   for i := 1 to 9 do
    for p := 1 to 9 do          //p selects the digits
      begin
       w := 1 shl p;            //form mask
       if (xboard[i,j] and w) <> 0 then
        begin
         inc(rowdigits[j,p]);
         inc(columndigits[i,p]);
         inc(groupdigits[IJtoGroupNr(i,j),p]);
        end;
      end;//for

//search for missing digits

 for j := 1 to 9 do           //analyze rows
   for p := 1 to 9 do          //i = digit
    if rowdigits[j,p] = 0 then
     begin
      status := hd0row;
      row := j;
      digit := p;
      exit;
     end;

  for i := 1 to 9 do           //analyze columns
   for p := 1 to 9 do          //i = digit
    if columndigits[i,p] = 0 then
     begin
      status := hd0column;
      column := i;
      digit := p;
      exit;
     end;

  for i := 1 to 9 do           //analyze groups
    for p := 1 to 9 do          //p = digit
     if groupdigits[i,p]  = 0 then
      begin
       status := hd0group;
       group := i;
       digit := p;
       exit;
      end;

//search single digit fields

 for j := 1 to 9 do
  for i := 1 to 9 do
   begin
     p := popcount(Xboard[i,j]);
     if (p = 1) and (board[i,j].nr = 0) then
      begin
       row := j;
       column := i;
       status := hdOnly;
       digit := bitNRtoDigit(Xboard[i,j],1);
       exit;
      end;//if p
    end;//for

//search only digit in row/column/group

  for j := 1 to 9 do           //analyze rows
   for p := 1 to 9 do          //i = digit
    if rowdigits[j,p] = 1 then
     if ((1 shl p) and rowsums[j]) <> 0 then  //if not number
      begin
       status := hd1row;
       row := j;
       digit := p;
       w := 1 shl p;
       for q := 1 to 9 do                     //find column
        if (xboard[q,row] and w) <> 0 then
         begin
          column := q;
          break;
         end;
       exit;
      end;

  for i := 1 to 9 do           //analyze columns
   for p := 1 to 9 do          //i = digit
    if columndigits[i,p] = 1 then
     if (1 shl p) and (colsums[i]) <> 0 then   //if not number
      begin
       status := hd1column;
       column := i;
       digit := p;
       w := 1 shl p;
       for q := 1 to 9 do                      //find row
        if (xboard[column,q] and w) <> 0 then
         begin
          row := q;
          break;
         end;
       exit;
      end;

   for i := 1 to 9 do           //analyze groups
    for p := 1 to 9 do          //p = digit
     if groupdigits[i,p] = 1 then
      if (1 shl p) and groupsums[i] <> 0 then
       begin
        status := hd1group;
        group := i;
        digit := p;
        w := 1 shl p;
        x := ((group-1) mod 3)*3 + 1;
        y := ((group-1) div 3)*3 + 1;
        for n := 0 to 2 do
         for m := 0 to 2 do
          if (xboard[x+m,y+n] and w) <> 0 then
           begin
            column := x + m;
            row := y + n;
            exit;
           end;
       end;
 end;//with hintdata
end;

procedure clearhints;
//remove hints from fields
var i,j : byte;
begin
 hintflag := false;
 hintData.status := hdNone;
 for j := 1 to 9 do
  for i := 1 to 9 do
   if board[i,j].nr = 0 then erasenumberField(i,j);
end;

procedure HintProcs;
//common code
begin
 makeHintFields;
 showhintfields;
 analyzehints;
 reportHintData;
end;

procedure AutoFill(m : byte);
//m=0: continue, m=1: 1 digit only
begin
 form1.timer1.enabled := false;     //stop timer
 if GameStatus <> gsSearch then exit;
// 
 if (hintData.status = hdEmptyfield) or
    (hintdata.status = hd0row) or          //numbers missing in row,..
    (hintdata.status = hd0column) or
    (hintdata.status = hd0group) then
  begin
   timercontrol := coIdle;                 //stop on error detection
   exit;
  end;
//
 with HintData do
  if (status = hdonly) or (status = hd1row) or
     (status = hd1column) or (status = hd1group) then
   begin
    setMarker(column,row);
    tryAddnumber(digit);
    if gamestatus = gsEnd then
     begin
      timercontrol := coIdle;
      exit;                            //solution found
     end;
    if goodnumber then
     begin
      HintProcs;
      if m = 1 then timercontrol := coIdle
      else form1.timer1.enabled := true;
     end
    else
     timercontrol := coIdle;//if error & timer running
   end
   else timercontrol := coIdle;//stop timer, no fills left
end;

//---------board background , make bitmaps

procedure makeBoardBackground;
const bgColors : array[0..1,0..2] of longInt =
                 (($0d0f0,$0d8f8,$0c8e8),    //lite
                  ($0c0e0,$0c8e8,$0b8d8));   //dark, 32bit format
type Ta = array[0..10000] of longInt;
var h,i,b,n : byte;
    ch : char;
    w,x,y : word;
    p : ^Ta;
    tempBM : Tbitmap;
begin
 tempBm := Tbitmap.create;
 with tempBm do
  begin
   width := 3*square;
   height := width;
   pixelformat := pf32bit;
  end;
 randomize;
 with tempBm do
  with canvas do
   for n := 0 to 1 do                //lite & dark groups on board
    begin
     for w := 0 to height-1 do
      begin
       p := scanline[w];
       for x := 0 to width-1 do p^[x] := bgColors[n,random(3)];
      end;//for w
     for i := 0 to 4 do
      begin
       w := n + 2*i;
       x := (w mod 3)*square*3;
       y := (w div 3)*square*3;
       BGmap.canvas.draw(x,y,tempBM);
      end;//for i
   end;//for n
//
 tempBm.free;
 tempBM := nil;
//
 with BGmap do
  with canvas do
   begin
    font.height := square*3;
    font.color := $e0c080;
    brush.Style := bsClear;        //for big group numbers
    for i := 1 to 9 do
     begin
      x := ((i-1) mod 3)*3*square;
      y := ((i-1) div 3)*3*square;
      ch := chr(i+byte('0'));
      h := textheight(ch);
      b := textwidth(ch);
      x := x + (3*square - b) div 2 + 2;
      y := y + (3*square - h) div 2 + 2;
      textout(x,y,ch);
     end;
    pen.color := $d0d0ff;
    for i := 1 to 8 do
     begin
      w := i*square;
      moveto(w,0);
      lineto(w,height);
      moveto(0,w);
      lineto(width,w);
     end;
    pen.color := $0000ff;
    for i := 0 to 3 do
     begin
      w := i*3*square;
      moveto(w,0);
      lineto(w,height);
      moveto(0,w);
      lineto(width,w);
     end;
   end;
end;

procedure makemarkMap;
//marker bitmap
const marklight = $00ffff;
      markDark  = $00c0ff;
var i : byte;
begin
 with markMap do
  with canvas do
   begin
    brush.color := $0;
    fillrect(rect(0,0,width,height));
    for i := 0 to 3 do
     begin
      pen.color := markdark;
      moveto(width-1-i,i);
      lineto(i,i);
      lineto(i,height-1-i);
      pen.color := marklight;
      lineto(width-1-i,height-1-i);
      lineto(width-1-i,i);
     end;
   end;//with
end;

//---------paint events

procedure TForm1.PaintBox1Paint(Sender: TObject);
const lookAtP1 : boolean = true;
var i,j : byte;
begin
 if lookatP1 then loadfromP1;//only first paint
 lookatP1 := false;
//
 with paintbox1 do
  with canvas do
   begin
    copymode := cmSRCcopy;
    draw(0,0,BGmap);
   end;
//
 for i := 1 to 9 do                    //contents of fields
  for j := 1 to 9 do
   if board[i,j].nr <> 0 then writeNumfield(i,j);
//
 if showflag then ShowHintFields;
//
 if (markI <> 0) then setmarker(markI,markJ);   
end;

function CalcTextwidth(s : string) : word;
begin
 with form1.davarrayBtn1 do result := canvas.textwidth(s);
end;

procedure TForm1.DavArrayBtn1BtnPaint(sender: TObject; BtnNr: Byte;
  status: TBtnStatus);
const BtnText : array[btOpen..btHelp] of string =
                ('open',
                 'save',
                 'clipboard',
                 'erase/board',
                 'enter/puzzle',
                 'restore/puzzle',
                 'search',
                 'help/info');
var r : Trect;
    x1,x2,y1,y2,p : word;
    s1,s2 : string;
    btn : TbtnType;
begin
 with davarrayBtn1 do
  with canvas do
   begin
    btn := TBtnType(btnNr);
    p := pos('/',BtnText[btn]);
    if p = 0 then
     font.height := trunc(btnheight*0.45)
    else font.height := trunc(btnHeight*0.35);
    r := getBtnRect(BtnNr);
    if status = stHI then font.Style := [fsBold] else font.style := [];
    if p <> 0 then
     begin s1 := copy(BtnText[btn],1,p-1);
           s2 := copy(BtnText[btn],p+1,length(btnText[Btn]) - p);
           x1 := r.left + ((btnwidth-calctextwidth(s1)) div 2);
           x2 := r.left + ((btnwidth-calctextwidth(s2)) div 2);
           y1 := r.top+5;
           y2 := r.top + btnheight div 2;
           textout(x1,y1,s1);
           textout(x2,y2,s2);
     end
    else
    begin
     x1 := r.left + (btnwidth-calctextwidth(btnText[btn])) div 2;
     y1 := r.top+(btnheight - textheight('I')) div 2;
     textout(x1,y1,btntext[btn]);
    end;
   end;//with
//hint messages
 if status = stHI then
  begin
   case btn of
    btOpen     : s1 := 'open puzzle from disc';
    btSave     : s1 := 'save puzzle on disc';
    btClipBoard : begin
                   setTotaldigits;
                   if Totaldigits = 0 then
                    s1 := 'paste new puzzle from clipboard'
                   else
                    s1 := 'copy puzzle to clipboard';
                  end;
    btErase    : s1 := 'erase board';
    btplace    : s1 := 'enter digits for new puzzle';
    btBegin    : s1 := 'restore to original puzzle';
    btSearch   : s1 := 'search for solution';
    btHelp     : s1 := 'show help information';
   end;
   msg(msgHelp,s1);
  end
  else begin
        s1 := '';
        msg(msgsaved,s1);
       end;
end;

procedure TForm1.DavArrayBtn2BtnPaint(sender: TObject; BtnNr: Byte;
  status: TBtnStatus);
const BtnText : array[sbShow..sbBackspace] of string =
                ('options',
                 'hints',
                 'fill',
                 'fill all',
                 'reduce',
                 'backspace');
var r : Trect;
    x,y : word;
    bb : TsearchBtnType;
    s : string;
begin
 with davArrayBtn2 do
  with canvas do
   begin
    bb := TsearchBtntype(btnNr);
    s := BtnText[bb];
    r := getBtnRect(btnNr);
    font.height := trunc(btnheight*0.6);
    if status = stHI then font.style := [fsBold]
     else font.style := [];
    x := r.left+(btnWidth - textwidth(s)) div 2;
    y := r.top + (btnheight - textheight('H')) div 2;
    textout(x,y,s);
   end;
//hint
 if status = stHI then
  begin
   case bb of
         sbShow : s := 'show options';
         sbWarn : s := 'show hints and warnings';
         sbFill : s := 'fill hint-field';
      sbFillAll : s := 'fill all hint-fields';
       sbReduce : s := 'analyze / reduce options';
    sbBackspace : s := 'remove last digit(s) entered';
   end;//case
   msg(msgHelp,s);
  end
  else begin
        s := '';
        msg(msgSaved,s);
       end;
end;

procedure TForm1.FormPaint(Sender: TObject);
var i,dx : byte;
    x1,y1,x2,y2 : word;
begin
 with paintbox1 do
  begin
   x1 := left-2;
   y1 := top-2;
   x2 := left+width+1;
   y2 := top+height+1;
  end;
 with canvas do           //randje om paintbox1
  begin
   for i := 0 to 1 do
    begin
     pen.color := $0;
     moveto(x2-i,y1+i);
     lineto(x1+i,y1+i);
     lineto(x1+i,y2-i);
     pen.color := $00ffff;
     lineto(x2-i,y2-i);
     lineto(x2-i,y1+i);
    end;
//
   brush.Style := bsclear;            //column,row indicators
   font.height := trunc(square*0.4);
   dx := square div 2;
   x1 := paintbox1.left + dx;
   y1 := paintbox1.top - dx;
   for i := 1 to 9 do
    textout(x1 + (i-1)*square,y1,inttostr(i));
   x1 := paintbox1.left - dx;
   y1 := paintbox1.top + dx;
   for i := 1 to 9 do
    textout(x1,y1+(i-1)*square,inttostr(i));
  end;
end;

//--------search button control

procedure SearchBtnShow;
//shows all search buttons that have no momentary action
var i : TSearchBtnType;
begin
 for i := sbShow to sbBackspace do
  case i of
   sbShow,sbBackspace : form1.davarrayBtn2.BtnShow(byte(i));
  end;//case
end;

procedure SearchBtnHide;
//hides all search buttons
var i : TSearchBtnType;
begin
 for i := sbShow to sbBackSpace do
  form1.DavArrayBtn2.BtnHide(byte(i));
end;

//----------array button changes

procedure TForm1.DavArrayBtn1BtnChange(sender: TObject; BtnNr: Byte;
  status: TBtnStatus; button: TMouseButton);
var s : string;
begin
 s := '';
 msg(msgInfo,s);                //clear old messages
 case gamestatus of             //release actions
  gsSearch,
       gsEnd : begin
                if hintflag then clearHints;
                hintflag := false;
                showflag := false;
                SearchBtnHide;
               end;
 end;//case
 if status = stDown then        //activate actions
  case TbtnType(BtnNr) of
        btOpen : OpenGame;
        btsave : SaveGame;
   btClipBoard : procClipboard;
       btPlace : begin
                  gamestatus := gsPlace;
                  setmarker(1,1);
                 end;
       btErase : begin
                  clearall;     //also clears Addlist
                  gamestatus := gsInit;
                 end;
       btBegin : begin
                   initOrg;
                   gamestatus := gsWait;
                 end;
      btSearch : begin
                   gamestatus := gsSearch;
                   searchBtnShow;
                   lastEntry := 0;//clear addlist
                   setTotaldigits;
                   setmarker(1,1);
                  end;
        btHelp : InfoForm.show;      
  end;//case
end;

procedure TForm1.DavArrayBtn2BtnChange(sender: TObject; BtnNr: Byte;
  status: TBtnStatus; button: TMouseButton);
begin
 with davarrayBtn2 do
  begin
   if status = stFlat then
    case TsearchBtnType(btnNr) of
     sbshow : begin                     //btnshow off
               btnHide(byte(sbWarn));
               btnHide(byte(sbFill));
               btnhide(byte(sbFillall));
               btnHide(byte(sbreduce));
               showflag := false;
               hintflag := false;
               clearhints;
              end;
     sbwarn : begin
               btnHide(byte(sbFill));
               btnhide(byte(sbFillall));
               hintflag := false;
               statictext1.caption := '';
              end;
    end;//case
//
 if status = stDown then
  case TsearchBtntype(btnNr) of     //select actions
        sbShow : begin
                  btnShow(byte(sbWarn));
                  btnShow(byte(sbreduce));
                  showflag := true;
                  hintflag := false;
                  makehintFields;
                  showHintFields;
                 end;
        sbWarn : begin
                  btnShow(byte(sbFill));
                  btnShow(byte(sbFillall));
                  hintflag := true;
                  HintProcs;
                 end;
     sbFillAll : begin
                  timer1.interval := 500;
                  timercontrol := coFillSingles;
                  AutoFill(0);
                 end;
       sbFill  : begin
                  timercontrol := coFillSingles;
                  AutoFill(1);
                 end;
      sbReduce : begin
                  HintReduction;
                  HintReduction2;
                  HintReductionShow;
                 end;
   sbbackspace : moveback;
  end;//case
 end;//with
end;

//------------mouse events

procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var i,j : byte;
begin
 if timercontrol <> coIdle then exit;
 i := pixelToIJ(x);
 j := pixelToIJ(y);
 case gamestatus of
  gsPlace,
  gssearch : setMarker(i,j);
 end;//case
end;

//----------key events

procedure procSpace;
//if ' ' typed
var K : word;
begin
 k := VK_RIGHT;
 case gamestatus of
             gsplace,
             gssearch  : form1.formkeydown(form1,K,[]);
 end;//case
end;

procedure procBackspace;
//if backspace typed
var K : word;
begin
 k := VK_LEFT;
 case gamestatus of
              gsplace  : form1.formkeydown(form1,K,[]);
              gssearch : moveback;
 end;//case
end;

procedure procDelete;
//if del key typed
begin
 case gamestatus of
              gsplace   : addOrgNumber(0);
              gssearch  : tryAddNumber(0);
 end;
end;

procedure proczero;
//if zero typed in
begin
 case gamestatus of
             gsplace   : AddorgNumber(0);
             gssearch  : tryAddNumber(0);
 end;//case
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 if key = VK_ESCAPE then timercontrol := coIdle;
 if timercontrol <> coIdle then exit;
//
 if ((gamestatus = gsPlace) or (gamestatus = gsSearch))
    and (markI <> 0) and (markJ <> 0) then
  begin
   case key of
    VK_RIGHT,
    VK_DOWN,
    VK_UP,
    VK_LEFT,
    VK_DELETE,
    VK_BACK : paintUnMark(markI,markJ);
   end;//case
   case key of
    VK_right : begin
                inc(markI);
                if markI > 9 then
                 begin
                  dec(markI,9);
                  inc(markJ);
                  if markJ > 9 then dec(markJ,9);
                 end;
               end;
    VK_UP    : begin
                dec(markJ);
                if markJ < 1 then inc(markJ,9);
               end;
    VK_DOWN  : begin
                inc(markJ);
                if markJ > 9 then dec(markJ,9);
               end;
    VK_LEFT  : begin
                dec(markI);
                if markI < 1 then
                 begin
                  inc(markI,9);
                  dec(markJ);
                  if markJ < 1 then inc(markJ,9);
                 end;
               end;
    VK_DELETE : procDelete;
    VK_BACK   : procBackspace;
   end;//case
   setmarker(markI,markJ);
  end;//if
 key := 0;
end;

function Isdigit(key : char) : boolean;
begin
 result := key in ['1','2','3','4','5','6','7','8','9'];
end;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
 if timercontrol <> coIdle then exit;
 case key of
      ' ' : procSpace;
      '0' : procZero;
 end;//case
 if Isdigit(key) then
 case gamestatus of
   gsplace : begin
               if markI <> 0 then
                begin
                 AddOrgNumber(strtoint(key));
                 if GoodNumber then MarkNextfield;
                end;
               end;//if isdigit
  gsSearch : if markI <> 0 then tryAddNumber(strToInt(key));//if marked
 end;//case
 key := #0;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
 case timercontrol of
         coIdle : timer1.enabled := false;
  coFillSingles : AutoFill(0);
    coBackspace : begin
                   timercontrol := coIdle;
                   movebackdelayed;
                  end;
     coClearBtn : begin
                   timer1.enabled := false;
                   davArrayBtn1.btnRelease(byte(btClipboard));
                   timercontrol := coIdle;
                   gamestatus := gsInit;
                  end;
 end;//case
end;

//------------create & destroy

procedure TForm1.FormCreate(Sender: TObject);
var i : TsearchBtnType;
    s : string;
begin
 if screen.width > 1024 then square := 60
 else if screen.Width < 1024 then square := 40;
 BGmap := Tbitmap.create;
 with BGmap do
  begin
   width := 9*square+1;
   height := width;
   pixelformat := pf32bit;
   font.name := 'arial';
   transparent := false;
  end;
 makeBoardBackground;
 Markmap := TBitmap.create;
 with Markmap do
  begin
   width := square - 1;
   height := width;
   pixelformat := pf32bit;
   transparent := true;
   transparentcolor := $0;
  end;
 makeMarkMap;
 unMarkMap := Tbitmap.create;  //used to clear portions of field
 with unmarkmap do
  begin
   width := markmap.width;
   height := width;
   pixelformat := pf32bit;
   transparent := true;
   transparentcolor := $0;
  end;
 with davarrayBtn1 do
  begin
   left := 10;
   top := 10;
   btnwidth := trunc(1.75*square);
   btnheight := square;
   Pcolortable := @ArrayBtn1Colors;
   setBtnOpmode(byte(btErase),omMom);
   setBtnOpmode(byte(btBegin),omMom);
  end;
 clientwidth := davArrayBtn1.left+davarrayBtn1.width + square div 2;
 with paintbox1 do
  begin
   width := BGmap.width;
   height := BGmap.height;
   left := davArrayBtn1.getBtnRect(1).left + 2*davarraybtn1.btnspacing;
   top := davarraybtn1.top + davarraybtn1.height + 10 + square div 2;
  end;
 with statictext1 do
  begin
   top := paintbox1.top+paintbox1.height+10;
   height := trunc(0.6*square);
   width := form1.clientwidth - 20;
   color := $e0ffe0;
   font.height := trunc(height*0.8);
  end;
 clientheight := statictext1.top+statictext1.height + 10;
 top := (screen.height - height) div 2;
 left := (screen.width - width) div 2;
 with davarrayBtn2 do
  begin
   Rows := byte(sbBackspace)+1;
   left := paintbox1.left + paintbox1.width + square div 2;
   top := paintbox1.top;
   btnheight := trunc(square * 0.7);
   btnwidth := trunc(square*3);
   Pcolortable := @ArrayBtn2Colors;
   for i := sbShow to sbBackspace do
    case i of
        sbShow : begin
                  setBtnGroup(byte(sbShow),1);
                  setBtnOpmode(byte(sbShow),omToggle);
                 end;
        sbWarn : begin
                  setBtnGroup(byte(sbWarn),2);
                  setBtnOpmode(byte(sbwarn),omToggle);
                 end;
        sbFill : setBtnOpmode(byte(sbFill),omMom);
     sbFillAll : setBtnOpmode(byte(sbFillAll),omMom);
     sbReduce  : setBtnOpmode(byte(sbReduce),omMom);
   sbBackspace : setBtnOpmode(byte(sbBackSpace),omMom);
    end;//case
  end;
 gamestatus := gsInit;
 searchbtnhide;
 s := welcome;
 msg(msgInfo,s);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 BGmap.free;
 BGmap := nil;
 markmap.free;
 markmap := nil;
 unMarkmap.free;
 unmarkmap := nil;
end;

//------------file operations

procedure saveGame;
var s : string;
    i,j : byte;
begin
 for j := 1 to 9 do
  for i := 1 to 9 do
   begin
    ioBoard[i,j].nr := board[i,j].nr;
    ioboard[i,j].org := (board[i,j].et = etOrg);
   end;
//
 with form1.Savedialog1 do
  begin
   title := 'save puzzle';
   initialDir := paramstr(0);
   defaultext := 'sdk';
   filter := 'SUDOKU file (*sdk)|*sdk';
   if execute then
    begin
     assignfile(fsodoku,filename);
     rewrite(fsodoku);
     write(fsodoku,ioboard);
     closefile(fsodoku);
     s := extractfilename(form1.savedialog1.filename);
     s := 'saved : '+s;
     msg(msgInfo,s);
     form1.davArraybtn1.Btnrelease(byte(btSave));
    end;
  end;//with
end;

procedure OpenGame;
var s : string;
    i,j : byte;
begin
 with form1 do with Opendialog1 do
  begin
   title := 'Open puzzle';
   initialDir := paramstr(0);
   filter := 'SUDOKU file (*sdk)|*sdk';
   if execute then
    begin
     assignfile(fsodoku,filename);
     reset(fsodoku);
     read(fsodoku,ioboard);
     closefile(fsodoku);
//
     s := extractfilename(form1.opendialog1.filename);
     caption := leader + s;
     s := 'opened : '+s;
     msg(msgInfo,s);
     for j := 1 to 9 do
      for i := 1 to 9 do
       begin
        board[i,j].nr := ioboard[i,j].nr;
        if ioboard[i,j].org then board[i,j].et := etOrg
        else board[i,j].et := etManual;
       end;//for i,j
    end;//if execute
  end;//with
 form1.PaintBox1.Invalidate;                  //paint board
 form1.DavArrayBtn1.BtnRelease(byte(btOpen));
end;

//-------------clipboard

procedure clipboardload;
//original numbers in [..]
//x is an empty field
const err : string = 'no Sudoku puzzle/format';
      wrong : string = 'wrong numbers';
var s : string;
    tempBoard : TSudoku3;
    i,j : byte;
    ett : TentryType;
    nr,n : word;
    ch : char;
label error,wrongpuzzle;
begin
 ett := etManual;
 s := '';
 nr := 0;
 if clipboard.HasFormat(CF_TEXT) then
  s := clipboard.asText;
 if length(s) = 0 then
  begin
   s := 'clipboard contains no text';
   msg(msgError,s);
   exit;
  end;
//
 for n := 1 to length(s) do
  begin
   ch := s[n];
   case ch of
    '[','('     : ett := etOrg;
    ']',')'     : ett := etManual;
    'x','X','.' : begin
                   inc(nr);
                   i := (nr-1) mod 9 + 1;
                   j := (nr-1) div 9 + 1;
                   tempboard[i,j].nr := 0;
                  end;
  '1'..'9'  : begin
               inc(nr);
               i := (nr-1) mod 9 + 1;
               j := (nr-1) div 9 + 1;
               tempboard[i,j].et := ett;
               tempboard[i,j].nr := ord(s[n]) - ord('0');
              end;
   end;//case
  end;//for 
 if nr = 81 then
  begin
   for j := 1 to 9 do
    for i := 1 to 9 do
     if (checkduplicate(i,j,tempboard[i,j].nr) = dupOK) or
        (tempboard[i,j].nr = 0) then
      begin
       board[i,j].et := tempboard[i,j].et;
       board[i,j].nr := tempboard[i,j].nr;
      end
      else
       begin
        msg(msgerror,wrong);
        exit;
       end;
  end//if                   not 81 fields
  else
   begin
    msg(msgerror,err);
    exit;
   end;
//
 s := 'opened puzzle from clipboard';
 msg(msgInfo,s);
 form1.paintbox1.invalidate;
end;

procedure clipboardsave;
const creol : string[2] = #13+#10;
var s : string;
    i,j : byte;
    cc : char;
begin
 s := 'SUDOKU - puzzle' + creol + creol ;
 for j := 1 to 9 do
  begin
   for i := 1 to 9 do
    begin
     cc := chr(board[i,j].nr + ord('0'));
     if (board[i,j].nr <> 0) and (board[i,j].et = etOrg) then
      s := s + '['+ cc + ']'
     else
      begin
       if board[i,j].nr = 0 then cc := 'x';
       s := s + ' ' + cc + ' ';
      end;
    end;
   s := s + creol; //add cr eol
  end;
 s := s + creol; 
 clipboard.asText := s;
 s := 'puzzle placed on clipboard';
 msg(msgInfo,s);
end;

procedure procClipboard;
begin
 gamestatus := gsWait;
 settotaldigits;
 if totaldigits = 0 then clipboardLoad
  else clipBoardsave;
//
 form1.timer1.Enabled := false;
 form1.timer1.Enabled := true;
 timercontrol := coClearBtn;
end;

procedure HintReductionShow;
var s : string;
begin
 s := '';
 msg(msgInfo,s);
//
 showHintFields;
 if hintflag then
  begin
   AnalyzeHints;
   ReportHintData;
  end;
end;

//-------hint reduction by row/column

procedure LoadPfromRow(row : byte);
var i : byte;
begin
 for i := 1 to 9 do
  begin
   xvalue[i] := Xboard[i,row];
   Psum[i] := 0;
  end;
end;

procedure LoadPfromColumn(col : byte);
var j : byte;
begin
 for j := 1 to 9 do
  begin
   xvalue[j] := Xboard[col,j];
   Psum[j] := 0;
  end;
end;

procedure loadPfromGroup(gr : byte);
var i,j,n,x,y : byte;
begin
 x := ((gr-1) mod 3)*3 + 1;  //[x,y] is left top of group
 y := ((gr-1) div 3)*3 + 1;
 for n:= 1 to 9 do
  begin
   i := x+((n-1) mod 3);     //[i,j] is field
   j := y+((n-1) div 3);
   Xvalue[n] := xboard[i,j];
   Psum[n] := 0;
  end;
end;

procedure UpdatePsums;
//or masks into Psum
var n : byte;
begin
 for n := 1 to 9 do PSum[n] := Psum[n] or Pmask[n];
end;

function PC(action : byte) : boolean;
//action-0 : reset, 1: increment
//on exit :
//true: digit 9 incremented properly
//false: digit 1 overflow
var xxx : word;
    digit : byte;      //counter digit
label PCreset,PCincr;
begin
 if action = 0 then digit := 1 else begin
                                     digit := 9; goto PCincr;
                                    end;
 Pallow[1] := $3fe;

PCreset :

 Pmask[digit] := 1;
 if digit > 1 then Pallow[digit] :=
   Pallow[digit-1] and (Pmask[digit-1] xor $3fe);

PCincr :

  xxx := Pallow[digit] and xvalue[digit];
  repeat
   Pmask[digit] := Pmask[digit] shl 1;       //find next mask
  until (Pmask[digit] = $400) or ((Pmask[digit] and xxx) <> 0);
  if Pmask[digit] = $400 then          //not found
   begin
    Pmask[digit] := 0;
    if digit = 1 then
     begin
      result := false; exit;      //exit if digit 1
     end
    else begin
          dec(digit); goto PCIncr;//no mask, digit > 1 = inc previous
         end;
  end //if mask...
  else                            //good mask found
   if digit < 9 then
    begin
     inc(digit); goto PCreset;    //new mask found, reset next
    end
   else result := true;
end;

procedure HintReduction;
//use Xboard values and reduce per row,column
var i,j,n,gf,x,y : byte;
    s : string;
begin
 s := 'please wait ...';
 msg(msgInfo,s);
 for n := 1 to 9 do           //rows
  begin
   loadPfromRow(n);                   //if reset OK
   if PC(0) then
    begin
     UpdatePsums;
     while PC(1) do UpdatePsums;      //if incr OK
    end;
   for i := 1 to 9 do xboard[i,n] := Psum[i];//store results
  end;//for n
//

 for n := 1 to 9 do           //columns
  begin
   loadPfromcolumn(n);
   if PC(0) then
    begin
     UpdatePsums;
     while PC(1) do UpdatePsums;      //if incr OK
    end;
   for i := 1 to 9 do xboard[n,i] := Psum[i];//store results
  end;//for

 for n := 1 to 9 do           //groups
  begin
   loadPfromgroup(n);
   if PC(0) then                      //if reset OK
    begin
     UpdatePsums;
     while PC(1) do UpdatePsums;      //if incr OK
    end;
   x := ((n-1) mod 3)*3 + 1;
   y := ((n-1) div 3)*3 + 1;  //[I,J] of field
   for gf:= 1 to 9 do
    begin                     //for group fields 1..9
     i := x+((gf-1) mod 3);
     j := y+((gf-1) div 3);
     xboard[i,j] := Psum[gf];
    end; 
  end;//for n
//
end;

//------------hint reduction2

procedure loadTriplesHor;
//i:column  j:row  sum: or of 3 cons. fields in row
var i,j,x,n : byte;
    sum : word;
begin
 for j := 1 to 9 do       //all columns
  for i := 1 to 3 do      //all 3 triples
   begin
    x := (i-1)*3 + 1;
    sum := 0;
    for n := 0 to 2 do sum := sum or Xboard[x+n,j];
    triple[i,j] := sum;
   end;
end;

procedure loadtriplesvert;
//reflect row/column to use same chech later
var i,j,y,n : byte;
    sum : word;
begin
 for i := 1 to 9 do
  for j := 1 to 3 do
   begin
    y := (j-1)*3 + 1;
    sum := 0;
    for n := 0 to 2 do sum := sum or Xboard[i,y+n];
    triple[j,i] := sum;
   end;
end;

procedure CheckTriples;
var block, i,j,y,n : byte;
    a,b,c : word;
begin
 for block := 1 to 3 do
  begin
   y := (block-1)*3 + 1;
   for j := 1 to 3 do
    for i := 1 to 3 do
     begin
      a := 0; b := 0;
      for n := 1 to 3 do
       begin
        if n <> i then a := a or triple[n,y+j-1];
        if n <> j then b := b or triple[i,y+n-1];
       end;
      c := a and b;
      for n := 1 to 3 do
       begin
        if n <> i then triple[n,y+j-1] := triple[n,y+j-1] and c;
        if n <> j then triple[i,y+n-1] := triple[i,y+n-1] and c;
       end;
     end;
  end;
end;

procedure reduceRowsbyTriples;
var i,j,x : byte;
begin
 for j := 1 to 9 do
  for i := 1 to 9 do
   begin
    x := (i-1) div 3 + 1;
    Xboard[i,j] := Xboard[i,j] and triple[x,j]
   end;
end;

procedure reduceColumnsbyTriples;
var i,j,x : byte;
begin
 for j := 1 to 9 do
  begin
   x := (j-1) div 3 + 1;
   for i := 1 to 9 do Xboard[i,j] := Xboard[i,j] and triple[x,i];
  end; 
end;

procedure Hintreduction2;
//sum options in triples, hor. vert.
//compare groups vs row, column
//select triple in group:
//options not present in row outside group,
//are cancelled in other triples in group
var s : string;
begin
 loadTriplesHor;
 CheckTriples;
 reduceRowsbyTriples;
 loadtriplesvert;
 CheckTriples;
 reduceColumnsbyTriples;
end;

//----check for *.sdk file

procedure LoadFromP1;
var s : string;
    i,j : byte;
begin
 if paramCount < 1 then exit;
 s := paramstr(1);
 if s <> '' then
  begin
    assignfile(fsodoku,s);
    reset(fsodoku);
    read(fsodoku,ioboard);
    closefile(fsodoku);
//
    s := extractfilename(s);
    form1.caption := leader + s;
    s := 'opened : '+s;
    msg(msgInfo,s);
    for i :=1 to 9 do
     for j := 1 to 9 do
      begin
       board[i,j].nr := ioboard[i,j].nr;
       if ioboard[i,j].org then board[i,j].et := etOrg
       else board[i,j].et := etManual;
      end;//for i,j
  end;//if s
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
//plaatje saven van form
var bbb : Tbitmap;
begin
 bbb := getformImage;
 bbb.SaveToFile('plaatje.bmp');
end;

procedure TForm1.DavArrayBtn1Leave(Sender: TObject);
begin
 with statictext1 do
  if font.color = $b0b0b0 then caption := '';
end;

end.