The mailbox trials

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

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

Re: The mailbox trials

Post by hgm »

Overkill pruning

Updating the attack map is expensive, especially since restoring it to its old state will be almost equally expensive. So it would be a significant waste if we did it in vain, i.e. without using any of the information in it. This would happen when after the move we would have a stand-pat cutoff on the lazy evaluation. (For now we will assume that a full evaluation would need a fully updated attack map.) But futility pruning already takes care of that, by pruning moves that do not raise the lazy evaluation enough to get near alpha:

Code: Select all

lazyEval = currentEval + value[victim];
if(lazyEval < alpha - MARGIN) PRUNE;
The other case where the info in the attack map is 'under-used' is basically the reverse of this: when our move raises the evaluation so much above beta that the opponent cannot possibly push it back below beta. But the condition

Code: Select all

if(lazyEval > beta + MARGIN) FAIL_HIGH; // flawed
would not work, as it only means the opponent's stand-pat attempt is futile. The opponent could still have good captures (perhaps even of our King?) that push the lazyEval down (from the moving player's POV) a lot. So if we want to do a fail-high-without-search, we must also make sure all the opponent's captures are futile:

Code: Select all

if(lazyEval > beta + MARGIN && Overkill(u)) FAIL_HIGH; // correct
The question now is: would it be possible to decide on this without updating the attack map first? With an updated attack map the Overkill() test would be pretty easy:

Code: Select all

gap = lazyEval - beta - MARGIN; // what opponent must capture to jeopardize our fail-high
victims = summary[OPPONENT][SIDE_THAT_MOVES]; // attack counters of our pieces
if(victims) {
  int bit = BSF(victims);
  int mvv = stm + 15 - (bit >> 2); // calculate number of piece corresponding to this counter
  if(value[mvv] > gap) return 0;
}
return 1;
Now in practice this gets a bit more cluttered, because the summaries are split into Pawns and non-Pawns, both attacker-wise and victim-wise. So we would get

Code: Select all

victims = summary[OPPO_P][STM_X] + summary[OPPO_X][STM_X]; // total attacks (by P and non-P) on moving player's non-P
if(victims) { // some of our non-P are attacked
  int bit = BSF(victims); // extract most valuable
  int mvv = stm + 15 - (bit >> 2);
  if(value[mvv] > gap) return 0;
}
if(value[PAWN] < gap) return 1; // Pawn doesn't cut it
victims = summary[OPPO_P][STM_P] + summary[OPPO_X][STM_P]; // total attacks (by P and non-P) on moving player's P
if(victims) {
 return 0;
}
return 1;
When we do this with a not-yet-updated attack map, there are several causes of error:
1) the identified victim that spoiled the fun could have moved away
2) that victim could have been attacked only by the piece that was just captured, so it is in fact now safe
3) the only capture of that victim was blocked by the move we played
4) the piece that we move could be attacked in its new location
5) the piece that moved away could have been soft-pinned, so that moving it exposes another piece

Now (3) can only happen on a non-capture, and since only 6% of the moves were non-captures, we don't worry about that case, and just never overkill-prune on those. (1) and (2) apply to attacks in the stale attack map that have become invalid. So when we find an attack through the above procedure, we would have to vet it for not being one of these two cases. And if it fails that test, we would have to keep looking for a victim that is really attacked. But none of that is very difficult:

Code: Select all

victims = summary[OPPO_P][STM_X] + summary[OPPO_X][STM_X]; // total attacks (by P and non-P) on moving player's non-P
while(victims) { // loop through our attacked non-P
  int bit = BSF(victims);            // extract most valuable
  int mvv = stm + 15 - (bit >> 2);   // piece number of non-P for this counter
  if(value[mvv] < gap) return 1;     // this (and all future) not valuable enough
  if(mvv != u->piece) {              // the piece that just moved is no longer there!
    int unit = victim2unit[mvv-16];  // mask with lowest bit of counter for this victim
    int counter = victims & 15*unit; // isolate counter
    if(counter > unit) return 0;     // the piece is still there, valuable enough, and multiply attacked
    // the piece is attacked only once; test whether the attacker survives the current move
    int kind = u->victim >> 3 & 3;   // determine kind of attacker (w/b, P/non-P)
    if(attacker2bit[u->victim] != attackers[kind][mvv]) return 0; // was not captured this move
  }
  bit &= clearMask[bit];
}
if(value[PAWN] < gap) return 1; // Pawn doesn't cut it
victims = summary[OPPO_P][STM_P] + summary[OPPO_X][STM_P]; // total attacks (by P and non-P) on moving player's P
while(victims) { // loop through our attacked non-P
  int bit = BSF(victims);            // extract most valuable
  int mvv = stm + 7 - (bit >> 2);    // piece number of P for this counter
  if(mvv != u->piece) {              // the piece that just moved is no longer there!
    int unit = victim2unit[mvv-16];  // lowest bit of counter for this victim
    int counter = victims & 15*unit; // isolate counter
    if(counter > unit) return 0;     // the piece is still there, valuable enough, and multiply attacked
    // the piece is attacked only once; test whether the attacker survives the current move
    int kind = u->victim >> 3 & 3;   // determine kind of piece that was captured (w/b, P/non-P)
    if(attacker2bit[u->victim] != attackers[kind][mvv]) return 0; // this was not the attacker of the MVV
  }
  bit &= clearMask[bit];
}
return 1;
Umm, this already starts to become complex. Still far less work than updating + restoring the attack map, though, for which we would have to generate (amongst others) moves for 3 pieces (mover in old & new location and captured piece) both on update and restore. And very often not much of it has to be executed. It would be uncommon that the first piece extracted in one of the loops would be the moved piece, or would have the captured piece as its only attacker. So typically only a single iteration of one of the two loops would be performed.

The bad thing is that this was not all yet: we still need to address cases (4) and (5)! It was assumed we would do that first. Case (4) is the easier one: the moved piece will now have the former protectors of the piece that was captured as attackers. In other words, if the moved piece is valuable enough that its loss would spoil the fun, we have to test whether its victim was protected.

Code: Select all

if(value[u->piece] > gap) {
   int kind = u->victim >> 3 & 3; // determine kind of victim (w/b, P/non-P)
   int protectors = summary[OPPO_P][kind] + summary[OPPO_X][kind]; // attack counters on pieces of this kind
   int counter = 15*victim2unit[u->victim]; // counter bits for this piece
   if(protectors & counter) return 0; // the counter was non-zero (i.e. the victim was protected)
 }
That leaves case (5), the (soft-)pinning. For this we would have to loop through all opponent sliders that attacked the moved piece, and test whether what they hit instead belongs to the moving player, and is valuable enough. With the especially nasty case that what they hit now seems an opponent, but actually si the piece that the current move will capture, so that it is really the mover. (And the discovered slider attack that captured it was not amongst the protectors of the captured piece; it was an X-ray.) The upside is that only a small minority of the pieces will be attacked by an enemy slider. So very often there is nothing to do at all.

Code: Select all

int sliders = attackers[OPPO_X][u->piece] & SLIDERS; // slider attackers of the moving piece
while(sliders) {
  int bit = BSF(sliders);
  int piece = xstm + 8 + (bit >> 2);   // enemy piece corresponding to this bit in attackers set
  int from = location[piece];          // where the attack was coming from
  int d = direction[u->from-from];     // direction in which it was going
  int to = neighbor[u->from].d[d];     // downstream target
  if(to == u->to) { // moving piece discovered an attack on itself!
    if(value[u->piece] > gap) return 0; // this spoils the fun
  } else {
    int target = board[to];             // what is found there
    if(!(target & xstm)) {              // it is a piece of the moving player (not enemy or edge guard)
      if(value[target] > gap) return 0; // this also spoils the fun
    }
  }
  sliders &= sliders - 1;
}
Note that the presented code for the Overkill routine is exactly what you have to do for testing whether a move exposes your King; only in that case 'gap' has to be set larger than Queen. But it is rather inefficient for that; it would be much faster to just determine the direction the evacuated square lies in from the King, and extend a virtual slider attack of the King to that square, testing whether it hits a slider moving towards it. The test whether the captured piece was protected could serve a dual purpose, though. By moving that out of Overkill(), and doing it for any move being made, we could use the information to abort King moves, as well as for refraining from searchless fail-highs:

Code: Select all

int protectedVictim = IsProtected(u->victim);
if(protectedVictim && value[u->piece] == KINGVALUE) PRUNE;
int gap = lazyEval - beta - MARGIN;
if(gap > 0) {
  if(!(protectedVictim && value[u->piece] > gap) && Overkill(u)) FAIL_HIGH;
}
The bottom line is that the Overkill() test is doable, and on average pretty fast. But that it will require a lot of (rather bug-prone) code to handle all special cases.
User avatar
hgm
Posts: 27788
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: The mailbox trials

Post by hgm »

Well, I started implementing all this, for speed testing. But I pretty much have to rewrite everything. To start with something relatively simple, which I can hope to get quickly running without bugs, I made a routine MapFromScratch(). This runs through the piece list, and calls AddMoves() for every piece on the board to add its moves to an (initially initialized empty) attack map. I can use that as a reference for checking whether any incremental update reproduces it. But for now I just implemented a stack of attack maps, so it can create a fresh map in every node without disturbing the maps of any node closer to the root.

Code: Select all

#define ATTACKERS(KIND, VICTIM) attackers[VICTIM - 16 + 32*(KIND)]

int attackersStack[12800];
int summaryStack[400][4];
int *attackers = attackersStack;
int (*summary)[4] = summaryStack;

int attacker2bit[] = {
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
};

int victim2unit[] = {
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
};

int AddMoves(int piece, int sqr, int addsub)
{ // generate moves for the piece from the square, and add or remove them from the attack map
  int kind = piece >> 3 & 3;
  int *sum = summary[kind];                // summary section for this kind of attacker
  int *att = &ATTACKERS(kind, 0);          // attack map section for attackers of this kind
  int bit = attacker2bit[piece-16]*addsub; // addsub is 1 or -1, and determines we set or clear
  if(code[piece] & 0200) { // piece is slider
    int d, dir = firstSlide[slideOffs[piece-16] + sqr];
    while((d = slides[dir++])) {           // direction nr (+1)
      int to = neighbor[sqr].d[d-1];       // the directions were offset by 1 to avoid the 0 sentinel
      int victim = board[to];
      if(victim != COLOR) {                // ignore edge guards
        int victimKind = victim >> 3 & 3;
        int unit = victim2unit[victim-16]; // LSB of counter for this victim
        sum[victimKind] += unit;           // increment / decrement counter
        att[victim] += bit;                // set/clear the bit for this attacker
      }
    }
  } else {
    int v, dir = firstLeap[slideOffs[piece-16] + sqr];
    while((v = leaps[dir++])) {
      int to = sqr + v;                    // target square
      int victim = board[to];              // occupant of that square
      if(victim) {                         // ignore empty squares
//printf("to=%02x, victim=%d\n", to, victim);
        int victimKind = victim >> 3 & 3;
        int unit = victim2unit[victim-16]; // LSB of counter for this victim
        sum[victimKind] += unit;           // increment / decrement counter
        att[victim] += bit;                // set/clear the bit for this attacker
      }
    }
  }
}
The first step will then be to implement the new method of capture generation & sorting using these from-scratch maps. To eventually search the extracted captures I already wrote a routine SearchCapture(), which can be called with a move (specified as (attacker, victim) rather than (fromSqr, toSqr), passed in the UndoInfo struct) to make, search and unmake it, and process the returned score. To make the latter possible the alpha of the caller (which tracks the best score in this fail-hard implementation) is now also put in the UndoInfo struct (under the name parentAlpha), so that SearchCapture() has access to it.

Code: Select all

typedef struct {
  long long int hashKey, oldKey; // keys
  Neighbor savedNeighbor;        // 8 neighbors
  int pstEval, oldEval, curEval; // scores
  int alpha, beta, parentAlpha;  // scores
  int from, to;                  // squares
  int piece, victim, nextVictim; // pieces
  int depth;                     // depth
} UndoInfo;

int SearchCapture(UndoInfo *u)
{ // make / search / unmake and score minimaxing all in one

  // decode move
  int score;
  u->to = location[u->victim];

  // detect futility at earliest opportunity
  u->pstEval = u->oldEval + PST(u->victim, u->to);
  if(u->depth <= 0 && u->pstEval < u->parentAlpha - MARGIN) return 1; // futility (child will stand pat)

  // finish decoding move and updating evaluation
  u->from = location[u->piece];
  u->pstEval = -(u->pstEval + PST(u->piece, u->to) - PST(u->piece, u->from));

  // update hash key, and possibly abort on repetition
  u->hashKey =   u->oldKey  ^ KEY(u->piece, u->to) ^ KEY(u->piece, u->from) ^ KEY(u->victim, u->to);
  // if(REPEAT) score = 0; else

  // committed to move
  {
    // apply move to board
    board[u->from] = 0;
    board[u->to]   = u->piece;
    location[u->piece]  = u->to;
    location[u->victim] = CAPTURED;

    Evacuate(u->from); // update neighbor table
    attackers += 128;  // reserve space for fresh attack map
    summary   += 4;
    MapFromScratch(attackers, (int*) summary);

    u->beta = -u->parentAlpha;
    score = - Search(u);

    // unmake move
    board[u->from] = u->piece;
    board[u->to]   = u->victim;
    location[u->piece]  = u->from;
    location[u->victim] = u->to;

    attackers -= 128;
    summary   -= 4;
    Reoccupy(u->from); // restore neighbor table
  }

  // minimax
  if(score > u->parentAlpha) {
    u->parentAlpha = score;
    if(score + u->alpha >= 0) { // u->alpha = -parentBeta!
      return 1; // signal cutoff to caller
    }
  }
    return 0; // no cutoff requested
}
The generation of the attack map seems to work; I used it on the KiwiPete position, and visual inspection of the resulting map did not reveal any errors:

Code: Select all

piece             attackers                  piece
nr        black               white         location
    pppppppp  kqrrbbnn  PPPPPPPP  KQRRBBNN
16. 00010000  00000011  00000010  00000010    P@d5
17. 00000000  00000010  00000000  01000010    P@e4
18. 00000000  00000000  00000000  00010010    P@a2
19. 00000000  00000000  00000000  00000000    P@b2
20. 00000000  00000000  00000000  00000000    P@c2
21. 00000000  00000000  00000000  11000000    P@f2
22. 10000000  00000000  00000000  01000000    P@g2
23. 00000000  00000000  00000000  00100000    P@h2
24. 00000000  00000000  00000000  00000000    N@e5
25. 01000000  00000000  00001000  01000100    N@c3
26. 00000000  00000000  00000000  10000000    B@d2
27. 00000000  00001000  00000000  11000010    B@e2
28. 00000000  00000000  00000000  00000000    R@a1
29. 00000000  00000000  00000000  00000000    R@h1
30. 00000000  00000000  01000000  00001001    Q@f3
31. 00000000  00000000  00000000  00110100    K@e1
32. 00000000  00010000  00000000  00000000    p@a7
33. 00000000  00000000  00000000  00000000    p@c7
34. 00000000  11000011  00000000  00000001    p@d7
35. 00000000  11000000  00000000  00000001    p@f7
36. 00001100  01000000  00000001  00000000    p@e6
37. 00001000  00000000  00000000  00000001    p@g6
38. 00000000  01000000  00000000  00000000    p@b4
39. 00000000  00100000  01000000  01000000    p@h3
40. 00000011  00000000  00000000  00000000    n@b6
41. 00000000  01000100  00000000  01000000    n@f6
42. 00000000  00000000  00000000  00000000    b@g7
43. 00000000  00000000  00000000  00001000    b@a6
44. 00000000  00000001  00000000  00000000    r@a8
45. 00000000  00000100  00000000  00000000    r@h8
46. 00000000  10000000  00000000  00000000    q@e7
47. 00000000  01110010  00000000  00000000    k@e8

summary (packed counters for  nr of attacks):
 0. 00002100  20000000  10000010  01000000  black P attackers
 1. 10421011  02001114  21000000  00010000  black non-P attackers
 2. 00001001  00000000  10000000  01000010  white P attackers
 3. 00110101  01010000  12200211  02130023  white non-P attackers
    pppppppp  nnbbrrqk  PPPPPPPP  NNBBRRQK
           black              white
                    victims
User avatar
hgm
Posts: 27788
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: The mailbox trials

Post by hgm »

OK, some more results. Because the time needed to do an 8-ply search was becoming a bit short (leading to a large relative error in the timing), I switched to doing 9-ply searches. For the nps this makes no difference, as the result of the old version (based on the neighbor table) shows:

Code: Select all

 1  -16       718 0 e2a6 b4c3 b2c3 e6d5
 2  -16      8338 0 e2a6 b4c3 b2c3 e6d5
 3  -27     11701 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
 4  -27     69990 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
 5  -37    147413 0 e2a6 e6d5 c3d5 b6d5 e4d5 e7e5
 6  -32    404904 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 7  -32    850164 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 8  -32   4723911 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 9  -96  12878122 0 d5e6 e7e6 e2a6 e6e5 g2h3 b4c3 d2c3 e5h5 f3f4
t =  2.140 sec
  12878122 nodes (6.0 Mnps)
  11935085 QS (92.7%)
   2074737 evals (17.4%)
    710266 stand-pats (6.0%)
   3605202 leaves (30.2%)
  11520171 move gens
captures: 93.1%
I now completed a version that generates the captures from an attack map. But it still creates the map from scratch in every node. Which of course defeats the purpose, as it generates these maps by normal (by attacker) move generation, and just stores them differently. So basically it is just using the intermediate stage of the map for sorting the captures it generates. So a slowdown can be expected. (Especially since it has to clear the entire map before it can start adding the attacks to it, something you don't have to do for a move list.) But even then my first test was disappointing:

Code: Select all

 1  -16       596 0 e2a6 b4c3 b2c3 e6d5
 2  -16      2804 0 e2a6 b4c3 b2c3 e6d5
 3  -27      6785 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
 4  -27     75631 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
 5  -37    178653 0 e2a6 e6d5 c3d5 b6d5 e4d5 e7e5
 6  -32    523512 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 7  -32   1144515 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 8  -32   5423298 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 9  -96  14354167 0 d5e6 e7e6 e2a6 e6e5 g2h3 b4c3 d2c3 e5h5 f3f4
t =  6.140 sec
  14354167 nodes (2.3 Mnps)
  13315411 QS (92.8%)
   2371770 evals (17.8%)
    852751 stand-pats (6.4%)
  12747275 leaves (95.7%)
     35984 move gens
captures: 93.3%
Only 2.3Mnps! (But at least the same scores and PVs; since the move ordering is somewhat different we cannot expect exactly the same node counts.) But of course I was doing something stupid here: the MapFromScratch() calculated a complete attack map for both players, while the move generation only needs the attacks by the side to move. Having that for both players is only useful for incrementally updating it 2 ply later. So I changed it to only generate attacks for one player. After realizing that this would also require me to recalculate a map after null move, this gave the following result:

Code: Select all

 1  -16       596 0 e2a6 b4c3 b2c3 e6d5
 2  -16      2804 0 e2a6 b4c3 b2c3 e6d5
 3  -27      6785 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
 4  -27     75631 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
 5  -37    178653 0 e2a6 e6d5 c3d5 b6d5 e4d5 e7e5
 6  -32    523512 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 7  -32   1144515 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 8  -32   5423298 0 e2a6 e6d5 c3d5 b6d5 a6b7 a8b8 b7d5 f6d5 e5f7 e7f7 e4d5 g7b2 f3f7 e8f7
 9  -96  14354167 0 d5e6 e7e6 e2a6 e6e5 g2h3 b4c3 d2c3 e5h5 f3f4
t =  3.330 sec
  14354167 nodes (4.3 Mnps)
  13315411 QS (92.8%)
   2371770 evals (17.8%)
    852751 stand-pats (6.4%)
   4823471 leaves (36.2%)
     35984 move gens
captures: 93.3%
OK, so we dropped from 6.0Mnps to 4.3Mnps. Part of this is due to the need for clearing the map when generating a new one; when I still cleared the entire map (but then only added the attacks by pieces of the side to move) the speed was 3.4 Mnps (4.26 sec). Clearing only the part for stm attacks on opponent pieces (1/4 of the map) took off 0.92 sec. Extrapolating from that 0.31 sec of the 3.34 still goes into this clearing, and without that we would do slightly over 4.7Mnps. One reason we are slower is that the AddMoves() routine that generates moves for a single piece (adding those to the attack map) does not only generate true attacks, but also protections. (And typical chess positions are such that there usually are many more of the latter.) This is also done for the benefit of the incremental update (because protections turn into attacks when their target gets captured). But it is wasted effort now.

We will have to earn this investment back by generating the attack map of the node through incremental update of the one in the parent. Such an update would require only 3 calls to AddMoves() (two for the moved piece, one for the captured piece), instead of calling it for all pieces. Unfortunately we would have to make these same calls (well, with opposit sign for the modification) to restore the map to the old state. Copy-make doesn't seem very attractive, due to the large map side. There it really hurts that the map is stored in such a sparse format, using only every 4th bit in a word, to make merging easy. Possible cure for that is store the attackers and protectors in the same word (masking away the protectors before merging, through color-dependent move-generation code). Or switch to using every 2nd bit instead of every 4th, so that you can easily merge the attackers of only 2 victims, and do something complex in the (hopefully rare) case that 3 or 4 minors are attacked. Anyway, this is of later concern.

The code that achieved the above result is shown below. To avoid boiler-plate code I implemented the loops that merge the attackers sets and that finally play the moves as a preprocessor macro.

Code: Select all

#define ATTACKERS(KIND, VICTIM) attackers[VICTIM - 16 + 32*(KIND)]

int attackersStack[12800];
int summaryStack[400][4];
int *attackers = attackersStack;
int (*summary)[4] = summaryStack;

int attacker2bit[] = {
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
  1, 1<<4, 1<<8, 1<<12, 1<<16, 1<<20, 1<<24, 1<<28,
};

int victim2unit[] = {
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
  1<<28, 1<<24, 1<<20, 1<<16, 1<<12, 1<<8, 1<<4, 1,
};

int clearMask[] = {
  0xFFFFFFF0, 0xFFFFFF0F, 0xFFFFF0FF, 0xFFFF0FFF, 0xFFF0FFFF, 0xFF0FFFFF, 0xF0FFFFFF, 0x0FFFFFFF,
};

int valueGroup[] = { // maps bit numbers to masks for counters of the value group the bit belongs to
  0x0000000F, 0x0000000F, 0x0000000F, 0x0000000F, 0x000000F0, 0x000000F0, 0x000000F0, 0x000000F0,
  0x0000FF00, 0x0000FF00, 0x0000FF00, 0x0000FF00, 0x0000FF00, 0x0000FF00, 0x0000FF00, 0x0000FF00,
  0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000,
  0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000, 0xFFFF0000,
};

#define BSF(X) asm(" bsf %1, %0\n" : "=r" (bit) : "r" (X))

#define TREATGROUP(ATTOFFS, FRIEND, FOE) \
/* COL = attacker color (W=0, B=2)       */\
/* BASE = lowest attacker piece number   */\
/* KIND = kind of attackers (PWN or PCE) */\
    if(group) {                                  /* group could not be attacked at all       */\
      int victimStack[4];                        /* */\
      int captures = 0;                          /* for merging the attacker sets            */\
      int *victimSP = victimStack + 4;           /* clear victim stack                       */\
      do {                                       /* loop over victims in value group         */\
        int bit; BSF(group); bit >>= 2;          /* get next attacked one                    */\
        int victim = FOE - bit;                  /* calculate its piece number               */\
        *--victimSP = victim;                    /* remember it by pushing on victim stack   */\
        captures *= 2;                           /* shift accumulated attackers to make room */\
        captures += attackers[ATTOFFS+victim];   /* accumulate attackers of this victim      */\
        group &= clearMask[bit];                 /* clear counter for this victim            */\
      } while(group);                            /* next victim (if any)                     */\
      do {                                       /* loop over identified captures            */\
        int bit; BSF(captures);                  /* find next in LVA order                   */\
        undo.victim = victimSP[bit&3];           /* get its victim                           */\
        undo.piece = FRIEND + (bit >> 2);        /* calculate the attacker                   */\
        if(SearchCapture(&undo)) goto cutoff;    /* and search the capture                   */\
        captures &= captures - 1;                /* done with this capture; clear it         */\
      } while(captures);                         /* next capture (if any)                    */\
    }

int AddMoves(int piece, int sqr, int addsub)
{ // generate moves for the piece from the square, and add or remove them from the attack map
  int kind = piece >> 3 & 3;
  int *sum = summary[kind];                // summary section for this kind of attacker
  int *att = &ATTACKERS(kind, 0);          // attack map section for attackers of this kind
  int bit = attacker2bit[piece-16]*addsub; // addsub is 1 or -1, and determines we set or clear
  if(code[piece] & 0200) { // piece is slider
    int d, dir = firstSlide[slideOffs[piece-16] + sqr];
    while((d = slides[dir++])) {           // direction nr (+1)
      int to = neighbor[sqr].d[d-1];       // the directions were offset by 1 to avoid the 0 sentinel
      int victim = board[to];
      if(victim != COLOR) {                // ignore edge guards
        int victimKind = victim >> 3 & 3;
        int unit = victim2unit[victim-16]; // LSB of counter for this victim
        sum[victimKind] += unit;           // increment / decrement counter
        att[victim] += bit;                // set/clear the bit for this attacker
      }
    }
  } else {
    int v, dir = firstLeap[slideOffs[piece-16] + sqr];
    while((v = leaps[dir++])) {
      int to = sqr + v;                    // target square
      int victim = board[to];              // occupant of that square
      if(victim) {                         // ignore empty squares
        int victimKind = victim >> 3 & 3;
        int unit = victim2unit[victim-16]; // LSB of counter for this victim
        sum[victimKind] += unit;           // increment / decrement counter
        att[victim] += bit;                // set/clear the bit for this attacker
      }
    }
  }
}

void MapFromScratch(int *attackMap, int *sums)
{
  int i;
if(stm == WHITE) {
  for(i=80; i<96; i++) attackMap[i] = 0;
  for(i=112; i<128; i++) attackMap[i] = 0;
  for(i=8; i<16; i++) sums[i] = 0;
} else {
  for(i=0; i<16; i++) attackMap[i] = 0;
  for(i=032; i<48; i++) attackMap[i] = 0;
  for(i=0; i<8; i++) sums[i] = 0;
}
  for(i=0; i<16; i++) {
    int sqr = location[i+stm];
    if(sqr != CAPTURED) AddMoves(stm + i, sqr, 1);
  }
}

int SearchCapture(UndoInfo *u);

int Search(UndoInfo *u) // pass all parameters in a struct
{
  UndoInfo undo;
  int pvMove;
  int first = msp, curMove, noncapts = 10000, searched;
  undo.pv = pvPtr;
  nodeCnt++;
  *pvPtr++ = 0; // empty PV
  undo.parentAlpha = u->alpha;

if(PATH) printf("%2d    Search(%d,%d,%d) eval=%d\n", ply, u->alpha, u->beta, u->depth, u->pstEval), fflush(stdout);
  // QS / stand pat
  if(u->depth <= 0) {
    qsCnt++;
    if(u->pstEval > undo.parentAlpha - MARGIN) { // don't bother with full eval if hopeless
      undo.curEval = Evaluate(u->pstEval); evalCnt++;
      if(undo.curEval > undo.parentAlpha) {
        if(undo.curEval >= u->beta) { patCnt++; return u->beta; }
        undo.parentAlpha = undo.curEval;
      }
    }
  } else if(u->pstEval > u->beta - MARGIN) { // null-move pruning
    int score;
    undo.hashKey =  u->hashKey ^ STMKEY;
    undo.pstEval = -u->pstEval;
    undo.alpha   = -u->beta;
    undo.beta    = 1 - u->beta;
    undo.depth   = (u->depth > 3 ? u->depth - 3 : 0);
    undo.from    = -1;
    stm ^= COLOR; path[ply++]=0;
    attackers += 128; summary += 4;
    MapFromScratch(attackers, (int*) summary);
    score = -Search(&undo);
    attackers -= 128; summary -= 4;
    stm ^= COLOR; ply--;
    if(score >= u->beta) return u->beta;
  }

  // generate moves
  if(followPV >= 0) {              // first branch
    pvMove = pv[followPV++];       // get the move
    if(pvMove) {
      moveStack[msp++] = pvMove;   // and add it to move list
    } else followPV = -1;
  } else pvMove = 0;
if(PATH) printf("pvMove = %08x msp=%d\n", pvMove, msp), fflush(stdout);
  // set child & make-move parameters that are always the same
  undo.oldKey  =  u->hashKey ^ STMKEY;
  undo.oldEval =  u->pstEval;
  undo.depth   =  u->depth - 1;
  undo.alpha   = -u->beta;
  stm ^= COLOR;
  searched = 0;
{
  int STM = stm - WHITE >> 3;
  int total = summary[STM][3-STM];             // Pawn attackers (of non-P)                
  int group;                                                                               
  total += summary[STM+1][3-STM];              // add to piece attackers                   
  if(total & 15) {                             // King can be captured
    undo.parentAlpha = u->beta; goto cutoff;   // beta cutoff
  }
  while(total) {                               // loop over value groups                   
    int bit; BSF(total);                       // next attacked group                      
    int groupMask = valueGroup[bit];           // counter bits for noncapt'd group members 
    group = summary[STM][3-STM] & groupMask;   // Pawn attackers                           
    TREATGROUP(32*STM-16, 48-stm, stm+15)      // search all P x other                     
    group = summary[STM+1][3-STM] & groupMask; // non-Pawn attackers                       
    TREATGROUP(32*STM+16, 56-stm, stm+15)      // search all other x other                 
    total &= ~groupMask;                       // clear counters for entire value group    
  }
  group = summary[STM][2-STM] & 0xFFFF;
  TREATGROUP(32*STM-16, 48-stm, stm+7)         // P x P1-3
  group = summary[STM][2-STM] & 0xFFFF0000;
  TREATGROUP(32*STM-16, 48-stm, stm+7)         // P x P4-8
  group = summary[STM+1][2-STM] & 0xFFFF;
  TREATGROUP(32*STM+16, 56-stm, stm+7)         // other x P1-3
  group = summary[STM+1][2-STM] & 0xFFFF0000;
  TREATGROUP(32*STM+16, 56-stm, stm+7)         // other x P4-8
}

  if(u->depth <= 0) goto cutoff;
  GenNoncapts(); genCnt++;

  // move loop
  for(curMove = first; curMove < msp; curMove++) {
    int score;
    unsigned int move;

    move = moveStack[curMove];

    if(move == pvMove && !followPV) continue; // duplicat moves

    // recursion
    undo.beta = -undo.parentAlpha;
    score = MakeMove(move, &undo); // rejected moves get their score here
    if(score < -INF) break;        // move is futile, and so will be all others
    if(score > INF) { // move successfully made
      searched++;
      score = -Search(&undo);
      UnMake(&undo);
    }
    // minimaxing
    if(score > undo.parentAlpha) {
      int *p;
      if(score >= u->beta) {
        undo.parentAlpha = u->beta;
        break;
      }
      undo.parentAlpha = score;
      p = pvPtr; pvPtr = undo.pv; // pop old PV
      *pvPtr++ = move;            // push new PV, starting with this move
      while((*pvPtr++ = *p++)) {} // and append child PV
    }
  }
 cutoff:
  stm ^= COLOR;
 abort:
  leafCnt += !searched;
  msp = first;         // pop move list
  pvPtr = undo.pv;     // pop PV (but remains above stack top)
  return undo.parentAlpha;
}

int SearchCapture(UndoInfo *u)
{ // make / search / unmake and score minimaxing all in one
  int move;

  // decode move
  int score;
  u->to = location[u->victim];

  // detect futility at earliest opportunity
  u->pstEval = u->oldEval + PST(u->victim, u->to);
  if(u->depth <= 0 && u->pstEval < u->parentAlpha - MARGIN) return 1; // futility (child will stand pat)\}

  // finish decoding move and updating evaluation
  u->from = location[u->piece];
  u->pstEval = -(u->pstEval + PST(u->piece, u->to) - PST(u->piece, u->from));

  // update hash key, and possibly abort on repetition
  u->hashKey =   u->oldKey  ^ KEY(u->piece, u->to) ^ KEY(u->piece, u->from) ^ KEY(u->victim, u->to);
  // if(REPEAT) score = 0; else

  // committed to move
  move = MOVE(u->from, u->to);
  {

    // apply move to board
    board[u->from] = 0;
    board[u->to]   = u->piece;
    location[u->piece]  = u->to;
    location[u->victim] = CAPTURED;

    Evacuate(u->from);
    attackers += 128;
    summary   += 4;
    MapFromScratch(attackers, (int*) summary);

    path[ply++] = move; captCnt[0]++;
    u->beta = -u->parentAlpha;
    score = - Search(u);
    ply--;

    // unmake move
    board[u->from] = u->piece;
    board[u->to]   = u->victim;
    location[u->piece]  = u->from;
    location[u->victim] = u->to;

    attackers -= 128;
    summary   -= 4;
    Reoccupy(u->from);
  }

  // minimax
  if(score > u->parentAlpha) {
    int *p;
    u->parentAlpha = score;
    if(score + u->alpha >= 0) { // u->alpha = -parentBeta!
      return 1;
    }
    p = pvPtr; pvPtr = u->pv;   // pop old PV
    *pvPtr++ = move;            // push new PV, starting with this move
    while((*pvPtr++ = *p++)) {} // and append child PV
  }
  return 0;
}
User avatar
hgm
Posts: 27788
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: The mailbox trials

Post by hgm »

Incremental update of the attack map

We are now in a position to replace the generation from scratch with an incremental update of the attack map. We will use a make-unmake strategy, i.e. the attack map will be modified in place. The order in which the changes are applied is critical here.

1) 'Disable' the captured piece (i.e. erase all its attacks from the map, possibly including the attack on the moved piece).
2) Disable the moved piece (including its attack on the piece it is now capturing).
3) Discover the slider attacks on the moved piece (from which any of the captured piece have already removed). This could create an attack on the piece we are capturing, which is still sitting on the board as 'dead wood'.
4) Replace the attacks on the moved piece by those on the captured piece. This includes copying of the attackers sets of the latter, as well as copying its summary counters to those of the moved piece.
5) Clear the summary counters of the captured piece. (The attackers sets can stay; these would not be consulted when the summary counter is 0.)
6) Add the moves of the moved piece from its new location.

For step (1) and (2) it is important that the mover and victim are still in their original location, or the attacks to be removed would be mapped to the wrong victim. That also applies for the captured piece in step (3). Step (4) and (5) do not reference the board, and can probably be merged, as when we are doing the bit juggling needed to copy one packed counter to another we might as well take the opportunity to clear the counter we are copying. During (6) the moved piece should no longer be on the from-square, or it would put a protection on itself in the attack map! So the board update will have to take place between (3) and (6).

Code: Select all

AddMoves(u, u->victim, u->to, -1);
AddMoves(u, u->piece, u->from, -1);
Discover(u, u->piece, u->from);
int pKind = u->piece >> 3 & 3;                // table lookups perhaps faster?
int vKind = u->victim >> 3 & 3;               // always != pKind: opposit color
int pShift = victim2bitnr[u->piece];          // location of counter in summary word
int vShift = victim2bitnr[u->victim];
int pMask = ~(15 << pShift);                  // masks to clear counter bits
int vMask = ~(15 << vShift);
for(kind=0; kind<4; kind++) {
  int offset = 32*kind - 16;                  // offset for attackers of this kind in attack map
  aSave[kind] = attackers[offset + u->piece]; // save attackers on old piece location
  attackers[offset + u->piece] = attackers[offset + u->victim];
  pSave[kind] = summary[kind][pKind];         // save attacks counts on moved piece
  vSave[kind] = summary[kind][vKind];         // save attacks counts on captured piece
  summary[kind][pKind] &= pMask;              // clear attacks counts on moved piece
  summary[kind][pKind] |= (summary[kind][vKind] >> vShift & 15) << pShift;
  summary[kind][vKind] &= vMask;              // clear attacks counts on captured piece
}
// perform move on board
board[u->to]   = u->piece;
board[u->from] = 0;

AddMoves(u, u->piece, u->to, +1);
For restoring the map we will need to save the data we irreversibly overwrite, i.e. the summary counters and attackers sets of the moved piece. But it is probably more efficient to remember what we modify, and safe all old values. The unmake would then be a simple loop that copies the stored info back to the map. Otherwise we would have to run through all the steps in reverse order, to reverse their operation. This means AddMoves() will have to safe index (or a pointer to) and value of every attackers set and summary word in which it puts or removes an attack.

Code: Select all

// restore attack map
for(i=0; i<undoSP; i++)   *modifiedElement[i] = savedValue[i];
That looks pretty simple, but how much work it is depends of course on how many elements got changed. Pieces can make up to 8 attacks, so that alone could give 24 modified attackers sets. In practice some of the attacks will hit the board edge. Or, in the case of leapers, hit empty squares. And some sliders have only 4 attacks in the first place, and Pawns only 2. So the typical number will probably be more like 12. And then there are the summary counters. There are only 16 of those in total. And 8 of them will change for sure, in the process of inheriting the attacks on the victim, and clearing the latter. (They might already have been 0, but to figure that out would probably take even longer.) And the number of attacks on pieces of other kinds could also change, made by the moved or captured piece, but also by discovered sliders of either color. So we might as well assume that all summaries change, and just copy the entire array. Which means that copy-make would be the most efficient way to handle the summaries.
Mike Sherwin
Posts: 860
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: The mailbox trials

Post by Mike Sherwin »

But did you notice total number of nodes searched?
12878122 nodes (6.0 Mnps)
14354167 nodes (2.3 Mnps)
There is no point making it faster if it searches more nodes. :(
Dann Corbit
Posts: 12538
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: The mailbox trials

Post by Dann Corbit »

Mike Sherwin wrote: Sat Apr 03, 2021 2:31 pm But did you notice total number of nodes searched?
12878122 nodes (6.0 Mnps)
14354167 nodes (2.3 Mnps)
There is no point making it faster if it searches more nodes. :(
what if it also becomes much stronger
Taking ideas is not a vice, it is a virtue. We have another word for this. It is called learning.
But sharing ideas is an even greater virtue. We have another word for this. It is called teaching.
User avatar
hgm
Posts: 27788
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: The mailbox trials

Post by hgm »

Yes, I did notice that. But I am not sure this is a systematic effect. Tree size can be extremely sensitive to move ordering, in a very noisy way. Swapping the order of two PxN captures in some position can easily double the tree size. To judge whether one move ordering is better than an other you would really have to average the tree size for a thousand positions or so. On a single position the effect is almost entirely decided by luck.

I tried to keep the move ordering a similar as possible, by using a sort key like 16*victimValue - attackerNumber rather than 16*victimValue - attackerValue. So even for equal attackers it prefers to try the one with the lowest piece number first. But there was no predictable order between equivalent victims, if they could be captured by the same piece. It would just depend on the order in which the by-piece generation discovered them. I could try to modify the old version to also work the piece number of the victim in the sort key.

But the main difference in move ordering is because the Pawns were split into two groups, For the PxP captures this could still be mimicked by different MVV keys for the various Pawns. But for the non-P x P captures the order is not strictly LVA anymore. I don't expect this to have a systematic effect, though. You would have to order by SEE to do the best thing here, and be not such much dependent on luck for picking the capture of the unprotected Pawn rather than the protected Pawn. This would have to be tested. It would not be that costly in terms of execution time to force strict LVA order on the non-P x P captures (just extracting from the two merged groups of 4 Pawns simultaneously, and always trying the one with the LVA first). But even if there would be an improvement by this on average, I don't expect that improvement to be nearly as much as you would get by sorting on SEE. Or at least postponing those on protected victims, and then doing the multiply-attacked victims that were not protected by Pawns (could still have SEE > 0 if the protector is more valuable than the attacker), and then those Protected by Pawns (best result 2 Pawns for a piece), and finally those not multiply attacked (one Pawn for a piece). Or perhaps the last two categories should be pruned completely, in QS.
Mike Sherwin
Posts: 860
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: The mailbox trials

Post by Mike Sherwin »

Dann Corbit wrote: Sat Apr 03, 2021 2:47 pm
Mike Sherwin wrote: Sat Apr 03, 2021 2:31 pm But did you notice total number of nodes searched?
12878122 nodes (6.0 Mnps)
14354167 nodes (2.3 Mnps)
There is no point making it faster if it searches more nodes. :(
what if it also becomes much stronger
It is only a move ordering experiment. At one extreme we can do zero work ahead of time and just do a full move generation to list of moves and do a one pass sort to hopefully get the best move the first time. However, if the tt move or the pv move if there is no tt is played and causes a cutoff then zero pre work is done for all those moves not searched. At the other extreme a boatload of work can be done to incrementally keep attack tables up to date only for all that work to prove useless if the tt/pv move proves to be sufficient. For what Harm is trying to demonstrate to prove favorable it must result in an overall reduction in the number of nodes searched to reach depth in a time per node that does not cancel the benefits of searching fewer nodes. So on average per root position it must search fewer nodes and substantially so. So in this endeavor there must be a gain in search depth in a given time to be able to show a gain in strength. It will prove futile if time per node searched is 'doubled' if the tree size is not more than halved.

INHO this might work for regular search if it were possible that the number of Q-searches is greatly reduced. On the other hand if all that work is also done in Qsearch() then fewer Q-searches may not make a favorable trade off. Then there is the problem of the endgame that has far fewer pieces on the board and yet the amount of pre work stays the same, i.e. pre work / many pieces compared to pre work / few pieces is not going to be easy to justify.

Edit: The above logic supposes that there is some cheap moves like tt/pv or killer moves.

Or as Bob would say, do not do any work until it is necessary because it may not be necessary!
Mike Sherwin
Posts: 860
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: The mailbox trials

Post by Mike Sherwin »

hgm wrote: Sat Apr 03, 2021 3:11 pm Yes, I did notice that. But I am not sure this is a systematic effect. Tree size can be extremely sensitive to move ordering, in a very noisy way. Swapping the order of two PxN captures in some position can easily double the tree size. To judge whether one move ordering is better than an other you would really have to average the tree size for a thousand positions or so. On a single position the effect is almost entirely decided by luck.

I tried to keep the move ordering a similar as possible, by using a sort key like 16*victimValue - attackerNumber rather than 16*victimValue - attackerValue. So even for equal attackers it prefers to try the one with the lowest piece number first. But there was no predictable order between equivalent victims, if they could be captured by the same piece. It would just depend on the order in which the by-piece generation discovered them. I could try to modify the old version to also work the piece number of the victim in the sort key.

But the main difference in move ordering is because the Pawns were split into two groups, For the PxP captures this could still be mimicked by different MVV keys for the various Pawns. But for the non-P x P captures the order is not strictly LVA anymore. I don't expect this to have a systematic effect, though. You would have to order by SEE to do the best thing here, and be not such much dependent on luck for picking the capture of the unprotected Pawn rather than the protected Pawn. This would have to be tested. It would not be that costly in terms of execution time to force strict LVA order on the non-P x P captures (just extracting from the two merged groups of 4 Pawns simultaneously, and always trying the one with the LVA first). But even if there would be an improvement by this on average, I don't expect that improvement to be nearly as much as you would get by sorting on SEE. Or at least postponing those on protected victims, and then doing the multiply-attacked victims that were not protected by Pawns (could still have SEE > 0 if the protector is more valuable than the attacker), and then those Protected by Pawns (best result 2 Pawns for a piece), and finally those not multiply attacked (one Pawn for a piece). Or perhaps the last two categories should be pruned completely, in QS.
I did not sleep well last night so I probably should not be posting on such a complicated subject. Anyway though the technique I use for move ordering that no one seems to want to try is if depth > 3 (> 2 may be better) is to do a reduced search with a widened window on each of the remaining moves. The formula (depth > 3) is depth/4. All I can do is say that it works gaining on average ~1.5 ply deeper searches in a given time.
Mike Sherwin
Posts: 860
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: The mailbox trials

Post by Mike Sherwin »

FEN: r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1

Stockfish_21031012_x64_avx2:
1/1 00:00 1k 651k -2.87 d5xe6
1/1 00:00 1k 651k +0.27 Be2xa6
---------------------------------------------------------------------------
2/2 00:00 2k 1,172k -2.87 d5xe6 Ba6xe2
2/2 00:00 2k 1,172k -0.10 Be2xa6 b4xc3
---------------------------------------------------------------------------
3/3 00:00 3k 1,672k -2.87 d5xe6 Ba6xe2 Nc3xe2
3/3 00:00 3k 1,672k -0.10 Be2xa6 b4xc3 Bd2xc3
---------------------------------------------------------------------------
4/4 00:00 5k 2,473k -2.87 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6
4/4 00:00 5k 2,473k -0.42 Be2xa6 b4xc3 Bd2xc3 e6xd5
---------------------------------------------------------------------------
5/6 00:00 7k 3,689k -3.10 d5xe6 Ba6xe2 e6xf7+ Ke8-d8 Nc3xe2 Qe7xe5
5/5 00:00 7k 3,689k -0.82 Be2xa6 b4xc3 Bd2xc3 e6xd5 O-O-O
---------------------------------------------------------------------------
6/7 00:00 9k 4,405k -3.13 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4
6/6 00:00 9k 4,405k -0.82 Be2xa6 b4xc3 Bd2xc3 e6xd5 O-O-O Nf6xe4
---------------------------------------------------------------------------
7/7 00:00 15k 7,624k -3.13 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4
7/7 00:00 15k 7,624k -1.49 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O
---------------------------------------------------------------------------
8/9 00:00 36k 12,165k -3.10 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4
8/9 00:00 36k 12,165k -2.58 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4
---------------------------------------------------------------------------
9/11 00:00 62k 20,719k -3.10 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Rh1-g1 Rh8-h5 Qe4xb4
9/9 00:00 62k 20,719k -2.58 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4 f2-f4
---------------------------------------------------------------------------
10/14 00:00 107k 26,647k -2.58 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O
10/10 00:00 107k 26,647k -2.56 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5
---------------------------------------------------------------------------
11/15 00:00 166k 33,165k -2.58 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4
11/21 00:00 166k 33,165k -2.56 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4
---------------------------------------------------------------------------
12/18 00:00 228k 37,918k -2.57 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4
12/16 00:00 228k 37,918k -2.54 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 f2-f4 Nf6xe4 O-O Ke8-f8 Ba6-d3
---------------------------------------------------------------------------
13/15 00:00 297k 37,088k -3.00 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Kg1-h1 Rh8-h5 Ra1-e1
13/21 00:00 297k 37,088k -2.79 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3
---------------------------------------------------------------------------
14/22 00:00 487k 44,308k -2.87 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Ne5-d3 Nf6xe4 Bd2-e3 h3xg2 Qf3xg2 Rh8-h4 Ne2-f4 Qe6-f5 O-O-O O-O-O Rh1-e1 Rd8-e8 f2-f3 Ne4-d6 Kc1-b1 Re8xe3 Re1xe3 Rh4xf4 Nd3xf4
14/21 00:00 487k 44,308k -2.68 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ne4xc3 b2xc3 d7-d6 Bd3xg6 d6xe5
---------------------------------------------------------------------------
15/31 00:00 740k 46,251k -3.04 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Ra1-e1 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5
15/23 00:00 740k 46,251k -2.85 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3
---------------------------------------------------------------------------
16/27 00:00 1,065k 48,421k -2.96 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-g3 Qe3xf4 Rh1-f1 Qf4-c4 b2-b3 Qc4-c6 Qg2xc6 d7xc6 Rf1xf7 Rh8xh2
16/23 00:00 1,065k 48,421k -2.95 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O-O Nf6xe4 Ne5xg6 f7xg6 Qg2xg6+ Ke8-f8 Qg6-f5+ Bg7-f6 Rh1-e1 Qe7-h7 Bc3-b4+ d7-d6 Rd1xd5 Nb6xd5 Qf5xd5 Ra8-e8 Ba6-d3
---------------------------------------------------------------------------
17/26 00:00 1,457k 48,569k -3.24 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Kg1-h1 Rh8-h5 a2-a4 d7-d6 a4-a5 d6xe5 Bd3xe4 d5xe4
17/39 00:00 1,457k 48,569k -2.79 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1
---------------------------------------------------------------------------
18/34 00:00 2,301k 48,949k -3.30 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 f2-f4 Nf6xe4 O-O Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 Rf1-f3 Re8-e7 a2-a4 a7-a5 Bd3xe4 d5xe4
18/37 00:00 2,301k 48,949k -2.87 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 h3xg2 Qf3xg2 O-O-O a2-a3 b4-b3 c2xb3 Rd8-e8 Ra1-c1 Nf6-h5 Ne5-d3 Nh5xf4 Ne2xf4 Qe6xe4+ Qg2xe4 Re8xe4+ Nf4-e2 Rh8-h3 Rc1-d1 Re4-h4 Ke1-f1 Rh3xh2
---------------------------------------------------------------------------
19/34 00:00 3,367k 48,792k -2.72 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O-O Nf6xe4 Ne5xg6 f7xg6 Qg2xg6+ Ke8-f8 Qg6-f5+ Bg7-f6 Rh1-e1 Qe7-h7 Bc3-b4+ d7-d6 Rd1xd5 Nb6xd5 Qf5xd5 Ra8-e8 Ba6-d3
19/35 00:00 3,367k 48,792k -2.65 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 h3xg2 Qf3xg2 O-O-O a2-a3 b4-b3 c2xb3 Rd8-e8 O-O Nf6-h5 Ne5-d3 Qe6xe4 Qg2xe4 Re8xe4 Ra1-e1 Rh8-e8 Bf4-e3 Nb6-d5 Ne2-c1 Re8-h8 b3-b4 Nh5-f6 Nc1-b3 Re4-h4
---------------------------------------------------------------------------
20/30 00:00 4,707k 48,027k -2.90 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 f2-f4 Nf6xe4 O-O Ke8-f8 Ba6-d3 Ra8-e8 Ra1-d1 Rh8-h5 Bd3-e2 Ne4xc3 b2xc3 Rh5-h4 Be2-d3 d7-d6 Bd3xg6 d6xe5 f4xe5 Bg7xe5
20/42 00:00 4,707k 48,027k -2.48 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 Qd5-c6 Rf4xf7 Re8-e2 Nd3-c5
---------------------------------------------------------------------------
21/35 00:00 8,346k 45,857k -2.99 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Ra1-d1 Ne4xc3 b2xc3 d7-d6 Ne5xg6+ f7xg6 f4-f5 Bg7-e5 h2-h3 Rh8-h4 f5-f6 Qe7-d7 Bd3xg6 Qd7xh3 Qg2xh3 Rh4xh3 Bg6xe8
21/37 00:00 8,346k 45,857k -2.78 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Ne5-d3 Nf6xe4 Bd2-e3 h3xg2 Qf3xg2 Rh8-h4 O-O O-O-O Ne2-f4 Qe6-f5 Nf4-e2 Nb6-d5 Ne2-g3 Qf5-e6 Ra1-e1 Nd5xe3 f2xe3 d7-d5 Rf1-f4 Rh4xf4 Nd3xf4 Qe6-g4 Re1-f1 Qg4-g5 Ng3xe4 Qg5xg2+ Nf4xg2 d5xe4 Rf1xf7 Bg7xb2
---------------------------------------------------------------------------
22/35 00:00 12,635k 44,648k -3.19 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 f2-f4 Ke8-f8 O-O-O Nf6xe4 Rh1-e1 Qe7-h4 Re1xe4 d5xe4 Qg2xe4 Ra8-e8 Qe4-f3 Qh4xh2 Bc3-d2 Qh2-h5 Qf3-b7 Bg7xe5 f4xe5 Qh5xe5 Bd2-c3
22/32 00:00 12,635k 44,648k -2.70 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Ne5-d3 Nf6xe4 Bd2-e3 h3xg2 Qf3xg2 O-O-O a2-a3 b4-b3 c2xb3 Rd8-e8 O-O Qe6xb3 Ra1-d1 Rh8-h4 Be3-f4 g6-g5 Bf4xg5 Ne4xg5 Qg2xg5 Re8-e4 Ne2-f4
---------------------------------------------------------------------------
23/40 00:00 20,403k 43,503k -3.13 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 b2-b3 Qc4-b5 Qg2-f2 f7-f5 Rd1-e1 d7-d6 h2-h4 Nb6-d5 Qf2xa7 Qb5-c6
23/34 00:00 20,403k 43,503k -3.12 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 f2-f4 Nf6xe4 O-O Ke8-f8 Ba6-d3 Ra8-e8 Ra1-e1 d7-d6 Ne5-c6 Qe7-h4 Bc3-d4 Bg7xd4+ Nc6xd4 Rh8-h5 Bd3-b5 Re8-e7 Nd4-c6 Re7-e6 Bb5-e2 Ne4-d2 Be2xh5 Re6xe1 Qg2xd2
---------------------------------------------------------------------------
24/37 00:00 34,904k 42,514k -3.31 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 f2-f4 Nf6xe4 O-O Ke8-f8 Ba6-d3 Ra8-e8 Ra1-e1 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 Rf1-f3 a7-a5 Ne3-f1 Rh5-f5 Bd3xe4 Re8xe4 Re2xe4 d5xe4
24/37 00:00 34,904k 42,514k -2.83 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Ne5-d3 Nf6xe4 Bd2-e3 h3xg2 Qf3xg2 Rh8-h4 O-O O-O-O Ne2-f4 Qe6-f5 Nf4-e2 Qf5-b5 a2-a4 Qb5-a5 Ne2-g3 f7-f5 h2-h3 Rd8-h8 Ng3xe4 f5xe4 Nd3-f4 Bg7xb2 Ra1-b1 Bb2-c3 Nf4xg6
---------------------------------------------------------------------------
25/37 00:00 41,965k 42,389k -2.89 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Kg1-h1 Rh8-h5 Bd3-e2 Rh5-h6 Be2-d3 Ne4xc3 b2xc3 Bg7xe5 Ra1-e1 Nb6-c4 Bd3xc4 d5xc4 Re1xe5 Qe7-d8 Re5xe8+ Qd8xe8
25/45 00:00 41,965k 42,389k -2.84 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 h2-h3 Qf4-f5 h3-h4 a7-a5 Rh1-f1 Qf5-e4 Qg2-f2 f7-f5 Rf1-e1 Qe4-c4 b2-b3 Re8xe1
---------------------------------------------------------------------------
26/37 00:01 58,466k 42,001k -2.96 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Kg1-h1 Rh8-h5 Bd3-e2 Rh5-h7 Be2-d3 Qe7-h4 Bd3xe4 d5xe4 Ra1-e1 d7-d5 b2-b3 c7-c5 Bc3-a1 Bg7xe5 Ba1xe5 Nb6-d7 Be5-d6+ Kf8-g7 f4-f5 g6-g5 f5-f6+ Kg7-g6
26/51 00:01 58,466k 42,001k -2.67 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 h2-h4 Re8-e7
---------------------------------------------------------------------------
27/45 00:02 120,162k 40,459k -3.37 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 a2-a4 Nb6-c4 Bd3xe4 d5xe4 Qg2xe4 Nc4xe5 f4xe5 Rh8-h5 Ra1-e1 Bg7xe5 Bc3xe5 Rh5xe5 Qe4xe5 Qe7xe5 Re1xe5 Re8xe5 Rf1-f4 d7-d6 Rf4-c4 c7-c5 h2-h4 Kf8-e7 Kg1-g2 a7-a5 Kg2-f3 Re5-f5+ Kf3-g4 Ke7-d7
27/55 00:02 120,162k 40,459k -2.94 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 h2-h4 Nb6-d5 Rf4-f3 Rh8-h5 Qg1-a7 Kc8-d7 Qa7-f2 Re8-e7 Qf2-f1 Rh5-e5 Qf1-b5+ Kd7-c8 Nc1-b3 Nd5-e3 Qb5-a6+ Kc8-b8
---------------------------------------------------------------------------
28/43 00:03 130,125k 40,336k -3.13 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 Rf1-f3 Re8-e7 Rf3-f1 f7-f5 Bd3xe4 d5xe4 c3-c4 Qh4-f6
28/53 00:03 130,125k 40,336k -2.88 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-f6 Kc1-b1 Rd8-e8 Ne2-c1 Qf6xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 Qg1-f1 Rh8-h5 Rf4xf7 Rh5xh2 Rf7-f8 Re8xf8 Qf1xf8+ Kc8-d7
---------------------------------------------------------------------------
29/52 00:04 168,072k 39,903k -3.11 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 h2-h4 Rh8-h5 Qg1-f2 Re8-e7 Rf4-f6 Qe6-e3 Qf2-f1 Rh5xh4 Rf6xf7 Re7xf7 Qf1xf7 Qe3-e4
29/50 00:04 168,072k 39,903k -3.10 Be2xa6 b4xc3 Bd2xc3 h3xg2 Qf3xg2 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 a2-a4 a7-a5 f4-f5 Rh5-g5 f5xg6 Rg5xg2+ Re2xg2 Re8-e5 Bd3xe4 Re5xe4 Ne3-f5 Qh4-h3 Nf5-g3 Re4-g4 Rf1xf7+ Kf8-g8
---------------------------------------------------------------------------
30/55 00:05 216,049k 39,461k -3.25 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 a2-a4 a7-a5 f4-f5 Rh5-g5 f5xg6 Rg5xg2+ Re2xg2 Re8-e5 Bd3xe4 Re5xe4 Rf1xf7+ Kf8-g8 Rg2-g3 Qh4xg3+ h2xg3 Re4xe3
30/49 00:05 216,049k 39,461k -3.09 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 h2-h4 Nb6-d5 Rf4-f3 Rh8xh4 Qg1-a7 Nd5-e3 Rd1-e1 Rh4-e4 Nc1-d3 Kc8-d8
---------------------------------------------------------------------------
31/52 00:05 228,575k 39,376k -3.07 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Ra1-e1 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 Re2-e1 a7-a5 Bd3xe4 d5xe4 c3-c4 d6-d5 c4xd5 Nb6xd5 Ne3xd5 Rh5xd5 Re1xe4 Re8xe4 Qg2xe4 Rd5-d2 Qe4-a8+ Kf8-g7
31/46 00:05 228,575k 39,376k -3.01 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 Qg1-f1 Rh8-h5 Rf4xf7 Rh5xh2 Rf7-f4 Qe6-e5 Rf4-f8 Re8xf8 Qf1xf8+ Kc8-b7 Qf8-f3+ d6-d5 Nc1-b3 Nb6-c4 Nb3xa5+ Nc4xa5
---------------------------------------------------------------------------
32/43 00:06 256,763k 39,230k -2.90 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O Ne2-d4 Rh8-h4 Rh1-e1 Qe5-f6 Nd4-b3 d7-d5 Nb3-c5 Nb6-c4 Nc5-d3 a7-a5 f2-f4 Rh4xf4 Re1-f1 Rf4xf1 Rd1xf1 Qf6-e7 Rf1-e1 Qe7-h4 Kc1-b1 a5-a4 Re1-e2 Qh4-d4 Qg2-f2 Qd4xf2 Re2xf2 Nc4-d6 Nd3xb4 Rd8-e8 Nb4-d3
32/48 00:06 256,763k 39,230k -2.86 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 Re2-e1 a7-a5 Bd3-e2 Rh5-h6 Be2-d3 Ne4-f6 Qg2-g3 Rh6-h5 Re1-e2 Re8-e7 Rf1-e1 Qh4-h3
---------------------------------------------------------------------------
33/57 00:08 322,686k 39,000k -3.03 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Ra1-e1 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-h4 Nc2-e3 Kg7-f8 Re2-e1 f7-f5 a2-a4 a7-a5 b2-b4 Ne4xc3 b4xa5 Nb6xa4 a5-a6 c5-c4 a6-a7 c4xd3 Ne3xf5 Qh4xe1 Rf1xe1 Re8xe1+ Kg1-f2 Re1-e2+ Kf2-f3
33/53 00:08 322,686k 39,000k -2.99 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 h2-h4 Re8-e7 Qg1-f2 Rh8-h5 Rf4-f6 Qe6-e4 Rf6-f4 Qe4-e3 Rf4xf7 Qe3xf2 Rf7xf2 Rh5xh4 Rf2-g2 Re7-e6
---------------------------------------------------------------------------
34/57 00:14 541,364k 38,297k -3.15 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 Ra1-e1 Ke8-f8 f2-f4 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Re1-e2 Rh8-h5 Nc6xa7 Qh4-f6 c2-c3 Re8-h8 Na7-b5 c7-c6 Nb5-d4 Rh5xh2 Qg2xh2 Rh8xh2 Re2xh2 c6-c5 Nd4-b5 Qf6-f5 Bd3-c2 Qf5-e6 Nb5-c7 Qe6-g4+ Rh2-g2 Ne4-g3 Nc7-e8+ Kg7-f8
34/67 00:14 541,364k 38,297k -2.92 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O Ne2-d4 Rh8-h4 Nd4-f3 Qe5-f4+ Kc1-b1 Rh4-g4 Qg2-h3 Qf4-f5 Rd1-d3 Rg4-f4 Qh3xf5 Rf4xf5 a2-a3 Nb6-c4 Nf3-d4 Rf5xf2 a3xb4 Rd8-h8 h2-h3 Rf2-f4 Rh1-d1 Nc4-e5 Rd3-c3 Rh8-h5 Kb1-c1 Kc8-b7 Nd4-b3 d7-d6 b4-b5 Rh5-h4 Nb3-a5+ Kb7-b6 Rc3-a3 Kb6xb5 Rd1-d5+ Kb5-b6 Ra3-b3+ Kb6-a6
---------------------------------------------------------------------------
35/60 00:17 675,888k 38,055k -3.25 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 Ra1-e1 Ke8-f8 f2-f4 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6xa7 Rh8-h5 Re1-e2 Qh4-f6 c2-c3 Re8-h8 Na7-b5 Rh5xh2 Qg2xh2 Rh8xh2 Re2xh2 c7-c6 Nb5-d4 c6-c5 Nd4-b5 Kg7-f8 Rh2-g2 Nb6-c4 Bd3xc4 d5xc4 Nb5-c7 Qf6-f5 a2-a4 Kf8-e7 a4-a5 Ke7-d7 Nc7-d5 Qf5xd5
35/56 00:17 675,888k 38,055k -3.12 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 Bf4xe5 Qe6xe5 O-O-O O-O-O f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 Qg1-f1 Rh8-h5 h2-h4 Re8-e7 Nc1-d3 Nb6-d5 Rd1-e1 Qe6xe1+ Nd3xe1 Nd5xf4 Ne1-f3 Rh5-c5 Nf3-d4
---------------------------------------------------------------------------
36/52 00:22 856,559k 37,817k -3.37 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ba6-d3 Ra8-e8 Ra1-e1 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6xa7 Rh8-h5 Na7-b5 Re8-h8 Re1-e3 Qh4xh2+ Qg2xh2 Rh5xh2 Bd3xe4 d5xe4 Re3xe4 Nb6-d5 Rf1-e1 Rh2xc2 Re1-e2 Rc2-c5 a2-a4 Rh8-h4 b2-b4 Rc5-c6 Re2-f2 f7-f5 Re4-d4 Nd5-f6 Rf2-e2 Rc6-c1+ Kg1-g2 Nf6-e4 Re2xe4 f5xe4 Rd4xe4
36/63 00:22 856,559k 37,817k -3.17 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 Qg1-f1 Rh8-h5 Rf4xf7 Rh5xh2 Rf7-f4 Qe6-e5 Rf4-f8 Re8xf8 Qf1xf8+ Kc8-b7 Qf8-f3+ Kb7-b8 Qf3-f8+ Nb6-c8 Qf8-f3 Qe5-h5 Qf3-d3 Nc8-b6 Rd1-g1 a5-a4 Qd3xg6 Qh5-c5 Rg1-g2 Rh2-h1 Qg6-e8+ Kb8-b7 Qe8-e4+ c7-c6 Qe4-e7+ Kb7-a6
---------------------------------------------------------------------------
37/59 00:28 1,054,531k 37,646k -3.29 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Re1-e2 Rh8-h5 b2-b4 Qh4-f6 Qg2-f3 Rh5-h4 a2-a3 a7-a6 Bd3xe4 d5xe4 Re2xe4 Re8-h8 Re4-e2 Rh8-h5 Qf3-e4 Nb6-d5 Qe4-d4 Qf6xd4+ Nc6xd4 Nd5xf4 Re2-e7 Nf4-h3+ Kg1-h1 Rh4xd4 Rf1xf7+ Kg7-h6 Rf7-f1
37/56 00:28 1,054,531k 37,646k -3.21 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 Qg1-f1 Rh8-h5 Rf4xf7 Rh5xh2 Rf7-f4 Qe6-e5 Rf4-f8 Kc8-b7 Rf8xe8 Qe5xe8 Nc1-b3 Rh2-h5 Qf1-g2+ Kb7-a7 Rd1-g1 a5-a4 Nb3-c1 Nb6-d5 Nc1-d3 c7-c5 Rg1-e1 Qe8-c6
---------------------------------------------------------------------------
38/61 00:42 1,579,122k 37,418k -3.36 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 f2-f4 Ke8-f8 Ra1-e1 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Re1-e2 Rh8-h5 b2-b4 Kg7-f8 Qg2-f3 Nb6-c4 Qf3-g2 Nc4-e5 Bd3xe4 Ne5xc6 Be4-f3 Re8xe2 Bf3xe2 Rh5-h6 c2-c3 Nc6-e7 Qg2-g3 Ne7-f5 Qg3xh4 Rh6xh4 Rf1-f3 c7-c6 Kg1-h1 Kf8-g7 Be2-d3
38/56 00:42 1,579,122k 37,418k -2.95 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 f2-f4 Qe5-e3+ Kc1-b1 Rd8-e8 Ne2-c1 Qe3xf4 Nc1-d3 Qf4-c4 Rh1-f1 a7-a5 Rf1-f4 Qc4-d5 Qg2-g1 d7-d6 Nd3-c1 Qd5-e6 Qg1-f1 Rh8-h5 Rf4xf7 Rh5xh2 Rf7-f4 Qe6-e5 Nc1-b3 a5-a4 Nb3-d4 Kc8-b7 a2-a3 b4xa3 b2xa3 Qe5-c5 Qf1-g1 Re8-h8 Qg1xg6 Nb6-c4 Qg6-d3 Nc4xa3+ Kb1-a2 Na3xc2
---------------------------------------------------------------------------
39/63 00:55 2,062,590k 37,335k -2.95 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 Ne2-d4 Rh8-h4 Nd4-f3 Qe5-f4+ Kc1-b1 Rh4-g4 Qg2-h3 Qf4-f5 Rd1-d3 Rg4-f4 Qh3xf5 Rf4xf5 Rh1-e1 Nb6-c4 Re1-e4 d7-d5 Re4-e2 Rd8-h8 b2-b3 Nc4-d6 Nf3-d4 Rf5-f4 Re2-e5 Rf4xf2 h2-h3 Rf2-f1+ Kb1-b2 Kc8-b7 Re5xd5 Kb7-b6 Rd3-f3 Rf1xf3 Nd4xf3 Rh8xh3 Nf3-e5 Rh3-h2
39/64 00:55 2,062,590k 37,335k -2.93 Be2xa6 h3xg2 Qf3xg2 b4xc3 Bd2xc3 e6xd5 O-O Nf6xe4 Ra1-e1 Ke8-f8 f2-f4 Ra8-e8 Ba6-d3 d7-d6 Ne5-c6 Qe7-h4 Bc3xg7+ Kf8xg7 Nc6-d4 Qh4-f6 c2-c3 c7-c5 Nd4-c2 Rh8-h5 Re1-e2 Qf6-e6 Nc2-e3 f7-f5 Rf1-e1 Re8-h8 Qg2-f1 Rh5-h3 Re2-g2 Rh8-h4 a2-a4 Nb6-d7 a4-a5 Nd7-f6 Bd3xe4 Qe6xe4 Ne3-c2 Qe4xf4 Qf1xf4 Rh4xf4 Re1-e7+ Kg7-h6 Re7-e6 Rh3-f3 Nc2-e3 Rf4-a4
---------------------------------------------------------------------------
40/2 01:09 2,576,448k 37,278k -3.22 Be2xa6
40/66 01:09 2,576,448k 37,278k -2.95 d5xe6 Ba6xe2 Nc3xe2 Qe7xe6 Bd2-f4 Nf6xe4 Qf3xe4 h3xg2 Qe4xg2 Bg7xe5 O-O-O O-O-O Bf4xe5 Qe6xe5 Qg2-g4 Qe5-e6 Qg4xe6 d7xe6 h2-h4 Rd8xd1+ Kc1xd1 Nb6-d5 Kd1-d2 Kc8-d7 Kd2-d3 a7-a5 a2-a3 b4xa3 b2xa3 Nd5-f6 Ne2-g3 Rh8-d8 Kd3-c3 Kd7-e7 f2-f3 Rd8-d5 Rh1-b1 Rd5-c5+ Kc3-d3 Nf6-d7 f3-f4 Nd7-b6 Ng3-e2 Rc5-d5+ Ne2-d4 c7-c5

Piece value change: int pieceVal[] = { 0, 100, 250, 290, 400, 850, 0 };

1 -1 981 0 e2a6 b4c3 b2c3 e6d5
2 -1 13849 0 e2a6 b4c3 b2c3 e6d5
3 -12 20684 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
4 -2 101033 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
5 -2 227843 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
6 -2 586951 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
7 -2 1337604 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
8 -2 6310545 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
t = 0.617 sec
6310545 nodes (10.2 Mnps)
6020805 QS (95.4%)
1205886 evals (20.0%)
554367 stand-pats (9.2%)
1588339 leaves (26.4%)
5537873 move gens
captures: 95.8%

Edit: oops. int pieceVal[] = { 0, 100, 250, 290, 400, 750, 0 };

1 -1 981 0 e2a6 b4c3 b2c3 e6d5
2 -1 13849 0 e2a6 b4c3 b2c3 e6d5
3 -12 20684 0 e2a6 e6d5 g2h3 b4c3 d2c3 d5e4
4 -2 100545 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
5 -2 227098 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
6 -2 586354 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
7 -2 1336680 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
8 -2 6295390 0 d5e6 a6e2 e6f7 e8d8 f3e2 e7e5 g2h3 h8h3
t = 0.614 sec
6295390 nodes (10.3 Mnps)
6007561 QS (95.4%)
1194491 evals (19.9%)
550171 stand-pats (9.2%)
1582969 leaves (26.3%)
5528366 move gens
captures: 95.9%