You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7263 8e941d3f-bd1b-0410-a28a-d453659cc2b4
476 lines
12 KiB
ObjectPascal
476 lines
12 KiB
ObjectPascal
unit SudokuMain;
|
|
|
|
{
|
|
***************************************************************************
|
|
* Copyright (C) 2006 Matthijs Willemstein *
|
|
* *
|
|
* Note: the original code by Matthijs was checked in as revision 7217 *
|
|
* in Lazarus-CCR subversion repository *
|
|
* *
|
|
* This source is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This code is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License for more details. *
|
|
* *
|
|
* A copy of the GNU General Public License is available on the World *
|
|
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
|
|
* obtain it by writing to the Free Software Foundation, *
|
|
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
* *
|
|
***************************************************************************
|
|
}
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Grids,
|
|
Buttons, StdCtrls, SudokuType, ScratchPad;
|
|
|
|
type
|
|
|
|
{ TForm1 }
|
|
|
|
TForm1 = class(TForm)
|
|
btnClear: TButton;
|
|
btnLoad: TButton;
|
|
btnSave: TButton;
|
|
btnSolve: TButton;
|
|
btnEdit: TButton;
|
|
OpenDialog: TOpenDialog;
|
|
SaveDialog: TSaveDialog;
|
|
SGrid: TStringGrid;
|
|
procedure btnClearClick(Sender: TObject);
|
|
procedure btnEditClick(Sender: TObject);
|
|
procedure btnLoadClick(Sender: TObject);
|
|
procedure btnSaveClick(Sender: TObject);
|
|
procedure btnSolveClick(Sender: TObject);
|
|
procedure EditorKeyPress(Sender: TObject; var Key: char);
|
|
procedure FormActivate(Sender: TObject);
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
|
|
{%H-}aState: TGridDrawState);
|
|
procedure SGridSelectEditor(Sender: TObject; {%H-}aCol, {%H-}aRow: Integer;
|
|
var Editor: TWinControl);
|
|
private
|
|
{ private declarations }
|
|
const
|
|
MaxSteps = 50;
|
|
private
|
|
//theValues: TValues;
|
|
FSolveUsesRawData: Boolean;
|
|
FRawData: TRawGrid;
|
|
procedure OnCopyBackValues(Sender: TObject; Values: TValues);
|
|
procedure OnCopyBackRawData(Sender: TObject; RawData: TRawGrid);
|
|
procedure SetSolveUsesRawData(AValue: Boolean);
|
|
function SolveSudoku(out Values: TValues; out RawData: TRawGrid; out Steps: Integer): Boolean;
|
|
function SolveSudoku(var RawData: TRawGrid; out Values: TValues; out Steps: Integer): Boolean;
|
|
procedure GridToValues(out Values: TValues);
|
|
procedure ValuesToGrid(const Values: TValues);
|
|
procedure RawDataToGrid(const RawData: TRawGrid);
|
|
procedure ShowScratchPad(RawData: TRawGrid);
|
|
procedure LoadSudokuFromFile(const Fn: String);
|
|
procedure SaveSudokuToFile(const Fn: String);
|
|
function IsValidSudokuFile(Lines: TStrings): Boolean;
|
|
procedure LinesToGrid(Lines: TStrings);
|
|
procedure GridToLines(Lines: TStrings);
|
|
procedure EnableEdit;
|
|
procedure DisableEdit;
|
|
public
|
|
{ public declarations }
|
|
property SolveUsesRawData: Boolean read FSolveUsesRawData write SetSolveUsesRawData default False;
|
|
end;
|
|
|
|
ESudokuFile = Class(Exception);
|
|
|
|
var
|
|
Form1: TForm1;
|
|
|
|
implementation
|
|
|
|
{$R *.lfm }
|
|
|
|
const
|
|
FileEmptyChar = '-';
|
|
VisualEmptyChar = #32;
|
|
AllFilesMask = {$ifdef windows}'*.*'{$else}'*'{$endif}; //Window users are used to see '*.*', so I redefined this constant
|
|
SudokuFileFilter = 'Sudoku files|*.sudoku|All files|' + AllFilesMask;
|
|
|
|
{ TForm1 }
|
|
|
|
procedure TForm1.btnEditClick(Sender: TObject);
|
|
begin
|
|
EnableEdit;
|
|
SGrid.SetFocus;
|
|
end;
|
|
|
|
procedure TForm1.btnLoadClick(Sender: TObject);
|
|
begin
|
|
if OpenDialog.Execute then
|
|
try
|
|
LoadSudokuFromFile(OpenDialog.Filename);
|
|
SolveUsesRawData := False;
|
|
except
|
|
on E: Exception do ShowMessage(E.Message);
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.btnSaveClick(Sender: TObject);
|
|
begin
|
|
if SaveDialog.Execute then
|
|
try
|
|
SaveSudokuToFile(SaveDialog.Filename);
|
|
except
|
|
on E: Exception do ShowMessage(E.Message);
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.btnClearClick(Sender: TObject);
|
|
begin
|
|
SGrid.Clean;
|
|
end;
|
|
|
|
procedure TForm1.btnSolveClick(Sender: TObject);
|
|
var
|
|
Res: Boolean;
|
|
Values: TValues;
|
|
Steps: Integer;
|
|
begin
|
|
DisableEdit;
|
|
try
|
|
if not FSolveUsesRawData then
|
|
Res := SolveSudoku(Values, FRawData, Steps)
|
|
else
|
|
Res := SolveSudoku(FRawData, Values, 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(FRawData);
|
|
end;
|
|
except
|
|
on E: ESudoku do ShowMessage(E.Message);
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.EditorKeyPress(Sender: TObject; var Key: char);
|
|
var
|
|
Ed: TStringCellEditor;
|
|
begin
|
|
if (Sender is TStringCellEditor) then
|
|
begin
|
|
Ed := TStringCellEditor(Sender);
|
|
Ed.SelectAll; //Key will now overwrite selection, in effect allowing to enter only 1 key
|
|
if not (Key in [#8, '1'..'9']) then Key := #0;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TForm1.FormActivate(Sender: TObject);
|
|
begin
|
|
Self.OnActivate := nil;
|
|
SGrid.ClientWidth := 9 * SGrid.DefaultColWidth;
|
|
SGrid.ClientHeight := 9 * SGrid.DefaultRowHeight;
|
|
ClientWidth := 2 * SGrid.Left + SGrid.Width;
|
|
end;
|
|
|
|
procedure TForm1.FormCreate(Sender: TObject);
|
|
begin
|
|
SolveUsesRawData := False;
|
|
OpenDialog.Filter := SudokuFileFilter;
|
|
SaveDialog.Filter := SudokuFileFilter;
|
|
end;
|
|
|
|
|
|
procedure TForm1.SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
|
|
aState: TGridDrawState);
|
|
var
|
|
NeedsColor: Boolean;
|
|
GridTextStyle: TTextStyle;
|
|
begin
|
|
GridTextStyle := (Sender as TStringGrid).Canvas.TextStyle;
|
|
GridTextStyle.Alignment := taCenter;
|
|
GridTextStyle.Layout := tlCenter;
|
|
(Sender as TStringGrid).Canvas.TextStyle := GridTextStyle;
|
|
NeedsColor := False;
|
|
if aCol in [0..2, 6..8] then
|
|
begin
|
|
if aRow in [0..2, 6..8] then
|
|
NeedsColor := True;
|
|
end
|
|
else
|
|
begin
|
|
if aRow in [3..5] then
|
|
NeedsColor := True;
|
|
end;
|
|
if NeedsColor then
|
|
(Sender as TStringGrid).Canvas.Brush.Color := $00EEEEEE;
|
|
end;
|
|
|
|
procedure TForm1.SGridSelectEditor(Sender: TObject; aCol, aRow: Integer;
|
|
var Editor: TWinControl);
|
|
var
|
|
Ed: TStringCellEditor;
|
|
begin
|
|
if Editor is TStringCellEditor then
|
|
begin
|
|
Ed := TStringCellEditor(Editor);
|
|
Ed.OnKeyPress := @EditorKeyPress;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TForm1.SolveSudoku(out Values: TValues; out RawData: TRawGrid; out Steps: Integer): Boolean;
|
|
var
|
|
aSudoku: TSudoku;
|
|
begin
|
|
GridToValues(Values);
|
|
RawData := Default(TRawGrid);
|
|
aSudoku := TSudoku.Create;
|
|
try
|
|
Result := aSudoku.GiveSolution(Values, RawData, Steps);
|
|
finally
|
|
aSudoku.Free;
|
|
end;
|
|
end;
|
|
|
|
function TForm1.SolveSudoku(var RawData: TRawGrid; out Values: TValues; out Steps: Integer): Boolean;
|
|
var
|
|
aSudoku: TSudoku;
|
|
begin
|
|
aSudoku := TSudoku.Create;
|
|
try
|
|
Result := aSudoku.GiveSolution(RawData, Values, Steps);
|
|
finally
|
|
aSudoku.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.GridToValues(out Values: TValues);
|
|
var
|
|
Col, Row: Integer;
|
|
S: String;
|
|
AValue: Longint;
|
|
begin
|
|
Values := Default(TValues); //initialize all to zero
|
|
for Col := 0 to 8 do
|
|
begin
|
|
for Row := 0 to 8 do
|
|
begin
|
|
S := Trim(SGrid.Cells[Col, Row]);
|
|
if Length(S) = 1 then
|
|
begin
|
|
if TryStrToInt(S, AValue) then
|
|
Values[Col + 1, Row + 1] := AValue;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.OnCopyBackValues(Sender: TObject; Values: TValues);
|
|
begin
|
|
ValuesToGrid(Values);
|
|
SolveUsesRawData := False;
|
|
end;
|
|
|
|
procedure TForm1.OnCopyBackRawData(Sender: TObject; RawData: TRawGrid);
|
|
begin
|
|
FRawData := RawData;
|
|
RawDataToGrid(RawData);
|
|
SolveUsesRawData := True;
|
|
end;
|
|
|
|
procedure TForm1.SetSolveUsesRawData(AValue: Boolean);
|
|
begin
|
|
if FSolveUsesRawData = AValue then Exit;
|
|
FSolveUsesRawData := AValue;
|
|
if FSolveUsesRawData then
|
|
DisableEdit
|
|
else
|
|
EnableEdit;
|
|
btnEdit.Enabled := not FSolveUsesRawData;
|
|
btnClear.Enabled := not FSolveUsesRawData;
|
|
end;
|
|
|
|
|
|
procedure TForm1.ValuesToGrid(const Values: TValues);
|
|
var
|
|
Col, Row: Integer;
|
|
Ch: Char;
|
|
begin
|
|
for Col := 0 to 8 do
|
|
begin
|
|
for Row := 0 to 8 do
|
|
begin
|
|
Ch := IntToStr(Values[Col + 1, Row + 1])[1];
|
|
if Ch = '0' then
|
|
Ch := VisualEmptyChar;
|
|
SGrid.Cells[Col, Row] := Ch;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.RawDataToGrid(const RawData: TRawGrid);
|
|
var
|
|
Col, Row: Integer;
|
|
Ch: Char;
|
|
begin
|
|
for Col := 0 to 8 do
|
|
begin
|
|
for Row := 0 to 8 do
|
|
begin
|
|
Ch := IntToStr(RawData[Col + 1, Row + 1].Value)[1];
|
|
if Ch = '0' then
|
|
Ch := VisualEmptyChar;
|
|
SGrid.Cells[Col, Row] := Ch;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.ShowScratchPad(RawData: TRawGrid);
|
|
begin
|
|
ScratchForm.OnCopyValues := @OnCopyBackValues;
|
|
ScratchForm.OnCopyRawData := @OnCopyBackRawData;
|
|
ScratchForm.RawData := RawData;
|
|
ScratchForm.ScratchGrid.Options := SGrid.Options - [goEditing];
|
|
ScratchForm.Left := Left + Width + 10;
|
|
if (ScratchForm.ShowModal <> mrOK) then
|
|
SolveUsesRawData := False;
|
|
end;
|
|
|
|
procedure TForm1.LoadSudokuFromFile(const Fn: String);
|
|
var
|
|
SL: TStringList;
|
|
begin
|
|
SL := TStringList.Create;
|
|
try
|
|
SL.LoadFromFile(Fn);
|
|
SL.Text := AdjustLineBreaks(SL.Text);
|
|
if not IsValidSudokuFile(SL) then
|
|
Raise ESudokuFile.Create(Format('File does not seem to be a valid Sudoku file:'^m'"%s"',[Fn]));
|
|
LinesToGrid(SL);
|
|
finally
|
|
SL.Free
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.SaveSudokuToFile(const Fn: String);
|
|
var
|
|
SL: TStringList;
|
|
begin
|
|
SL := TStringList.Create;
|
|
try
|
|
SL.SkipLastLineBreak := True;
|
|
GridToLines(SL);
|
|
{$if fpc_fullversion >= 30200}
|
|
SL.WriteBom := False;
|
|
{$endif}
|
|
SL.SaveToFile(Fn);
|
|
finally
|
|
SL.Free;
|
|
end;
|
|
end;
|
|
|
|
{
|
|
A valid SudokuFile consists of 9 lines, each line consists of 9 characters.
|
|
Only the characters '1'to '9' and spaces and FileEmptyChar ('-') are allowed.
|
|
Empty lines and lines starting with '#' (comments) are discarded
|
|
Future implementations may allow for adding a comment when saving the file
|
|
}
|
|
function TForm1.IsValidSudokuFile(Lines: TStrings): Boolean;
|
|
var
|
|
i: Integer;
|
|
S: String;
|
|
Ch: Char;
|
|
begin
|
|
Result := False;
|
|
for i := Lines.Count - 1 downto 0 do
|
|
begin
|
|
S := Lines[i];
|
|
if (S = '') or (S[1] = '#') then Lines.Delete(i);
|
|
end;
|
|
if (Lines.Count <> 9) then Exit;
|
|
for i := 0 to Lines.Count - 1 do
|
|
begin
|
|
S := Lines[i];
|
|
if (Length(S) <> 9) then Exit;
|
|
for Ch in S do
|
|
begin
|
|
if not (Ch in [FileEmptyChar, '1'..'9',VisualEmptyChar]) then Exit;
|
|
end;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
{
|
|
Since this should only be called if IsValidSudokuFile retruns True,
|
|
We know that all lines consist of 9 chactres exactly and that there are exactly 9 lines in Lines
|
|
}
|
|
procedure TForm1.LinesToGrid(Lines: TStrings);
|
|
var
|
|
Row, Col: Integer;
|
|
S: String;
|
|
Ch: Char;
|
|
begin
|
|
for Row := 0 to Lines.Count - 1 do
|
|
begin
|
|
S := Lines[Row];
|
|
for Col := 0 to Length(S) - 1 do
|
|
begin
|
|
Ch := S[Col+1];
|
|
if (Ch = FileEmptyChar) then
|
|
Ch := VisualEmptyChar;
|
|
SGrid.Cells[Col, Row] := Ch;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.GridToLines(Lines: TStrings);
|
|
var
|
|
ALine, S: String;
|
|
Ch: Char;
|
|
Row, Col: Integer;
|
|
begin
|
|
Lines.Clear;
|
|
for Row := 0 to SGrid.RowCount - 1 do
|
|
begin
|
|
ALine := StringOfChar(FileEmptyChar,9);
|
|
for Col := 0 to SGrid.ColCount - 1 do
|
|
begin
|
|
S := SGrid.Cells[Col, Row];
|
|
if (Length(S) >= 1) then
|
|
begin
|
|
Ch := S[1];
|
|
if (Ch = VisualEmptyChar) then
|
|
Ch := FileEmptyChar;
|
|
ALine[Col+1] := Ch;
|
|
end;
|
|
end;
|
|
Lines.Add(ALine);
|
|
end;
|
|
end;
|
|
|
|
procedure TForm1.EnableEdit;
|
|
begin
|
|
SGrid.Options := SGrid.Options + [goEditing];
|
|
end;
|
|
|
|
procedure TForm1.DisableEdit;
|
|
begin
|
|
SGrid.Options := SGrid.Options - [goEditing];
|
|
end;
|
|
|
|
end.
|
|
|