diff --git a/applications/sudoku/sample.sudoku b/applications/sudoku/sample.sudoku new file mode 100644 index 000000000..26cc6ccb7 --- /dev/null +++ b/applications/sudoku/sample.sudoku @@ -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-- diff --git a/applications/sudoku/sudokumain.lfm b/applications/sudoku/sudokumain.lfm index cf2b2010e..7f02d3795 100644 --- a/applications/sudoku/sudokumain.lfm +++ b/applications/sudoku/sudokumain.lfm @@ -1,6 +1,6 @@ object Form1: TForm1 Left = 566 - Height = 359 + Height = 380 Top = 203 Width = 333 HorzScrollBar.Page = 271 @@ -8,9 +8,10 @@ object Form1: TForm1 ActiveControl = btnEdit BorderStyle = bsDialog Caption = 'Sudoku Solver' - ClientHeight = 359 + ClientHeight = 380 ClientWidth = 333 OnActivate = FormActivate + OnCreate = FormCreate LCLVersion = '2.1.0.0' object SGrid: TStringGrid Left = 16 @@ -40,10 +41,13 @@ object Form1: TForm1 TabOrder = 0 end object btnSolve: TButton + AnchorSideRight.Control = SGrid + AnchorSideRight.Side = asrBottom Left = 208 Height = 25 Top = 312 - Width = 75 + Width = 100 + Anchors = [akTop, akLeft, akRight] BorderSpacing.InnerBorder = 2 Caption = 'Solve' OnClick = btnSolveClick @@ -58,4 +62,34 @@ object Form1: TForm1 OnClick = btnClearClick TabOrder = 3 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 diff --git a/applications/sudoku/sudokumain.pas b/applications/sudoku/sudokumain.pas index 27f496537..db250729b 100644 --- a/applications/sudoku/sudokumain.pas +++ b/applications/sudoku/sudokumain.pas @@ -39,14 +39,21 @@ type 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; @@ -56,9 +63,16 @@ type theValues: TValues; function SolveSudoku: Boolean; 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 declarations } - end; + end; + + ESudokuFile = Class(Exception); var Form1: TForm1; @@ -67,6 +81,12 @@ 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); @@ -75,6 +95,26 @@ begin SGrid.SetFocus; 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); begin SGrid.Clean; @@ -108,6 +148,12 @@ begin ClientWidth := 2 * SGrid.Left + SGrid.Width; end; +procedure TForm1.FormCreate(Sender: TObject); +begin + OpenDialog.Filter := SudokuFileFilter; + SaveDialog.Filter := SudokuFileFilter; +end; + procedure TForm1.SGridPrepareCanvas(sender: TObject; aCol, aRow: Integer; aState: TGridDrawState); @@ -183,11 +229,122 @@ begin begin Ch := IntToStr(theValues[Col + 1, Row + 1])[1]; if Ch = '0' then - Ch := #32; + Ch := VisualEmptyChar; SGrid.Cells[Col, Row] := Ch; 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.