dos_compilers/Borland Turbo Pascal v55/chess/PLAY.PAS
2024-07-02 06:49:04 -07:00

720 lines
35 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{****************************************************************************}
{* 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
{*** 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 ***}
if Display then begin
DisplayClearLines (MSG_HINT, 21);
SetTextStyle (TriplexFont, HorizDir, 1);
SetColor (LightGreen);
CenterText (MSG_HINT, 'Pre Scanning...');
end;
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, CumOwnProtected : integer;
CumEnemyAttacked, CumEnemyProtected : 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;
CumOwnProtected := 0;
CumEnemyAttacked := 0;
CumEnemyProtected := 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;
CumOwnProtected := CumOwnProtected + Protected;
end else begin
CumEnemyAttacked := CumEnemyAttacked + Attacked;
CumEnemyProtected := CumEnemyProtected + Protected;
end;
end;
PosStrength := PosStrength + 2 * CumOwnProtected
- 2 * CumOwnAttacked + 2 * CumEnemyAttacked;
EvalPosStrength := PosStrength;
end; {EvalPosStrength}
{----------------------------------------------------------------------------}
begin {GetComputerMove}
{*** 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);
key := GetKey; {*** check for user pressing ESCape ***}
if key <> 'x' then begin
{*** 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;
key := GetKey;
end;
{*** check for user pressing ESCape after pre-scan ***}
if key = 'x' then begin
Escape := true;
i := 0;
end;
{*** check each possible move - same method as in Search ***}
while (i > 0) do begin
UpDateTime (Turn); {*** player's elapsed time ***}
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 ***}
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;
{*** 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 ***}
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;
{*** 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 ***}
key := GetKey;
if key = 'x' then begin
Escape := true;
i := 0;
end else
if (key = 'M') and (HiScore <> NegInfinity) then i := 0;
end;
{*** beep when done thinking ***}
MakeSound (true);
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. *}
{****************************************************************************}
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}
{****************************************************************************}
{* 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. *}
{****************************************************************************}
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}
{****************************************************************************}
{* 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.*}
{****************************************************************************}
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}
{*** end of PLAY.PAS include file ***}