While tinkering with a new array based engine I've realized that I am in need of a fast way to identify passed pawns.
I knew the naive approach would be kinda slow, but it's actually VERY slow.
Anyone out there willing to divulge their idea(s), implemented in practice or otherwise, for fast passed pawn identification in an array based board representation?
Passed pawn detection in array based board representations
Moderators: hgm, Rebel, chrisw
-
- Posts: 193
- Joined: Wed Mar 11, 2015 3:34 am
- Location: United States
-
- Posts: 1494
- Joined: Thu Mar 30, 2006 2:08 pm
Re: Passed pawn detection in array based board representatio
In my old "mailbox" program NOW, I merely kept an incrementally updated value for each color and side which indicated the least advanced pawn rank for that side. Then to detect a passed pawn you just had to check the values of the appropriate files to see there was no opponent pawn in front of the pawn being score.
If I was to do it now, I would probably just add logic for each pawn move and pawn capture to maintain a list of the passed pawns. Slightly annoying to program, but then the info is always available.
Later versions of NOW had mailbox, but duplicated the pawn locations in 2 64 bit values. Detection passed pawns them is a simple mask operation, and once pawns are represented as bitmaps, isolated, doubled, backward, passed, open files.... all get a lot easier. It is a gentle way to learn about bitmaps without requiring the whole program get converted at once.
If I was to do it now, I would probably just add logic for each pawn move and pawn capture to maintain a list of the passed pawns. Slightly annoying to program, but then the info is always available.
Later versions of NOW had mailbox, but duplicated the pawn locations in 2 64 bit values. Detection passed pawns them is a simple mask operation, and once pawns are represented as bitmaps, isolated, doubled, backward, passed, open files.... all get a lot easier. It is a gentle way to learn about bitmaps without requiring the whole program get converted at once.
-
- Posts: 589
- Joined: Tue Jun 04, 2013 10:15 pm
Re: Passed pawn detection in array based board representatio
I have a 10-element array approach in the study engine I'm working from time to time (which doesn't play chess yet, but that is not the purpose):zd3nik wrote:While tinkering with a new array based engine I've realized that I am in need of a fast way to identify passed pawns.
I knew the naive approach would be kinda slow, but it's actually VERY slow.
Anyone out there willing to divulge their idea(s), implemented in practice or otherwise, for fast passed pawn identification in an array based board representation?
Declaration of helper arrays:
Code: Select all
/*
* Maximum distance of pawn to rank1, for each file and both sides.
* And then the same from rank8 point of view.
* Used to detect open files, doubled pawns, passers, etc.
*/
int maxPawnFromRank1[10][2];
int maxPawnFromRank8[10][2];
Code: Select all
case whitePawn:
case blackPawn:
setMax(e.maxPawnFromRank1[1+fileIndex][side], rank ^ rank1);
setMax(e.maxPawnFromRank8[1+fileIndex][side], rank ^ rank8);
Code: Select all
for (int fileIndex=0; fileIndex<8; fileIndex++) {
e.pawns[white] += evaluatePawn(v,
&e.maxPawnFromRank1[1+fileIndex],
&e.maxPawnFromRank8[1+fileIndex],
fileIndex,
white,
// ...
e.pawns[black] += evaluatePawn(v,
&e.maxPawnFromRank8[1+fileIndex], // flipped!
&e.maxPawnFromRank1[1+fileIndex],
fileIndex,
black,
// ...
}
Code: Select all
static int evaluatePawn(const int v[vectorLen],
const int maxPawnFromRank1[][2],
const int maxPawnFromRank8[][2],
int fileIndex,
int side,
// ...
{
#define maxRank(i, j) maxPawnFromRank1[i][j]
int frontPawn = maxRank(0, side);
[... snip ...]
/*
* Passer
*/
if (maxRank(-1, xside) <= frontPawn
&& maxRank( 0, xside) <= frontPawn
&& maxRank(+1, xside) <= frontPawn)
pawnScore += v[passerA_0 + fileIndex]
+ v[passerA_1 + fileIndex] * (frontPawn - 1)
+ v[passerA_2 + fileIndex] * (frontPawn - 1) * (frontPawn - 2) / 4;
Last edited by mvk on Mon Sep 21, 2015 10:39 am, edited 3 times in total.
[Account deleted]
-
- Posts: 27790
- Joined: Fri Mar 10, 2006 10:06 am
- Location: Amsterdam
- Full name: H G Muller
Re: Passed pawn detection in array based board representatio
I use basically the same method as Mark mentions: keep track of the most backward Pawn in every file (setting it to last rank if there are no Pawns in that file). Then detecting of a given Pawn is a passer is just a matter of comparing it with the rank of the most-backward opponent Pawns in its own file and the files next to it.
On several occasions I thought on how I could do this incrementally. This is rather cumbersome, though, because there are many situations in which the information changes. (Pawns move, capture, are captured. If they leave the file by capture or being captured you would have to scan the board to see what now becomes the rear-most Pawn in that file.)
The point is that, although doing it from scratch (starting from the Pawn section of a piece list) is somewhat expensive, the fact that you have a Pawn hash hides this cost so well that it becomes very difficult to compete with it through an incremental method that has to be performed on every MakeMove (and UnMake...). The only incremental stuff becomes the update of a 64-bit Pawn key, which is easily done through copy-modify.
In KingSlayer (formerly called Simple) I now do it as follows (in a function only called on a Pawn-hash miss, to replace the old entry):
I deleted stuff not associated with passers, like backward/doubled Pawns (which can easily be determined based on the same info), Pawn shields around the default King locations (after castling) etc.
Note KingSlayer does not even have a piece list, so the first step is creating the Pawn sections of such a piece list by scanning the board...
IMO with modern hardware more could be gained by improving the hashing algorithm (increasing hit rate or access time) than by trying to speed up the actual calculation. A RAM access would probably still be cheaper than the calculation of everything you want to calculate about Pawns, so you could take a huge table that would virtually never cause a miss and use a much smaller table with copies of recently-used values that easily fits in L2/L3 and satisfies >90% of the demand.
On several occasions I thought on how I could do this incrementally. This is rather cumbersome, though, because there are many situations in which the information changes. (Pawns move, capture, are captured. If they leave the file by capture or being captured you would have to scan the board to see what now becomes the rear-most Pawn in that file.)
The point is that, although doing it from scratch (starting from the Pawn section of a piece list) is somewhat expensive, the fact that you have a Pawn hash hides this cost so well that it becomes very difficult to compete with it through an incremental method that has to be performed on every MakeMove (and UnMake...). The only incremental stuff becomes the update of a 64-bit Pawn key, which is easily done through copy-modify.
In KingSlayer (formerly called Simple) I now do it as follows (in a function only called on a Pawn-hash miss, to replace the old entry):
Code: Select all
int
PawnEval(PawnInfo *info)
{
int i, sq, pawnFiles, w=0, b=0, score = 0;
char white[8], black[8], rearWhite[10], rearBlack[10];
for(i=0; i<10; i++) rearWhite[i] = 0x70, rearBlack[i] = 0; // initialize most backward Pawn for each file
info->passer[0] = info->passer[1] = -1; // invalid square number means no passer
...
// create pawn list
for(sq=1*16; sq<7*16; sq=sq+9&~8) { // scan Pawn area of 0x88 board rank by rank
int piece = board[sq];
if(piece == WHITE+PAWN) white[w++] = sq; else // and create pawn lists
if(piece == BLACK+PAWN) black[b++] = sq; // for white and black separately
}
// determine rear guards
for(i=w-1; i >= 0; i--) {
sq = white[i];
rearWhite[(sq & 7) + 1] = sq & 0x70; // because of scan order last is always most backward
}
for(i=0; i<b; i++) {
sq = black[i];
rearBlack[(sq & 7) + 1] = sq & 0x70;
}
// determine pawn types
pawnFiles = 0;
for(i=w-1; i>=0; i--) { // loop through white Pawns, high to low rank
int d, r = (sq = white[i]) & 0x70, f = sq & 7; // get file and rank
...
if(!(pawnFiles & 1<<f) && // only first (= most forward Pawn) in file
r >= rearBlack[f] && r >= rearBlack[f+1] && r >= rearBlack[f+2]) { // Pawn is passer
score += pst[PASSER+WHITE][sq]; // give position-dependent bonus for that
if(f == 0 || f == 7) info->passerFlags |= 0x10; // remember passer is on edge file
if(f != 0 && board[sq-17] == WHITE+PAWN || // test for Pawns diagonally behind it
f != 7 && board[sq-15] == WHITE+PAWN ) { // we have protected passer!
info->passerFlags |= 1; // flag that fact
score += PROTPASS + (r == 0x60)*PROTPASS7; // flag that fact, and give bonus
}
if(info->passer[0] < 0) { // first passer (and thus most forward)
info->passer[0]= sq; // remember its location
info->promoSqr[0] = sq | 0x70; // promotion square
d = dist[sq - (sq|0x70)]; info->promoDist[0] = d - (d == 6); // and promotion distance (in moves)
} else info->passerFlags |= 4; // multiple passers, just flag
}
...
}
...
for(i=0; i<b; i++) {
int d, r = (sq = black[i]) & 0x70, f = sq & 7; // get file and rank
...
if(!(pawnFiles & 1<<f) && // only first (= most forward Pawn) in file
r <= rearWhite[f] && r <= rearWhite[f+1] && r <= rearWhite[f+2]) { // Pawn is passer
score -= pst[PASSER+BLACK][sq]; // give position-dependent bonus for that
if(f == 0 || f == 7) info->passerFlags |= 0x20; // remember passer is on edge file
if(f != 0 && board[sq+15] == BLACK+PAWN || // test for Pawns diagonally behind it
f != 7 && board[sq+17] == BLACK+PAWN ) { // we have protected passer!
info->passerFlags |= 2; // flag that fact
score -= PROTPASS + (r == 0x10)*PROTPASS7; // and give bonus
}
if(info->passer[1] < 0) { // first passer (and thus most forward)
info->passer[1]= sq; // remember its location
info->promoSqr[1] = f; // promotion square
d = dist[sq - f]; info->promoDist[1] = d - (d == 6); // and promotion distance (in moves)
} else info->passerFlags |= 8; // multiple passers, just flag
}
...
}
...
info->score = score; // white POV score
}
Note KingSlayer does not even have a piece list, so the first step is creating the Pawn sections of such a piece list by scanning the board...
IMO with modern hardware more could be gained by improving the hashing algorithm (increasing hit rate or access time) than by trying to speed up the actual calculation. A RAM access would probably still be cheaper than the calculation of everything you want to calculate about Pawns, so you could take a huge table that would virtually never cause a miss and use a much smaller table with copies of recently-used values that easily fits in L2/L3 and satisfies >90% of the demand.
-
- Posts: 6991
- Joined: Thu Aug 18, 2011 12:04 pm
Re: Passed pawn detection in array based board representatio
I incrementally maintain an 10 byte pawn table for white and black. From the start position it looks like this:zd3nik wrote:While tinkering with a new array based engine I've realized that I am in need of a fast way to identify passed pawns.
I knew the naive approach would be kinda slow, but it's actually VERY slow.
Anyone out there willing to divulge their idea(s), implemented in practice or otherwise, for fast passed pawn identification in an array based board representation?
Code: Select all
+----+----+----+----+----+-----+----+---+----+----+
| 0 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | 0 | [ black ]
+----+----+----+----+----+----+----+----+----+----+
| 9 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 9 | [ white ]
+----+----+----+----+-----+----+---+----+----+----+
a b c d e f g h
-
- Posts: 180
- Joined: Mon Sep 03, 2007 9:15 am
Re: Passed pawn detection in array based board representatio
You can use a pawn hash table to speed up the evaluation of pawns.
If you need to carry the position of passers to other parts of the evaluation (you may want to evalutate blocked or threatened squares in front of passers) you can put the position of all passers found into the pawn hash table and pass a pointer to the pawn hash table entry to other functions.
Each side has at most 8 passers even in pathological cases. That's 16 bytes per entry. My pawn hash table has 80 bytes per entry.
If you need to carry the position of passers to other parts of the evaluation (you may want to evalutate blocked or threatened squares in front of passers) you can put the position of all passers found into the pawn hash table and pass a pointer to the pawn hash table entry to other functions.
Each side has at most 8 passers even in pathological cases. That's 16 bytes per entry. My pawn hash table has 80 bytes per entry.
-
- Posts: 193
- Joined: Wed Mar 11, 2015 3:34 am
- Location: United States
Re: Passed pawn detection in array based board representatio
Excellent feedback everyone. Thanks!
I am actually using 2 file arrays as well, but with extra logic to identify potential passers (which I actually don't have working quite right yet). And I'm setting high bits in each file to mark the file as containing a passed|backward|supported pawn - for use in eval of other pieces (bonus for rooks behind passers, etc). That potential passer code, and perhaps the masking I need to occasionally do to deal with those high bit markers, is probably why it's so slow.
Mark, I agree that doing passer detection with bitboards is pretty trivial, and that's the way I usually do it. However, for "fun", I'm trying to see how fast I can make this engine without using bitboards. Pointless, I know. But so are the other couple dozen chess engines I've written: pointless. I just like writing them. Tinkering with new ideas. See if I can make it work. Then drop it and try a new engine...
Marcel, I like your idea of keeping both the most advanced and least advanced positions per file. Perhaps I can adapt this into something that works for potential passer detection without too much overhead. If I figure something out I'll post it here (or just make the engine open source when I'm done). I'm not sure how to properly incorporate your xor trick though - seems like that would only work for the first pawn per file.
I am actually using 2 file arrays as well, but with extra logic to identify potential passers (which I actually don't have working quite right yet). And I'm setting high bits in each file to mark the file as containing a passed|backward|supported pawn - for use in eval of other pieces (bonus for rooks behind passers, etc). That potential passer code, and perhaps the masking I need to occasionally do to deal with those high bit markers, is probably why it's so slow.
Mark, I agree that doing passer detection with bitboards is pretty trivial, and that's the way I usually do it. However, for "fun", I'm trying to see how fast I can make this engine without using bitboards. Pointless, I know. But so are the other couple dozen chess engines I've written: pointless. I just like writing them. Tinkering with new ideas. See if I can make it work. Then drop it and try a new engine...
Marcel, I like your idea of keeping both the most advanced and least advanced positions per file. Perhaps I can adapt this into something that works for potential passer detection without too much overhead. If I figure something out I'll post it here (or just make the engine open source when I'm done). I'm not sure how to properly incorporate your xor trick though - seems like that would only work for the first pawn per file.
-
- Posts: 193
- Joined: Wed Mar 11, 2015 3:34 am
- Location: United States
Re: Passed pawn detection in array based board representatio
Nevermind. For some reason my brain interpreted:zd3nik wrote:Marcel,
... I'm not sure how to properly incorporate your xor trick though - seems like that would only work for the first pawn per file.
Code: Select all
case whitePawn:
case blackPawn:
setMax(e.maxPawnFromRank1[1+fileIndex][side], rank ^ rank1);
setMax(e.maxPawnFromRank8[1+fileIndex][side], rank ^ rank8);
Code: Select all
case whitePawn:
case blackPawn:
e.maxPawnFromRank1[1+fileIndex][side] ^= rank;
e.maxPawnFromRank8[1+fileIndex][side] ^= rank;
-
- Posts: 4675
- Joined: Mon Mar 13, 2006 7:43 pm
Use a transposition table for pawn structures
Use a transposition table for pawn structures where each entry contains, possibly along with other data, a bitboard with passed pawn locations. With a sufficiently large table capacity, the hit rate on a pawn structure transposition table will be 99+%, and so the total calculation time for detecting passed pawns won't be very significant because the calculation won't be performed very often.
-
- Posts: 589
- Joined: Tue Jun 04, 2013 10:15 pm
Re: Passed pawn detection in array based board representatio
Indeed. The '^ rank1' operation ensures that the counting starts from the point of view of rank 1, and the opposite for '^ rank8'. I try to write this program without propagating the underlying square indexing throughout the code. So far, that results in symmetric and slightly better self-documenting code, so I like that experiment. But if it confused you here, maybe it is not clear enough. Maybe the 'setMax' macro is obfuscating things a bit too much. Thanks for your feedback.zd3nik wrote:Nevermind. For some reason my brain interpreted:zd3nik wrote:Marcel,
... I'm not sure how to properly incorporate your xor trick though - seems like that would only work for the first pawn per file.
Code: Select all
[...snip...]
BTW, updating only 'max'es is instead of both 'min' and 'max' is a bit easier because you can initialize the arrays with zeroes. So I did it from both rank1 and rank8 point of view, and got rid of the pawn move direction as well while doing so.
Of course, everything can be sped up by hiding it behind a hash table, as many have pointed out. This program doesn't have that because it is a study for a different purpose. I might convert it into a playing program and release it as MSCP v2 however.
[Account deleted]