Guide me through building a chess program

Discussion of chess software programming and technical issues.

Moderator: Ras

Sapta

Re: Guide me through building a chess program

Post by Sapta »

Sven Schüle wrote: It is sufficient to use two zobrist keys per colour. But if someone wants to implement it differently, e.g. by looking at four possible combinations of two castle rights per colour, and assigning one zobrist key to each combination, then he may do it this way, as long as he handles it correctly. It just adds more complexity than necessary.

Btw, to which source of information do you refer where four numbers are used?

Sven
Thanks, this is the source.

http://mediocrechess.varten.org/guides/zobristkeys.html

I can't find any other source but I am sure I saw another site that used something similar.
wgarvin
Posts: 838
Joined: Thu Jul 05, 2007 5:03 pm
Location: British Columbia, Canada

Re: Guide me through building a chess program

Post by wgarvin »

Fguy64 wrote:
hgm wrote:Probably. But who cares? If you want speed, you should not loop over the board. Most squares will be empty, or not contain what you are looking for. Accessing those is a waste of time, no matter how fast and well-optimized you do it.
Interesting. Piecelists did give me a nice little improvement, but it was a royal pain in the ass to get it right, that is maintaining pieceLists during search.

I just use them to keep track of the locations of the pieces, I don't bother to keep track of what type each piece is, just where to find them. and I may try some "pre-ordering" also. What about you? any other uses for piece lists?
If that is all you use them for, it might be simpler to just keep a bitboard of all occupied squares. That is pretty easy to update when making a move. If you also keep a bitboard of "all opponent (sntm) pieces" then you can use it to generate captures, and you can flip sides by xoring the "all occupied squares" bitboard into it. Or you could keep "all side-to-move pieces" instead-whichever is most convenient.
wgarvin
Posts: 838
Joined: Thu Jul 05, 2007 5:03 pm
Location: British Columbia, Canada

Re: Guide me through building a chess program

Post by wgarvin »

Sapta wrote:
Sven Schüle wrote: It is sufficient to use two zobrist keys per colour. But if someone wants to implement it differently, e.g. by looking at four possible combinations of two castle rights per colour, and assigning one zobrist key to each combination, then he may do it this way, as long as he handles it correctly. It just adds more complexity than necessary.

Btw, to which source of information do you refer where four numbers are used?

Sven
Thanks, this is the source.

http://mediocrechess.varten.org/guides/zobristkeys.html

I can't find any other source but I am sure I saw another site that used something similar.
By the way, a suggestion: if you need extra zobrist keys to represent castling status, enpassant square, etc. I suggest storing them in the parts of the zobrist key table which would be used for pawns of either color, on the first or last rank. There are 4*8 = 32 such entries, and they will just waste space otherwise because a pawn can never be on the first or last rank of the board (unless you plan to support some strange variant I'm unaware of!)
Sapta

Re: Guide me through building a chess program

Post by Sapta »

wgarvin wrote: By the way, a suggestion: if you need extra zobrist keys to represent castling status, enpassant square, etc. I suggest storing them in the parts of the zobrist key table which would be used for pawns of either color, on the first or last rank. There are 4*8 = 32 such entries, and they will just waste space otherwise because a pawn can never be on the first or last rank of the board (unless you plan to support some strange variant I'm unaware of!)
Oh right, how did it never occur to me? But is it 32 entries or 16 entries? 1st rank for white pawns, last rank for black pawns. :?
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Guide me through building a chess program

Post by Sven »

Sapta wrote:
wgarvin wrote: By the way, a suggestion: if you need extra zobrist keys to represent castling status, enpassant square, etc. I suggest storing them in the parts of the zobrist key table which would be used for pawns of either color, on the first or last rank. There are 4*8 = 32 such entries, and they will just waste space otherwise because a pawn can never be on the first or last rank of the board (unless you plan to support some strange variant I'm unaware of!)
Oh right, how did it never occur to me? But is it 32 entries or 16 entries? 1st rank for white pawns, last rank for black pawns. :?
32. Usually you have a zobrist key table for square x piece x color, so for each possible color there are 2x8 unused squares for piece==Pawn.

Sven
User avatar
hgm
Posts: 28391
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Guide me through building a chess program

Post by hgm »

I don't have any spare entries in my Zobrist table. Because I store piece numbers on the board, rather than piece types, accessing the table as Zobrist[pieceType][square] or Zobrist[128*pieceType + square], would require an extra memory access to translate the piece number to the piece type, e.g. from the piece list. I might as well do that lookup to directly get the addres of a board-size table. So I have a table of pointers Zobrist[pieceNr] as part of my piece list, that maps the 64-entry table for a piece of that type onto a memory area of randoms. Then I can access the table as Zobrist[pieceNr][square]. There is no need for regular distances between the actual random tables for the various piece types, so if Pawn tables need only 6 ranks, they use only 6 ranks. (I.e. their virtual 1st and 8th trank overlaps with another table.

With a 0x88 board I can interleave the white and black tables. E.g. for 0x88 square numbering:

Code: Select all

KeyType randomTable[1024];
KeyType *Zobrist[MAX_PIECE_NR];
Zobrist[WN] = randomTable;
Zobrist[BN] = randomTable+8;
Zobrist[WR] = randomTable+128;
Zobrist[BR] = randomTable+136;
Zobrist[WP] = randomTable+240; // in stead of 256
Zobrist[BP] = randomTable+248;
zobrist[WQ] = randomTable+352;
...
wgarvin
Posts: 838
Joined: Thu Jul 05, 2007 5:03 pm
Location: British Columbia, Canada

Re: Guide me through building a chess program

Post by wgarvin »

hgm wrote:I don't have any spare entries in my Zobrist table. Because I store piece numbers on the board, rather than piece types, accessing the table as Zobrist[pieceType][square] or Zobrist[128*pieceType + square], would require an extra memory access to translate the piece number to the piece type, e.g. from the piece list. I might as well do that lookup to directly get the addres of a board-size table. So I have a table of pointers Zobrist[pieceNr] as part of my piece list, that maps the 64-entry table for a piece of that type onto a memory area of randoms. Then I can access the table as Zobrist[pieceNr][square]. There is no need for regular distances between the actual random tables for the various piece types, so if Pawn tables need only 6 ranks, they use only 6 ranks. (I.e. their virtual 1st and 8th trank overlaps with another table.

With a 0x88 board I can interleave the white and black tables. E.g. for 0x88 square numbering:

Code: Select all

KeyType randomTable[1024];
KeyType *Zobrist[MAX_PIECE_NR];
Zobrist[WN] = randomTable;
Zobrist[BN] = randomTable+8;
Zobrist[WR] = randomTable+128;
Zobrist[BR] = randomTable+136;
Zobrist[WP] = randomTable+240; // in stead of 256
Zobrist[BP] = randomTable+248;
zobrist[WQ] = randomTable+352;
...
That sounds pretty sensible. If you have a piece list anyway, then remembering a pointer to the first zobrist key for that pieces's type makes sense (because the type of a piece in the piece list doesn't change on most moves, probably only on captures and promotions). And you can just put 48 entries in your table, and arrange that pointer so that all of the valid squares for the pawn land on those 48 entries.

But for engines using a Zobrist[pieceType][square] type of table (such as pure bitboard engines) the two pawn types will never use their squares 0-7 and 56-63 because pawns start on the second rank, and they can't be moved onto the last rank without being promoted into something else. So re-using those entries for another purpose is fine. Of course there should be a macro or inline function or something that accesses them, so that readers of the code see ZobristKeyForCastle(rights) instead of something confusing like Zobrist[WHITE_PAWN][rights+56]. [Edit: you might write that to use the last 8 entries of WHITE_PAWN and the first 8 entries of BLACK_PAWN for example, if BLACK_PAWN == WHITE_PAWN+1. And you'd start with 4 keys but put all combinations of them into those 16 entries, to make it simple to get the zkey for any combination of castling rights with one lookup].
Sapta

Re: Guide me through building a chess program

Post by Sapta »

hgm wrote:
Sapta wrote:Won't this work only when the last or rightmost branch from root is the move computer finds out to be best? How many board positions are kept in memory during the search? :s
No, becuse I do the comparison after the search of every move in the root. Like:

Code: Select all

static MoveType userMove;

int Search(ply, ...)
{
  ...
  if(CAN_CAPTURE_KING) return INFINITY;
  for(ALL_MOVES) {
    ...
    MakeMove(move);
    score = -Search(ply+1, ...);
    if(ply == ROOT && score > -INFINITY && move == userMove) return OK_CODE;
    UnMake();
    ...
  }
  ...
  return bestScore;
}
If you call this with userMove initialized to the input move, a legal move would lead to returning OK_CODE and the move is done. As MakeMove also changes stm, flips the evauation, etc., you are immediately ready to play the next move. An illegal move, recognizable by the return of another score, would be refused, and the game state would not be altered.
Thanks for this pseudo-code. I get it but I have 2 questions:

1. This would only work for usermoves. Since when the computer is giving the move, I can't tell beforehand which move would be best, I have to apply Makemove() to root with the best move saved in TT. Is this right? Any other/better way to do it (can't be that all programs use TT)?

2. The CAN_CAPTURE_KING boolean is set during pseudo move generation and if it's set, then we return from move generation right then(because the previous move was an illegal one). Is this right?
hgm wrote:When you put an invalid move n userMove before calling search, it would do a normal search. You can then copy the best move found to userMove, and call Search again (with a depth of 2 ply) to perform it. (In micro-Max I do this even in the same call: when at the end of an iteration time is up, the best move is copied to userMove, and the depth reset to 2, before the next iteration starts. As the move will always be legal, that will make the root Search all return, with the move done.)
I only get the first line of this paragraph. But why would I copy bestmove to usermove? (Since the user gave invalid move, he has to give a valid one now) :?
User avatar
hgm
Posts: 28391
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Guide me through building a chess program

Post by hgm »

On (2): this is correct. In fact you don't even have tio finish move generation: as soon as you generate a move that captures the King, you can abort the generation and return +INFINITY.

(1) is related with your last remark. Indeed what I wrote was unclear. When the user inputs an illegal move, putting it in userMove and calling Searc() will make the latter return something different from OK_CODE, without performing a move, and you could print an illegal-move error message for the user.

The case I was discussing was when you want the computer to make a move, however. You want it to make the best move according to its search. So the program must put something invalid in userMove before it calls Search(), to make sure the test move == userMove in the root will always fail. Because if the test would succeed, Search() would immediately return, having played that move, even if it was not the best. And it might not have searched all moves, and thus might not even have seen the best.

So you would do smoething like

Code: Select all

userMove = INVALID;
Search(ply=1, depth=maxDepth, ...); // this would set the variable bestMove
userMove = bestMove; // now do what you would do if the user had typed that move
Search(ply=1, depth=2, ...);
The rest of what I was saying in the paragraph you quoted was this:
Around the for(ALL_MOVES) loop, you usually have another loop that iterates over increasing search depth. (Definitely in the root, but my engines do it in any node.) Such iteration would increase the actually used depth' iterDepth' until you reach the depth specified in the call of Search(). But in the root you in general would want to stop based on time, not on a maximum depth. So you would request a liberal depth in the call to Search, like 99, which would not be reached anyway, so that it always will continue as long as time allows.

In micro-Max I then do something like this:

Code: Select all

Search(ply, depth, ...)
{
    iterDepth = START_DEPTH; // in the root, this would aleways be 1
    while(iterDepth <= depth) {
        ...
        for(ALL_MOVES) {
            ...
            if(score > bestScore) { bestScore = score; bestMove = move; }
        }
        ...
        iterDepth = NEXT_DEPTH(iterDepth); // in the root, this would add 1
        if(ply == 1 && TIME_IS_UP) { // in the root only
            iterDepth = 2; depth = 2; // fool the loop to do exactly one more iteration
            userMove = bestMove; // and to think the user typed this move
        }
    }
}
Sapta

Re: Guide me through building a chess program

Post by Sapta »

^thanks, it's quite clear now. Trying to implement.