Files
lazarus-ccr/applications/fpchess/engines/kcchess/MOVES.PAS
sekelsenmat af711ad83f fpchess: AI playing actually works now =D
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@1874 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2011-08-30 14:40:22 +00:00

539 lines
26 KiB
Plaintext

{****************************************************************************}
{* MOVES.PAS: This file contains the routines which generate, move, and *}
{* otherwise have to do (on a low level) with moves. *}
{****************************************************************************}
{****************************************************************************}
{* Attacked By: Given the row and column of a square, this routine will *}
{* tally the number of other pieces which attack (enemy pieces) or protect *}
{* (friendly pieces) the piece on that square. This is accomplished by *}
{* looking around the piece to all other locations from which a piece *}
{* could protect or protect. This routine does not count an attack by *}
{* en passent. This routine is called to see if a king is in check and *}
{* as part of the position strength calculation. *}
{****************************************************************************}
procedure AttackedBy (row, col : RowColType; var Attacked, _Protected : integer);
var dir, i, distance : integer;
PosRow, PosCol, IncRow, IncCol : RowColType;
FriendColor, EnemyColor : PieceColorType;
begin
{*** initialize ***}
Attacked := 0;
_Protected := 0;
FriendColor := Board[row, col].color;
if (FriendColor = C_WHITE) then begin
dir := 1; {*** row displacement from which an enemy pawn would attack ***}
EnemyColor := C_BLACK;
end else begin
dir := -1;
EnemyColor := C_WHITE;
end;
{*** count number of attacking pawns ***}
with Board[row + dir, col - 1] do
if (image = PAWN) and (color = EnemyColor) then Attacked := Attacked + 1;
with Board[row + dir, col + 1] do
if (image = PAWN) and (color = EnemyColor) then Attacked := Attacked + 1;
{*** count number of protecting pawns ***}
with Board[row - dir, col - 1] do
if (image = PAWN) and (color = FriendColor) then _Protected := _Protected + 1;
with Board[row - dir, col + 1] do
if (image = PAWN) and (color = FriendColor) then _Protected := _Protected + 1;
{*** check for a knight in all positions from which it could attack/protect ***}
for i := 1 to PossibleMoves[KNIGHT].NumDirections do begin
with PossibleMoves[KNIGHT].UnitMove[i] do begin
PosRow := row + DirRow;
PosCol := col + DirCol;
end;
if (Board[PosRow, PosCol].image = KNIGHT) then begin
{*** color determines if knight is attacking or protecting ***}
if (Board[PosRow, PosCol].color = FriendColor) then
_Protected := _Protected + 1
else
Attacked := Attacked + 1;
end;
end;
{*** check for king, queen, bishop, and rook attacking or protecting ***}
for i := 1 to 8 do begin
{*** get the current search direction ***}
with PossibleMoves[QUEEN].UnitMove[i] do begin
IncRow := DirRow;
IncCol := DirCol;
end;
{*** set distance countdown and search position ***}
distance := 7;
PosRow := row;
PosCol := col;
{*** search until something is run into ***}
while distance > 0 do begin
{*** get new position and check it ***}
PosRow := PosRow + IncRow;
PosCol := PosCol + IncCol;
with Board[PosRow, PosCol] do begin
if ValidSquare then begin
if image = BLANK then
{*** continue searching if search_square is blank ***}
distance := distance - 1
else begin
{*** separate cases of straight or diagonal attack/protect ***}
if (IncRow = 0) or (IncCol = 0) then begin
{*** straight: check for queen, rook, or one-away king ***}
if (image = QUEEN) or (image = ROOK) or
((image = KING) and (distance = 7)) then
if (color = FriendColor) then
_Protected := _Protected + 1
else
Attacked := Attacked + 1;
end else begin
{*** diagonal: check for queen, bishop, or one-away king ***}
if (image = QUEEN) or (image = BISHOP) or
((image = KING) and (distance = 7)) then
if (color = FriendColor) then
_Protected := _Protected + 1
else
Attacked := Attacked + 1;
end;
{*** force to stop searching if piece encountered ***}
distance := 0;
end;
end else
{*** force to stop searching if border of board encountered ***}
distance := 0;
end;
end;
end;
end; {AttackedBy}
{****************************************************************************}
{* Gen Move List: Returns the list of all possible moves for the given *}
{* player. Searches the board for all pieces of the given color, and *}
{* adds all of the possible moves for that piece to the move list to be *}
{* returned. *}
{****************************************************************************}
procedure GenMoveList (Turn : PieceColorType; var Movelist : MoveListType);
var row, col : RowColType;
{----------------------------------------------------------------------------}
{ Gen Piece Move List: Generates all of the moves for the given piece at }
{ the given board position, and adds them to the move list for the player. }
{ If the piece is a pawn then all of the moves are checked by brute force. }
{ If the piece is a king, then the castling move is checked by brute force. }
{ For all other moves, each allowed direction for the piece is checked, and }
{ each square off in that direction from the piece, up to the piece's move }
{ distance limit, is added to the list, until the edge of the board is }
{ encountered or another piece is encountered. If the piece encountered }
{ is an enemy piece, then taking that enemy piece is added to the player's }
{ move list as well. }
{----------------------------------------------------------------------------}
procedure GenPieceMoveList (Piece : PieceType; row, col : integer);
var dir : integer;
PosRow, PosCol : RowColType; {*** current scanning position ***}
OrigRow, OrigCol : RowColType; {*** location to search from ***}
IncRow, IncCol : integer; {*** displacement to add to scanning position ***}
DirectionNum : 1..8;
distance : 0..7;
EPPossible : boolean;
EnemyColor : PieceColorType;
Attacked, _Protected : integer;
{----------------------------------------------------------------------------}
{ Gen Add Move: Adds the move to be generated to the player's move list. }
{ Given the location and displacement to move the piece, takes PieceTaken }
{ and PieceMoved from the current board configuration. }
{----------------------------------------------------------------------------}
procedure GenAddMove (row, col : RowColType; drow, dcol : integer);
begin
MoveList.NumMoves := MoveList.NumMoves + 1;
with MoveList.Move[MoveList.NumMoves] do begin
FromRow := row;
FromCol := col;
ToRow := row + drow;
ToCol := col + dcol;
PieceTaken := Board [ToRow, ToCol];
PieceMoved := Board [FromRow, FromCol];
{*** get image of piece after it is moved... ***}
{*** same as before the movement except in the case of pawn promotion ***}
MovedImage := Board [FromRow, FromCol].image;
end;
end; {GenAddMove}
{----------------------------------------------------------------------------}
{ Gen Add Pawn Move: Adds the move to be generated for a pawn to the }
{ player's move list and checks for pawn promotion. }
{----------------------------------------------------------------------------}
procedure GenAddPawnMove (row, col : RowColType; drow, dcol : integer);
begin
GenAddMove (row, col, drow, dcol);
{*** if pawn will move to an end row ***}
if (row + drow = 1) or (row + drow = 8) then
{*** assume the pawn will be promoted to a queen ***}
MoveList.Move[MoveList.NumMoves].MovedImage := QUEEN;
end; {GenAddPawnMove}
{----------------------------------------------------------------------------}
begin {GenPieceMoveList}
OrigRow := row;
OrigCol := col;
{*** pawn movement is a special case ***}
if Piece.image = PAWN then begin
{*** check for En Passent ***}
if Piece.color = C_WHITE then begin
dir := 1;
EPPossible := (row = 5);
EnemyColor := C_BLACK;
end else begin
dir := -1;
EPPossible := (row = 4);
EnemyColor := C_WHITE;
end;
{*** make sure enemy's last move was push pawn two, beside player's pawn ***}
with Player[EnemyColor].LastMove do begin
if EPPossible and Game.EnPassentAllowed and (FromRow <> NULL_MOVE) then
if (abs (FromRow - ToRow) = 2) then
if (abs (ToCol - col) = 1) and (PieceMoved.image = PAWN) then
GenAddPawnMove (row, col, dir, ToCol - col);
end;
{*** square pawn is moving to (1 or 2 ahead) is guaranteed to be valid ***}
if (Board [row + dir, col].image = BLANK) then begin
GenAddPawnMove (row, col, dir, 0);
{*** see if pawn can move two ***}
if (not Piece.HasMoved) and (Board [row + 2*dir, col].image = BLANK) then
GenAddPawnMove (row, col, 2*dir, 0);
end;
{*** check for pawn takes to left ***}
with Board[row + dir, col - 1] do begin
if (image <> BLANK) and (color = EnemyColor) and ValidSquare then
GenAddPawnMove (row, col, dir, -1);
end;
{*** check for pawn takes to right ***}
with Board[row + dir, col + 1] do begin
if (image <> BLANK) and (color = EnemyColor) and ValidSquare then
GenAddPawnMove (row, col, dir, +1);
end;
end else begin
{*** check for the king castling ***}
if (Piece.image = KING) and (not Piece.HasMoved) and (not Player[Turn].InCheck) then begin
{*** check for castling to left ***}
if (Board [row, 1].image = ROOK) and (not Board [row, 1].HasMoved) then
if (Board [row, 2].image = BLANK) and (Board [row, 3].image = BLANK)
and (Board [row, 4].image = BLANK) then begin
{*** make sure not moving through check ***}
Board [row, 4].color := Turn;
AttackedBy (row, 4, Attacked, _Protected);
if (Attacked = 0) then
GenAddMove (row, 5, 0, -2);
end;
{*** check for castling to right ***}
if (Board [row, 8].image = ROOK) and (not Board [row, 8].HasMoved) then
if (Board [row, 6].image = BLANK) and (Board [row, 7].image = BLANK) then begin
{*** make sure not moving through check ***}
Board [row, 6].color := Turn;
AttackedBy (row, 6, Attacked, _Protected);
if (Attacked = 0) then
GenAddMove (row, 5, 0, 2);
end;
end;
{*** Normal moves: for each allowed direction of the piece... ***}
for DirectionNum := 1 to PossibleMoves [Piece.image].NumDirections do begin
{*** initialize search ***}
distance := PossibleMoves [Piece.image].MaxDistance;
PosRow := OrigRow;
PosCol := OrigCol;
with PossibleMoves[Piece.image].UnitMove[DirectionNum] do begin
IncRow := DirRow;
IncCol := DirCol;
end;
{*** search until something is run into ***}
while distance > 0 do begin
PosRow := PosRow + IncRow;
PosCol := PosCol + IncCol;
with Board [PosRow, PosCol] do begin
if (not ValidSquare) then
distance := 0
else begin
if image = BLANK then begin
{*** sqaure is empty: can move there; keep searching ***}
GenAddMove (OrigRow, OrigCol, PosRow - OrigRow, PosCol - OrigCol);
distance := distance - 1;
end else begin
{*** piece is there: can take if enemy; stop searching ***}
distance := 0;
if color <> Turn then
GenAddMove (OrigRow, OrigCol, PosRow - OrigRow, PosCol - OrigCol);
end;
end;
end;
end;
end;
end;
end; {GenPieceMoveList}
{----------------------------------------------------------------------------}
begin {GenMoveList}
{*** empty out player's move list ***}
MoveList.NumMoves := 0;
{*** search for player's pieces on board ***}
for row := 1 to BOARD_SIZE do
for col := 1 to BOARD_SIZE do
with Board [row, col] do begin
{*** if player's piece, add its moves ***}
if (image <> BLANK) and (color = Turn) then
GenPieceMoveList (Board [row, col], row, col);
end;
end; {GenMoveList}
{****************************************************************************}
{* Make Move: Update the Board to reflect making the given movement. If *}
{* given is a PermanentMove, then the end of game pointers is set to the *}
{* current move. Returns the score for the move, which is given by the *}
{* number of points for the piece taken. *}
{****************************************************************************}
procedure MakeMove (Movement : MoveType; PermanentMove : boolean; var Score : integer);
var Row: RowColType;
Attacked, _Protected : integer;
begin
{$ifdef KCCHESS_VERBOSE}
//WriteLn(Format('[Moves.pas.MakeMove] Movement.PieceTaken.image=%d', [Integer(Movement.PieceTaken.image)]));
{$endif}
Score := 0;
{*** update board for most moves ***}
with Movement do begin
{*** pick piece up ***}
Board[FromRow, FromCol].image := BLANK;
{*** put piece down ***}
Board[ToRow, ToCol] := PieceMoved;
end;
{*** check for en passent, pawn promotion, and castling ***}
case Movement.PieceMoved.image of
PAWN: begin
{*** en passent: blank out square taken; get points ***}
if (Movement.FromCol <> Movement.ToCol) and (Movement.PieceTaken.image = BLANK) then begin
Board[Movement.FromRow, Movement.ToCol].image := BLANK;
Score := Score + CapturePoints[PAWN];
end;
{*** pawn promotion: use the after-moved image; get trade-up points ***}
if Movement.PieceMoved.color = C_WHITE then Row := 8 else Row := 1;
if Movement.ToRow = Row then begin
Board[Movement.ToRow, Movement.ToCol].image := Movement.MovedImage;
Score := Score + CapturePoints[Movement.MovedImage] - CapturePoints[PAWN];
end;
end;
KING: begin
{*** update king position in player record ***}
with Player[Movement.PieceMoved.color] do begin
KingRow := Movement.ToRow;
KingCol := Movement.ToCol;
end;
{*** castling left/right: move the rook too ***}
if abs (Movement.FromCol - Movement.ToCol) > 1 then begin
if (Movement.ToCol < Movement.FromCol) then begin
Board[Movement.FromRow, 4] := Board[Movement.FromRow, 1];
Board[Movement.FromRow, 4].HasMoved := true;
Board[Movement.FromRow, 1].image := BLANK;
end else begin
Board[Movement.FromRow, 6] := Board[Movement.FromRow, 8];
Board[Movement.FromRow, 6].HasMoved := true;
Board[Movement.FromRow, 8].image := BLANK;
end;
end;
end;
end;
{*** update player attributes ***}
Player[Movement.PieceMoved.color].LastMove := Movement;
Player[Movement.PieceMoved.color].InCheck := false;
with Player[EnemyColor[Movement.PieceMoved.color]] do begin
AttackedBy (KingRow, KingCol, Attacked, _Protected);
InCheck := Attacked <> 0;
end;
{*** remember that piece has been moved, get score ***}
Board[Movement.ToRow, Movement.ToCol].HasMoved := true;
Score := Score + CapturePoints[Movement.PieceTaken.image];
{*** update game attributes ***}
Game.MoveNum := Game.MoveNum + 1;
Game.MovesPointer := Game.MovesPointer + 1;
Game.Move[Game.MovesPointer] := Movement;
if PermanentMove then
Game.MovesStored := Game.MovesPointer;
Game.InCheck[Game.MovesPointer] := Attacked <> 0;
{*** update nondevelopmental moves counter ***}
if (Movement.PieceMoved.image = PAWN) or (Movement.PieceTaken.image <> BLANK) then begin
Game.NonDevMoveCount[Game.MovesPointer] := 0;
end else begin
{*** if 50 nondevelopmental moves in a row: stalemate ***}
Game.NonDevMoveCount[Game.MovesPointer] := Game.NonDevMoveCount[Game.MovesPointer-1] + 1;
if (Game.NonDevMoveCount[Game.MovesPointer] >= NON_DEV_MOVE_LIMIT) then
Score := STALE_SCORE;
end;
end; {MakeMove}
{****************************************************************************}
{* Un Make Move: Updates the board for taking back the last made move. *}
{* This routine returns (is not given) the last made movement (so it may *}
{* be passed to DisplayUnMadeMove). *}
{****************************************************************************}
procedure UnMakeMove (var Movement: Movetype);
begin
{*** make sure there is a move to un-make ***}
if (Game.MovesPointer > 0) then begin
Movement := Game.Move[Game.MovesPointer];
{*** restore whether player who made move was in check ***}
Player[Movement.PieceMoved.color].InCheck := Game.InCheck[Game.MovesPointer - 1];
{*** the enemy could not have been in check ***}
Player[EnemyColor[Movement.PieceMoved.color]].InCheck := false;
{*** restore the From and To squares ***}
Board[Movement.FromRow, Movement.FromCol] := Movement.PieceMoved;
Board[Movement.ToRow, Movement.ToCol] := Movement.PieceTaken;
{*** special cases ***}
case Movement.PieceMoved.image of
KING: begin
{*** restore position of king in player attributes ***}
with Player[Movement.PieceMoved.color] do begin
KingRow := Movement.FromRow;
KingCol := Movement.FromCol;
end;
{*** un-castle left/right if applicable ***}
if abs (Movement.FromCol - Movement.ToCol) > 1 then begin
if (Movement.FromCol < Movement.ToCol) then begin
Board[Movement.FromRow, 8] := Board[Movement.FromRow, 6];
Board[Movement.FromRow, 6].image := BLANK;
Board[Movement.FromRow, 8].HasMoved := false;
end else begin
Board[Movement.FromRow, 1] := Board[Movement.FromRow, 4];
Board[Movement.FromRow, 4].image := BLANK;
Board[Movement.FromRow, 1].HasMoved := false;
end;
end;
end;
PAWN:
{*** un-en passent: restore the pawn that was taken ***}
if (Movement.FromCol <> Movement.ToCol) and (Movement.PieceTaken.image = BLANK) then
with Board[Movement.FromRow, Movement.ToCol] do begin
image := PAWN;
if Movement.PieceMoved.color = C_WHITE then
color := C_BLACK
else
color := C_WHITE;
HasMoved := true;
end;
end;
{*** roll back move number and pointer ***}
Game.MoveNum := Game.MoveNum - 1;
Game.MovesPointer := Game.MovesPointer - 1;
{*** restore player previous move attributes ***}
if (Game.MovesPointer > 1) then
Player[Movement.PieceMoved.color].LastMove := Game.Move[Game.MovesPointer - 1]
else
Player[Movement.PieceMoved.color].LastMove.FromRow := NULL_MOVE;
end else
{*** indicate no move to take back ***}
Movement.FromRow := NULL_MOVE;
end; {UnMakeMove}
{****************************************************************************}
{* Trim Checks: Given a move list, remove all of the moves which would *}
{* result in the given player moving into check. This is not done *}
{* directly by GenMoveList because it is an expensive operation, since *}
{* each move will have to be made and then unmade. This routine is called *}
{* by the Human Movement routine. The Computer Movement routine *}
{* incorporates this code into its code, since the moves have to be made *}
{* and unmade there anyway. *}
{****************************************************************************}
procedure TrimChecks (Turn : PieceColorType; var MoveList : MoveListType);
var DummyMove : MoveType;
DummyScore, Attacked, _Protected : integer;
ValidMoves, i : integer;
begin
ValidMoves := 0;
for i := 1 to MoveList.NumMoves do begin
MakeMove (MoveList.Move[i], false, DummyScore);
{*** check if the king is attacked (in check) ***}
AttackedBy (Player[Turn].KingRow, Player[Turn].KingCol, Attacked, _Protected);
if Attacked = 0 then begin
{*** if not in check, then re-put valid move ***}
ValidMoves := ValidMoves + 1;
MoveList.Move[ValidMoves] := MoveList.Move[i];
end;
UnMakeMove (DummyMove);
end;
MoveList.NumMoves := ValidMoves;
end; {TrimChecks}
{****************************************************************************}
{* Randomize Move List: Scrambles the order of the given move list. This *}
{* is done to make choosing between tie scores random. It is necassary *}
{* to scramble the moves here because the move list will always be *}
{* generated in the same order. *}
{****************************************************************************}
procedure RandomizeMoveList (var MoveList : MoveListType);
var i, Exch : integer;
ExchMove : MoveType;
begin
for i := 1 to MoveList.NumMoves do begin
{*** swap the current move with some random other one ***}
Exch := Random (MoveList.NumMoves) + 1;
ExchMove := MoveList.Move[Exch];
MoveList.Move[Exch] := MoveList.Move[i];
MoveList.Move[i] := ExchMove;
end;
end; {RandomizeMoveList}
{****************************************************************************}
{* Goto Move: Rolls back/forth the game to the given move number. Each *}
{* move between the current move and the destination is made/retracted to *}
{* update the board and other game status information. If the given move *}
{* number is too small or too large, the game is rolled to the first/last *}
{* move. *}
{****************************************************************************}
procedure GotoMove (GivenMoveTo : integer);
var Movement : MoveType;
DummyScore : integer;
MoveTo, BeginMoveNum, EndMoveNum : integer;
begin
{*** check the bounds of given move number ***}
MoveTo := GivenMoveTo;
BeginMoveNum := Game.MoveNum - Game.MovesPointer;
EndMoveNum := Game.MoveNum + (Game.MovesStored - Game.MovesPointer);
if (MoveTo < BeginMoveNum) then MoveTo := BeginMoveNum;
if (MoveTo > EndMoveNum) then MoveTo := EndMoveNum;
{*** make / retract moves of the game until the destination is reached ***}
while (Game.MoveNum <> MoveTo) do begin
if MoveTo > Game.MoveNum then begin
Movement := Game.Move[Game.MovesPointer + 1];
MakeMove (Movement, false, DummyScore);
end else
UnMakeMove (Movement);
end;
Game.GameFinished := false;
end;
{*** end of the MOVES.PAS include file ***}