
{**
  @abstract(Programme principal du moteur d'échecs.)
  Interface du moteur d'échecs.
}

program Alouette;

uses
{$IFDEF UNIX}
  CThreads,
{$ENDIF}
  Classes, SysUtils, Math,
  Chess, Move, Best2, Utils, Perft, Trees, Settings, History{$IFDEF DEBUG}, Log{$ENDIF};

const
  CAppName    = 'Alouette';
  CAppVersion = '0.1.8';
  CAppAuthor  = 'Roland Chastain';
  CBuild      = 'FPC ' + {$I %FPCVERSION%} + ' ' + {$I %DATE%} + ' ' + {$I %TIME%} + ' ' + {$I %FPCTARGETOS%} + '-' + {$I %FPCTARGETCPU%};

{$IFDEF DEBUG}
var
  LLog: TLog;
{$ENDIF}

procedure Send(const AMessage: string; const AFlush: boolean = TRUE);
{$IFDEF DEBUG}
var
  LList: TStringList;
  i: integer;
{$ENDIF}
begin
  WriteLn(output, AMessage);
  if AFlush then
    Flush(output);
{$IFDEF DEBUG}
  if Pos(LineEnding, AMessage) = 0 then
    LLog.LogLn(Concat('<- ', AMessage))
  else
  begin
    LList := TStringList.Create;
    LList.Text := AMessage;
    for i := 0 to Pred(LList.Count) do
      LLog.LogLn(Concat('<- ', LList[i]));
    LList.Free;
  end;
{$ENDIF}
end;

type
  {** Fil d'exécution pour la recherche du meilleur coup. }
  TSearchThread = class(TThread)
    protected
      procedure Execute; override;
  end;

var
  LPos: TPosition;
  LFrc: boolean;
  LMoveStr: string;
  LTimeAv: cardinal;
  LRandMove: boolean;
  
procedure TSearchThread.Execute;
var
  LTime: TTime;
begin
  LTime := Now;
  LMoveStr := GetBestMove(LPos, LFrc, LTimeAv, LMoveStr, LRandMove);
  LTime := Now - LTime;
  Send(Format('bestmove %s', [LMoveStr]));
{$IFDEF DEBUG}
  LLog.LogLn('** Time used ' + TimeToStr(LTime));
{$ENDIF}
end;

const
  CBoolStr: array[boolean] of string = ('false', 'true');
  
var
  LCmd: string;
  LIdx: integer;
  LMTime, LWTime, LBTime, LMTG, LWInc, LBInc: integer;
  LThread: TSearchThread;
  LDepth: integer;
  LFen: string;
  LBookLine, LBookMove: string;
  LBook: array[boolean] of TTreeList;
  LBookName: array[boolean] of TFileName;
  LColor: boolean;
  LUseBook, LReadBook: boolean;
  
begin
  Randomize;
  LoadSettings(LUseBook, LFactorPiecePrior, LFactorPawnStruct, LFactorTargets, LFactorCastling, LFactorEnPassant, LFactorCheck, LFactorProtect, LFactorAttack, LFactorPawnMobil, LFactorNewPos);
{$IFDEF DEBUG}
  LLog := TLog.Create(ParamStr(0), TRUE, TRUE);
  LLog.LogLn(CAppName + ' ' + CAppVersion + ' ' + CBuild);
{$ENDIF}
  LFrc := FALSE;
  LRandMove := (ParamCount = 1) and ((ParamStr(1) = '-r') or (ParamStr(1) = '--random'));
  LBookName[FALSE] := Concat(ExtractFilePath(ParamStr(0)), 'white.txt');
  LBookName[TRUE]  := Concat(ExtractFilePath(ParamStr(0)), 'black.txt');
  for LColor := FALSE to TRUE do
  begin
    LBook[LColor] := TTreeList.Create;
    if FileExists(LBookName[LColor]) then
      LBook[LColor].LoadFromFileCompact(LBookName[LColor])
{$IFDEF DEBUG}
    else
      LLog.LogLn(Format('** File not found: %s', [LBookName[LColor]]))
{$ENDIF};
  end;
  LBookLine := '';
  LReadBook := FALSE;
  
  while not Eof do
  begin
    ReadLn(input, LCmd);
{$IFDEF DEBUG}
    LLog.LogLn(Concat('-> ', LCmd));
{$ENDIF}
    if LCmd = 'quit' then
      Break
    else
      if LCmd = 'uci' then
      begin
        Send(Format('id name %s %s', [CAppName, CAppVersion]), FALSE);
        Send(Format('id author %s', [CAppAuthor]), FALSE);
        Send(Format('option name UCI_Chess960 type check default %s', [CBoolStr[LFrc]]), FALSE);
        Send('uciok');
      end else
        if LCmd = 'isready' then
        begin
          Send('readyok');
        end else
          if LCmd = 'ucinewgame' then
            LPos := CNewPos
          else
            if BeginsWith('position ', LCmd) then
            begin
              ClearHistory;
              if WordPresent('startpos', LCmd) then
              begin
                LPos := EncodePosition;
                AppendToHistory(LPos);
                LReadBook := LUseBook and not LRandMove;
              end else
                if WordPresent('fen', LCmd) then
                begin
                  LFen := GetFen(LCmd);
                  LPos := EncodePosition(LFen, LFrc);
                  AppendToHistory(LPos);
                  LReadBook := LUseBook and IsUsualStartPos(LFen) and not LRandMove;  
                end
{$IFDEF DEBUG}
                else
                  LLog.LogLn(Format('** Unknown command: %s', [LCmd]))
{$ENDIF};
              
              LBookLine := '';
              if WordPresent('moves', LCmd) then
                for LIdx := 4 to WordsNumber(LCmd) do
                begin
                  LMoveStr := GetWord(LIdx, LCmd);
                  if IsChessMove(LMoveStr) then
                  begin
                    if Move.DoMove(LPos, LMoveStr) then
                      AppendToHistory(LPos)
{$IFDEF DEBUG}
                    else
                      LLog.LogLn(Format('** Impossible move: %s', [LMoveStr]))
{$ENDIF};
                    LBookLine := Concat(LBookLine, ' ', LMoveStr);
                  end;
                end;
            end else
              if BeginsWith('go', LCmd) then
              begin
                if IsGoCmd(LCmd, LWTime, LBTime, LWInc, LBinc) then // go wtime 60000 btime 60000 winc 1000 binc 1000
                  LTimeAv := IfThen(LPos.Side, LBTime div 100 + LBinc, LWTime div 100 + LWInc)
                else
                  if IsGoCmd(LCmd, LWTime, LBTime, LMTG) then       // go wtime 59559 btime 56064 movestogo 38
                    LTimeAv := IfThen(LPos.Side, LBTime div LMTG, LWTime div LMTG)
                  else
                    if IsGoCmd(LCmd, LWTime, LBTime) then           // go wtime 600000 btime 600000
                      LTimeAv := IfThen(LPos.Side, LBTime div 100, LWTime div 100)
                    else
                      if IsGoCmd(LCmd, LMTime) then                 // go movetime 500
                        LTimeAv := LMTime
                      else
                      begin
                        LTimeAv := 1000;
{$IFDEF DEBUG}
                        LLog.LogLn(Format('** Unknown command: %s', [LCmd]));
{$ENDIF}
                      end;

                if LReadBook then
                begin
                  LBookMove := LBook[LPos.Side].FindMoveToPlay(Trim(LBookLine), TRUE);
                  if LBookMove <> EmptyStr then
                  begin
{$IFDEF DEBUG}
                    LLog.LogLn(Format('** Book %s', [LBookMove]));
{$ENDIF}
                    Send(Format('bestmove %s', [LBookMove]));
                    Continue;
                  end;
                end;

                LThread := TSearchThread.Create(TRUE);
                with LThread do
                begin
                  FreeOnTerminate := TRUE;
                  Priority := tpNormal;
                  Start;
                end;
              end else
                if LCmd = 'stop' then
                begin
                  (*
                  if Assigned(LThread) then
                    LThread.Terminate;
                  *)
                  Send(Format('bestmove %s', [LMoveStr]));
                end else
                  if BeginsWith('setoption name UCI_Chess960 value ', LCmd) then
                    LFrc := WordPresent('true', LCmd)
                  else
                    if LCmd = 'board' then
                      Send(PosToText(LPos))
                    else
                      if LCmd = 'moves' then
                        DisplayLegalMoves(LPos)
                      else
                        if IsPerftCmd(LCmd, LDepth) then
                          Start(LPos, LDepth)
                        else
                          if LCmd = 'help' then
                            Send(
                              'UCI commands:' + LineEnding +
                              '  go movetime <x>' + LineEnding +
                              '  go wtime <x> btime <x>' + LineEnding +
                              '  go wtime <x> btime <x> movestogo <x>' + LineEnding +
                              '  go wtime <x> btime <x> winc <x> binc <x>' + LineEnding +
                              '  isready' + LineEnding +
                              '  position fen <fen> [moves ...]' + LineEnding +
                              '  position startpos [moves ...]' + LineEnding +
                              '  quit' + LineEnding +
                              '  setoption name UCI_Chess960 value <true,false>' + LineEnding +
                              '  stop' + LineEnding +
                              '  uci' + LineEnding +
                              '  ucinewgame' + LineEnding +
                              'Custom commands:' + LineEnding +
                              '  board (display the current board)' + LineEnding +
                              '  help' + LineEnding +
                              '  moves (display legal moves for the current position)' + LineEnding +
                              '  perft <x>'
                            )
{$IFDEF DEBUG}
                          else
                            LLog.LogLn(Format('** Unknown command: %s', [LCmd]))
{$ENDIF};
  end;
  if not SettingsFileExists then
    SaveSettings(LUseBook, LFactorPiecePrior, LFactorPawnStruct, LFactorTargets, LFactorCastling, LFactorEnPassant, LFactorCheck, LFactorProtect, LFactorAttack, LFactorPawnMobil, LFactorNewPos);
  LBook[FALSE].Free;
  LBook[TRUE].Free;
{$IFDEF DEBUG}
  LLog.Free;
{$ENDIF}
end.
