{****************************************************************************}
{*  PLAY.PAS: This file contains the computer thinking routines, the        *}
{*  human player move input routine, and the play game routines.            *}
{****************************************************************************}

{****************************************************************************}
{*  Get Computer Move:  Given whose turn, return the "best" move for that   *}
{*  player.  Also given is whether to display the best move found so far    *}
{*  information, and returned is a flag telling if the user typed escape    *}
{*  to terminate this routine.  A recursive depth-first tree search         *}
{*  algorithm is used, and enhancements like cutting off unnecessary        *}
{*  subtrees, pre-scanning to select the best candidate moves, and a        *}
{*  positional evaluation are also included.  The actual algorithm is very  *}
{*  simple, but it is burried in all kinds of special cases.                *}
{****************************************************************************}
  procedure GetComputerMove (Turn : PieceColorType; Display : boolean;
                             var HiMovement : MoveType; var Escape : boolean);
    var MoveList : MoveListType;
        i, MaxDepth, NegInfinity, L1CutOff: integer;
        HiScore, SubHiScore, InitialScore, SubEnemyMaxScore : integer;
        Movement : MoveType;
        Attacked, _Protected : integer;
        PosStrength, HiPosStrength : integer;
        cstr : string10;
        key : char;
        PosEvalOn : boolean;

{----------------------------------------------------------------------------}
{  Search:  This routine handles all of the tree searching except the first  }
{  level which of the tree which is handled by the main routine.  Given the  }
{  player, all of his moves are generated, and then each one is made.  The   }
{  enemy's maximum countermove score is subtracted from the move score, and  }
{  this gives the net value for the player making the move.  The maximum     }
{  net score is remembered and returned by this function.  The player's move }
{  is then taken back, and all of his other possible moves are tried in this }
{  same way.  If the score of any move exceeds the given cutoff value, then  }
{  no other of the player's moves are checked, and the score that exceeded   }
{  (or matched) the cutoff value is returned.  If the given depth is one or  }
{  less, then the enemy's countermoves are not checked, only the points for  }
{  taking the pieces, minus the points of the player's piece if the enemy's  }
{  piece is _Protected.  However, if the current player's move being          }
{  considered is a take and it is given that all of moves to that point      }
{  have been AllTakes, then enemy countermoves will be checked down to a     }
{  given depth of -1.  To calculate the enemy's best score in retaliation    }
{  to the given player's move, this routine is called recursively with the   }
{  enemy as the player to move, and a depth of the one originally given, -1. }
{  The new cutoff value is the score of the move that was just made, minus   }
{  the the best score that was found so far.  If the enemy's countermoves    }
{  score exceeds or matches this countermove value, then the net score of    }
{  the original player's move cannot exceed the best found so far, and the   }
{  move will be thrown out.  The new value for AllTakes is passed on as true }
{  if all moves heretofor have been takes, and the current player's move is  }
{  a take.  This routine is the core of the computer's 'thinking'.           }
{----------------------------------------------------------------------------}
  function Search (Turn: PieceColorType; CutOff, Depth: integer; AllTakes : boolean) : integer;
    var MoveList: MoveListType;
        j, LineScore, Score, BestScore, STCutOff: integer;
        Movement: MoveType;
        Attacked, _Protected: integer;
        NoMoves, TakingPiece: boolean;
    begin
      {$ifdef KCCHESS_VERBOSE}
      //WriteLn('[Play.pas.GetComputerMove.Search]');
      {$endif}

      {*** get the player's move list ***}
      GenMoveList (Turn, MoveList);
      NoMoves := true;
      BestScore := NegInfinity;
      j := MoveList.NumMoves;

      {*** go through all of the possible moves ***}
      while (j > 0) do begin
          Movement := MoveList.Move[j];
          {*** make the move ***}
          MakeMove (Movement, false, Score);
          {*** make sure it is legal (not moving into check) ***}
          AttackedBy (Player[Turn].KingRow, Player[Turn].KingCol, Attacked, _Protected);
          if (Attacked = 0) then begin
              NoMoves := false;
              if (Score = STALE_SCORE) then
                  {*** end the search on a stalemate ***}
                  LineScore := Score
              else begin
                  TakingPiece := Movement.PieceTaken.image <> BLANK;
                  if (Depth <= 1) and not (AllTakes and TakingPiece and (Depth >= PLUNGE_DEPTH)) then begin
                      {*** have reached horizon node of tree: score points for piece taken ***}
                      {*** but assume own piece will be taken if enemy's piece is _Protected ***}
                      if Movement.PieceTaken.image <> BLANK then begin
                          AttackedBy (Movement.ToRow, Movement.ToCol, Attacked, _Protected);
                          if Attacked > 0 then
                              LineScore := Score - CapturePoints[Movement.PieceMoved.image]
                          else
                              LineScore := Score;
                      end else
                          LineScore := Score;
                  end else begin
                      {*** new cutoff value ***}
                      STCutOff := Score - BestScore;
                      {*** recursive call for enemy's best countermoves score ***}
                      LineScore := Score - Search (EnemyColor[Turn], STCutOff,
                                                   Depth - 1, AllTakes and TakingPiece);
                  end;
              end;
              {*** remember player's maximum net score ***}
              if (LineScore > BestScore) then BestScore := LineScore;
          end;
          {*** un-do the move and check for cutoff ***}
          UnMakeMove (Movement);
          if BestScore >= CutOff then j := 0 else j := j - 1;
      end;
      if (BestScore = STALE_SCORE) then
          BestScore := -STALE_SCORE;   {stalemate means both players lose}
      if NoMoves then
          {*** player cannot move ***}
          if Player[Turn].InCheck then
              {*** if he is in check and cannot move, he loses ***}
              BestScore := - CapturePoints[KING]
          else
              {*** if he is not in check, then both players lose ***}
              BestScore := -STALE_SCORE; {prefer stalemate to checkmate}
      Search := BestScore;
    end; {Search}

{----------------------------------------------------------------------------}
{  Pre Search:  Returns the given move list of the given player, sorted into }
{  the order of ascending score for the given depth to look ahead.  The      }
{  main computer move routine calls this routine to sort the move list such  }
{  that it will probably find a good move early in a greater depth search.   }
{----------------------------------------------------------------------------}
    procedure PreSearch (Turn : PieceColorType; Depth : integer; var MoveList : MoveListType);
      var i, j, Attacked, _Protected : integer;
          Score : integer;
          Movement : MoveType;
          TempScore : integer;
          Temp : string80;
          PreScanScore : array [1..MOVE_LIST_LEN] of integer;
          BestScore : integer;
      begin

        {*** display message ***}
        {$ifdef FPCHESS_DISPLAY_ON}
        if Display then begin
            DisplayClearLines (MSG_HINT, 21);
            SetTextStyle (TriplexFont, HorizDir, 1);
            SetColor (LightGreen);
            CenterText (MSG_HINT, 'Pre Scanning...');
        end;
        {$endif}
        BestScore := NegInfinity;

        {*** scan each move in same order as main routine ***}
        for i := MoveList.NumMoves downto 1 do begin
            {*** get points for move as in Search routine ***}
            Movement := MoveList.Move[i];
            MakeMove (Movement, false, Score);
            AttackedBy (Player[Turn].KingRow, Player[Turn].KingCol, Attacked, _Protected);
            if (Attacked = 0) then begin
                Score := Score - Search (EnemyColor[Turn], Score - BestScore, Depth - 1, false);
                {*** remember the score of the move ***}
                PreScanScore[i] := Score;
            end else
                {*** invalid moves get lowest score ***}
                PreScanScore[i] := NegInfinity;
            UnMakeMove (Movement);
            {*** remember best score for purpose of making cutoffs ***}
            if (Score > BestScore) then BestScore := Score;
        end;

        {*** sort the movelist by score: O(n^2) selection sort used ***}
        for i := 1 to MoveList.NumMoves do
            for j := i + 1 to MoveList.NumMoves do
                if PreScanScore[i] > PreScanScore[j] then begin
                    Movement := MoveList.Move[i];
                    MoveList.Move[i] := MoveList.Move[j];
                    MoveList.Move[j] := Movement;
                    TempScore := PreScanScore[i];
                    PreScanScore[i] := PreScanScore[j];
                    PreScanScore[j] := TempScore;
                end;
      end; {PreSearch}

{----------------------------------------------------------------------------}
{  Eval Pos Strength:  Use a number of ad-hoc rules to evaluate the          }
{  positional content (rather than material content) of the given move,      }
{  considering the current board configuration.  Generally important         }
{  considerations are friendly piece protection and enemy piece threatening, }
{  as well as number of possible future moves allowed for the player.        }
{----------------------------------------------------------------------------}
      function EvalPosStrength (Turn : PieceColorType; Movement : MoveType) : integer;
        var PosMoveList : MoveListType;
            PosStrength : integer;
            row, col, KingRow, KingCol : RowColType;
            Attacked, _Protected : integer;
            CumOwnAttacked, CumOwn_Protected : integer;
            CumEnemyAttacked, CumEnemy_Protected : integer;
            PawnDir, KingProtection, KingFront : integer;
            CheckCol : RowColType;
            CastlePossible, IsDevMove : boolean;
            NoDevCount : integer;

        begin
          {*** points for putting enemy in check ***}
          NoDevCount := Game.NonDevMoveCount[Game.MovesPointer];
          if Player[EnemyColor[Turn]].InCheck then begin
              if NoDevCount < 9 then
                  PosStrength := 40
              else begin
                  if NoDevCount < 12 then
                      PosStrength := 4
                  else
                      PosStrength := 0;
              end;
          end else
              PosStrength := 0;

          {*** points for pieces in front of king if he is (probably) in castled position ***}
          KingProtection := 0;
          KingRow := Player[Turn].KingRow;
          KingCol := Player[Turn].KingCol;
          if ((KingRow = 1) or (KingRow = 8)) and (KingCol <> 5) then begin
              if KingRow = 1 then KingFront := 2 else KingFront := 7;
              for CheckCol := KingCol - 1 to KingCol + 1 do
                  with Board[KingFront, CheckCol] do begin
                      if ValidSquare and (image <> BLANK) and (color = Turn) then
                          KingProtection := KingProtection + 1;
                  end;
          end;
          PosStrength := PosStrength + KingProtection * 3;

          {*** determine if castling is still possible ***}
          with Board[KingRow, 1] do
              CastlePossible := (image = ROOK) and (not HasMoved);
          with Board[KingRow, 8] do
              CastlePossible := CastlePossible or ((image = ROOK) and (not HasMoved));
          CastlePossible := CastlePossible and (not Board[KingRow, KingCol].HasMoved);

          {*** points for castling or not moving king/rook if castling still possible ***}
          if Movement.PieceMoved.image = KING then begin
              if (abs(Movement.FromCol - Movement.ToCol) > 1)
                                        and (KingProtection >= 2) then
                  PosStrength := PosStrength + 140
              else
                  if CastlePossible then PosStrength := PosStrength - 80;
          end;

          {*** points for pushing a pawn; avoids pushing potential castling protection ***}
          IsDevMove := false;
          if Movement.PieceMoved.image = PAWN then begin
              if ((Movement.FromCol <= 3) or (Movement.FromCol >= 6))
                       and ((Movement.FromRow = 1) or (Movement.FromRow = 8))
                       and CastlePossible then
                  PosStrength := PosStrength - 12
              else
                  PosStrength := PosStrength + 1;
              IsDevMove := true;
          end;

          {*** points for developmental move if one has not happened in a while ***}
          IsDevMove := IsDevMove or (Movement.PieceTaken.image <> BLANK);
          if IsDevMove then begin
              if NoDevCount >= 9 then
                  PosStrength := PosStrength + NoDevCount;
          end;

          {*** points for number of positions that can be moved to ***}
          GenMoveList (Turn, PosMoveList);
          PosStrength := PosStrength + PosMoveList.NumMoves;

          {*** points for pieces attacked / _Protected ***}
          CumOwnAttacked := 0;
          CumOwn_Protected := 0;
          CumEnemyAttacked := 0;
          CumEnemy_Protected := 0;
          for row := 1 to BOARD_SIZE do
              for col := 1 to BOARD_SIZE do
                  if (Board[row, col].image <> BLANK) then begin
                      AttackedBy (row, col, Attacked, _Protected);
                      if (Board[row, col].color = Turn) then begin
                          CumOwnAttacked := CumOwnAttacked + Attacked;
                          CumOwn_Protected := CumOwn_Protected + _Protected;
                      end else begin
                          CumEnemyAttacked := CumEnemyAttacked + Attacked;
                          CumEnemy_Protected := CumEnemy_Protected + _Protected;
                      end;
                  end;
          PosStrength := PosStrength + 2 * CumOwn_Protected
                         - 2 * CumOwnAttacked + 2 * CumEnemyAttacked;
          EvalPosStrength := PosStrength;
      end; {EvalPosStrength}

{----------------------------------------------------------------------------}
    begin {GetComputerMove}
      {$ifdef KCCHESS_VERBOSE}
      WriteLn('[Play.pas.GetComputerMove]');
      {$endif}
      {*** initialize ***}
      PosEvalOn := Player[Turn].PosEval;
      MaxDepth := Player[Turn].LookAhead;
      NegInfinity := - CapturePoints[KING] * 5;
      Escape := false;
      HiScore := NegInfinity;
      HiPosStrength := -maxint;
      HiMovement.FromRow := NULL_MOVE;

      {*** get the move list and scramble it (to randomly choose between ties) ***}
      GenMoveList (Turn, MoveList);
      RandomizeMoveList (MoveList);
      {$ifdef FPCHESS_DISPLAY_ON}
      key := GetKey;  {*** check for user pressing ESCape ***}
      if key <> 'x' then begin
      {$endif}
          {*** perform pre-scan of two or three-ply if feasible ***}
          if MaxDepth >= 3 then begin
              if MaxDepth = 3 then
                  PreSearch (Turn, 2, MoveList)
              else
                  PreSearch (Turn, 3, MoveList);
          end;
          i := MoveList.NumMoves;
          {$ifdef FPCHESS_DISPLAY_ON}
          key := GetKey;
      end;
      {*** check for user pressing ESCape after pre-scan ***}
      if key = 'x' then begin
          Escape := true;
          i := 0;
      end;
      {$endif}

      {$ifdef KCCHESS_VERBOSE}
      WriteLn(Format('[Play.pas.GetComputerMove] MaxDepth=%d Loop i=%d', [MaxDepth, i]));
      {$endif}
      {*** check each possible move - same method as in Search ***}
      while (i > 0) do
      begin
          {$ifdef KCCHESS_VERBOSE}
          WriteLn(Format('[Play.pas.GetComputerMove] Loop i=%d MoveList.NumMoves=%d', [i, MoveList.NumMoves]));
          {$endif}
          {$ifdef FPCHESS_DISPLAY_ON}
          UpDateTime (Turn);  {*** player's elapsed time ***}
          {$endif}
          Movement := MoveList.Move[i];
          MakeMove (Movement, false, InitialScore);
          AttackedBy (Player[Turn].KingRow, Player[Turn].KingCol, Attacked, _Protected);
          if (Attacked = 0) then begin
              if (InitialScore = STALE_SCORE) then
                  SubHiScore := STALE_SCORE
              else begin
                  {*** display scan count-down ***}
                  {$ifdef FPCHESS_DISPLAY_ON}
                  if Display and (MaxDepth >= 3) then begin
                      DisplayClearLines (MSG_HINT, 21);
                      SetTextStyle (TriplexFont, HorizDir, 1);
                      SetColor (LightGreen);
                      Str (i, cstr);
                      CenterText (MSG_HINT, 'Scan=' + cstr);
                  end;
                  {$endif}

                  {*** calculate one-ply score ***}
                  if (MaxDepth <= 1) then begin
                      if Movement.PieceTaken.image <> BLANK then begin
                          AttackedBy (Movement.ToRow, Movement.ToCol, Attacked, _Protected);
                          if Attacked > 0 then
                              SubEnemyMaxScore := CapturePoints[Movement.PieceMoved.image]
                          else
                              SubEnemyMaxScore := 0;
                      end else
                          SubEnemyMaxScore := 0;
                  end else begin
                      {*** get net score ***}
                      if PosEvalOn then
                          {*** position evaluation needs to check all scores tying for best ***}
                          L1CutOff := InitialScore - HiScore + 1
                      else
                          L1CutOff := InitialScore - HiScore;
                      SubEnemyMaxScore := Search (EnemyColor[Turn], L1CutOff, MaxDepth - 1,
                                                  Movement.PieceTaken.image <> BLANK);
                  end;
                  {*** subtree score ***}
                  SubHiScore := InitialScore - SubEnemyMaxScore;
              end;

              {*** check if new score is highest ***}
              if (SubHiScore > HiScore) or (PosEvalOn and (SubHiScore = HiScore)) then begin
                  if PosEvalOn then
                      PosStrength := EvalPosStrength (Turn, Movement)
                  else
                      PosStrength := 0;
                  if (SubHiScore > HiScore) or (PosStrength > HiPosStrength) then begin
                      {*** remember new high score ***}
                      HiMovement := Movement;
                      HiScore := SubHiScore;
                      HiPosStrength := PosStrength;
                      {*** display new best movement ***}
                      {$ifdef FPCHESS_DISPLAY_ON}
                      if Display then begin
                          DisplayClearLines (MSG_MOVE, 15);
                          SetTextStyle (DefaultFont, HorizDir, 2);
                          SetColor (White);
                          CenterText (MSG_MOVE, MoveStr (HiMovement));
                          DisplayClearLines (MSG_SCORE, MSG_POS_EVAL+8-MSG_SCORE);
                          SetTextStyle (DefaultFont, HorizDir, 1);
                          SetColor (LightCyan);
                          Str (HiScore, cstr);
                          CenterText (MSG_SCORE, 'Score=' + cstr);
                          Str (SubEnemyMaxScore, cstr);
                          CenterText (MSG_ENEMY_SCORE, 'EnemyScore=' + cstr);
                          Str (HiPosStrength, cstr);
                          CenterText (MSG_POS_EVAL, 'Pos=' + cstr);
                      end;
                      {$endif}
                      {*** for zero-ply lookahead, take first move looked at ***}
                      if MaxDepth = 0 then i := 1;
                  end;
              end;
          end;
          UnMakeMove (Movement);
          i := i - 1;

          {*** check for escape or forced move by user ***}
          {$ifdef FPCHESS_DISPLAY_ON}
          key := GetKey;
          if key = 'x' then begin
              Escape := true;
              i := 0;
          end else
              if (key = 'M') and (HiScore <> NegInfinity) then i := 0;
          {$endif}
      end;
      {*** beep when done thinking ***}
      {$ifdef FPCHESS_DISPLAY_ON}
      MakeSound (true);
      {$endif}
    end; {GetComputerMove}

{****************************************************************************}
{*  Get Human Move:  Returns the movement as input by the user.  Invalid    *}
{*  moves are screened by this routine.  The user moves the cursor to the   *}
{*  piece to pick up and presses RETURN, and then moves the cursor to the   *}
{*  location to which the piece is to be moved and presses RETURN.          *}
{*  Pressing ESCape will exit this routine and return a flag indicating     *}
{*  escape; pressing H will make the computer suggest a move (hint); and    *}
{*  pressing A will report the attack/protect count of the cursor square.   *}
{*  BACKSPACE will delete the from-square and allow the user to select a    *}
{*  different piece.                                                        *}
{****************************************************************************}
{$ifdef FPCHESS_DISPLAY_ON}
  procedure GetHumanMove (Turn : PieceColorType; var Movement : MoveType;
                          var Escape : boolean);
    const MSG1X = 510;
    var key : char;
        HumanMoveList : MoveListType;
        ValidMove, BadFromSq, PickingUp : boolean;
        i : integer;

{----------------------------------------------------------------------------}
{  Move Cursor With Hint:  Moves the cursor around until the player presses  }
{  RETURN or SPACE.  Also handles keys A (Attack/protect) and H (Hint).      }
{----------------------------------------------------------------------------}
    procedure MoveCursorWithHint;
      var HintMove : MoveType;
          Att, Pro, i : integer;
          cstr : string10;
          bstr : string80;

      begin
        repeat
            {*** position cursor ***}
            MoveCursor(Player[Turn].CursorRow, Player[Turn].CursorCol, Turn, true, key);
            if key = ' ' then key := 'e';

            {*** check your move list ***}
            if key = 'C' then begin
                RestoreCrtMode;
                writeln ('Your list of possible moves:');
                writeln;
                for i := 1 to HumanMoveList.NumMoves do
                    write (i:2, '.', copy(MoveStr(HumanMoveList.Move[i]) + '       ',1,7));
                writeln;
                writeln;
                write ('Press any key to continue...');
                key := ReadKey;
                SetGraphMode (GraphMode);
                DisplayGameScreen;
                if not PickingUp then begin
                    SetTextStyle (DefaultFont, HorizDir, 2);
                    SetColor (White);
                    OutTextXY (MSG1X, MSG_MOVE, SqStr(Movement.FromRow, Movement.FromCol) + '-');
                end;
                DisplayInstructions (INS_GAME);
            end;

            {*** attack / protect count ***}
            if key = 'A' then begin
                DisplayClearLines (MSG_HINT, 17);
                SetTextStyle (TriplexFont, HorizDir, 1);
                SetColor (LightRed);
                with Player[Turn] do begin
                    if Board[CursorRow, CursorCol].image = BLANK then
                        Board[CursorRow, CursorCol].color := Turn;
                    AttackedBy (CursorRow, CursorCol, Att, Pro);
                end;
                Str (Att, cstr);
                bstr := 'Attk=' + cstr;
                Str (Pro, cstr);
                bstr := bstr + ' Prot=' + cstr;
                CenterText (MSG_HINT, bstr);
            end;

            {*** ask computer for hint ***}
            if key = 'H' then begin
                DisplayClearLines (MSG_HINT, 17);
                SetTextStyle (TriplexFont, HorizDir, 1);
                SetColor (LightRed);
                CenterText (MSG_HINT, 'Thinking...');
                GetComputerMove (Turn, false, HintMove, Escape);
                if not Escape then begin
                    SetTextStyle (TriplexFont, HorizDir, 1);
                    SetColor (LightRed);
                    DisplayClearLines (MSG_HINT, 21);
                    CenterText (MSG_HINT, 'Hint: ' + MoveStr (HintMove));
                end;
            end else begin
                Escape := key = 'x';
            end;
        until (key = 'e') or (key = 'b') or Escape;
      end; {MoveCursorWithHint}

{----------------------------------------------------------------------------}
{  Sq Move:  Returns whether the given two moves are equal on the basis of   }
{  the from/to squares.                                                      }
{----------------------------------------------------------------------------}
    function EqMove (M1, M2 : MoveType) : boolean;
      begin
        EqMove := (M1.FromRow = M2.FromRow) and (M1.FromCol = M2.FromCol)
                  and (M1.ToRow = M2.ToRow) and (M1.ToCol = M2.ToCol);
      end; {EqMove}

{----------------------------------------------------------------------------}
    begin {GetHumanMove}
      Escape := false;

      {*** make sure the human has a move to make ***}
      GenMoveList (Turn, HumanMoveList);
      TrimChecks (Turn, HumanMoveList);
      if HumanMoveList.NumMoves = 0 then
          Movement.FromRow := NULL_MOVE
      else begin
          repeat
              repeat
                  {*** get the from-square ***}
                  DisplayClearLines (MSG_MOVE, 15);
                  PickingUp := true;
                  MoveCursorWithHint;
                  DisplayClearLines (MSG_HINT, 21);
                  if not Escape then begin
                      {*** make sure there is a piece of player's color on from-square ***}
                      with Player[Turn] do
                          BadFromSq := (Board[CursorRow, CursorCol].image = BLANK)
                                       or (Board[CursorRow, CursorCol].color <> Turn);
                      if (BadFromSq) then
                          MakeSound (false);
                  end;
                  if (not Escape) and (key <> 'b') and (not BadFromSq) then begin
                      {*** if all is well, display the from square ***}
                      Movement.FromRow := Player[Turn].CursorRow;
                      Movement.FromCol := Player[Turn].CursorCol;
                      SetTextStyle (DefaultFont, HorizDir, 2);
                      SetColor (White);
                      OutTextXY (MSG1X, MSG_MOVE, SqStr(Movement.FromRow, Movement.FromCol) + '-');
                      {*** get the to-square ***}
                      PickingUp := false;
                      MoveCursorWithHint;
                  end;
                  {*** if user typed Backspace, go back to getting the from-square ***}
              until ((key = 'e') and (not BadFromSq)) or Escape;
              ValidMove := false;
              if not Escape then begin
                  {*** store rest of move attributes ***}
                  Movement.ToRow := Player[Turn].CursorRow;
                  Movement.ToCol := Player[Turn].CursorCol;
                  Movement.PieceMoved := Board[Movement.FromRow, Movement.FromCol];
                  Movement.MovedImage := Board[Movement.FromRow, Movement.FromCol].image;
                  Movement.PieceTaken := Board[Movement.ToRow, Movement.ToCol];
                  {*** display the move ***}
                  DisplayClearLines (MSG_MOVE, 15);
                  SetTextStyle (DefaultFont, HorizDir, 2);
                  SetColor (White);
                  CenterText (MSG_MOVE, MoveStr (Movement));

                  {*** search for the move in the move list ***}
                  ValidMove := false;
                  for i := 1 to HumanMoveList.NumMoves do
                      if EqMove(HumanMoveList.Move[i], Movement) then ValidMove := true;
                  DisplayClearLines (MSG_HINT, 17);
                  {*** if not found then move is not valid: give message ***}
                  if not ValidMove then begin
                      SetTextStyle (TriplexFont, HorizDir, 1);
                      SetColor (LightRed);
                      CenterText (MSG_HINT, 'Invalid Move');
                      MakeSound (false);
                  end;
              end;
              {*** keep trying until the user gets it right ***}
          until ValidMove or Escape;
      end;
    end; {GetHumanMove}
{$endif}

{****************************************************************************}
{*  Get Player Move:  Updates the display, starts the timer, figures out    *}
{*  whose turn it is, calls either GetHumanMove or GetComputerMove, stops   *}
{*  the timer, and returns the move selected by the player.                 *}
{****************************************************************************}
{$ifdef FPCHESS_DISPLAY_ON}
  procedure GetPlayerMove (var Movement : MoveType; var Escape : boolean);
    var Turn : PieceColorType;
        Dummy: longint;
    begin
      DisplayWhoseMove;
      {*** start timer ***}
      Dummy := ElapsedTime;
      {*** which color is to move ***}
      if Game.MoveNum mod 2 = 1 then
          Turn := C_WHITE
      else
          Turn := C_BLACK;
      {*** human or computer ***}
      if Player[Turn].IsHuman then
          GetHumanMove (Turn, Movement, Escape)
      else
          GetComputerMove (Turn, true, Movement, Escape);
      {*** stop timer ***}
      UpDateTime (Turn);
    end; {GetPlayerMove}
{$endif}

{****************************************************************************}
{*  Play Game:  Call for the move of the current player, make it, and go on *}
{*  to the next move and the next player.  Continue until the game is over  *}
{*  (for whatever reason) or the user wishes to escape back to the main     *}
{*  menu.  When making the move, this routine checks if it is a pawn        *}
{*  promotion of a human player.  This routine is called from the main menu.*}
{****************************************************************************}
{$ifdef FPCHESS_DISPLAY_ON}
  procedure PlayGame;
    var Movement : MoveType;
        DummyScore : integer;
        NoMoves, Escape : boolean;
        TimeOutWhite, TimeOutBlack, Stalemate, NoStorage : boolean;

{----------------------------------------------------------------------------}
{  Check Finish Status:  Updates the global variables which tell if the      }
{  game is over and for what reason.  This routine checks for a player       }
{  exceeding the set time limit, the 50-move stalemate rule occuring, or     }
{  the game being too long and there being not enough room to store it.      }
{----------------------------------------------------------------------------}
  procedure CheckFinishStatus;
    begin
      Game.TimeOutWhite := (Game.TimeLimit > 0) and (Player[C_WHITE].ElapsedTime >= Game.TimeLimit);
      Game.TimeOutBlack := (Game.TimeLimit > 0) and (Player[C_BLACK].ElapsedTime >= Game.TimeLimit);
      Game.Stalemate := Game.NonDevMoveCount[Game.MovesPointer] >= NON_DEV_MOVE_LIMIT;
      Game.NoStorage := Game.MovesStored >= GAME_MOVE_LEN - MAX_LOOKAHEAD + PLUNGE_DEPTH - 2;
    end; {CheckFinishStatus}

{----------------------------------------------------------------------------}
{  Check Human Pawn Promotion:  Checks if the given pawn move is a promotion }
{  by a human player.  If not, the move is displayed.  If so, the move is    }
{  displayed as usual, but then the player is asked what piece he wants to   }
{  promote the pawn to.  The possible responses are: Q = Queen, R = Rook,    }
{  B = Bishop, and N = kNight.  Then, the piece is promoted.  Note that the  }
{  computer will always promote to a queen.                                  }
{----------------------------------------------------------------------------}
    procedure CheckHumanPawnPromotion (var Movement : MoveType);
      var Turn : PieceColorType;
          LegalPiece : boolean;
          key : char;
          NewImage : PieceImageType;
          row, col : RowColType;

      begin
        {*** check if the destination row is an end row ***}
        row := Movement.ToRow;
        col := Movement.ToCol;
        if (row = 1) or (row = 8) then begin
            {*** see if the player is a human ***}
            Turn := Movement.PieceMoved.color;
            if Player[Turn].IsHuman then begin
                {*** show the pawn trotting up to be promoted ***}
                Board[row, col].image := PAWN;
                DisplayMove (Movement);
                DisplaySquare (row, col, true);
                DisplayInstructions (INS_PAWN_PROMOTE);

                {*** wait for the user to indicate what to promote to ***}
                repeat
                    repeat key := GetKey until key <> 'n';
                    LegalPiece := true;
                    case key of
                        'Q': NewImage := QUEEN;
                        'R': NewImage := ROOK;
                        'B': NewImage := BISHOP;
                        'N': NewImage := KNIGHT;
                        else begin
                            {*** buzz at user for pressing wrong key ***}
                            LegalPiece := false;
                            MakeSound (false);
                        end;
                    end;
                until LegalPiece;

                {*** put in the new piece image ***}
                Board[row, col].image := NewImage;
                Game.Move[Game.MovesPointer].MovedImage := NewImage;
                DisplaySquare (row, col, false);
                DisplayInstructions (INS_GAME);
            end else
                DisplayMove (Movement);
        end else
            DisplayMove (Movement);
    end; {CheckHumanPawnPromotion}

{----------------------------------------------------------------------------}
    begin {PlayGame}
      Game.GameFinished := false;
      DisplayInstructions (INS_GAME);
      Escape := false;
      NoMoves := false;
      CheckFinishStatus;

      {*** play until escape or game over ***}
      while not (NoMoves or Escape or Game.TimeOutWhite or Game.TimeOutBlack
                        or Game.Stalemate or Game.NoStorage) do begin
          GetPlayerMove (Movement, Escape);
          CheckFinishStatus;
          NoMoves := Movement.FromRow = NULL_MOVE;
          if not (NoMoves or Escape or Game.TimeOutWhite or Game.TimeOutBlack) then begin
              {*** display the move if everything is ok ***}
              MakeMove (Movement, true, DummyScore);
              if (Movement.PieceMoved.image = PAWN) then
                  CheckHumanPawnPromotion (Movement)
              else
                  DisplayMove (Movement);
          end;
          CheckFinishStatus;
      end;

      {*** game is over unless the exit reason is the user pressing ESCape ***}
      if not Escape then Game.GameFinished := true;
  end; {PlayGame}
{$endif}

{*** end of PLAY.PAS include file ***}

