You've already forked lazarus-ccr
Sudoku: implement loading and saving sudoku files (as text).
Include sample.sudoku file that can be loaded and solved. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7232 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
9
applications/sudoku/sample.sudoku
Normal file
9
applications/sudoku/sample.sudoku
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
--1--7--8
|
||||||
|
-8-2---5-
|
||||||
|
7-4---6--
|
||||||
|
--2-86---
|
||||||
|
5-------2
|
||||||
|
---35-1--
|
||||||
|
--9---7-5
|
||||||
|
-7---3-1-
|
||||||
|
8--7--3--
|
@ -1,6 +1,6 @@
|
|||||||
object Form1: TForm1
|
object Form1: TForm1
|
||||||
Left = 566
|
Left = 566
|
||||||
Height = 359
|
Height = 380
|
||||||
Top = 203
|
Top = 203
|
||||||
Width = 333
|
Width = 333
|
||||||
HorzScrollBar.Page = 271
|
HorzScrollBar.Page = 271
|
||||||
@ -8,9 +8,10 @@ object Form1: TForm1
|
|||||||
ActiveControl = btnEdit
|
ActiveControl = btnEdit
|
||||||
BorderStyle = bsDialog
|
BorderStyle = bsDialog
|
||||||
Caption = 'Sudoku Solver'
|
Caption = 'Sudoku Solver'
|
||||||
ClientHeight = 359
|
ClientHeight = 380
|
||||||
ClientWidth = 333
|
ClientWidth = 333
|
||||||
OnActivate = FormActivate
|
OnActivate = FormActivate
|
||||||
|
OnCreate = FormCreate
|
||||||
LCLVersion = '2.1.0.0'
|
LCLVersion = '2.1.0.0'
|
||||||
object SGrid: TStringGrid
|
object SGrid: TStringGrid
|
||||||
Left = 16
|
Left = 16
|
||||||
@ -40,10 +41,13 @@ object Form1: TForm1
|
|||||||
TabOrder = 0
|
TabOrder = 0
|
||||||
end
|
end
|
||||||
object btnSolve: TButton
|
object btnSolve: TButton
|
||||||
|
AnchorSideRight.Control = SGrid
|
||||||
|
AnchorSideRight.Side = asrBottom
|
||||||
Left = 208
|
Left = 208
|
||||||
Height = 25
|
Height = 25
|
||||||
Top = 312
|
Top = 312
|
||||||
Width = 75
|
Width = 100
|
||||||
|
Anchors = [akTop, akLeft, akRight]
|
||||||
BorderSpacing.InnerBorder = 2
|
BorderSpacing.InnerBorder = 2
|
||||||
Caption = 'Solve'
|
Caption = 'Solve'
|
||||||
OnClick = btnSolveClick
|
OnClick = btnSolveClick
|
||||||
@ -58,4 +62,34 @@ object Form1: TForm1
|
|||||||
OnClick = btnClearClick
|
OnClick = btnClearClick
|
||||||
TabOrder = 3
|
TabOrder = 3
|
||||||
end
|
end
|
||||||
|
object btnSave: TButton
|
||||||
|
Left = 120
|
||||||
|
Height = 25
|
||||||
|
Top = 344
|
||||||
|
Width = 100
|
||||||
|
Caption = 'Save to file'
|
||||||
|
OnClick = btnSaveClick
|
||||||
|
TabOrder = 4
|
||||||
|
end
|
||||||
|
object btnLoad: TButton
|
||||||
|
Left = 16
|
||||||
|
Height = 25
|
||||||
|
Top = 344
|
||||||
|
Width = 100
|
||||||
|
Caption = 'Load from file'
|
||||||
|
OnClick = btnLoadClick
|
||||||
|
TabOrder = 5
|
||||||
|
end
|
||||||
|
object OpenDialog: TOpenDialog
|
||||||
|
Title = 'Open a Sudoku text file'
|
||||||
|
Filter = 'Sudoku files|*.sudoku|All files|*'
|
||||||
|
Options = [ofPathMustExist, ofFileMustExist, ofEnableSizing]
|
||||||
|
left = 264
|
||||||
|
top = 16
|
||||||
|
end
|
||||||
|
object SaveDialog: TSaveDialog
|
||||||
|
Options = [ofPathMustExist, ofEnableSizing]
|
||||||
|
left = 208
|
||||||
|
top = 16
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -39,14 +39,21 @@ type
|
|||||||
|
|
||||||
TForm1 = class(TForm)
|
TForm1 = class(TForm)
|
||||||
btnClear: TButton;
|
btnClear: TButton;
|
||||||
|
btnLoad: TButton;
|
||||||
|
btnSave: TButton;
|
||||||
btnSolve: TButton;
|
btnSolve: TButton;
|
||||||
btnEdit: TButton;
|
btnEdit: TButton;
|
||||||
|
OpenDialog: TOpenDialog;
|
||||||
|
SaveDialog: TSaveDialog;
|
||||||
SGrid: TStringGrid;
|
SGrid: TStringGrid;
|
||||||
procedure btnClearClick(Sender: TObject);
|
procedure btnClearClick(Sender: TObject);
|
||||||
procedure btnEditClick(Sender: TObject);
|
procedure btnEditClick(Sender: TObject);
|
||||||
|
procedure btnLoadClick(Sender: TObject);
|
||||||
|
procedure btnSaveClick(Sender: TObject);
|
||||||
procedure btnSolveClick(Sender: TObject);
|
procedure btnSolveClick(Sender: TObject);
|
||||||
procedure EditorKeyPress(Sender: TObject; var Key: char);
|
procedure EditorKeyPress(Sender: TObject; var Key: char);
|
||||||
procedure FormActivate(Sender: TObject);
|
procedure FormActivate(Sender: TObject);
|
||||||
|
procedure FormCreate(Sender: TObject);
|
||||||
procedure SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
|
procedure SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
|
||||||
{%H-}aState: TGridDrawState);
|
{%H-}aState: TGridDrawState);
|
||||||
procedure SGridSelectEditor(Sender: TObject; {%H-}aCol, {%H-}aRow: Integer;
|
procedure SGridSelectEditor(Sender: TObject; {%H-}aCol, {%H-}aRow: Integer;
|
||||||
@ -56,9 +63,16 @@ type
|
|||||||
theValues: TValues;
|
theValues: TValues;
|
||||||
function SolveSudoku: Boolean;
|
function SolveSudoku: Boolean;
|
||||||
procedure ShowSolution;
|
procedure ShowSolution;
|
||||||
|
procedure LoadSudokuFromFile(const Fn: String);
|
||||||
|
procedure SaveSudokuToFile(const Fn: String);
|
||||||
|
function IsValidSudokuFile(Lines: TStrings): Boolean;
|
||||||
|
procedure LinesToGrid(Lines: TStrings);
|
||||||
|
procedure GridToLines(Lines: TStrings);
|
||||||
public
|
public
|
||||||
{ public declarations }
|
{ public declarations }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
ESudokuFile = Class(Exception);
|
||||||
|
|
||||||
var
|
var
|
||||||
Form1: TForm1;
|
Form1: TForm1;
|
||||||
@ -67,6 +81,12 @@ implementation
|
|||||||
|
|
||||||
{$R *.lfm }
|
{$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 }
|
{ TForm1 }
|
||||||
|
|
||||||
procedure TForm1.btnEditClick(Sender: TObject);
|
procedure TForm1.btnEditClick(Sender: TObject);
|
||||||
@ -75,6 +95,26 @@ begin
|
|||||||
SGrid.SetFocus;
|
SGrid.SetFocus;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.btnLoadClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
if OpenDialog.Execute then
|
||||||
|
try
|
||||||
|
LoadSudokuFromFile(OpenDialog.Filename);
|
||||||
|
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);
|
procedure TForm1.btnClearClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
SGrid.Clean;
|
SGrid.Clean;
|
||||||
@ -108,6 +148,12 @@ begin
|
|||||||
ClientWidth := 2 * SGrid.Left + SGrid.Width;
|
ClientWidth := 2 * SGrid.Left + SGrid.Width;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TForm1.FormCreate(Sender: TObject);
|
||||||
|
begin
|
||||||
|
OpenDialog.Filter := SudokuFileFilter;
|
||||||
|
SaveDialog.Filter := SudokuFileFilter;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TForm1.SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
|
procedure TForm1.SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
|
||||||
aState: TGridDrawState);
|
aState: TGridDrawState);
|
||||||
@ -183,11 +229,122 @@ begin
|
|||||||
begin
|
begin
|
||||||
Ch := IntToStr(theValues[Col + 1, Row + 1])[1];
|
Ch := IntToStr(theValues[Col + 1, Row + 1])[1];
|
||||||
if Ch = '0' then
|
if Ch = '0' then
|
||||||
Ch := #32;
|
Ch := VisualEmptyChar;
|
||||||
SGrid.Cells[Col, Row] := Ch;
|
SGrid.Cells[Col, Row] := Ch;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
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
|
||||||
|
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;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user