From f8067222d363fe3f65ade42616ec0de575fe704a Mon Sep 17 00:00:00 2001 From: sekelsenmat Date: Thu, 7 Apr 2011 06:26:28 +0000 Subject: [PATCH] fpchess: Starts adding AI play via a web service git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@1551 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- applications/fpchess/IDelphiChess.wsdl | 33 ++ applications/fpchess/IDelphiChess.wst | 7 + applications/fpchess/IDelphiChessWSDL.pas | 97 ++++ applications/fpchess/IDelphiChess_Intf.pas | 42 ++ applications/fpchess/IDelphiChess_proxy.pas | 85 +++ applications/fpchess/fpchess.lpi | 34 +- applications/fpchess/fpchess.lpr | 4 +- applications/fpchess/mainform.lfm | 44 +- applications/fpchess/mainform.pas | 36 +- applications/fpchess/tcpcomm.pas | 548 ++++++++++++++++++++ 10 files changed, 910 insertions(+), 20 deletions(-) create mode 100644 applications/fpchess/IDelphiChess.wsdl create mode 100644 applications/fpchess/IDelphiChess.wst create mode 100644 applications/fpchess/IDelphiChessWSDL.pas create mode 100644 applications/fpchess/IDelphiChess_Intf.pas create mode 100644 applications/fpchess/IDelphiChess_proxy.pas create mode 100644 applications/fpchess/tcpcomm.pas diff --git a/applications/fpchess/IDelphiChess.wsdl b/applications/fpchess/IDelphiChess.wsdl new file mode 100644 index 000000000..3b2ad99ac --- /dev/null +++ b/applications/fpchess/IDelphiChess.wsdl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/fpchess/IDelphiChess.wst b/applications/fpchess/IDelphiChess.wst new file mode 100644 index 000000000..b85ef0c30 --- /dev/null +++ b/applications/fpchess/IDelphiChess.wst @@ -0,0 +1,7 @@ + GetWSTResourceManager().AddResource('IDELPHICHESS', + #0#0#0#16'WST_METADATA_0.6'#0#0#0#12'IDelphiChess'#0#1#0#0#0#12'IDelphiChess' + +#0#1#0#0#0#15'XML_GetNextMove'#4#0#0#0#8'Position'#0#0#0#6'string'#0#0#0#0#0 + +#0#0#1#0#0#0#14'WhiteMovesNext'#0#0#0#7'boolean'#0#0#0#0#0#0#0#1#0#0#0#11'Se' + +'archDepth'#0#0#0#7'integer'#0#0#0#0#0#0#0#1#0#0#0#6'Result'#0#0#0#6'string'#0 + +#0#0#0#0#0#0#3'' + ); \ No newline at end of file diff --git a/applications/fpchess/IDelphiChessWSDL.pas b/applications/fpchess/IDelphiChessWSDL.pas new file mode 100644 index 000000000..c456c5d0f --- /dev/null +++ b/applications/fpchess/IDelphiChessWSDL.pas @@ -0,0 +1,97 @@ +{ +This unit has been produced by ws_helper. + Input unit name : "IDelphiChess". + This unit name : "IDelphiChess". + Date : "10/17/10 08:24:54 AM". +} +unit IDelphiChessWSDL; +{$IFDEF FPC} + {$mode objfpc} {$H+} +{$ENDIF} +{$IFNDEF FPC} + {$DEFINE WST_RECORD_RTTI} +{$ENDIF} +interface + +uses SysUtils, Classes, TypInfo, base_service_intf, service_intf; + +const + sNAME_SPACE = 'http://eBob42.org/'; + sUNIT_NAME = 'IDelphiChess'; + +type + IDelphiChess = interface(IInvokable) + ['{ECE02B6C-B051-2815-AE82-B2969FFEDC2A}'] + function XML_GetNextMove( + const Position : string; + const WhiteMovesNext : boolean; + const SearchDepth : integer + ):string; + end; + +procedure Register_IDelphiChess_ServiceMetadata(); + +Implementation + +uses metadata_repository, record_rtti, wst_types; + +procedure Register_IDelphiChess_ServiceMetadata(); +var + mm : IModuleMetadataMngr; +begin + mm := GetModuleMetadataMngr(); + mm.SetRepositoryNameSpace(sUNIT_NAME, sNAME_SPACE); + mm.SetServiceCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'TRANSPORT_Address', + 'http://www.bobswart.nl/cgi-bin/ChessISAPIServer.dll/soap/IDelphiChess' + ); + mm.SetServiceCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'FORMAT_Style', + 'rpc' + ); + mm.SetOperationCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'XML_GetNextMove', + '_E_N_', + 'XML_GetNextMove' + ); + mm.SetOperationCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'XML_GetNextMove', + 'style', + 'rpc' + ); + mm.SetOperationCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'XML_GetNextMove', + 'TRANSPORT_soapAction', + 'urn:DelphiChess-IDelphiChess#XML_GetNextMove' + ); + mm.SetOperationCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'XML_GetNextMove', + 'FORMAT_Input_EncodingStyle', + 'encoded' + ); + mm.SetOperationCustomData( + sUNIT_NAME, + 'IDelphiChess', + 'XML_GetNextMove', + 'FORMAT_OutputEncodingStyle', + 'encoded' + ); +end; + +var + typeRegistryInstance : TTypeRegistry = nil; +initialization + typeRegistryInstance := GetTypeRegistry(); +end. diff --git a/applications/fpchess/IDelphiChess_Intf.pas b/applications/fpchess/IDelphiChess_Intf.pas new file mode 100644 index 000000000..601538ec5 --- /dev/null +++ b/applications/fpchess/IDelphiChess_Intf.pas @@ -0,0 +1,42 @@ +unit IDelphiChess_Intf; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + IDelphiChessWSDL, IDelphiChess_proxy, chessgame, httpsend; + +procedure GetNextMoveFromBorlandWS(); + +implementation + +function ReadEntry(const APromp : string):string ; +begin + Result := ''; + Write(APromp); + while True do begin + ReadLn(Result); + Result := Trim(Result); + if ( Length(Result) > 0 ) then + Break; + end; +end; + +procedure GetNextMoveFromBorlandWS(); +var + locService : IDelphiChess; + rsps : string; +begin + Register_IDelphiChess_ServiceMetadata(); + // SYNAPSE_RegisterHTTP_Transport(); + + locService := wst_CreateInstance_IDelphiChess(); + + rsps := locService.XML_GetNextMove( + '', True, 5); +end; + +end. + diff --git a/applications/fpchess/IDelphiChess_proxy.pas b/applications/fpchess/IDelphiChess_proxy.pas new file mode 100644 index 000000000..6967c0b3c --- /dev/null +++ b/applications/fpchess/IDelphiChess_proxy.pas @@ -0,0 +1,85 @@ +{ +This unit has been produced by ws_helper. + Input unit name : "IDelphiChess". + This unit name : "IDelphiChess_proxy". + Date : "10/17/10 08:24:54 AM". +} + +Unit IDelphiChess_proxy; +{$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF} +Interface + +uses SysUtils, Classes, TypInfo, base_service_intf, service_intf, IDelphiChessWSDL; + +type + TDelphiChess_Proxy=class(TBaseProxy,IDelphiChess) + Protected + class function GetServiceType() : PTypeInfo;override; + function XML_GetNextMove( + const Position : string; + const WhiteMovesNext : boolean; + const SearchDepth : integer + ):string; + end; + + Function wst_CreateInstance_IDelphiChess(const AFormat : string = 'SOAP:'; const ATransport : string = 'HTTP:'; const AAddress : string = ''):IDelphiChess; + +Implementation + +uses wst_resources_imp, metadata_repository; + +Function wst_CreateInstance_IDelphiChess(const AFormat : string; const ATransport : string; const AAddress : string):IDelphiChess; +Var + locAdr : string; +Begin + locAdr := AAddress; + if ( locAdr = '' ) then + locAdr := GetServiceDefaultAddress(TypeInfo(IDelphiChess)); + Result := TDelphiChess_Proxy.Create('IDelphiChess',AFormat+GetServiceDefaultFormatProperties(TypeInfo(IDelphiChess)),ATransport + 'address=' + locAdr); +End; + +{ TDelphiChess_Proxy implementation } + +class function TDelphiChess_Proxy.GetServiceType() : PTypeInfo; +begin + result := TypeInfo(IDelphiChess); +end; + +function TDelphiChess_Proxy.XML_GetNextMove( + const Position : string; + const WhiteMovesNext : boolean; + const SearchDepth : integer +):string; +Var + locSerializer : IFormatterClient; + locCallContext : ICallContext; + locStrPrmName : string; +Begin + locCallContext := Self as ICallContext; + locSerializer := GetSerializer(); + Try + locSerializer.BeginCall('XML_GetNextMove', GetTarget(),locCallContext); + locSerializer.Put('Position', TypeInfo(string), Position); + locSerializer.Put('WhiteMovesNext', TypeInfo(boolean), WhiteMovesNext); + locSerializer.Put('SearchDepth', TypeInfo(integer), SearchDepth); + locSerializer.EndCall(); + + MakeCall(); + + locSerializer.BeginCallRead(locCallContext); + locStrPrmName := 'return'; + locSerializer.Get(TypeInfo(string), locStrPrmName, Result); + + Finally + locSerializer.Clear(); + End; +End; + + +initialization + {$i IDelphiChess.wst} + + {$IF DECLARED(Register_IDelphiChess_ServiceMetadata)} + Register_IDelphiChess_ServiceMetadata(); + {$IFEND} +End. diff --git a/applications/fpchess/fpchess.lpi b/applications/fpchess/fpchess.lpi index 710219b0d..93d255c2b 100644 --- a/applications/fpchess/fpchess.lpi +++ b/applications/fpchess/fpchess.lpi @@ -30,15 +30,18 @@ - + - + - + + + + - + @@ -71,6 +74,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applications/fpchess/fpchess.lpr b/applications/fpchess/fpchess.lpr index 3c0d5b90c..bb6e9c140 100644 --- a/applications/fpchess/fpchess.lpr +++ b/applications/fpchess/fpchess.lpr @@ -7,8 +7,8 @@ uses cthreads, {$ENDIF}{$ENDIF} Interfaces, // this includes the LCL widgetset - Forms, lnetvisual, mainform, chessdrawer, chessgame, chessconfig, - chesstcputils; + Forms, laz_synapse, mainform, chessdrawer, chessgame, chessconfig, + chesstcputils, IDelphiChess_Intf, wst_synapse; //{$R *.res} diff --git a/applications/fpchess/mainform.lfm b/applications/fpchess/mainform.lfm index 9492557a9..2174b419c 100644 --- a/applications/fpchess/mainform.lfm +++ b/applications/fpchess/mainform.lfm @@ -147,12 +147,12 @@ object formChess: TformChess OnClick = HandleMainScreenButton TabOrder = 7 end - object btnWebservice: TBitBtn + object btnAI: TBitBtn Left = 24 Height = 30 Top = 320 Width = 304 - Caption = 'Play with a friend through the chess Webservice' + Caption = 'Play against the Computer' OnClick = HandleMainScreenButton TabOrder = 8 end @@ -303,13 +303,43 @@ object formChess: TformChess TabOrder = 0 Text = 'http://www.bobswart.nl/cgi-bin/ChessISAPIServer.dll/wsdl/IDelphiChess' end - object Button1: TButton - Left = 35 + object btnPlayAgainstAI: TButton + Left = 64 Height = 25 - Top = 140 - Width = 75 - Caption = 'Button1' + Top = 160 + Width = 224 + Caption = 'Start game' + OnClick = btnPlayAgainstAIClick + TabOrder = 0 + end + object Label9: TLabel + Left = 8 + Height = 30 + Top = 40 + Width = 352 + AutoSize = False + Caption = 'The artificial intelligence is obtained from the Borland Chess WebService.' + ParentColor = False + WordWrap = True + end + object editWebServiceAI: TLabeledEdit + Left = 8 + Height = 22 + Top = 96 + Width = 344 + EditLabel.AnchorSideLeft.Control = editWebServiceAI + EditLabel.AnchorSideTop.Control = editWebServiceAI + EditLabel.AnchorSideTop.Side = asrCenter + EditLabel.AnchorSideRight.Control = editWebServiceAI + EditLabel.AnchorSideBottom.Control = editWebServiceAI + EditLabel.Left = 8 + EditLabel.Height = 17 + EditLabel.Top = 76 + EditLabel.Width = 104 + EditLabel.Caption = 'Web Service URL' + EditLabel.ParentColor = False TabOrder = 1 + Text = 'http://www.bobswart.nl/cgi-bin/ChessISAPIServer.dll/wsdl/IDelphiChess' end end end diff --git a/applications/fpchess/mainform.pas b/applications/fpchess/mainform.pas index d17ab1fad..81b965b37 100644 --- a/applications/fpchess/mainform.pas +++ b/applications/fpchess/mainform.pas @@ -8,6 +8,8 @@ uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, ComCtrls, StdCtrls, Buttons, Spin, // + IDelphiChess_Intf, + // chessdrawer, chessgame, chessconfig, chesstcputils; type @@ -27,15 +29,14 @@ type TformChess = class(TForm) btnConnect: TBitBtn; - btnWebservice: TBitBtn; + btnAI: TBitBtn; btnSinglePlayer: TBitBtn; btnDirectComm: TBitBtn; BitBtn3: TBitBtn; btnHotSeat: TBitBtn; - Button1: TButton; + btnPlayAgainstAI: TButton; checkTimer: TCheckBox; comboStartColor: TComboBox; - editWebserviceURL: TLabeledEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; @@ -44,6 +45,8 @@ type Label6: TLabel; Label7: TLabel; Label8: TLabel; + Label9: TLabel; + editWebServiceAI: TLabeledEdit; labelPos: TLabel; editRemoteID: TLabeledEdit; editLocalIP: TLabeledEdit; @@ -58,6 +61,7 @@ type timerChessTimer: TTimer; pageWebservice: TPage; procedure btnConnectClick(Sender: TObject); + procedure btnPlayAgainstAIClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure HandleMainScreenButton(Sender: TObject); procedure pageBeforeShow(Sender: TObject; ANewPage: TPage; ANewIndex: Integer); @@ -67,7 +71,8 @@ type public { public declarations } procedure UpdateCaptions; - end; + procedure InitializeGameModel; + end; var formChess: TformChess; @@ -82,6 +87,7 @@ const INT_PAGE_CONFIGCONNECTION = 1; INT_PAGE_CONNECTING = 2; INT_PAGE_GAME = 3; + INT_PAGE_AI = 4; { TformChess } @@ -90,14 +96,15 @@ begin if Sender = btnSinglePlayer then begin notebookMain.PageIndex := INT_PAGE_GAME; - vChessGame.StartNewGame(comboStartColor.ItemIndex, checkTimer.Checked, spinPlayerTime.Value); + InitializeGameModel(); end else if Sender = btnHotSeat then begin notebookMain.PageIndex := INT_PAGE_GAME; - vChessGame.StartNewGame(comboStartColor.ItemIndex, checkTimer.Checked, spinPlayerTime.Value); + InitializeGameModel(); end - else if Sender = btnDirectComm then notebookMain.PageIndex := INT_PAGE_CONFIGCONNECTION; + else if Sender = btnDirectComm then notebookMain.PageIndex := INT_PAGE_CONFIGCONNECTION + else if Sender = btnAI then notebookMain.PageIndex := INT_PAGE_AI; end; procedure TformChess.pageBeforeShow(Sender: TObject; ANewPage: TPage; ANewIndex: Integer); @@ -130,6 +137,11 @@ begin formChess.labelPos.Caption := lStr; end; +procedure TformChess.InitializeGameModel; +begin + vChessGame.StartNewGame(comboStartColor.ItemIndex, checkTimer.Checked, spinPlayerTime.Value); +end; + procedure TformChess.FormCreate(Sender: TObject); begin // Creation of internal components @@ -151,6 +163,16 @@ begin end; +procedure TformChess.btnPlayAgainstAIClick(Sender: TObject); +begin + InitializeGameModel(); + + notebookMain.PageIndex := INT_PAGE_GAME; + + if comboStartColor.ItemIndex = 0 then + GetNextMoveFromBorlandWS(); +end; + { TFormDrawerDelegate } procedure TFormDrawerDelegate.HandleMouseMove(Sender: TObject; diff --git a/applications/fpchess/tcpcomm.pas b/applications/fpchess/tcpcomm.pas new file mode 100644 index 000000000..0319b8d06 --- /dev/null +++ b/applications/fpchess/tcpcomm.pas @@ -0,0 +1,548 @@ +{ + fpChess - TCP communication module + + License: GPL version 2 or superior + + Copyright: Felipe Monteiro de Carvalho in 2010 +} +unit tcpcomm; + +{$mode objfpc}{$H+}{$packsets 1} + +interface + +uses + // RTL, FLC, LCL + Classes, SysUtils, Forms, + // TPacket declaration + chessgame, + // LNet + lnet, lnetcomponents + ; + +type + TProgressEvent = procedure (AStr: string; APorcent: Integer) of object; + + { TSimpleTcpConnection } + + TSimpleTcpConnection = class + private + FConnectionFinished: Boolean; + FEvent: THandle; + FIP: string; + FPort: Word; + FConnected: Boolean; + FSocket: TLTcpComponent; + ReceivedPacketsStart: TPacket; + ReceivedPacketsEnd: TPacket; + FOnProgress: TProgressEvent; + // Variables to read fragmented packets + UnfinishedPacket: TPacket; + UnfinishedPacketPos: Word; + procedure SendPacket(Packet: TPacket); + function ReceivePacket(ATimeOut: Word = 10000): TPacket; + function HashInList(APacket: TPacket; AHashes: array of Cardinal): Boolean; + protected + function Handshake: Boolean; + public + constructor Create; + destructor Destroy; override; + procedure AddPacketToList(Packet: TPacket); + procedure RemovePacketFromList(Packet: TPacket); + function GetConnected: Boolean; + function PacketPending: Boolean; + function GetNextMessage(AID: Cardinal): TPacket; + function Reconnect: Integer; + function Connect(AIP: string; APort: Integer; ATimeOut: Word = 10000): Boolean; + procedure Disconnect(ATimeOut: Word = 1000); + procedure Shutdown; + procedure DebugOutputMessageList(); + // Events for LNet + procedure OnConnect(aSocket: TLSocket); + procedure OnErrorEvent(const msg: string; aSocket: TLSocket); + procedure OnReceive(aSocket: TLSocket); + procedure OnDisconnect(aSocket: TLSocket); + // Properties + property Connected: Boolean read GetConnected; + {@@ + Indicates that the connection procedure, which is event-based, was finished. + See Connected to check if the connection was successful. + + @see Connected + } + property ConnectionFinished: Boolean read FConnectionFinished write FConnectionFinished; + end; + + ETcpTransportException = class(Exception); + EHandshakeException = class(ETcpTransportException); + ECryptoException = class(ETcpTransportException); + +function GetConnection(): TSimpleTcpConnection; + +implementation + +resourcestring + SInvalidDataPacket = 'Invalid data packet format'; + +var + ClientConnection: TSimpleTcpConnection = nil; + +procedure OPDebugLn(AStr: string); +begin + {$ifndef Win32} + WriteLn(AStr); + {$endif} +end; + +function GetConnection(): TSimpleTcpConnection; +begin + if ClientConnection = nil then + ClientConnection := TSimpleTcpConnection.Create(); + Result := ClientConnection; +end; + +{ TOPCTcpConnection } + +constructor TSimpleTcpConnection.Create; +begin + {$ifdef Synapse} + FSocket := TTCPBlockSocket.Create; + {$else} + FSocket := TLTcpComponent.Create(nil); + FSocket.OnConnect := @OnConnect; + FSocket.OnError := @OnErrorEvent; + FSocket.OnReceive := @OnReceive; + FSocket.OnDisconnect := @OnDisconnect; + FSocket.Timeout:= 1000; + {$endif} +end; + +function TSimpleTcpConnection.PacketPending: Boolean; +begin +// Result := FSocket.PacketPending(SizeOf(TPacketHeader)); +end; + +{@@ + Connects to a given server. This function is synchronous. + + @param API Server name or IP + @param APort Server port + @param ATimeOut The maximum time to wait for an answer of the server, + in miliseconds. +} +function TSimpleTcpConnection.Connect(AIP: string; APort: Integer; + ATimeOut: Word = 10000): Boolean; +var + Packet: TPacket; + i: Integer; +begin + OPDebugLn('[TSimpleTcpConnection.Connect] START'); + Result := False; + FIP := AIP; + FPort := APort; + FConnectionFinished := False; + + if Assigned(FOnProgress) then FOnProgress('', 30); // Values between 20 and 60 + FSocket.Connect(FIP, FPort); + + // Wait for either OnConnect or OnErrorEvent + for i := 0 to ATimeOut div 10 do + begin + if FConnectionFinished then + begin + if Assigned(FOnProgress) then + FOnProgress('Conection Response arrived', 40); // Values between 20 and 60 + Break; + end; + Sleep(10); + Application.ProcessMessages; + end; + + if not Connected then + raise Exception.Create('[TSimpleTcpConnection.Connect] Connection Timeout'); + + if Assigned(FOnProgress) then FOnProgress('Executing Handshake', 60); + Handshake; + + Result := True; + + OPDebugLn('[TSimpleTcpConnection.Connect] END'); +end; + +function TSimpleTcpConnection.Reconnect: Integer; +var + Packet: TPacket; +begin +// Result := RC_NOT_RESTORED; +{ if FSocket <> nil then FSocket.Free; + FSocket := TWinSocket.Create; + try + FSocket.Connect(FHostName, FHostIP, FServiceName, FServicePort); + Assert(FConnectionCookie <> nil); + FConnectionCookie.ResetPosition; + SendPacket(FConnectionCookie); + Packet := ReceivePacket; + case Packet.Action of + + asCookie: + // positive response on reconnect - connection found by cookie + begin + FConnectionCookie := Packet; + Result := RC_RESTORED + end; + + asRestart: + // No corresponding connection found on server. Client should be restarted + begin + FConnectionCookie := Packet; + FConnectionCookie.Action := asCookie; + Result := RC_FAIL + end; + + else + Assert(False); + end; + except + FSocket.Free; + FSocket := nil; + Result := RC_NOT_RESTORED; + end;} +end; + +destructor TSimpleTcpConnection.Destroy; +begin + if Connected then Disconnect; + FSocket.Free; + inherited; +end; + +procedure TSimpleTcpConnection.Disconnect(ATimeOut: Word = 1000); +var + i: Integer; +begin + {$ifdef Synapse} + FSocket.CloseSocket; + {$else} + FSocket.Disconnect(); + {$endif} + + for i := 0 to ATimeOut div 10 do + begin + if not FConnected then Break; + Sleep(10); + Application.ProcessMessages; + end; + + if FConnected then + OPDebugLn('[TSimpleTcpConnection.Disconnect] Disconection failed'); +end; + +function TSimpleTcpConnection.GetConnected: Boolean; +begin + Result := (FSocket <> nil) and FConnected; +end; + +function TSimpleTcpConnection.GetNextMessage(AID: Cardinal): TPacket; +var + CurrentPacket: TPacket; + PacketFound: Boolean = False; +begin + Result := nil; + + // Search the packets in the linked list + CurrentPacket := ReceivedPacketsStart; + while CurrentPacket <> nil do + begin + if (CurrentPacket.ID = AID) then + begin + PacketFound := True; + Break; + end; + + CurrentPacket := CurrentPacket.Next; + end; + + if not PacketFound then Exit; + + // Convert the Packet to a DataBlock + Result := CurrentPacket; + + // Remove the packet from the list + RemovePacketFromList(CurrentPacket); +end; + +{@@ + First step when disconnecting from the server + + @see Disconnect +} +procedure TSimpleTcpConnection.Shutdown; +var + Packet: TPacket; +begin +{ try + Packet := TPacket.Create(asShutdown, nil^, 0); + SendPacket(Packet); + Packet.Free; + except + // eat exception for user pleasure + end;} +end; + +procedure TSimpleTcpConnection.DebugOutputMessageList(); +var + CurPacket: TPacket; + lHash: LongWord; +begin + OPDebugLn('[TSimpleTcpConnection.DebugOutputMessageList]'); + CurPacket := ReceivedPacketsStart; + while CurPacket <> nil do + begin + lHash := CurPacket.ID; + OPDebugLn(Format('[Packege] Hash %d', [lHash])); + + CurPacket := CurPacket.Next; + end; + // Variables to read fragmented packets + if UnfinishedPacket <> nil then + OPDebugLn('[There is an unfinished packege]'); +end; + +{@@ + Event called by LNet indicating that the connection was finished successfully +} +procedure TSimpleTcpConnection.OnConnect(aSocket: TLSocket); +begin + FConnectionFinished := True; + FConnected := True; +end; + +{@@ + Event called by LNet when an error occured in the Connection +} +procedure TSimpleTcpConnection.OnErrorEvent(const msg: string; aSocket: TLSocket); +begin + FConnectionFinished := True; + FConnected := False; +end; + +{@@ + Event called by LNet when data is available to be read +} +procedure TSimpleTcpConnection.OnReceive(aSocket: TLSocket); +var + lPacket: TPacket; + lFreePacket: Boolean; + i, lPos, lRemaining, lSizeRead: Integer; +begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] BEGIN'); + repeat + // Finishes reading a fragmented packet + if UnfinishedPacket <> nil then + begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] Another part of a fragmented packet'); +{ lPacket := UnfinishedPacket; + + // Gets the data + lRemaining := lPacket.DataSize - UnfinishedPacketPos; + lSizeRead := ASocket.Get(lPacket.Data[UnfinishedPacketPos], lRemaining); + if lSizeRead = lRemaining then + begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] Read fragmented packet to the end'); + UnfinishedPacket := nil; + AddPacketToList(lPacket); + end + else + begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] Fragmented packet not yet finished, read: ' + + IntToStr(lSizeRead)); + UnfinishedPacketPos := UnfinishedPacketPos + lSizeRead; + OPDebugLn('[TSimpleTcpConnection.OnReceive] END'); + Break; + end;} + end + else + // Reads a new packet + begin + lPacket := TPacket.Create; + lFreePacket := True; + + try + // Gets the header + lSizeRead := ASocket.Get(lPacket.ID, 4); + if lSizeRead < 4 then // Expected if there are no more packets + begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] END'); + Exit; + end; + OPDebugLn('[TSimpleTcpConnection.OnReceive] ID: ' + IntToHex(Integer(lPacket.ID), 2)); + + lSizeRead := ASocket.Get(lPacket.Kind, 1); + if lSizeRead < 1 then + begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] Packet ended in lPacket.Kind'); + Exit; + end; + OPDebugLn('[TSimpleTcpConnection.OnReceive] Kind: ' + IntToHex( + {$ifdef VER2_4}Cardinal{$else}Byte{$endif}(lPacket.Kind), 2)); // Byte for FPC 2.5+ + + if lPacket.Kind = pkMove then + begin + lSizeRead := ASocket.Get(lPacket.MoveStartX, 1); +{ if lSizeRead < 1 then + begin + OPDebugLn('[TSimpleTcpConnection.OnReceive] Packet ended in MoveStartX'); + Exit; + end; + OPDebugLn('[TSimpleTcpConnection.OnReceive] MoveStartX: ' + IntToStr(lPacket.MoveStartX));} + + lSizeRead := ASocket.Get(lPacket.MoveStartY, 1); + lSizeRead := ASocket.Get(lPacket.MoveEndX, 1); + lSizeRead := ASocket.Get(lPacket.MoveEndY, 1); + end; + + // Because most packets are crypted, the raw data isn't very useful + // OPDebugData('[TSimpleTcpConnection.OnReceive]', lPacket.Data, lPacket.DataSize); + + // Updates the linked list + lFreePacket := False; + AddPacketToList(lPacket); + finally + if lFreePacket then lPacket.Free; + end; + end; + until (lSizeRead = 0); + OPDebugLn('[TSimpleTcpConnection.OnReceive] END'); +end; + +{@@ + Event of the LNet server. Happens when the disconnection procedure is + finished or when a disconnection occurs for another reason. +} +procedure TSimpleTcpConnection.OnDisconnect(aSocket: TLSocket); +begin + FConnectionFinished := True; + FConnected := False; + OPDebugLn('[TSimpleTcpConnection.OnDisconnect] Disconnected from server'); +end; + +function TSimpleTcpConnection.Handshake: Boolean; +begin + OPDebugLn('[TSimpleTcpConnection.Handshake] START'); + + Result := True; + + OPDebugLn('[TSimpleTcpConnection.Handshake] END'); +end; + +procedure CheckCryptoResult(Result: Integer); +begin +// if Result <> 1 then +// raise ECryptoException.Create(ERR_error_string(ERR_get_error, nil)); +end; + +{@@ + Returns the next received packet in the line or waits until one + arrives to a maximum of ATimeOut miliseconds. Returns nil in case + of a timeout of the packet otherwise. +} +function TSimpleTcpConnection.ReceivePacket(ATimeOut: Word = 10000): TPacket; +var + i: Integer; +begin + OPDebugLn('[TSimpleTcpConnection.ReceivePacket]'); + + Result := nil; + + for i := 0 to ATimeOut div 10 do + begin + // Takes one Packet from the linked list + if ReceivedPacketsStart <> nil then + begin + Result := ReceivedPacketsStart; + + if ReceivedPacketsStart = ReceivedPacketsEnd then + begin + ReceivedPacketsStart := nil; + ReceivedPacketsEnd := nil; + end + else + ReceivedPacketsStart := ReceivedPacketsStart.Next; + + Break; + end; + Application.ProcessMessages; + Sleep(10); + end; +end; + +function TSimpleTcpConnection.HashInList(APacket: TPacket; + AHashes: array of Cardinal): Boolean; +var + lHash: Cardinal; + i: Integer; +begin + Result := False; + lHash := APacket.ID; + for i := 0 to Length(AHashes) - 1 do + if lHash = AHashes[i] then Exit(True); +end; + +procedure TSimpleTcpConnection.AddPacketToList(Packet: TPacket); +begin + if ReceivedPacketsStart = nil then + ReceivedPacketsStart := Packet + else + ReceivedPacketsEnd.Next := Packet; + ReceivedPacketsEnd := Packet; +end; + +procedure TSimpleTcpConnection.RemovePacketFromList(Packet: TPacket); +var + CurPacket, PreviousPacket: TPacket; +begin + // First find the previous packet + PreviousPacket := nil; + CurPacket := ReceivedPacketsStart; + while CurPacket <> nil do + begin + if CurPacket.Next = Packet then + begin + PreviousPacket := CurPacket; + Break; + end; + + CurPacket := CurPacket.Next; + end; + + // Now fix the packets array + if Packet = ReceivedPacketsStart then + ReceivedPacketsStart := Packet.Next; + + if Packet = ReceivedPacketsEnd then + ReceivedPacketsEnd := PreviousPacket; + + if PreviousPacket <> nil then + PreviousPacket.Next := Packet.Next; + + // And finally free it +// Packet.Free; +end; + +procedure TSimpleTcpConnection.SendPacket(Packet: TPacket); +var + lSize: Integer; +begin + FSocket.Send(Packet.ID, 4); + FSocket.Send(Packet.Kind, 1); + if Packet.Kind = pkMove then + begin + FSocket.Send(Packet.MoveStartX, 1); + FSocket.Send(Packet.MoveStartY, 1); + FSocket.Send(Packet.MoveEndX, 1); + FSocket.Send(Packet.MoveEndY, 1); + end; +end; + +end. +