2011-08-27 20:53:13 +00:00
{
For playing through the internet via FICS - Free Internet Chess Server
Based on this article:
http: //blog.mekk.waw.pl/archives/7-How-to-write-a-FICS-bot-part-I.html
FICS website:
http: //www.freechess.org/
}
unit mod_fics;
{$mode objfpc} {$H+}
interface
uses
Classes, SysUtils,
2011-09-06 11:03:12 +00:00
StdCtrls, Forms, Controls, ExtCtrls, maskedit,
2011-09-06 06:20:37 +00:00
lTelnetex, lnet,
sorokinregexpr, // Rename to regexpr when FPC 2.8 comes with this
2011-09-06 11:03:12 +00:00
chessmodules, chessgame, chessdrawer;
2011-08-27 20:53:13 +00:00
type
2011-08-27 21:16:40 +00:00
{ TFICSChessModule }
2011-08-27 20:53:13 +00:00
2011-08-27 21:16:40 +00:00
TFICSChessModule = class( TChessModule)
2011-09-06 11:03:12 +00:00
private
radioConnectionType: TRadioGroup;
textPassword: TStaticText;
editPassword: TMaskEdit;
textSecondPlayerName: TStaticText;
editSecondPlayerName: TEdit;
2011-08-27 20:53:13 +00:00
public
SecondPlayerName: string ;
2011-09-06 06:20:37 +00:00
TelnetComm: TLTelnetClientEx;
2011-08-27 21:49:44 +00:00
FICS_HOST: string ;
FICS_PORT: Integer ;
FICS_USER: string ;
FICS_PASSWORD: string ;
2011-08-30 16:33:47 +00:00
constructor Create( ) ; override ;
2011-08-27 21:49:44 +00:00
destructor Destroy; override ;
2011-08-27 20:53:13 +00:00
procedure CreateUserInterface( ) ; override ;
procedure ShowUserInterface( AParent: TWinControl) ; override ;
procedure HideUserInterface( ) ; override ;
procedure FreeUserInterface( ) ; override ;
procedure PrepareForGame( ) ; override ;
function GetSecondPlayerName( ) : string ; override ;
procedure HandleOnMove( AFrom, ATo: TPoint) ; override ;
2011-09-06 11:03:12 +00:00
procedure HandleOnTimer( ) ; override ;
2011-09-06 06:20:37 +00:00
procedure HandleOnDebugOut( AStr: string ) ;
2011-08-27 20:53:13 +00:00
end ;
2011-09-06 06:20:37 +00:00
const
FICS_LineEnding = #10 ;
2011-09-06 11:03:12 +00:00
OPEN_TIMEOUT = 1 0 0 0 0 0 0 ;
PROTECT_LOGOUT_FREQ = 4 5 * 6 0 * 1 0 0 0 ; // Frequency to issue commands to avoid disconnection, in miliseconds
2011-09-06 06:20:37 +00:00
2011-08-27 20:53:13 +00:00
implementation
2011-08-27 21:16:40 +00:00
{ TFICSChessModule }
2011-08-27 20:53:13 +00:00
2011-08-27 21:16:40 +00:00
constructor TFICSChessModule. Create;
2011-08-27 20:53:13 +00:00
begin
inherited Create;
2011-09-06 06:20:37 +00:00
TelnetComm : = TLTelnetClientEx. Create( nil ) ;
TelnetComm. OnDebugOut : = @ HandleOnDebugOut;
2011-08-27 21:49:44 +00:00
( * $ telnet = new Net: : Telnet(
Timeout = > $ OPEN_TIMEOUT,
Binmode = > 1 ,
Errmode = > 'die' ,
) ; * )
2011-08-30 16:33:47 +00:00
Name : = 'mod_fics.pas' ;
SelectionDescription : = 'Play online - Free Internet Chess Server' ;
PlayingDescription : = 'Playing online - Free Internet Chess Server' ;
2011-08-30 13:46:20 +00:00
Kind : = cmkInternet;
2011-08-27 21:49:44 +00:00
FICS_HOST : = 'freechess.org' ;
FICS_PORT : = 5 0 0 0 ;
2011-09-06 06:20:37 +00:00
FICS_USER : = 'FPChess' ;
2011-08-27 21:49:44 +00:00
FICS_PASSWORD : = '' ;
end ;
destructor TFICSChessModule. Destroy;
begin
TelnetComm. Free;
inherited Destroy;
2011-08-27 20:53:13 +00:00
end ;
2011-08-27 21:16:40 +00:00
procedure TFICSChessModule. CreateUserInterface;
2011-08-27 20:53:13 +00:00
begin
2011-09-06 11:03:12 +00:00
radioConnectionType : = TRadioGroup. Create( nil ) ;
radioConnectionType. SetBounds( 1 0 , 1 0 , 3 0 0 , 1 0 0 ) ;
radioConnectionType. Caption : = 'FICS Connection Type' ;
radioConnectionType. Items. Add( 'Wait for a friend to connect to me' ) ;
radioConnectionType. Items. Add( 'Connect to a friend (he needs to be waiting for the connection)' ) ;
2011-09-06 12:24:08 +00:00
radioConnectionType. ItemIndex : = 0 ;
2011-09-06 11:03:12 +00:00
textPassword : = TStaticText. Create( nil ) ;
textPassword. SetBounds( 1 0 , 1 1 0 , 1 8 0 , 2 0 ) ;
textPassword. Caption : = 'Your FICS Password' ;
editPassword : = TMaskEdit. Create( nil ) ;
editPassword. SetBounds( 2 0 0 , 1 1 0 , 1 5 0 , 2 0 ) ;
editPassword. Text : = '' ;
textSecondPlayerName : = TStaticText. Create( nil ) ;
textSecondPlayerName. SetBounds( 1 0 , 1 3 0 , 1 8 0 , 4 0 ) ;
textSecondPlayerName. Caption : = 'FICS Login of the other player' ;
2011-08-27 20:53:13 +00:00
editSecondPlayerName : = TEdit. Create( nil ) ;
2011-09-06 11:03:12 +00:00
editSecondPlayerName. SetBounds( 2 0 0 , 1 3 0 , 1 5 0 , 4 0 ) ;
2011-09-06 12:24:08 +00:00
editSecondPlayerName. Text : = 'fpchesse' ;
2011-08-27 20:53:13 +00:00
end ;
2011-08-27 21:16:40 +00:00
procedure TFICSChessModule. ShowUserInterface( AParent: TWinControl) ;
2011-08-27 20:53:13 +00:00
begin
2011-09-06 11:03:12 +00:00
radioConnectionType. Parent : = AParent;
textPassword. Parent : = AParent;
editPassword. Parent : = AParent;
textSecondPlayerName. Parent : = AParent;
editSecondPlayerName. Parent : = AParent;
2011-08-27 20:53:13 +00:00
end ;
2011-08-27 21:16:40 +00:00
procedure TFICSChessModule. HideUserInterface( ) ;
2011-08-27 20:53:13 +00:00
begin
2011-09-06 11:03:12 +00:00
radioConnectionType. Parent : = nil ;
textPassword. Parent : = nil ;
editPassword. Parent : = nil ;
textSecondPlayerName. Parent : = nil ;
editSecondPlayerName. Parent : = nil ;
2011-08-27 20:53:13 +00:00
end ;
2011-08-27 21:16:40 +00:00
procedure TFICSChessModule. FreeUserInterface;
2011-08-27 20:53:13 +00:00
begin
2011-09-06 11:03:12 +00:00
radioConnectionType. Free;
textPassword. Free;
editPassword. Free;
textSecondPlayerName. Free;
editSecondPlayerName. Free;
2011-08-27 20:53:13 +00:00
end ;
2011-08-27 21:16:40 +00:00
procedure TFICSChessModule. PrepareForGame;
2011-08-27 21:49:44 +00:00
var
2011-09-05 16:41:42 +00:00
lResult, WaitTerminated: Boolean ;
lMsg: string ;
2011-08-27 20:53:13 +00:00
begin
2011-09-06 11:12:55 +00:00
FICS_USER : = vChessGame. PlayerName;
2011-09-06 11:03:12 +00:00
FICS_PASSWORD : = editPassword. Text ;
2011-08-27 20:53:13 +00:00
// SecondPlayerName := editSecondPlayerName.Text;
2011-08-27 21:16:40 +00:00
ChessModuleDebugLn( '[TFICSChessModule.PrepareForGame]' ) ;
2011-08-27 21:49:44 +00:00
// Opening telnet connection. This is what happens when you issue telnet freechess.org 5000.
lResult : = TelnetComm. Connect( FICS_HOST, FICS_PORT) ;
if not lResult then
begin
ChessModuleDebugLn( 'Failed to connect to FICS' ) ;
Exit;
end ;
2011-09-05 16:41:42 +00:00
repeat
TelnetComm. CallAction; // repeat this to get info
2011-09-06 06:20:37 +00:00
Application. ProcessMessages;
Sleep( 1 0 ) ;
2011-09-05 16:41:42 +00:00
until TelnetComm. Connected; // wait until timeout or we actualy connected
2011-08-27 21:49:44 +00:00
ChessModuleDebugLn( 'Connected to FICS' ) ;
// If $FICS_PASSWORD is given, we peform normal full login (give username and password). FICS is standard enough to have Net::Telnet::login routine perform this process properly.
if FICS_PASSWORD < > '' then
begin
//$telnet->login(Name => $FICS_USER, Password => $FICS_PASSWORD);
// $username = $FICS_USER;
// print STDERR "Successfully logged as user $FICS_USER\n" if $VERBOSE;
end
2011-09-05 16:41:42 +00:00
// Now let's go to the guest login. Again, try logging to FICS via telnet as guest to understand what we are testing for here.
2011-08-27 21:49:44 +00:00
else
begin
2011-09-05 16:41:42 +00:00
{
$ telnet- > waitfor(
Match = > '/login[: ]*$/i' ,
Match = > '/username[: ]*$/i' ,
Timeout = > $ OPEN_TIMEOUT) ;
$ telnet- > print( $F ICS_USER) ;
}
2011-09-06 06:20:37 +00:00
TelnetComm. WaitFor(
'.*login:.*' ,
'.*username:.*' ,
OPEN_TIMEOUT) ;
// ... and we send our username once prompted.
//ChessModuleDebugLn('Found the login!!!');
2011-09-06 12:24:08 +00:00
ChessModuleDebugLn( 'Sending: ' + FICS_USER) ;
2011-09-06 06:20:37 +00:00
TelnetComm. SendMessage( FICS_USER + FICS_LineEnding) ;
// Now we read obtained lines scanning for some patterns.
TelnetComm. WaitFor(
'.*Press return to enter.*' ,
'' ,
OPEN_TIMEOUT) ;
TelnetComm. SendMessage( FICS_LineEnding) ;
( * if ( $ line = ~ / ( "[^" ] * " is a registered name | \ S+ is already logged in ) / ) {
die "Can not login as $FICS_USER: $1\n" ;
}
2011-09-05 16:41:42 +00:00
2011-09-06 06:20:37 +00:00
Bad luck, we picked the name used by somebody, it is not possible to login as guest with this nick.
* )
( *
After accepting guest login we may face two things.
First, FICS may accept our login and send us a message like
Starting FICS session as BotTutorial.
This means everything is OK and we can go on.
Alternatively, FICS may notice another guest using the same name ,
in such case it will tell us something like BotTutorial is already
logged in and will disconnect.
print STDERR "Successfully logged as guest $username\n" if $ VERBOSE;
my( $ pre, $ match) = $ telnet- > waitfor(
Match = > "/Starting FICS session as ([a-zA-Z0-9]+)/" ,
Match = > "/\\S+ is already logged in/" ,
Timeout = > $ OPEN_TIMEOUT) ;
if ( $ match = ~ / Starting FICS session as ( [ a- zA- Z0- 9 ] + ) / ) {
$ username = $1 ;
2011-08-27 21:49:44 +00:00
}
2011-09-06 06:20:37 +00:00
else {
die "Can not login as $FICS_USER: $match\n" ;
2011-08-27 21:49:44 +00:00
}
}
2011-09-06 06:20:37 +00:00
* )
TelnetComm. WaitFor(
'.*Starting FICS session as.*' ,
'' ,
OPEN_TIMEOUT) ;
end ;
2011-09-06 11:03:12 +00:00
// Remove those annoying message of people seeking chess adversaries
TelnetComm. WaitFor(
'.*fics%.*' ,
'' ,
OPEN_TIMEOUT) ;
2011-09-06 12:24:08 +00:00
ChessModuleDebugLn( 'Sending: set seek 0' ) ;
2011-09-06 11:03:12 +00:00
TelnetComm. SendMessage( 'set seek 0' + FICS_LineEnding) ;
// Set the style
TelnetComm. WaitFor(
'.*fics%.*' ,
'' ,
OPEN_TIMEOUT) ;
2011-09-06 12:24:08 +00:00
ChessModuleDebugLn( 'Sending: set style 11' ) ;
2011-09-06 11:03:12 +00:00
TelnetComm. SendMessage( 'set style 11' + FICS_LineEnding) ;
// Wait for a match
if radioConnectionType. ItemIndex = 0 then
begin
2011-09-06 12:24:08 +00:00
vChessGame. FirstPlayerIsWhite : = False ;
2011-09-06 11:03:12 +00:00
// Challenge: GuestZMYL (----) fpchess (----) unrated blitz 2 12.
// You can "accept" or "decline", or propose different parameters.
TelnetComm. WaitFor(
'.*You can "accept" or "decline", or propose different parameters*' ,
'' ,
OPEN_TIMEOUT) ;
2011-09-06 12:24:08 +00:00
ChessModuleDebugLn( 'Sending: accept' ) ;
TelnetComm. SendMessage( 'accept' + FICS_LineEnding) ;
2011-09-06 11:03:12 +00:00
// You accept the match offer from GuestZMYL.
TelnetComm. WaitFor(
'.*You accept the match offer from*' ,
'' ,
OPEN_TIMEOUT) ;
end
// Challenge a partner
else
begin
TelnetComm. WaitFor(
'.*fics%.*' ,
'' ,
OPEN_TIMEOUT) ;
2011-09-06 12:24:08 +00:00
lMsg : = 'match ' + editSecondPlayerName. Text + ' 60 White' ;
ChessModuleDebugLn( 'Sending: ' + lMsg) ;
TelnetComm. SendMessage( lMsg + FICS_LineEnding) ;
2011-09-06 11:03:12 +00:00
// fpchess accepts the match offer.
TelnetComm. WaitFor(
'.*accepts the match offer*' ,
'' ,
OPEN_TIMEOUT) ;
end ;
2011-08-27 20:53:13 +00:00
end ;
2011-08-27 21:16:40 +00:00
function TFICSChessModule. GetSecondPlayerName: string ;
2011-08-27 20:53:13 +00:00
begin
// Result := SecondPlayerName;
end ;
// If a move came, it is because the local player did a move
2011-09-06 11:03:12 +00:00
// so send this move
2011-08-27 21:16:40 +00:00
procedure TFICSChessModule. HandleOnMove( AFrom, ATo: TPoint) ;
2011-09-06 11:03:12 +00:00
var
lMsg: String ;
2011-08-27 20:53:13 +00:00
begin
2011-09-06 11:03:12 +00:00
lMsg : = Format( '%s-%s' , [ TChessGame. BoardPosToChessCoords( AFrom) , TChessGame. BoardPosToChessCoords( ATo) ] ) ;
2011-09-06 12:24:08 +00:00
ChessModuleDebugLn( 'Sending: ' + lMsg) ;
TelnetComm. SendMessage( lMsg + FICS_LineEnding) ;
// Wait until it shows our move
2011-09-06 11:03:12 +00:00
TelnetComm. WaitFor(
2011-09-06 12:24:08 +00:00
'.*[PRNBQK]/[abcdefgh][0123456789]-[abcdefgh][0123456789].*' ,
2011-09-06 11:03:12 +00:00
'' ,
OPEN_TIMEOUT) ;
end ;
2011-08-27 20:53:13 +00:00
2011-09-06 11:03:12 +00:00
// listen for moves
procedure TFICSChessModule. HandleOnTimer;
var
lIndex: Integer ;
lAnimation: TChessMoveAnimation;
lFrom, lTo: TPoint;
lMoveStr: String ;
begin
// Example output in style 11
// #@#086GuestZMYL :fpchess *RNBQKBNR PPP PP P PP p p ppp ppprnbqkbnr003B3939-1628-0163P/a2-a4(1:03)@#@
lIndex : = TelnetComm. WaitFor(
'.*[PRNBQK]/[abcdefgh][0123456789]-[abcdefgh][0123456789].*' ,
'' ,
0 ) ;
2011-09-06 12:24:08 +00:00
// if TelnetComm.LastMsg <> '' then
// lIndex := lIndex;
2011-09-06 11:03:12 +00:00
if lIndex = 0 then
begin
lMoveStr : = Copy( TelnetComm. LastMsg, Pos( '/' , TelnetComm. LastMsg) + 1 , 5 ) ;
TChessGame. ChessMoveCoordsToBoardPos( lMoveStr, lFrom, lTo) ;
lAnimation : = TChessMoveAnimation. Create;
lAnimation. AFrom : = lFrom;
lAnimation. ATo : = lTo;
vChessDrawer. AddAnimation( lAnimation) ;
end ;
2011-08-27 20:53:13 +00:00
end ;
2011-09-06 06:20:37 +00:00
procedure TFICSChessModule. HandleOnDebugOut( AStr: string ) ;
begin
ChessModuleDebugOut( AStr) ;
end ;
2011-08-27 20:53:13 +00:00
initialization
2011-08-27 21:16:40 +00:00
RegisterChessModule( TFICSChessModule. Create) ;
2011-08-27 20:53:13 +00:00
end .