diff --git a/applications/sudoku/sudokumain.pas b/applications/sudoku/sudokumain.pas index 9775d9391..350ede3c1 100644 --- a/applications/sudoku/sudokumain.pas +++ b/applications/sudoku/sudokumain.pas @@ -60,6 +60,8 @@ type var Editor: TWinControl); private { private declarations } + const + MaxSteps = 50; //theValues: TValues; procedure OnCopyBackValues(Sender: TObject; Values: TValues); function SolveSudoku(out Values: TValues; out RawData: TRawGrid; out Steps: Integer): Boolean; @@ -131,14 +133,21 @@ var Steps: Integer; begin SGrid.Options := SGrid.Options - [goEditing]; - Res := SolveSudoku(Values, RawData, Steps); - ValuesToGrid(Values); - if Res then - ShowMessage(Format('Sudoku solved in %d steps.', [Steps])) - else - begin - ShowMessage(Format('Unable to completely solve sudoku (tried %d steps).',[Steps])); - ShowScratchPad(RawData); + try + Res := SolveSudoku(Values, RawData, Steps); + ValuesToGrid(Values); + if Res then + ShowMessage(Format('Sudoku solved in %d steps.', [Steps])) + else + begin + if (Steps < MaxSteps) then + ShowMessage(Format('Unable to solve sudoku (no progress after step %d).',[Steps-1])) + else + ShowMessage(Format('Unable to completely solve sudoku (tried %d steps).',[Steps])); + ShowScratchPad(RawData); + end; + except + on E: ESudoku do ShowMessage(E.Message); end; end; @@ -215,8 +224,11 @@ begin GridToValues(Values); RawData := Default(TRawGrid); aSudoku := TSudoku.Create; - Result := aSudoku.GiveSolution(Values, RawData, Steps); - aSudoku.Free; + try + Result := aSudoku.GiveSolution(Values, RawData, Steps); + finally + aSudoku.Free; + end; end; procedure TForm1.GridToValues(out Values: TValues); diff --git a/applications/sudoku/sudokutype.pas b/applications/sudoku/sudokutype.pas index 70e0f63d4..3cbf6bd7b 100644 --- a/applications/sudoku/sudokutype.pas +++ b/applications/sudoku/sudokutype.pas @@ -43,19 +43,29 @@ type { TSudoku } TSudoku = class(TObject) - function GiveSolution(var Values: TValues; out RawData: TRawGrid; out Steps: Integer): Boolean; private + FMaxSteps: Integer; Grid: TRawGrid; procedure CalculateValues(out IsSolved: Boolean); procedure CheckRow(Col, Row: Integer); procedure CheckCol(Col, Row: Integer); procedure CheckBlock(Col, Row: Integer); procedure CheckDigits(ADigit: Integer); - procedure FillGridFromValues(Values: TValues); + procedure CheckInput(Values: TValues); + procedure ValuesToGrid(Values: TValues); + function GridToValues: TValues; function Solve(out Steps: Integer): Boolean; - //function Solved: Boolean; + public + constructor Create; + function GiveSolution(var Values: TValues; out RawData: TRawGrid; out Steps: Integer): Boolean; + property MaxSteps: Integer read FMaxSteps write FMaxSteps default 50; end; + ESudoku = class(Exception); + +const + AllDigits: TDigitSet = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + function DbgS(ASet: TDigitSet): String; overload; function DbgS(ASquare: TSquare): String; overload; @@ -107,9 +117,27 @@ begin end; end; +function IsEqualGrid(const A, B: TRawGrid): Boolean; +var + Col, Row: Integer; +begin + Result := False; + for Col := 1 to 9 do + begin + for Row := 1 to 9 do + begin + if (A[Col,Row].DigitsPossible <> B[Col,Row].DigitsPossible) or + (A[Col,Row].Locked <> B[Col,Row].Locked) or + (A[Col,Row].Value <> B[Col,Row].Value) then + Exit; + end; + end; + Result := True; +end; + { TSudoku } -procedure TSudoku.FillGridFromValues(Values: TValues); +procedure TSudoku.ValuesToGrid(Values: TValues); var c, r: Integer; begin @@ -127,19 +155,34 @@ begin begin Grid[c, r].Locked := False; Grid[c, r].Value := 0; - Grid[c, r].DigitsPossible := [1, 2, 3, 4, 5, 6, 7, 8, 9]; + Grid[c, r].DigitsPossible := AllDigits; end; end; end; end; +function TSudoku.GridToValues: TValues; +var + Col, Row: Integer; +begin + for Col := 1 to 9 do + begin + for Row := 1 to 9 do + begin + Result[Col, Row] := Grid[Col, Row].Value; + end; + end; +end; + function TSudoku.Solve(out Steps: Integer): Boolean; var c, r: Integer; + OldState: TRawGrid; begin Steps := 0; repeat inc(Steps); + OldState := Grid; for c := 1 to 9 do begin for r := 1 to 9 do @@ -154,22 +197,25 @@ begin end; for c := 1 to 9 do CheckDigits(c); CalculateValues(Result); - until Result or (Steps > 50); + + //if IsConsole then + // writeln('Steps = ',Steps,', IsEqualGrid(OldState, Grid) = ',IsEqualGrid(OldState, Grid)); + + until Result or (Steps >= FMaxSteps) or (IsEqualGrid(OldState, Grid)); +end; + +constructor TSudoku.Create; +begin + inherited Create; + FMaxSteps := 50; end; function TSudoku.GiveSolution(var Values: TValues; out RawData: TRawGrid; out Steps: Integer): Boolean; -var - c, r: Integer; begin - FillGridFromValues(Values); + CheckInput(Values); + ValuesToGrid(Values); Result := Solve(Steps); - for c := 1 to 9 do - begin - for r := 1 to 9 do - begin - Values[c, r] := Grid[c, r].Value; - end; - end; + Values := GridToValues; RawData := Grid; end; @@ -289,21 +335,87 @@ begin end; end; -//function TSudoku.Solved: Boolean; -//var -// c, r: Integer; -//begin -// result := True; -// for c := 1 to 9 do begin -// for r := 1 to 9 do begin -// if not Grid[c, r].Locked then begin -// Result := False; -// Break; -// end; -// end; -// if not result then Break; -// end; -//end; +procedure TSudoku.CheckInput(Values: TValues); + procedure CheckColValues; + var + Col, Row: Integer; + DigitSet: TDigitSet; + D: Integer; + begin + for Col := 1 to 9 do + begin + DigitSet := []; + for Row := 1 to 9 do + begin + D := Values[Col, Row]; + if (D <> 0) then + begin + if (D in DigitSet) then + Raise ESudoku.CreateFmt('Duplicate value ("%d") in Col %d',[D, Col]); + Include(DigitSet, D); + end; + end; + end; + end; + + procedure CheckRowValues; + var + Col, Row: Integer; + DigitSet: TDigitSet; + D: Integer; + begin + for Row := 1 to 9 do + begin + DigitSet := []; + for Col := 1 to 9 do + begin + D := Values[Col, Row]; + if (D <> 0) then + begin + if (D in DigitSet) then + Raise ESudoku.CreateFmt('Duplicate value ("%d") in Row %d',[D, Row]); + Include(DigitSet, D); + end; + end; + end; + end; + + procedure CheckBlockValues(StartCol, StartRow: Integer); + var + Col, Row: Integer; + DigitSet: TDigitSet; + D: Integer; + begin + DigitSet := []; + for Col := StartCol to StartCol + 2 do + begin + for Row := StartRow to StartRow + 2 do + begin + D := Values[Col,Row]; + if (D <> 0) then + begin + if (D in DigitSet) then + Raise ESudoku.CreateFmt('Duplicate value ("%d") in block at Row: %d, Col: %d',[D, Row, Col]); + Include(DigitSet, D); + end; + end; + end; + end; + +begin + CheckRowValues; + CheckColValues; + CheckBlockValues(1,1); + CheckBlockValues(1,4); + CheckBlockValues(1,7); + CheckBlockValues(4,1); + CheckBlockValues(4,4); + CheckBlockValues(4,7); + CheckBlockValues(7,1); + CheckBlockValues(7,4); + CheckBlockValues(7,7); +end; + end.