diff --git a/applications/fpchess/mod_winboard.pas b/applications/fpchess/mod_winboard.pas new file mode 100644 index 000000000..556df0e14 --- /dev/null +++ b/applications/fpchess/mod_winboard.pas @@ -0,0 +1,103 @@ +unit mod_winboard; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + StdCtrls, Forms, Controls, + chessmodules, chessgame; + +type + + { TWinboardChessModule } + + TWinboardChessModule = class(TChessModule) + private + textEnginePatch : TStaticText; + editEnginePatch : TEdit; + EngineStringList: TStringList; + side : boolean; //Side to move true=human. + public + constructor Create; + procedure CreateUserInterface(); override; + procedure ShowUserInterface(AParent: TWinControl); override; + procedure HideUserInterface(); override; + procedure FreeUserInterface(); override; + procedure PrepareForGame(); override; + procedure HandleOnMove(AFrom, ATo: TPoint); override; + end; + +implementation +uses winboardConn; + +{ TSinglePlayerChessModule } + +constructor TWinboardChessModule.Create; +begin + inherited Create; + + SelectionDescription := 'Play against a winboard engine'; + PlayingDescription := 'Play against a winboard engine'; + Kind := cmkAgainstComputer; +end; + +procedure TWinboardChessModule.CreateUserInterface; +begin + textEnginePatch := TStaticText.Create(nil); + textEnginePatch.SetBounds(20, 20, 180, 50); + textEnginePatch.Caption := 'Full patch to the engine(with args)'; + + editEnginePatch := TEdit.Create(nil); + editEnginePatch.SetBounds(200, 20, 150, 50); + editEnginePatch.Text := 'gnuchess -x'; +end; + +procedure TWinboardChessModule.ShowUserInterface(AParent: TWinControl); +begin + textEnginePatch.Parent := AParent; + editEnginePatch.Parent := AParent; +end; + +procedure TWinboardChessModule.HideUserInterface(); +begin + textEnginePatch.Parent := nil; + editEnginePatch.Parent := nil; +end; + +procedure TWinboardChessModule.FreeUserInterface(); +begin + textEnginePatch.Free; + editEnginePatch.Free; +end; + +procedure TWinboardChessModule.HandleOnMove(AFrom, ATo: TPoint); +var + moveStr : String; + CompMove: moveInCoord; +begin + moveStr:=vwinboardConn.coordToString(AFrom,ATo,vChessGame.PreviousMove.PieceMoved,vChessGame.PreviousMove.PieceEaten); + + vwinboardConn.tellMove(moveStr); + compMove:=vwinboardConn.engineMove; + vChessGame.MovePiece(CompMove[1],CompMove[2]); +end; + +procedure TWinboardChessModule.PrepareForGame; +var i: integer; + CompMove: moveInCoord; +begin + vwinboardConn.startEngine(editEnginePatch.text); + if not vChessGame.FirstPlayerIsWhite then + begin + vwinboardConn.tellMove('go'); + compMove:=vwinboardConn.engineMove; + vChessGame.MovePiece(CompMove[1],CompMove[2]); + end; + +end; + +initialization + RegisterChessModule(TWinboardChessModule.Create); +end. diff --git a/applications/fpchess/winboardconn.pas b/applications/fpchess/winboardconn.pas new file mode 100644 index 000000000..8f3afa5c0 --- /dev/null +++ b/applications/fpchess/winboardconn.pas @@ -0,0 +1,198 @@ +{ +Unit with the basics to communicate with engines that use the winboard protocol. +I used some code of the TProcess example also: +https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process/ + +} +unit winboardConn; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, Process, StdCtrls, chessgame; + +type + moveInCoord = array[1..2] of TPoint; + + TwinboardConn = class + public + procedure startEngine(path : String); + procedure stopEngine; + procedure tellMove(move : String); + function engineMove() : moveInCoord; + function coordToString(AFrom, ATo : TPoint; pieceFrom, pieceTo : TChessTile) : String; + function stringToCoord(moveStr : String) : moveInCoord; + private + procedure readFromPipe; + function extractMove : string; + function letterToNumber(num:String) : String; + function numberToLetter(n : integer) : String; + function pieceToLetter(piece : TChessTile) : String; + end; + +var + engineProcess : TProcess; + outputText : String; + vwinboardConn : TwinboardConn; + +implementation + +procedure TwinboardConn.startEngine(path : String); +begin + engineProcess:= TProcess.Create(nil); + engineProcess.CommandLine := path; + + engineProcess.Options := engineProcess.Options + [poUsePipes]; + engineProcess.Execute; +end; + +procedure TwinboardConn.stopEngine; +begin + if engineProcess.Running then + engineProcess.Free; +end; + +procedure TwinboardConn.tellMove(move : String); +begin + move := move+#13+#10; + EngineProcess.Input.Write(move[1], length(move)); +end; + +//return the engine move. +function TwinboardConn.engineMove : moveInCoord; +var move : String; + points: moveInCoord; +begin + sleep(500); + repeat + readFromPipe; + until length(outputText)>10; + move := extractMove; + points := stringToCoord(move); + result:=points; +end; + +function TwinboardConn.coordToString(AFrom, ATo : TPoint; pieceFrom, pieceTo : TChessTile) : String; +var + move : String; +begin + //still need to verify castle an en passant + + move:= move + numberToLetter(AFrom.X); + move:= move + intToStr(AFrom.Y); + + move:= move + numberToLetter(ATo.X); + move:= move + IntToStr(ATo.Y); + result:=move; + +end; + +function TwinboardConn.pieceToLetter(piece : TChessTile) : String; +begin + case piece of + ctWRook : result:= 'R'; + ctBRook : result:= 'R'; + ctWKnight : result:= 'N'; + ctBKnight : result:= 'N'; + ctWBishop : result:= 'B'; + ctBBishop : result:= 'B'; + ctWQueen : result:= 'Q'; + ctBQueen : result:= 'Q'; + ctWKing : result:= 'K'; + ctBKing : result:= 'K'; + end; +end; + +function TwinboardConn.numberToLetter(n : integer) : String; +begin + case n of + 1 : result := 'a'; + 2 : result := 'b'; + 3 : result := 'c'; + 4 : result := 'd'; + 5 : result := 'e'; + 6 : result := 'f'; + 7 : result := 'g'; + 8 : result := 'h'; + end; +end; + +function TwinboardConn.stringToCoord(moveStr : String) : moveInCoord; +var move : moveInCoord; +begin + //need to verify castle here + //if moveStr[1] in ['R','N','B','Q','K'] then + // Delete(moveStr,1,1); + move[1].X:=strToInt(letterToNumber(moveStr[1])); + move[1].Y:=strToInt(moveStr[2]); + move[2].X:=strToInt(letterToNumber(moveStr[3])); + move[2].Y:=strToInt(moveStr[4]); + result:=move; +end; +//Transform collum number in letter, ex.: 1=a, 3=c .... +function TwinboardConn.letterToNumber(num:String) : String; +begin + case num[1] of + 'a' : result:='1'; + 'b' : result:='2'; + 'c' : result:='3'; + 'd' : result:='4'; + 'e' : result:='5'; + 'f' : result:='6'; + 'g' : result:='7'; + 'h' : result:='8'; + end; +end; + +//read all the output text from the TProcess pipe +procedure TwinboardConn.readFromPipe; +var + NoMoreOutput: boolean; + + procedure DoStuffForProcess; + var + Buffer: string; + BytesAvailable: DWord; + BytesRead:LongInt; + begin + if engineProcess.Running then + begin + BytesAvailable := engineProcess.Output.NumBytesAvailable; + BytesRead := 0; + while BytesAvailable>0 do + begin + SetLength(Buffer, BytesAvailable); + BytesRead := engineProcess.OutPut.Read(Buffer[1], BytesAvailable); + OutputText := OutputText + copy(Buffer,1, BytesRead); + BytesAvailable := engineProcess.Output.NumBytesAvailable; + NoMoreOutput := false; + end; + end; + end; +begin + if engineProcess.Running then + repeat + NoMoreOutput := true; + DoStuffForProcess; + until noMoreOutput; +end; + +function TwinboardConn.extractMove : string; +var move : String; + i,p : integer; +begin + p := pos('My move is: ', outputText); + p:=p+12; + if p>0 then + while outputText[p]<>#10 do + begin + move:= move+outputText[p]; + inc(p); + end; + result:=move; + outputText:=''; +end; + +end.