Files
lazarus-ccr/applications/fpchess/engines/kcchess/PLAY.PAS

757 lines
35 KiB
Plaintext
Raw Normal View History

{****************************************************************************}
{* 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 ***}