Compare commits

..

181 Commits

Author SHA1 Message Date
Marco Costalba 48cfdfcc46 Fix threads count setting
Was broken after "Optimal tune for 8 cores" patch.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 20:57:33 +01:00
Marco Costalba fa7b244dc9 Optimal tune for 8 cores
After deep tests Louis Zulli found on his OCTAL machine that
best setup for an 8 core CPU is as following

"Threads" = 8
"Minimum Split Depth" = 6 or 7 (mSD)
"Maximum Number of Threads per Split Point" = not important (MNTpSP)

Here are testing results:

mSD7 (8 threads) vs mSD4 (8 threads): 291 - 120 - 589
mSD6 vs mSD7: 168 - 188 - 644
mSD6-MNTpSP5 vs mSD6-MNTpSP6: 172 - 172 - 656
SF-7threads vs SF-8threads: 179 - 204 - 617

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 20:57:17 +01:00
Marco Costalba 29ad6a73fc Fix duplicated scaling function
We erroneusly added two times the same scaling function
to endgame's map.

Fix detected by valgrind becasue resulted in a memleak
of the first added scaling function.

Bug introduced by 30e8f0c9ad6a473 of 13/02/2009

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 19:40:43 +01:00
Marco Costalba ac48b16708 Update release number
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 14:59:55 +01:00
Marco Costalba 38b1c4b6b8 Another TT size limit fix attempt
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 14:17:00 +01:00
Marco Costalba 162dbeaee8 Remove a bogus assert
It is not true with old 1.6.xx code

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 14:09:23 +01:00
Marco Costalba 85146ca0a9 Check bounds in set_option_value()
Normally it's up to the GUI to check for option's limits,
but we could receive the new value directly from the user
by teminal window. So let's check the bounds anyway.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 14:06:59 +01:00
Joona Kiiski 02e12a69a7 Remove InfiniteSearch hack
With current search control system, I can see absolutely no
reason to classify fixed time search as infinite search.

So remove old dated hack

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 13:15:28 +01:00
Tord Romstad 6e8116e38f Make sure we make a move at the end of the search when reaching
maximum depth during a "go movetime ..." search. This prevents
Stockfish from hanging forever after finding a mate in two or
three while running a test suite at a level of a few seconds
per move.

No functional change when playing games at normal time controls.
2010-02-01 13:13:58 +01:00
Marco Costalba 29f7fab2a9 Do not wait when AbortSearch is set
It means we have already received "stop" or "quit" commands.

This fixes an hang in tactical test in Fritz GUI. Bug
introduced by previous bug fix :-(

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 13:13:20 +01:00
Marco Costalba 2af986bf31 Fix sending of best move during an infinite search
According to UCI standard once engine receives 'go infinite'
command it should search until the "stop" command and do not exit
the search without being told so, even if PLY_MAX has been reached.

Patch is quite invasive because it cleanups some hacks used
by fixed depth and fixed nodes modes, mainly during benchmarks.

Bug found by Pascal Georges.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 13:10:21 +01:00
Marco Costalba b67146b100 Add hardware POPCNT support for gcc
With new target 'make gcc-popcnt' it is now
possible to compile with enabled hardware POPCNT
support also with gcc. Until now was possible only
for Intel and MSVC compilers.

When this instruction is supported by CPU, for instance
on Intel i7 or i5 family, produced binary is a bit faster.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:48:49 +01:00
Joona Kiiski c1b1a94d81 Standardize set_option function
Previously input like "setoption name Use Search Log value true "
(note space at the end of the line) didn't work.

Now parse value same way as option name. This way we implicitly
left- and right-trim value.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:48:11 +01:00
Joona Kiiski 17212e5fcc Remove last use of uip.eof()
Value of uip.eof() should not be trusted.
input like "go infinite searchmoves " (note space in the end of line)
causes problems.

Check the return value of (uip >> token) instead

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:46:03 +01:00
Marco Costalba 46921dff27 Fix a couple of MSVC casting warnings
Also removed some trailing whitespaces and aligned
indentation to current standard.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:45:04 +01:00
Marco Costalba 941016e7a2 Check for thread creation successful completion
It is a good programming practice to verify a system
call has indeed succeed.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:44:11 +01:00
Tord Romstad 290caf9960 Fixes a Chess960 bug when playing with more than one search thread.
The init_eval() function corrupted the static array castleRightsMask[]
in the Position class, resulting in instant crashes in most Chess960
games. Fixed by repairing the damage directly after the function is
called. Also modified the Position::to_fen() function to display
castle rights correctly for Chess960 positions, and added sanity checks
for uncastled rook files in Position::is_ok().
2010-02-01 12:40:09 +01:00
Marco Costalba 43fa3a4d64 Fix some races in SMP code
When a search fails high then sp->alpha is increased and
slave threads are requested to stop.

So we have to check for a stop request before to start a search
otherwise we could end up with sp->alpha >= sp->beta
leading to an assert in debug run in search_pv().

This patch fixes the assert and get rid of some of possible races.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:39:53 +01:00
Marco Costalba 64b4836d12 Fix enum Value issue with gcc 4.4
Louis Zulli reports a miscompile with g++-4.4 from MacPorts.

Namely enum Value is compiled as unsigned instead of signed integer
and this yields an issue in score_string() where float(v) is incorrectly
casted when Value v is negative.

This patch ensure that compiler choses a signed variable to store a Value.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:39:21 +01:00
Marco Costalba 5df7d62eb9 Fix 'position ..... moves ' parsing bug
If after 'moves' there is a space then we crash.

The problem is that operator>>() trims whitespaces so that
after 'moves' has been extract we are still not at eof()
but remaining string contains only spaces. So that the next
extarction operation uip >> token ends up with unchanged token
value that remains 'moves', this garbage value is then feeded
to RootPosition.do_move() through move_from_string() that does
not detect the invalid move value leading to a crash.

This bug is triggered by Shredder 12 interface under Mac that
puts a space after 'moves' without any actual move list.

Bug fixed by Justin Blanchard

After reviewing UCI parsing code I spotted other possible weak
points due to the fact that we don't test if the last extract
operation has been succesful. So I have extended Justing patch
to fix the remaining possible holes in uci.cpp

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:36:30 +01:00
Marco Costalba 82179c70dc Fix en-passant parsing from fen string
According to standard en-passant is recorded in fen string regardless
of whether there is a pawn in position to make an en passant capture.

Instead internally we set ep square only if the pawn can be captured.
So teach from_fen() to correctly handle this difference.

Bug reported and fixed by Justin Blanchard.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:36:06 +01:00
Marco Costalba de17652e47 Fix a possible crash in thread_is_available()
When we have more then 2 threads then we do an array
access with index 'Threads[slave].activeSplitPoints - 1'
This should be >= 0 because we tested the variable just
few statements before, but because is a shared variable
it could be that the 'slave' thread set the value to zero
just after we test it, so that when we use the decremented
variable for array access we crash.

Bug spotted by Bruno Causse.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:34:31 +01:00
Marco Costalba 647b79b556 Extend maximum hash size to 8 GB
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2010-02-01 12:14:37 +01:00
Marco Costalba e0a8b36436 Stockfish 1.6.2
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-30 13:25:20 +01:00
Marco Costalba 8d724220a7 Better fix for gcc optimization issue
According to the standard, compiler is free to choose
the enum type as long as can keep its data.
Also cast to short and right shift are implementation
defined in case of a signed integer.

Normally all the compilers implement this stuff in
the "usual" way, but gcc with -O3 and -O2 pushes
aggressively the language to its limits to squeeze
even the last bit of speed. And this broke our
not 100% standard conforming code.

The fix is to rewrite the Score enum and the 16 bits
word extracting functions in a way that is 100% standard
compliant and with no speed regression on gcc and also on
the other compilers.

Verified it works on all compilers and with equivalent
functionality.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-30 13:25:02 +01:00
Marco Costalba 0973cc2ef6 Score enum should be at least 32 bits
The compiler is allowed to chose the size of an enum variable
based on the values it is expected to store. So force the compiler
to use at least a 32 bit integer type for the Score.

MSVC and Intel do not change, while gcc under -O3 is affected
by this change.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-27 19:45:19 +01:00
Marco Costalba 3f14f9a478 Revert small pop_1st_bit() optimization
We cannot cast a pointer type to an unrelated pointer type.
This is a violation of the strict aliasing rules.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-27 14:07:08 +01:00
Marco Costalba aa86d81f79 Remove a bogus assert
It is not clear why is not true, even in single thread
case, but as a matter of fact it is not!

So remove it.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-27 13:54:46 +01:00
Marco Costalba b884351cc7 Use THREAD_MAX instead of hardcoded 8
This will allow to change THREAD_MAX value in the future.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-27 13:52:29 +01:00
Marco Costalba 4d9e9ac3d4 Restore development version
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-27 08:35:44 +01:00
Marco Costalba 3dc9f95225 Set maximum hash table size to 2GB
We cannot allocate more then 2 GB, so let the limit
reflect this.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-27 00:44:08 +01:00
Marco Costalba bc0871acbc Stockfish 1.6.1
Workaround a gcc optimization bug.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-26 19:39:22 +01:00
Marco Costalba 2643f1552f Workaround optimization bug in gcc
Unfortunatly we need to slow down to -O1 to be sure
it works always.

Note that sometime it works also with -O2 or even -O3,
but user has to try himself.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-26 19:39:13 +01:00
Marco Costalba ba07b95ee0 Fix description of Score enum
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-26 19:39:04 +01:00
Marco Costalba ef58551a2d Fix a typo in ReducedStateInfo
It happened to work by accident because Score and
Value are both integer.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-26 19:38:53 +01:00
Marco Costalba 7d34e7bf84 Stockfish 1.6
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-22 22:11:10 +01:00
Marco Costalba 12aeac5e14 Score definition gives a compile error under gcc
For enum definitions a parenthesis is required.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-21 11:44:11 +01:00
Marco Costalba 734fb9a13b Setup Release Candidate 1
To be used by Jim for testing different compiles settings.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-19 17:00:33 +01:00
Marco Costalba c12364bb67 Fix a comment in HistoryMax description
Was obsoleted out some time ago.

Spotted by Justin Blanchard

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-19 10:43:12 +01:00
Marco Costalba ad5b5cef4a Fix book name is hard coded as book.bin
Instead should be read by the corresponding UCI
option "Book File".

Bug reported and fixed by Justin Blanchard (Arch Linux)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-18 10:11:50 +01:00
Marco Costalba 0d88b832e3 In non-PV IID don't call evaluate when in check
Was a long standing hidden bug from Glaurung times,
triggered only now that we enable IID at non PV nodes.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-15 12:07:23 +01:00
Marco Costalba 14c3da5cad Fix a compile error in debug mode
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-15 11:52:57 +01:00
Marco Costalba 0b9b34655f Enable IID at non-PV nodes
We want to increrase the opportunities
of doing an exclusion search.

After 999 games at 1+0
Mod vs Orig +216 =574 -209 50.35%  503.0/999  +2 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-14 11:44:37 +01:00
Joona Kiiski a66f31f129 Synchronize pruning rules in search and sp_search
Regression test passed:

Mod - Orig: 365 - 351

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-13 10:03:46 +01:00
Marco Costalba 2161d8b0b3 Remove history counters
Instead decrement history value on failure.

After 999 games at 1+0

Mod vs Orig  +236 =558 -204 51.60% +11 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-12 19:23:10 +01:00
Joona Kiiski 7bc72d092f Fix overflow risk in split point
Sizeof of search stack should be PLY_MAX+2 instead of PLY_MAX.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-10 19:00:19 +01:00
Joona Kiiski b056e5d40a Re-enable TT.insert_pv()
This time make sure that valuable TTentries are not overwritten.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-09 19:43:12 +01:00
Joona Kiiski d0b8bc5fdf Disable insert_pv
This way we avoid overwriting valuable TT entries which
are needed to calculate exclusion search extension for pv.

Mod - Orig: 483 - 410 (+28 elo!)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-08 18:13:38 +01:00
Marco Costalba 9a46ac6b2c Set IncrementalFutilityMargin to 8
After 999 games we are almost equal (+2 ELO),
but we have a good result against Rybka

Rybka 2.3.2a mp 32-bit vs Mod  254.5 - 242.5 +152/-140/=205 51.21%
Rybka 2.3.2a mp 32-bit vs Orig 259.5 - 236.5 +151/-128/=217 52.32%

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-08 11:16:14 +01:00
Joona Kiiski 0fc9d9ef61 Replace 100 with PLY_MAX in ok_to_use_TT
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-03 15:12:50 +01:00
Joona Kiiski bd618941ce Adjust SingleReplyMargin 0x64 -> 0x20
Mod - Orig: 920 - 890 (+6 elo)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-02 19:49:50 +01:00
Marco Costalba 403db5a6e9 Don't clear hash at the beginning of a new game
After 900 games at 1+0
Mod vs Orig +217 =480 -196 +8 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-02 15:13:13 +01:00
Marco Costalba 3f3365221b Try to prune also when approximateEval < beta
Now we always try to filter out moves, we will have
more wasted evaluation calls, but also more pruned
nodes.

After 786 games

Mod vs Orig +196 =413 -177 +8 ELO

Verified also against Rybka it increases score to 50-51%

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-12-01 11:49:33 +01:00
Joona Kiiski ae0b965711 Do not crash if we are asked to search mate or stalemate position.
We might be asked to ponder mate or stalemate position.
This being the case, simply wait for stop or ponderhit.
Currently we crash.

UCI specs aren't clear on the issue, but it cost nothing to
add little check.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-28 21:41:57 +01:00
Marco Costalba 455993b289 Fix get_option_value() for strings with spaces
Problem is that

istream& operator>> (istream& is, char* str );

according to C++ documentation "Ends extraction when the
next character is either a valid whitespace or a null character,
or if the End-Of-File is reached."

So if the parameter value is a string with spaces the currently
used instruction 'ss >> ret;' copies the chars only up to the first
white space and not the whole string.

Use a specialization of get_option_value() to fix this corner case.

Bug reported by xiaozhi

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-28 17:31:56 +01:00
Marco Costalba c7a77dd3c0 Retire FutilityMargins[] array
Now we use a formula to calculate margins on the fly.

Node count has changed because we fixed a leftover when
we still where using FutilityMargins to calculate futilityValue
in the case that we had the evaluation score in TT.

Also small indentation fix.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-28 11:56:08 +01:00
Marco Costalba af3dd21e90 IncrementalFutilityMargin to 4 and increased pruning
Increase pruning at low depths while tone downa bit at
higher depths (linearize a bit the logaritmic behaviour)

This goes togheter with IncrementalFutilityMargin decreased
to 4 compensate the bigger pruning effect.

Total pruned nodes are more or less the same. We go from 36%
of nodes after prune to 37% with this patch.

After 999 games at 1+0
Mod vs Orig +250 =526 -223 +9 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-27 21:17:34 +01:00
Joona Kiiski 1a7047f544 Drop OnlyMoveExt PV-condition from 8 plies to 6 plies
Orig - Mod: 731 - 750

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-27 15:56:26 +01:00
Marco Costalba 25c22ffe7a Use move not ttMove in exclude search
If we arrive until the exclusion search call then
we know that move == ttMove == tte->move()

But using ttMove in search call while, during excluded search
conditions we have used tte->Move()could be a little bit suboptimal.
On the other side using tte->move() also in search call is a bit ugly
so opt for the third choice that is the most clean becasue from the
conditions the reader easily understands that we are talking of ttMove
and that we ant to exclude the move we are evaluating in that moment.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-27 15:38:51 +01:00
Marco Costalba ae6157fcf3 Better document previous patch
If tte->move() != MOVE_NONE then tte->move() == ttMove

What could happen is that we have a ttMove without a tte, or,
we have a tte but tte->move() == MOVE_NONE

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-27 11:54:07 +01:00
Marco Costalba 5bec768d42 Fix a possible crash in excluded search condition
Due to IID we could have a ttMove and not a tte, or,
even if we have a tte they could belong to different
searches so that the depth and type of tte don't
have the same origin of the ttMove.

To fix this we always use tte entry in excluded search
condition and, after an IID, we reprobe the TT table.

No functional change. Apart from possible crash fix.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-26 13:58:55 +01:00
Marco Costalba 6ae30e7cb1 Document why we don't use TT to prune in search_pv()
From a Joona' s post on talkchess.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 17:42:52 +01:00
Joona Kiiski 5ea8167921 Revert last Only move extensions tweaks
They gave bad results:

Mod - Orig: 361 - 404

Master is now verified to be functional equivalent with F_63

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 17:42:51 +01:00
Marco Costalba 55b5b03273 Speed up sorting of non-captures
Becasue we have a lot of zero scores (around 30% of moves)
it is a good idea to do a couple a presorting loops across
the move list and shuffle the moves a bit so that with a
small effort we end up with 3 groups of moves: positives
scores, zero scores and negative scores.

We have two advantages

1) We don't need to sort zero scores

2) Sort two small groups is faster then sort a single big one

Speed up is of about 2%

Because equal scored moves could be reordered in a different way
this is not a "no functional change" although I have verified
the output list is always correctly sorted.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 17:42:41 +01:00
Marco Costalba 850c021f86 Rewrite messy LSN-code take 2
We already reset loseOnTime flag at the beginning of
a new game, so we can simplify a bit the ligic there.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 14:35:32 +01:00
Joona Kiiski cd0c7373cd Rewrite messy LSN-code
* New version is documented and logic should be easier to follow
* Add extra check to not use LSN with x moves / y seconds time control
* New code fixes some rear cases where old code (still) causes program to lose on time at move 1.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 12:10:47 +01:00
Joona Kiiski 9a59535962 Remove RootMoveList::scan_for_easy_move()
* The function is called only in one place
* It must not be called elsewhere
* The function call easily replaced with simple one line condition

No functional change (tested with usual set + 2000 random positions)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 11:57:20 +01:00
Joona Kiiski 48246468f2 Remove 2 FIXMEs from search.cpp
* First one is without any documentation, code is working just fine,
  so there seems to be nothing that really should be fixed.

* Second one requesting emergency measures on aspiration fail low
  when we are running out of time and we are without good move.
  After very long time, I've come to conclusion that this is
  impossible to fix, so remove request.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-25 11:52:09 +01:00
Marco Costalba 2b6bc70f7b Document and cleanup new effective-single-reply code
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 21:12:49 +01:00
Joona Kiiski 8b3fdec7ec Always extend full ply in PV
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 21:02:34 +01:00
Joona Kiiski 58452de86d Add mild extension in low depths
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 21:02:27 +01:00
Joona Kiiski 93c9f342ca Fix currentMove bug
Orig vs Master: +15 elo 887.5 - 812.5 (1700 games, finished) [4CPU]

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 21:01:05 +01:00
Joona Kiiski 16acf57773 Only move extension based on exclusion search
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 21:00:13 +01:00
Joona Kiiski 77eec9f9cb Base work for exclusion search
No functional change

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 21:00:05 +01:00
Marco Costalba bc35f4c42d Tone down a bit futility parameters
After 999 games at 1+0

Mod vs Orig +239 =542 -218  +7 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 20:59:27 +01:00
Marco Costalba 889c8538a8 Remove 4*IncrementalFutilityMargin from futilityValue
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 20:59:26 +01:00
Marco Costalba c52da3b806 Logaritmic futility margins
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 20:59:24 +01:00
Marco Costalba b599da01fa Exponential futility margins
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 20:59:23 +01:00
Marco Costalba 52bca81dcb History pruning exponential limit
Use an exponenital law instead of a linear one for
history pruning.

This should prune more at low depths and a bit less
at high depths.

After 965 games

Mod vs Orig +233 =504 -228 +2 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 20:59:21 +01:00
Marco Costalba 0eedf47661 Incremental Futility Margin
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-23 20:59:20 +01:00
Marco Costalba 989833205f In razor qsearch use corrected beta
Correct beta by razor margin when callin qsearch

After 1019 games on Joona's QUAD

Mod - Orig: 524 - 495 (+10 elo)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-22 21:15:13 +01:00
Marco Costalba 87507121d5 Code style triviality
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-22 21:12:33 +01:00
Marco Costalba 89fe8bc0a6 Micro-optimize get_material_info()
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-15 09:35:22 +01:00
Marco Costalba 4c58db0dab Convert pawns evaluation to Score
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-14 17:57:50 +01:00
Marco Costalba 71e852ea81 Move game phase computation to MaterialInfo
Game phase is a strictly function of the material
combination so its natural place is MaterialInfo,
not position.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-14 17:57:49 +01:00
Marco Costalba 314faa905a Null move dynamic reduction based on value
After 994 games at 1+0

Mod vs Orig +244 =521 -229 50.75%  504.5/994 +5 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-14 17:57:34 +01:00
Marco Costalba a530fc2b60 Use a more standard perft UCI interface
Call directly 'perft 6' to search up to depth 6*OnePly
instead of the old 'perft depth 6'.

It is more in line to what other engines do. Also a bit
of cleanup while there.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-13 10:35:56 +01:00
Marco Costalba 7d0e0ff95e Better document king safety evaluation
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-13 10:10:22 +01:00
Marco Costalba 764229a2e2 Rearrange table layout in evaluate.cpp
A bit more cache friendly.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-13 10:10:21 +01:00
Marco Costalba 5e340346db Remove dcCandidates data member from SplitPoint
It is no more used now that we have CheckInfo.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-13 10:10:21 +01:00
Marco Costalba bf395c6be1 Remove update_checkers()
Now that we have CheckInfo we don't need it anymore.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-11 22:26:29 +01:00
Marco Costalba ad44ff2bca Retire evaluate_mobility()
Move the code to the caller and also move mob_area
computation out of evaluate_pieces(). It is more clear
the code flow and it is also faster.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-11 22:25:08 +01:00
Marco Costalba 8e96149c8c Small sort_moves() deobfuscation
Write the for loop in a more idiomatic way, no assembly
change and of course no functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-11 22:25:07 +01:00
Marco Costalba 3c085775d7 Don't futility-prune ttMove
After 933 games
Mod vs Orig +219 =505 -208 +4 ELO

A small increase as expected.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-11 22:24:30 +01:00
Marco Costalba dd5a3ae4a6 Propagate "move is check" info to do_move()
When false (common case) we avoid to update checkers
bitboard that although not so costly slows down a bit
this very hot and critical path.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-10 18:12:19 +01:00
Marco Costalba f7f09b91ea Small update_checkers() cleanup
And is a bit faster too.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-10 17:18:00 +01:00
Marco Costalba 8a116ce691 Small update to pop_1st_bit()
Avoid a 64 bit load using a pointer. It saves a couple of push/pop
instructions so advantage is only theorical, but anyway we use
pop_1st_bit() as a reference implementation for 32 bit systems so
we keep it more for documentation purposes then for other reasons.

Idea of pointer is of Eric Mullins.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-10 17:18:00 +01:00
Marco Costalba 16626dd655 Small CheckInfo fallout
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 21:48:02 +01:00
Marco Costalba 2f01d67a92 Fully convert move_is_check() internally
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 21:38:39 +01:00
Marco Costalba 975d5e9c64 Convert move_is_check() to take a CheckInfo reference
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 21:02:07 +01:00
Marco Costalba 30075e4abc Use CheckInfo to compute dcCandidates
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 20:54:45 +01:00
Marco Costalba 37398d9456 Introduce CheckInfo struct
Keeps info used to speed-up move_is_check()

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 20:50:02 +01:00
Marco Costalba e05039156c Fix operator/(Score s, int i)
And remove some useless declarations

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 09:00:24 +01:00
Marco Costalba f35ddb04af Don't copy the key in do_move
It will be overwritten anyway.

Also other little small touches that seem to increase
speed more then the whole enum Score patch series :-(

Optimization is really a black art.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 08:45:30 +01:00
Marco Costalba ef6fca98a0 Define Score as an enum
Increases performance because now we use one integer
for both midgame and endgame scores.

Unfortunatly the latest patches seem to have reduced a bit
the speed so at the end we are more or less at the same
performance level of the beginning. But this patch series
introduced also some code cleanup so it is the main reason
we commit anyway.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-09 08:43:34 +01:00
Marco Costalba fea46a8212 Change Score definition to avoid the union
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 21:10:49 +01:00
Marco Costalba 776c7df30c Revert "Do not extend at low depths if not in PV"
On Joona's QUAD:
Orig - Mod: 414 - 373

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 19:47:48 +01:00
Marco Costalba 5e112f16da Revert "IID in pv also when TT move depth is too small"
After almost 900 games we are at -2 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 19:46:36 +01:00
Marco Costalba 15ec3e911e Last conversions to Score in evaluate.cpp
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 17:08:28 +01:00
Marco Costalba 1ecd8e13ee Convert ThreatBonus to Score
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 15:45:49 +01:00
Marco Costalba 444c7c5183 Convert RookOn7thBonus and QueenOn7thBonus to be Score
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 15:35:11 +01:00
Marco Costalba e9757f7610 Convert mobility bonus tables to Score
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 15:28:02 +01:00
Marco Costalba 1ab01f1c14 Convert apply_weight() to handle Score
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 14:33:40 +01:00
Marco Costalba 4626ec2890 Convert MaterialInfo and PawnInfo to use Score
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 14:17:10 +01:00
Marco Costalba dda7e4639a Introduce PieceSquareTable[16][64]
Instead of MgPieceSquareTable[16][64] and EgPieceSquareTable[16][64]

This allows to fetch mg and eg values from adjacent words in memory.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 13:14:24 +01:00
Marco Costalba 1ae8c59c0b Convert Position to use Score struct
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 12:40:48 +01:00
Marco Costalba 06f06a9be8 Introduce Score struct
Save mid and end game scores in an union so to
operate on both values in one instruction.

This patch just introduces the infrastructure and changes
EvalInfo to use a single Score value instead of mgValue
and egValue.

Speed is more or less the same because we still don't use
unified midgame-endgame tables where the single assignment
optimization can prove effective.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 12:35:04 +01:00
Marco Costalba 2f5ee9e4e8 Fix correct name of int64_t type
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-07 10:08:28 +01:00
Marco Costalba dd884b65b7 Do not extend at low depths if not in PV
Only check extensions are allowed.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 17:52:12 +01:00
Marco Costalba 0fdc75c0bd IID in pv also when TT move depth is too small
Try an internal iterative deepening not only when we don't
have a TT move but also if search depth is more then 4*OnePly
higher then TT move depth.

On some tests it seems that in around 20% of cases ttMove changes !

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 17:52:04 +01:00
Marco Costalba dae7cacd3b Better big-endian support wording in Makefile
Suggested by Joona.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 17:50:38 +01:00
Marco Costalba 7c0cb8e73d Enable POPCNT only through Makefile
Also remove some fallback templates that prevent a
compile error in case the user runs 'make icc-profile-popcnt'
from a non supported machine.

We want to loudly fail in that case instead of silently
fallback in a non-popcount compilation.

Updated documentation too.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 17:50:24 +01:00
Joona Kiiski 53ce6ce49c Add popcnt-support in Makefile
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 17:50:15 +01:00
Marco Costalba 7a68916ff9 Small code-style touches in movegen.cpp
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 14:42:48 +01:00
Marco Costalba 82a1e2d5fc Fix a small warning under icc
Variable 'f' in 'for' loop scope hides same named
one in outer scope.

Of curse no functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 10:39:33 +01:00
Marco Costalba dc286d2673 Big-endian compatible pop_1st_bit()
Thanks to Eric Mullins we have now endian friendly
pop_1st_bit() and also is removed the need to use
-fno-strict-aliasing compiler option with GCC.

Speed is almost as fast, very small difference if any in
perft test, so I assume almost no difference in real games.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 10:21:15 +01:00
Marco Costalba a9e536a7eb Fix a compile error in debug mode
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-06 09:23:47 +01:00
Marco Costalba e59d053984 Enable PH_TT_MOVES during evasion generation
This allow us to avoid the generation of the
evasion moves if we already have a TT move, and
in case we have a cut-off we skip evasion generation
altoghter.

Node count is changed because now we try TT move _before_
to generate evasions. The search on the TT move alters the
piece lists so that when we come back to generate evasions
we build the move list with a diferent order and this alters
the node count.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-05 19:14:17 +01:00
Marco Costalba 1c73c1c150 Extend move_is_legal() to work also when in check
This patch is a prerequisite to use TT phase
during evasions.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-05 14:58:57 +01:00
Marco Costalba 423b8b9ded Move locals definitions at the function start
It seems to me function are easier to read now.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-05 14:58:57 +01:00
Marco Costalba 94dcac1fee Retire MovePicker::discovered_check_candidates()
It is now no more needed to know dc candidates
inside MovePicker, so avoid calculating there.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-05 07:03:48 +01:00
Marco Costalba 0855d93de8 Rewrite generate_pawn_moves() and simplify evasions
Big cleanup and semplification of pawns evasions that
now are pseudo-legal as the remaining moves. This
allow us to remove a lot of tricky code.

Verified against perft: no functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-05 07:03:47 +01:00
Marco Costalba deecb3757c Generate pseudo-legal moves in generate_evasions()
This allow a big semplification in move generation
that will be committed with the next patch. And makes
handling of evasions similar to the other type of moves.

This patch plus the next seem to improve also on
the performance side because after 640 games to
verify there are no hidden regressions we are at +9 ELO

Verified with perft no functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-05 07:03:36 +01:00
Marco Costalba 53c2bf0697 Optimize generate_evasions()
Generate captures of checking piece and blocking
evasions in one go.

Also reduce of one indentation level early returning
when we have a double check.

Verified with perft no functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-04 11:18:43 +01:00
Marco Costalba 483a257618 Speed up perft
There is no need to do / undo the move at the last ply

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-04 11:18:05 +01:00
Marco Costalba 12461996a5 Remove SEE optimizations
Don't seem to help, perhaps because we
return an approximate SEE score instead of the
real negative score so that we have some bad capture
or evasion sub-optimal ordering that compensates
the speed up.

Anyhow after 999 games at 1+0
Mod vs Orig +240 =514 -245 -2 ELO

So almost no harm to remove and make the code simpler.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-04 11:17:42 +01:00
Marco Costalba 70b7404a63 Reorder evasions
Always try ttMove as first. Then try good captures ordered
by MVV/LVA, then non-captures if destination square is not
under attack, ordered by history value, and at the end
bad-captures and non-captures with a negative SEE. This
last group is ordered by the SEE score.

After 999 games at 1+0
Mod vs Orig +254 =546 -199 +19 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-01 21:08:30 +01:00
Marco Costalba dddaeff7d8 Another see() shortcut
Because we only generate legal moves we can assume
a king cannot be recaptured, so we can safely return
immediately with the captured piece score. If the move
turns out to be illegal it will be pruned anyhow,
independently from SEE value. This gives a good speed up
especially now that we SEE-test all the evasions that
are always legal and very often are king moves.

Another optimization catches almost 15% of cases, unfortunatly
we have already calculated the very expensive attacks, so
benefits are not so big anyway.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-01 21:06:26 +01:00
Marco Costalba 941d923bf8 Shortcut see_sign() when SEE is known negative
This patch cuts 30% of SEE calculations, as a drawback
a returned negative value is no more always correct if
a shortcut is found.

This could impact move order when based on negative see
score as example bad captures and evasions.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-01 21:06:14 +01:00
Marco Costalba 23de3e16f1 Remove castling moves in check generation
Check generation is used only in qsearch and
only at Depth(0), castling moves that give check
are very rare overall and even almost not exsistent
at Depth(0).

So retire this almost never used code that adds
a small but consistent slow down in the normal path.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-01 17:19:04 +01:00
Marco Costalba 8ebe5075eb Optimize check generation
Because discovery checks are very rare it is better to handle
them all in one go and strip from usual check generation
function.

Also rewrite direct checks generation to use piece lists instead
of pop_1st_bit()

On perft test we have a +6% of speed up and is verified we
generate the same moves, although in a different order.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-01 17:13:01 +01:00
Joona Kiiski fa49311b36 Implemented perft
Patch from Joona with extension to benchmark and inclusion
of Depth(0) moves generation by me.

Note that to test also qsearch and in particulary checks
generations a change in the end condition is needed.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-11-01 17:05:00 +01:00
Marco Costalba d9b920acfb Evaluation threat values after 39089 games
Verified against tuning branch.

After 100 games at 1+0 on Joona QUAD

Mod - Orig: 527.5 - 471.5 (+20 elo)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-31 09:42:59 +01:00
Marco Costalba 12b0517d1b Fix build under gcc
Also some warnings squashed.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-25 10:22:03 +01:00
Marco Costalba b38685625a Add threat evaluation
Give a bonus for each kind of attacked piece. Bonus
value is based on the type of attacked piece and the
type of attacking one.

Penalize pieces attacked by enemy pawns, also in
this case penality value depends on the type of
attacked piece.

This patch oboletes as redundant the increased mobility
count of the attcked squares that is then removed.

After 956 games at 1+0
Mod vs Orig  +262 =462 -232 51.57%  493.0/956 +11 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-25 08:21:35 +01:00
Marco Costalba 73da3a431c Micro optimize mobility calculation
Take out of mobility loop a constant expression.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-23 11:24:53 +01:00
Marco Costalba b50921fd5c Unify capture and promotion tests
Small code cleanup and a bit faster too.

The only functional change is that in extension
in pv node we extend promotions and not only captures
when condition met.

This is practically an undetectable change and has
no impact on strenght.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-22 07:22:54 +01:00
Marco Costalba a72c55283d Don't prune TT move in qsearch even if SEE < 0
Even if SEE is negative there is always a good possibility
that TT move is a cut move anyway. For instance a lot of
BXN exchanges that have negative SEE can very easily be
good exchanges.

A nice side effect is a bit reduced frequency of
see_sign() calls.

After 643 games at 1+0
Mod vs Orig +174 =327 -142 52.49%  337.5/643 +17 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-22 07:22:44 +01:00
Marco Costalba cf4df0327a Pick best moves one per cycle instead of sorting
When the move list is very small, like captures normally
are, it is faster to pick the best move with a linear
scan, one per cycle.

This has the added advantage that the picked capture move is
very possibly a cut-off move, so that other searches are
avoided. For non-captures it is still faster to sort in
advance.

Because scan-and-pick alghortim is not stable, node count
has changed.

After 885 games at 1+0
Mod vs Orig +196 =510 -179 50.96%  451.0/885

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-22 07:18:41 +01:00
Marco Costalba 51c3af9dd0 Avoid a needless locking in sp_search()
Only in less then 2% of cases we have a new sp->bestValue,
so check before to lock and save a costly locking
most of the times.

Patch suggested by Joona.

No functional search.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-19 07:47:18 +01:00
Joona Kiiski f0b0a3b135 Similarize pruning code in search() and sp_search()
Use futility pruning also in split points.
Do not use history pruning in split points when
getting mated.

After 1000 games on Joona QUAD
Orig - Mod: 496 - 504

Added an optimization to avoid a costly lock in the
very common case that sp->futilityValue <= sp->bestValue.
A test on a dual CPU shows only 114 hits on 23196 events,
so avoid a lock in all the other cases.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-18 21:31:19 +01:00
Marco Costalba 4dd7fccfd1 Use an homegrown insertion sort instead of std::sort()
It is stable and it is also a bit faster then std::sort()
on the tipical small move lists that we need to handle.

Verified to have same functionality of std::stable_sort()

After 999 games at 1+0
Mod vs Orig +240 =534 -225 50.75%  507.0/999  +5 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-17 09:24:58 +01:00
Marco Costalba c1ea5ed6f7 Do not prune the move if we are still under mate
If after the first tried 2 + int(depth) moves we still
have no any move that takes us out of a mate then do
not prune the following move, it is more important to
escape mate then speed up search.

This fixes an odd behaviour regarding mates, as example
the following diagram is a mate in 4, not in 3 as bogusly
reported before this patch.

1B2n3/8/2R5/5p2/3kp1n1/4p3/B3K3/8 w - - bm #4;

The performance impact should be minimal, the increment
in searched nodes is less then 0.1 %%

Idea and patch by Joona

After 999 games at 1+0
Mod vs Orig +193 =604 -202  -3 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-15 18:52:21 +01:00
Tord Romstad 53f882ff1a Minor improvement in eval of unstoppable pawns promoting one ply apart.
Marco's new code for evaluating two unstoppable passed pawns where
one pawn promotes a single ply before the other tried to detect
cases where the pawn that promotes first could immediately capture
the pawn that promotes a ply later, but didn't work in cases where
the two pawns are on the same file. An example of this is the
following position:

8/8/3K4/2P5/2p5/3k4/8/8 w - -

With the new code, such positions are handled correctly.
2009-10-15 12:39:55 +02:00
Marco Costalba d8e7ce1863 Fix a crash when reaching PLY_MAX in a check position
In this case we call evaluate() being in check and this
is not allowed.

Bug found testing with reduced PLY_MAX value as suggested
by Miguel A. Ballicora on talkchess.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-12 15:32:50 +01:00
Marco Costalba 181d34e5a0 Add a new rule on promoting pawns in evaluate_passed_pawns()
Add a rule about the situation when one side queens exactly
one ply before the other. To avoid difficult (but luckly rare)
cases we only handle the case of free paths to queen for
both sides.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-12 10:09:06 +02:00
Marco Costalba 2655f93c32 Fix x-ray attack from behind in evaluate_passed_pawns()
Fix a condition for x-ray attack of a queen or
a rook behind a pawn of us. Previous condition does
not check if the enemy slider behind our pawn is
really attacking the pawn.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-12 09:36:35 +02:00
Marco Costalba ab4d26f9bd Small cleanup and in evaluate_passed_pawns()
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-12 09:36:30 +02:00
Marco Costalba d6b04c2798 Revert "Use std::stable_sort() instead of std::sort()"
Unfortunatly std::stable_sort() implementation in gcc is
horrendously slow. We have a big performance regression on
Linux systems (-20% !)

So revert the commit and wait to fix the issue in a different
way, perhaps with an our home grown sorting, that should be
comparable in speed with std::sort()

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-12 09:36:23 +02:00
Marco Costalba e2e249eabd Use std::stable_sort() instead of std::sort()
Standard does not mandate std::sort() to be stable, so we
can have, and actually do have different node count on
different platforms.

So use the platform independent std::stable_sort() and gain
same functionality on any platform (Windows, Unix, Mac OS)
and with any compiler (MSVC, gcc or Intel C++).

This sort is teoretically slower, but profiling shows only a very
minimal drop in performance, probably due to the fact that
the set to sort is very small, mainly only captures and with
less frequency non-captures, anyhow we are talking of 30-40 moves
in the worst average case. Sorting alghortims are build to work on
thousands or even milions of elements. With such small sets
performance difference seems not noticable.

After 999 games at 1+0

Mod vs Orig +234 =523 -242 -3 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-10 15:45:46 +01:00
Marco Costalba ed19a9f909 Unroll color loops in evaluate_passed_pawns()
Speed increase is on 1.5% on Intel pgo build.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-10 14:56:56 +01:00
Marco Costalba eddfd46a10 Use piece_list to scan the pawns in evaluate_pawns()
No functional change and small speed increase.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-10 14:56:10 +01:00
Marco Costalba a806d7c3d6 Fix pieceList initialization in Position::clear()
We want piece list to be terminated with SQ_NONE.

This happens with all the pieces but the pawns that
being 8 make the inner loop exit just before writing
the SQ_NONE value at the tail of the list.

This bug was hidden because currently we don't use
piece list to scan pawns, but this will change in the
future and in any case an initialization should be done
correctly for the whole array to avoid subtle bugs in
the future.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-10 09:49:41 +01:00
Marco Costalba ccdb634b77 Unroll color loops in get_pawn_info
This allow to resolve a lot of addresses at compile time
instead of an indirect access at runtime.

Speed up on pgo compile is of 1.3%

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-09 16:48:45 +01:00
Marco Costalba 564ed5b38c Small micro-optimization in get_pawn_info()
No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-09 11:29:11 +01:00
Marco Costalba f05d059b17 Restore pliesFromNull counter
It is not equivalent because the check for the
50 moves rule get badly affected.

// Draw by the 50 moves rule?
if (st->rule50 > 100 || (st->rule50 == 100 && !is_check()))
    return true;

So we _really_ need two counters.

Thanks to Joona and Tord to be patience with a silly guy ;-)

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-09 10:11:43 +01:00
Marco Costalba 06a5b602dc Fix an off-by-one bug in extract_pv()
In case we reach ply == PLY_MAX we exit the function
writing

pv[PLY_MAX] = MOVE_NONE;

And because SearchStack is defined as:

struct SearchStack {
  Move pv[PLY_MAX];
  Move currentMove;
  .....

We end up with the unwanted assignment

SearchStack.currentMove = MOVE_NONE;

Fortunatly this is harmless because currentMove is not used where
extarct_pv() is called. But neverthless this is a bug that
needs to be fixed.

Thanks to Uri Blass for spotting out this.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-09 10:04:55 +01:00
Marco Costalba d892063cd3 Rewrite previous patch using only one counter
Use only rule50 and retire pliesFromNull.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-09 04:35:11 +01:00
Marco Costalba 64d6ba2e98 Do not claim repetition after null move
Null moves can artificially create a repetition
draw where instead there is no one.

So use a second counter to reset history after
a null move.

Idea from Joona.

After 999 games at 1+0

Mod vs Orig +238 =553 -208 51.50%  514.5/999  +10 ELO

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-09 03:55:10 +01:00
Marco Costalba 5a5dc6fa10 Restore development version
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-08 15:16:47 +01:00
Marco Costalba 8ee0842c81 Stockfish 1.5.1
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-08 15:16:34 +01:00
Marco Costalba e59ff49a55 Fix the polling frequency when pondering
When pondering InfiniteSearch == false but myTime == 0 so that
NodesBetweenPolls = 1000 instead of the standard.

The patch fixes the bug and is more robust because checks
directly myTime for a non-zero value, without relying on
an indirect test (InfiniteSearch in this case).

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-08 09:09:19 +01:00
Tord Romstad 225dcfeeb7 Use slightly lower polling frequency in the last few seconds.
Instead of checking the time every 100 nodes in the last second,
and every 1000 nodes in the last five seconds, Stockfish now checks
every 1000 nodes in the last second and every 5000 nodes in the last
five seconds.  This was tested in 1036 games at a time control of
40 moves/10 seconds, and no losses on time occured.

Also fixed a bug pointed out by Marco:  In infinite mode, myTime
is actually 0, but of course we still don't want to check the time
more frequently than the standard once per 30000 nodes in this
case.
2009-10-08 08:55:25 +02:00
Tord Romstad 8dd01fda12 Minor change to time management code, to make sure we don't lose on
time at the last move before the time control when there is very
little time left.
2009-10-07 18:27:00 +02:00
Tord Romstad 18cd83a380 Display fail high/fail low in search log file. 2009-10-06 12:51:15 +02:00
Marco Costalba fd2b3df770 Fix bogus comment in extract_pv()
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-06 11:15:05 +01:00
Marco Costalba da948cc94e Fix use of an initialized SearchStack
In RootMoveList c'tor we allocate a search stack and then
call directly qsearch.

There is called init_node() that clears all the fields of the search
stack array that refers to current ply but not the the killer moves.

The killer moves cleared correspond to ply+2.

In id_loop() this is not a problem because killer moves of
corresponding ply are cleared anyway few instructions later,
but in RootMoveList c'tor we leave them uninitialized.

This patch fixes this very old bug. It comes direclty
from Glaurung age.

Bug spotted by Valgrind.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-06 11:12:41 +01:00
Marco Costalba e49b21eacb Remove a redundant assignment in PawnInfo c'tor
We don't need to set key to 0 because clear() already
takes care of that.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-06 09:18:15 +01:00
Marco Costalba 60bc30275d Small code reformat in TranspositionTable::extract_pv()
In particular don't use an array of StateInfo, this
avoids a possible overflow and is in any case redundant.

Also pass as argument the pv[] array size to avoid a second
possible overflow on this one.

Fix suggested by Joona.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-06 07:14:12 +01:00
Tord Romstad 32dfaa56b0 Fixed an embarassing Chess960 bug found by Alexander Schmidt.
It turned out that we used do_move_bb to update the king and rook
bitboards when making and unmaking castling moves, which obviously
doesn't work in Chess960, where the source and destination squares
for the king or rook could be identical.

No functional change in normal chess.
2009-10-05 16:46:18 +02:00
Marco Costalba 3701a8e57d Restore development version
Signed-off-by: Marco Costalba <mcostalba@gmail.com>
2009-10-05 07:15:13 +01:00
34 changed files with 2412 additions and 2018 deletions
+87 -88
View File
@@ -1,91 +1,90 @@
1. Introduction 1. Introduction
--------------- ---------------
Stockfish is a free UCI chess engine derived from Glaurung 2.1. It is Stockfish is a free UCI chess engine derived from Glaurung 2.1. It is
not a complete chess program, but requires some UCI compatible GUI not a complete chess program, but requires some UCI compatible GUI
(like XBoard with PolyGlot, eboard, Jos, Arena, Sigma Chess, Shredder, (like XBoard with PolyGlot, eboard, Jos, Arena, Sigma Chess, Shredder,
Chess Partner, or Fritz) in order to be used comfortably. Read the Chess Partner, or Fritz) in order to be used comfortably. Read the
documentation for your GUI of choice for information about how to use documentation for your GUI of choice for information about how to use
Stockfish with your GUI. Stockfish with your GUI.
This version of Stockfish supports up to 8 CPUs, but has not been This version of Stockfish supports up to 8 CPUs, but has not been
tested thoroughly with more than 2. The program tries to detect the tested thoroughly with more than 2. The program tries to detect the
number of CPUs on your computer and set the number of search threads number of CPUs on your computer and set the number of search threads
accordingly, but please be aware that the detection is not always accordingly, but please be aware that the detection is not always
correct. It is therefore recommended to inspect the value of the correct. It is therefore recommended to inspect the value of the
"Threads" UCI parameter, and to make sure it equals the number of CPU "Threads" UCI parameter, and to make sure it equals the number of CPU
cores on your computer. If you are using more than four threads, it cores on your computer. If you are using more than four threads, it
is recommended to raise the value of "Minimum Split Depth" UCI parameter is recommended to raise the value of "Minimum Split Depth" UCI parameter
to 6. to 6.
2. Files 2. Files
-------- --------
This distribution of Stockfish consists of the following files: This distribution of Stockfish consists of the following files:
* Readme.txt, the file you are currently reading. * Readme.txt, the file you are currently reading.
* Copying.txt, a text file containing the GNU General Public * Copying.txt, a text file containing the GNU General Public
License. License.
* src/, a subdirectory containing the full source code, including a * src/, a subdirectory containing the full source code, including a
Makefile that can be used to compile Stockfish on Unix-like Makefile that can be used to compile Stockfish on Unix-like
systems. For further information about how to compile Stockfish systems. For further information about how to compile Stockfish
yourself, read section 4 below. yourself, read section 4 below.
* polyglot.ini, for using Stockfish with Fabien Letouzey's PolyGlot * polyglot.ini, for using Stockfish with Fabien Letouzey's PolyGlot
adapter. adapter.
3. Opening books 3. Opening books
---------------- ----------------
This version of Stockfish has experimental support for PolyGlot opening This version of Stockfish has experimental support for PolyGlot opening
books. For information about how to create such books, consult the books. For information about how to create such books, consult the
PolyGlot documentation. The book file can be selected by setting the PolyGlot documentation. The book file can be selected by setting the
UCI parameter "Book File". UCI parameter "Book File".
4. Compiling it yourself 4. Compiling it yourself
------------------------ ------------------------
On Unix-like systems, it should usually be possible to compile On Unix-like systems, it should usually be possible to compile
Stockfish directly from the source code with the included Makefile. Stockfish directly from the source code with the included Makefile.
The exception is computer with big-endian CPUs, like PowerPC
Macintoshes. Some of the bitboard routines in the current version of For big-endian machines like Power PC you need to enable the proper
Stockfish are endianness-sensitive, and won't work on a big-endian CPU. flag changing from -DNBIGENDIAN to -DBIGENDIAN in the Makefile.
Stockfish has POPCNT instruction runtime detection and support. This can Stockfish has POPCNT instruction runtime detection and support. This can
give an extra speed on Core i7 or similar systems. To enable this feature give an extra speed on Core i7 or similar systems. To enable this feature
(disabled by default) simply uncomment #define USE_POPCNT in bitcount.h compile with 'make icc-profile-popcnt'
before to compile.
On 64 bit Unix-like systems the 'bsfq' assembly instruction will be used
On 64 bit Unix-like systems the 'bsfq' assembly instruction will be used for bit counting. Detection is automatic at compile time, but in case you
for bit counting. Detection is automatic at compile time, but in case you experience compile problems you can comment out #define USE_BSFQ line in types.h
experience compile problems you can comment out #define USE_BSFQ line in types.h
5. Terms of use
5. Terms of use ---------------
---------------
Stockfish is free, and distributed under the GNU General Public License
Stockfish is free, and distributed under the GNU General Public License (GPL). Essentially, this means that you are free to do almost exactly
(GPL). Essentially, this means that you are free to do almost exactly what you want with the program, including distributing it among your
what you want with the program, including distributing it among your friends, making it available for download from your web site, selling
friends, making it available for download from your web site, selling it (either by itself or as part of some bigger software package), or
it (either by itself or as part of some bigger software package), or using it as the starting point for a software project of your own.
using it as the starting point for a software project of your own.
The only real limitation is that whenever you distribute Stockfish in
The only real limitation is that whenever you distribute Stockfish in some way, you must always include the full source code, or a pointer
some way, you must always include the full source code, or a pointer to where the source code can be found. If you make any changes to the
to where the source code can be found. If you make any changes to the source code, these changes must also be made available under the GPL.
source code, these changes must also be made available under the GPL.
For full details, read the copy of the GPL found in the file named
For full details, read the copy of the GPL found in the file named Copying.txt.
Copying.txt.
6. Feedback
6. Feedback -----------
-----------
The author's e-mail address is mcostalba@gmail.com
The author's e-mail address is mcostalba@gmail.com
+54 -3
View File
@@ -39,6 +39,14 @@ ICCFLAGS += -DNDEBUG
ICCFLAGS-OSX += -DNDEBUG ICCFLAGS-OSX += -DNDEBUG
### ==========================================================================
### Remove below comments to compile for a big-endian machine
### ==========================================================================
#GCCFLAGS += -DBIGENDIAN
#ICCFLAGS += -DBIGENDIAN
#ICCFLAGS-OSX += -DBIGENDIAN
### ========================================================================== ### ==========================================================================
### Run built-in benchmark for pgo-builds with: 32MB hash 1 thread 10 depth ### Run built-in benchmark for pgo-builds with: 32MB hash 1 thread 10 depth
### These settings are generally fast, but may be changed experimentally ### These settings are generally fast, but may be changed experimentally
@@ -47,9 +55,9 @@ PGOBENCH = ./$(EXE) bench 32 1 10 default depth
### General compiler settings. Do not change ### General compiler settings. Do not change
GCCFLAGS += -g -Wall -fno-exceptions -fno-rtti -fno-strict-aliasing GCCFLAGS += -g -Wall -fno-exceptions -fno-rtti
ICCFLAGS += -g -Wall -fno-exceptions -fno-rtti -fno-strict-aliasing -wd383,869,981,10187,10188,11505,11503 ICCFLAGS += -g -Wall -fno-exceptions -fno-rtti -wd383,869,981,10187,10188,11505,11503
ICCFLAGS-OSX += -g -Wall -fno-exceptions -fno-rtti -fno-strict-aliasing -wd383,869,981,10187,10188,11505,11503 ICCFLAGS-OSX += -g -Wall -fno-exceptions -fno-rtti -wd383,869,981,10187,10188,11505,11503
### General linker settings. Do not change ### General linker settings. Do not change
@@ -72,8 +80,10 @@ help:
@echo "Makefile options:" @echo "Makefile options:"
@echo "" @echo ""
@echo "make > Default: Compiler = g++" @echo "make > Default: Compiler = g++"
@echo "make gcc-popcnt > Compiler = g++ + popcnt-support"
@echo "make icc > Compiler = icpc" @echo "make icc > Compiler = icpc"
@echo "make icc-profile > Compiler = icpc + automatic pgo-build" @echo "make icc-profile > Compiler = icpc + automatic pgo-build"
@echo "make icc-profile-popcnt > Compiler = icpc + automatic pgo-build + popcnt-support"
@echo "make osx-ppc32 > PPC-Mac OS X 32 bit. Compiler = g++" @echo "make osx-ppc32 > PPC-Mac OS X 32 bit. Compiler = g++"
@echo "make osx-ppc64 > PPC-Mac OS X 64 bit. Compiler = g++" @echo "make osx-ppc64 > PPC-Mac OS X 64 bit. Compiler = g++"
@echo "make osx-x86 > x86-Mac OS X 32 bit. Compiler = g++" @echo "make osx-x86 > x86-Mac OS X 32 bit. Compiler = g++"
@@ -99,6 +109,13 @@ gcc:
CXXFLAGS="$(GCCFLAGS)" \ CXXFLAGS="$(GCCFLAGS)" \
all all
gcc-popcnt:
$(MAKE) \
CXX='g++' \
CXXFLAGS="$(GCCFLAGS) -DUSE_POPCNT" \
all
icc: icc:
$(MAKE) \ $(MAKE) \
CXX='icpc' \ CXX='icpc' \
@@ -133,6 +150,40 @@ icc-profile:
$(MAKE) icc-profile-use $(MAKE) icc-profile-use
@rm -rf profdir bench.txt @rm -rf profdir bench.txt
icc-profile-make-with-popcnt:
$(MAKE) \
CXX='icpc' \
CXXFLAGS="$(ICCFLAGS) -DUSE_POPCNT" \
CXXFLAGS+='-prof-gen=srcpos -prof_dir ./profdir' \
all
icc-profile-use-with-popcnt:
$(MAKE) \
CXX='icpc' \
CXXFLAGS="$(ICCFLAGS) -DUSE_POPCNT" \
CXXFLAGS+='-prof_use -prof_dir ./profdir' \
all
icc-profile-popcnt:
@rm -rf profdir
@mkdir profdir
@touch *.cpp *.h
$(MAKE) icc-profile-make
@echo ""
@echo "Running benchmark for pgo-build (popcnt disabled)..."
@$(PGOBENCH) > /dev/null
@touch *.cpp *.h
$(MAKE) icc-profile-make-with-popcnt
@echo ""
@echo "Running benchmark for pgo-build (popcnt enabled)..."
@$(PGOBENCH) > /dev/null
@echo "Benchmarks finished. Build final executable now ..."
@echo ""
@touch *.cpp *.h
$(MAKE) icc-profile-use-with-popcnt
@rm -rf profdir bench.txt
osx-ppc32: osx-ppc32:
$(MAKE) \ $(MAKE) \
CXX='g++' \ CXX='g++' \
+4 -2
View File
@@ -104,7 +104,7 @@ void benchmark(const string& commandLine) {
if (limitType == "time") if (limitType == "time")
secsPerPos = val * 1000; secsPerPos = val * 1000;
else if (limitType == "depth") else if (limitType == "depth" || limitType == "perft")
maxDepth = val; maxDepth = val;
else else
maxNodes = val; maxNodes = val;
@@ -153,7 +153,9 @@ void benchmark(const string& commandLine) {
int dummy[2] = {0, 0}; int dummy[2] = {0, 0};
Position pos(*it); Position pos(*it);
cerr << "\nBench position: " << cnt << '/' << positions.size() << endl << endl; cerr << "\nBench position: " << cnt << '/' << positions.size() << endl << endl;
if (!think(pos, true, false, 0, dummy, dummy, 0, maxDepth, maxNodes, secsPerPos, moves)) if (limitType == "perft")
totalNodes += perft(pos, maxDepth * OnePly);
else if (!think(pos, false, false, 0, dummy, dummy, 0, maxDepth, maxNodes, secsPerPos, moves))
break; break;
totalNodes += nodes_searched(); totalNodes += nodes_searched();
} }
+44 -13
View File
@@ -317,8 +317,8 @@ Square pop_1st_bit(Bitboard* b) {
#elif !defined(USE_BSFQ) #elif !defined(USE_BSFQ)
CACHE_LINE_ALIGNMENT static CACHE_LINE_ALIGNMENT
static const int BitTable[64] = { const int BitTable[64] = {
63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, 61, 29, 2, 63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, 61, 29, 2,
51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, 62, 31, 40, 4, 49, 5, 52, 51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, 62, 31, 40, 4, 49, 5, 52,
26, 60, 6, 23, 44, 46, 27, 56, 16, 7, 39, 48, 24, 59, 14, 12, 55, 38, 28, 26, 60, 6, 23, 44, 46, 27, 56, 16, 7, 39, 48, 24, 59, 14, 12, 55, 38, 28,
@@ -336,30 +336,61 @@ union b_union {
Bitboard b; Bitboard b;
struct { struct {
#if defined (BIGENDIAN)
uint32_t h;
uint32_t l;
#else
uint32_t l; uint32_t l;
uint32_t h; uint32_t h;
#endif
} dw; } dw;
}; };
// WARNING: Needs -fno-strict-aliasing compiler option
Square pop_1st_bit(Bitboard* bb) { Square pop_1st_bit(Bitboard* bb) {
b_union u; b_union u;
Square ret;
u.b = *bb; u.b = *bb;
if (u.dw.l) if (u.dw.l)
{ {
*((uint32_t*)bb) = u.dw.l & (u.dw.l - 1); ret = Square(BitTable[((u.dw.l ^ (u.dw.l - 1)) * 0x783a9b23) >> 26]);
return Square(BitTable[((u.dw.l ^ (u.dw.l - 1)) * 0x783a9b23) >> 26]); u.dw.l &= (u.dw.l - 1);
} *bb = u.b;
return ret;
*((uint32_t*)bb+1) = u.dw.h & (u.dw.h - 1); // Little endian only? }
return Square(BitTable[((~(u.dw.h ^ (u.dw.h - 1))) * 0x783a9b23) >> 26]); ret = Square(BitTable[((~(u.dw.h ^ (u.dw.h - 1))) * 0x783a9b23) >> 26]);
u.dw.h &= (u.dw.h - 1);
*bb = u.b;
return ret;
} }
#endif #endif
int bitScanReverse32(uint32_t b)
{
int result = 0;
if (b > 0xFFFF) {
b >>= 16;
result += 16;
}
if (b > 0xFF) {
b >>= 8;
result += 8;
}
if (b > 0xF) {
b >>= 4;
result += 4;
}
if (b > 0x3) {
b >>= 2;
result += 2;
}
return result + (b > 0) + (b > 1);
}
namespace { namespace {
// All functions below are used to precompute various bitboards during // All functions below are used to precompute various bitboards during
+1
View File
@@ -349,6 +349,7 @@ extern Square pop_1st_bit(Bitboard* b);
extern void print_bitboard(Bitboard b); extern void print_bitboard(Bitboard b);
extern void init_bitboards(); extern void init_bitboards();
extern int bitScanReverse32(uint32_t b);
#endif // !defined(BITBOARD_H_INCLUDED) #endif // !defined(BITBOARD_H_INCLUDED)
+28 -22
View File
@@ -22,19 +22,12 @@
#if !defined(BITCOUNT_H_INCLUDED) #if !defined(BITCOUNT_H_INCLUDED)
#define BITCOUNT_H_INCLUDED #define BITCOUNT_H_INCLUDED
// To enable POPCNT support uncomment USE_POPCNT define. For PGO compile on a Core i7
// you may want to collect profile data first with USE_POPCNT disabled and then, in a
// second profiling session, with USE_POPCNT enabled so to exercise both paths. Don't
// forget to leave USE_POPCNT enabled for the final optimized compile though ;-)
//#define USE_POPCNT
#include "types.h" #include "types.h"
// Select type of intrinsic bit count instruction to use // Select type of intrinsic bit count instruction to use, see
// README.txt on how to pgo compile with POPCNT support.
#if defined(__INTEL_COMPILER) && defined(IS_64BIT) && defined(USE_POPCNT) // Intel compiler #if defined(__INTEL_COMPILER) && defined(USE_POPCNT) // Intel compiler
#include <nmmintrin.h> #include <nmmintrin.h>
@@ -45,17 +38,9 @@ inline bool cpu_has_popcnt() {
return (CPUInfo[2] >> 23) & 1; return (CPUInfo[2] >> 23) & 1;
} }
// Define a dummy template to workaround a compile error if _mm_popcnt_u64() is not defined.
//
// If _mm_popcnt_u64() is defined in <nmmintrin.h> it will be choosen first due to
// C++ overload rules that always prefer a function to a template with the same name.
// If not, we avoid a compile error and because cpu_has_popcnt() should return false,
// our templetized _mm_popcnt_u64() is never called anyway.
template<typename T> inline unsigned _mm_popcnt_u64(T) { return 0; } // Is never called
#define POPCNT_INTRINSIC(x) _mm_popcnt_u64(x) #define POPCNT_INTRINSIC(x) _mm_popcnt_u64(x)
#elif defined(_MSC_VER) && defined(IS_64BIT) && defined(USE_POPCNT) // Microsoft compiler #elif defined(_MSC_VER) && defined(USE_POPCNT) // Microsoft compiler
#include <intrin.h> #include <intrin.h>
@@ -66,11 +51,32 @@ inline bool cpu_has_popcnt() {
return (CPUInfo[2] >> 23) & 1; return (CPUInfo[2] >> 23) & 1;
} }
// See comment of _mm_popcnt_u64<>() few lines above for an explanation.
template<typename T> inline unsigned __popcnt64(T) { return 0; } // Is never called
#define POPCNT_INTRINSIC(x) __popcnt64(x) #define POPCNT_INTRINSIC(x) __popcnt64(x)
#elif defined(__GNUC__) && defined(USE_POPCNT) // Gcc compiler
inline void __cpuid(unsigned int op,
unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
*eax = op;
*ecx = 0;
__asm__("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
: "0" (*eax), "2" (*ecx));
}
inline bool cpu_has_popcnt() {
unsigned int eax, ebx, ecx, edx;
__cpuid(1, &eax, &ebx, &ecx, &edx);
return (ecx >> 23) & 1;
}
#define POPCNT_INTRINSIC(x) ({ \
unsigned long __ret; \
__asm__("popcnt %1, %0" : "=r" (__ret) : "r" (x)); \
__ret; })
#else // Safe fallback for unsupported compilers or when USE_POPCNT is disabled #else // Safe fallback for unsupported compilers or when USE_POPCNT is disabled
inline bool cpu_has_popcnt() { return false; } inline bool cpu_has_popcnt() { return false; }
+452 -418
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -25,6 +25,8 @@
//// Includes //// Includes
//// ////
#include <iostream>
#include "material.h" #include "material.h"
#include "pawns.h" #include "pawns.h"
@@ -46,7 +48,7 @@ class Position;
struct EvalInfo { struct EvalInfo {
// Middle game and endgame evaluations // Middle game and endgame evaluations
Value mgValue, egValue; Score value;
// Pointers to material and pawn hash table entries // Pointers to material and pawn hash table entries
MaterialInfo* mi; MaterialInfo* mi;
@@ -89,7 +91,7 @@ struct EvalInfo {
Move mateThreat[2]; Move mateThreat[2];
// Middle game and endgame mobility scores. // Middle game and endgame mobility scores.
Value mgMobility, egMobility; Score mobility;
// Extra futility margin. This is added to the standard futility margin // Extra futility margin. This is added to the standard futility margin
// in the quiescence search. // in the quiescence search.
+4 -17
View File
@@ -42,8 +42,6 @@ History::History() { clear(); }
void History::clear() { void History::clear() {
memset(history, 0, 2 * 8 * 64 * sizeof(int)); memset(history, 0, 2 * 8 * 64 * sizeof(int));
memset(successCount, 0, 2 * 8 * 64 * sizeof(int));
memset(failureCount, 0, 2 * 8 * 64 * sizeof(int));
} }
@@ -58,7 +56,6 @@ void History::success(Piece p, Square to, Depth d) {
assert(square_is_ok(to)); assert(square_is_ok(to));
history[p][to] += int(d) * int(d); history[p][to] += int(d) * int(d);
successCount[p][to]++;
// Prevent history overflow // Prevent history overflow
if (history[p][to] >= HistoryMax) if (history[p][to] >= HistoryMax)
@@ -72,12 +69,14 @@ void History::success(Piece p, Square to, Depth d) {
/// called for each non-capturing move which failed to produce a beta cutoff /// called for each non-capturing move which failed to produce a beta cutoff
/// at a node where a beta cutoff was finally found. /// at a node where a beta cutoff was finally found.
void History::failure(Piece p, Square to) { void History::failure(Piece p, Square to, Depth d) {
assert(piece_is_ok(p)); assert(piece_is_ok(p));
assert(square_is_ok(to)); assert(square_is_ok(to));
failureCount[p][to]++; history[p][to] -= int(d) * int(d);
if (history[p][to] < 0)
history[p][to] = 0;
} }
@@ -91,15 +90,3 @@ int History::move_ordering_score(Piece p, Square to) const {
return history[p][to]; return history[p][to];
} }
/// History::ok_to_prune() decides whether a move has been sufficiently
/// unsuccessful that it makes sense to prune it entirely.
bool History::ok_to_prune(Piece p, Square to, Depth d) const {
assert(piece_is_ok(p));
assert(square_is_ok(to));
return (int(d) * successCount[p][to] < failureCount[p][to]);
}
+2 -5
View File
@@ -47,14 +47,11 @@ public:
History(); History();
void clear(); void clear();
void success(Piece p, Square to, Depth d); void success(Piece p, Square to, Depth d);
void failure(Piece p, Square to); void failure(Piece p, Square to, Depth d);
int move_ordering_score(Piece p, Square to) const; int move_ordering_score(Piece p, Square to) const;
bool ok_to_prune(Piece p, Square to, Depth d) const;
private: private:
int history[16][64]; // [piece][square] int history[16][64]; // [piece][square]
int successCount[16][64];
int failureCount[16][64];
}; };
@@ -64,7 +61,7 @@ private:
/// HistoryMax controls how often the history counters will be scaled down: /// HistoryMax controls how often the history counters will be scaled down:
/// When the history score for a move gets bigger than HistoryMax, all /// When the history score for a move gets bigger than HistoryMax, all
/// entries in the table are divided by 2. It is difficult to guess what /// entries in the table are divided by 4. It is difficult to guess what
/// the ideal value of this constant is. Scaling down the scores often has /// the ideal value of this constant is. Scaling down the scores often has
/// the effect that parts of the search tree which have been searched /// the effect that parts of the search tree which have been searched
/// recently have a bigger importance for move ordering than the moves which /// recently have a bigger importance for move ordering than the moves which
+1 -1
View File
@@ -63,7 +63,7 @@ int main(int argc, char *argv[]) {
if (string(argv[1]) != "bench" || argc < 4 || argc > 8) if (string(argv[1]) != "bench" || argc < 4 || argc > 8)
cout << "Usage: stockfish bench <hash size> <threads> " cout << "Usage: stockfish bench <hash size> <threads> "
<< "[time = 60s] [fen positions file = default] " << "[time = 60s] [fen positions file = default] "
<< "[time, depth or node limited = time] " << "[time, depth, perft or node limited = time] "
<< "[timing file name = none]" << endl; << "[timing file name = none]" << endl;
else else
{ {
+38 -13
View File
@@ -37,10 +37,15 @@ using namespace std;
namespace { namespace {
// Values modified by Joona Kiiski
const Value MidgameLimit = Value(15581);
const Value EndgameLimit = Value(3998);
// Polynomial material balance parameters // Polynomial material balance parameters
const Value RedundantQueenPenalty = Value(320); const Value RedundantQueenPenalty = Value(320);
const Value RedundantRookPenalty = Value(554); const Value RedundantRookPenalty = Value(554);
const int LinearCoefficients[6] = { 1617, -162, -1172, -190, 105, 26 };
const int LinearCoefficients[6] = { 1617, -162, -1172, -190, 105, 26 };
const int QuadraticCoefficientsSameColor[][6] = { const int QuadraticCoefficientsSameColor[][6] = {
{ 7, 7, 7, 7, 7, 7 }, { 39, 2, 7, 7, 7, 7 }, { 35, 271, -4, 7, 7, 7 }, { 7, 7, 7, 7, 7, 7 }, { 39, 2, 7, 7, 7, 7 }, { 35, 271, -4, 7, 7, 7 },
@@ -129,6 +134,22 @@ MaterialInfoTable::~MaterialInfoTable() {
} }
/// MaterialInfoTable::game_phase() calculates the phase given the current
/// position. Because the phase is strictly a function of the material, it
/// is stored in MaterialInfo.
Phase MaterialInfoTable::game_phase(const Position& pos) {
Value npm = pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK);
if (npm >= MidgameLimit)
return PHASE_MIDGAME;
else if (npm <= EndgameLimit)
return PHASE_ENDGAME;
return Phase(((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit));
}
/// MaterialInfoTable::get_material_info() takes a position object as input, /// MaterialInfoTable::get_material_info() takes a position object as input,
/// computes or looks up a MaterialInfo object, and returns a pointer to it. /// computes or looks up a MaterialInfo object, and returns a pointer to it.
/// If the material configuration is not already present in the table, it /// If the material configuration is not already present in the table, it
@@ -151,6 +172,9 @@ MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
mi->clear(); mi->clear();
mi->key = key; mi->key = key;
// Store game phase
mi->gamePhase = MaterialInfoTable::game_phase(pos);
// Let's look if we have a specialized evaluation function for this // Let's look if we have a specialized evaluation function for this
// particular material configuration. First we look for a fixed // particular material configuration. First we look for a fixed
// configuration one, then a generic one if previous search failed. // configuration one, then a generic one if previous search failed.
@@ -269,8 +293,8 @@ MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
{ pos.piece_count(BLACK, BISHOP) > 1, pos.piece_count(BLACK, PAWN), pos.piece_count(BLACK, KNIGHT), { pos.piece_count(BLACK, BISHOP) > 1, pos.piece_count(BLACK, PAWN), pos.piece_count(BLACK, KNIGHT),
pos.piece_count(BLACK, BISHOP), pos.piece_count(BLACK, ROOK), pos.piece_count(BLACK, QUEEN) } }; pos.piece_count(BLACK, BISHOP), pos.piece_count(BLACK, ROOK), pos.piece_count(BLACK, QUEEN) } };
Color c, them; Color c, them;
int sign; int sign, pt1, pt2, pc;
int matValue = 0; int v, vv, matValue = 0;
for (c = WHITE, sign = 1; c <= BLACK; c++, sign = -sign) for (c = WHITE, sign = 1; c <= BLACK; c++, sign = -sign)
{ {
@@ -304,25 +328,27 @@ MaterialInfo* MaterialInfoTable::get_material_info(const Position& pos) {
matValue -= sign * ((pieceCount[c][ROOK] - 1) * RedundantRookPenalty + pieceCount[c][QUEEN] * RedundantQueenPenalty); matValue -= sign * ((pieceCount[c][ROOK] - 1) * RedundantRookPenalty + pieceCount[c][QUEEN] * RedundantQueenPenalty);
them = opposite_color(c); them = opposite_color(c);
v = 0;
// Second-degree polynomial material imbalance by Tord Romstad // Second-degree polynomial material imbalance by Tord Romstad
// //
// We use NO_PIECE_TYPE as a place holder for the bishop pair "extended piece", // We use NO_PIECE_TYPE as a place holder for the bishop pair "extended piece",
// this allow us to be more flexible in defining bishop pair bonuses. // this allow us to be more flexible in defining bishop pair bonuses.
for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; pt1++) for (pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; pt1++)
{ {
int c1 = sign * pieceCount[c][pt1]; pc = pieceCount[c][pt1];
if (!c1) if (!pc)
continue; continue;
matValue += c1 * LinearCoefficients[pt1]; vv = LinearCoefficients[pt1];
for (int pt2 = NO_PIECE_TYPE; pt2 <= pt1; pt2++) for (pt2 = NO_PIECE_TYPE; pt2 <= pt1; pt2++)
{ vv += pieceCount[c][pt2] * QuadraticCoefficientsSameColor[pt1][pt2]
matValue += c1 * pieceCount[c][pt2] * QuadraticCoefficientsSameColor[pt1][pt2]; + pieceCount[them][pt2] * QuadraticCoefficientsOppositeColor[pt1][pt2];
matValue += c1 * pieceCount[them][pt2] * QuadraticCoefficientsOppositeColor[pt1][pt2];
} v += pc * vv;
} }
matValue += sign * v;
} }
mi->value = int16_t(matValue / 16); mi->value = int16_t(matValue / 16);
return mi; return mi;
@@ -348,7 +374,6 @@ EndgameFunctions::EndgameFunctions() {
add<ScalingFunction<KBPPKB> >("KBPPKB"); add<ScalingFunction<KBPPKB> >("KBPPKB");
add<ScalingFunction<KBPKN> >("KBPKN"); add<ScalingFunction<KBPKN> >("KBPKN");
add<ScalingFunction<KRPPKRP> >("KRPPKRP"); add<ScalingFunction<KRPPKRP> >("KRPPKRP");
add<ScalingFunction<KRPPKRP> >("KRPPKRP");
} }
EndgameFunctions::~EndgameFunctions() { EndgameFunctions::~EndgameFunctions() {
+16 -3
View File
@@ -51,9 +51,10 @@ class MaterialInfo {
public: public:
MaterialInfo() : key(0) { clear(); } MaterialInfo() : key(0) { clear(); }
Value material_value() const; Score material_value() const;
ScaleFactor scale_factor(const Position& pos, Color c) const; ScaleFactor scale_factor(const Position& pos, Color c) const;
int space_weight() const; int space_weight() const;
Phase game_phase() const;
bool specialized_eval_exists() const; bool specialized_eval_exists() const;
Value evaluate(const Position& pos) const; Value evaluate(const Position& pos) const;
@@ -66,6 +67,7 @@ private:
EndgameEvaluationFunctionBase* evaluationFunction; EndgameEvaluationFunctionBase* evaluationFunction;
EndgameScalingFunctionBase* scalingFunction[2]; EndgameScalingFunctionBase* scalingFunction[2];
int spaceWeight; int spaceWeight;
Phase gamePhase;
}; };
/// The MaterialInfoTable class represents a pawn hash table. It is basically /// The MaterialInfoTable class represents a pawn hash table. It is basically
@@ -81,6 +83,8 @@ public:
~MaterialInfoTable(); ~MaterialInfoTable();
MaterialInfo* get_material_info(const Position& pos); MaterialInfo* get_material_info(const Position& pos);
static Phase game_phase(const Position& pos);
private: private:
unsigned size; unsigned size;
MaterialInfo* entries; MaterialInfo* entries;
@@ -92,12 +96,13 @@ private:
//// Inline functions //// Inline functions
//// ////
/// MaterialInfo::material_value simply returns the material balance /// MaterialInfo::material_value simply returns the material balance
/// evaluation that is independent from game phase. /// evaluation that is independent from game phase.
inline Value MaterialInfo::material_value() const { inline Score MaterialInfo::material_value() const {
return Value(value); return make_score(value, value);
} }
@@ -141,6 +146,14 @@ inline int MaterialInfo::space_weight() const {
return spaceWeight; return spaceWeight;
} }
/// MaterialInfo::game_phase() returns the game phase according
/// to this material configuration.
inline Phase MaterialInfo::game_phase() const {
return gamePhase;
}
/// MaterialInfo::specialized_eval_exists decides whether there is a /// MaterialInfo::specialized_eval_exists decides whether there is a
/// specialized evaluation function for the current material configuration, /// specialized evaluation function for the current material configuration,
+1 -1
View File
@@ -50,7 +50,7 @@ using namespace std;
/// Version number. If this is left empty, the current date (in the format /// Version number. If this is left empty, the current date (in the format
/// YYMMDD) is used as a version number. /// YYMMDD) is used as a version number.
static const string EngineVersion = "1.5"; static const string EngineVersion = "1.6.3";
static const string AppName = "Stockfish"; static const string AppName = "Stockfish";
static const string AppTag = ""; static const string AppTag = "";
+99 -1
View File
@@ -62,9 +62,103 @@ struct MoveStack {
int score; int score;
}; };
// Note that operator< is set up such that std::sort() will sort in descending order // Note that operator< is set up such that sorting will be in descending order
inline bool operator<(const MoveStack& f, const MoveStack& s) { return s.score < f.score; } inline bool operator<(const MoveStack& f, const MoveStack& s) { return s.score < f.score; }
// An helper insertion sort implementation
template<typename T>
inline void insertion_sort(T* firstMove, T* lastMove)
{
T value;
T *cur, *p, *d;
if (firstMove != lastMove)
for (cur = firstMove + 1; cur != lastMove; cur++)
{
p = d = cur;
value = *p--;
if (value < *p)
{
do *d = *p;
while (--d != firstMove && value < *--p);
*d = value;
}
}
}
// Our dedicated sort in range [firstMove, lastMove), it is well
// tuned for non-captures where we have a lot of zero scored moves.
template<typename T>
inline void sort_moves(T* firstMove, T* lastMove)
{
T tmp;
T *p, *d;
d = lastMove;
p = firstMove - 1;
d->score = -1; // right guard
// Split positives vs non-positives
do {
while ((++p)->score > 0);
if (p != d)
{
while (--d != p && d->score <= 0);
tmp = *p;
*p = *d;
*d = tmp;
}
} while (p != d);
// Sort positives
insertion_sort<T>(firstMove, p);
d = lastMove;
p--;
// Split zero vs negatives
do {
while ((++p)->score == 0);
if (p != d)
{
while (--d != p && d->score < 0);
tmp = *p;
*p = *d;
*d = tmp;
}
} while (p != d);
// Sort negatives
insertion_sort<T>(p, lastMove);
}
// Picks up the best move in range [curMove, lastMove), one per cycle.
// It is faster then sorting all the moves in advance when moves are few,
// as normally are the possible captures. Note that is not a stable alghoritm.
template<typename T>
inline T pick_best(T* curMove, T* lastMove)
{
T bestMove, tmp;
bestMove = *curMove;
while (++curMove != lastMove)
{
if (*curMove < bestMove)
{
tmp = *curMove;
*curMove = bestMove;
bestMove = tmp;
}
}
return bestMove;
}
//// ////
//// Inline functions //// Inline functions
@@ -82,6 +176,10 @@ inline PieceType move_promotion_piece(Move m) {
return PieceType((int(m) >> 12) & 7); return PieceType((int(m) >> 12) & 7);
} }
inline int move_is_special(Move m) {
return m & (0x1F << 12);
}
inline int move_is_promotion(Move m) { inline int move_is_promotion(Move m) {
return m & (7 << 12); return m & (7 << 12);
} }
+233 -318
View File
@@ -52,16 +52,12 @@ namespace {
EVASION EVASION
}; };
// Functions
bool castling_is_check(const Position&, CastlingSide);
// Helper templates // Helper templates
template<CastlingSide Side> template<CastlingSide Side>
MoveStack* generate_castle_moves(const Position&, MoveStack*); MoveStack* generate_castle_moves(const Position&, MoveStack*);
template<Color Us, MoveType Type> template<Color Us, MoveType Type>
MoveStack* generate_pawn_moves(const Position&, MoveStack*, Bitboard = EmptyBoardBB, MoveStack* generate_pawn_moves(const Position&, MoveStack*, Bitboard, Square);
Square = SQ_NONE, Bitboard = EmptyBoardBB);
// Template generate_piece_moves (captures and non-captures) with specializations and overloads // Template generate_piece_moves (captures and non-captures) with specializations and overloads
template<PieceType> template<PieceType>
@@ -71,37 +67,29 @@ namespace {
MoveStack* generate_piece_moves<KING>(const Position&, MoveStack*, Color, Bitboard); MoveStack* generate_piece_moves<KING>(const Position&, MoveStack*, Color, Bitboard);
template<PieceType Piece, MoveType Type> template<PieceType Piece, MoveType Type>
inline MoveStack* generate_piece_moves(const Position& p, MoveStack* m, Color us) { inline MoveStack* generate_piece_moves(const Position& p, MoveStack* m, Color us, Bitboard t) {
assert(Piece == PAWN); assert(Piece == PAWN);
assert(Type == CAPTURE || Type == NON_CAPTURE); assert(Type == CAPTURE || Type == NON_CAPTURE || Type == EVASION);
return (us == WHITE ? generate_pawn_moves<WHITE, Type>(p, m) return (us == WHITE ? generate_pawn_moves<WHITE, Type>(p, m, t, SQ_NONE)
: generate_pawn_moves<BLACK, Type>(p, m)); : generate_pawn_moves<BLACK, Type>(p, m, t, SQ_NONE));
} }
// Template generate_piece_checks with specializations // Templates for non-capture checks generation
template<PieceType Piece>
MoveStack* generate_discovered_checks(const Position&, MoveStack*, Square);
template<PieceType> template<PieceType>
MoveStack* generate_piece_checks(const Position&, MoveStack*, Color, Bitboard, Square); MoveStack* generate_direct_checks(const Position&, MoveStack*, Color, Bitboard, Square);
template<> template<>
inline MoveStack* generate_piece_checks<PAWN>(const Position& p, MoveStack* m, Color us, Bitboard dc, Square ksq) { inline MoveStack* generate_direct_checks<PAWN>(const Position& p, MoveStack* m, Color us, Bitboard dc, Square ksq) {
return (us == WHITE ? generate_pawn_moves<WHITE, CHECK>(p, m, dc, ksq) return (us == WHITE ? generate_pawn_moves<WHITE, CHECK>(p, m, dc, ksq)
: generate_pawn_moves<BLACK, CHECK>(p, m, dc, ksq)); : generate_pawn_moves<BLACK, CHECK>(p, m, dc, ksq));
} }
// Template generate_piece_evasions with specializations
template<PieceType>
MoveStack* generate_piece_evasions(const Position&, MoveStack*, Color, Bitboard, Bitboard);
template<>
inline MoveStack* generate_piece_evasions<PAWN>(const Position& p, MoveStack* m,
Color us, Bitboard t, Bitboard pnd) {
return (us == WHITE ? generate_pawn_moves<WHITE, EVASION>(p, m, pnd, SQ_NONE, t)
: generate_pawn_moves<BLACK, EVASION>(p, m, pnd, SQ_NONE, t));
}
} }
@@ -125,7 +113,7 @@ MoveStack* generate_captures(const Position& pos, MoveStack* mlist) {
mlist = generate_piece_moves<ROOK>(pos, mlist, us, target); mlist = generate_piece_moves<ROOK>(pos, mlist, us, target);
mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target); mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target);
mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target); mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target);
mlist = generate_piece_moves<PAWN, CAPTURE>(pos, mlist, us); mlist = generate_piece_moves<PAWN, CAPTURE>(pos, mlist, us, target);
return generate_piece_moves<KING>(pos, mlist, us, target); return generate_piece_moves<KING>(pos, mlist, us, target);
} }
@@ -141,7 +129,7 @@ MoveStack* generate_noncaptures(const Position& pos, MoveStack* mlist) {
Color us = pos.side_to_move(); Color us = pos.side_to_move();
Bitboard target = pos.empty_squares(); Bitboard target = pos.empty_squares();
mlist = generate_piece_moves<PAWN, NON_CAPTURE>(pos, mlist, us); mlist = generate_piece_moves<PAWN, NON_CAPTURE>(pos, mlist, us, target);
mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target); mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target);
mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target); mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target);
mlist = generate_piece_moves<ROOK>(pos, mlist, us, target); mlist = generate_piece_moves<ROOK>(pos, mlist, us, target);
@@ -152,175 +140,111 @@ MoveStack* generate_noncaptures(const Position& pos, MoveStack* mlist) {
} }
/// generate_non_capture_checks() generates all pseudo-legal non-captures and /// generate_non_capture_checks() generates all pseudo-legal non-captures and knight
/// underpromotions that give check. Returns a pointer to the end of the move list. /// underpromotions that give check. Returns a pointer to the end of the move list.
MoveStack* generate_non_capture_checks(const Position& pos, MoveStack* mlist, Bitboard dc) { MoveStack* generate_non_capture_checks(const Position& pos, MoveStack* mlist) {
assert(pos.is_ok()); assert(pos.is_ok());
assert(!pos.is_check()); assert(!pos.is_check());
Bitboard b, dc;
Square from;
Color us = pos.side_to_move(); Color us = pos.side_to_move();
Square ksq = pos.king_square(opposite_color(us)); Square ksq = pos.king_square(opposite_color(us));
assert(pos.piece_on(ksq) == piece_of_color_and_type(opposite_color(us), KING)); assert(pos.piece_on(ksq) == piece_of_color_and_type(opposite_color(us), KING));
// Pieces moves // Discovered non-capture checks
mlist = generate_piece_checks<PAWN>(pos, mlist, us, dc, ksq); b = dc = pos.discovered_check_candidates(us);
mlist = generate_piece_checks<KNIGHT>(pos, mlist, us, dc, ksq);
mlist = generate_piece_checks<BISHOP>(pos, mlist, us, dc, ksq);
mlist = generate_piece_checks<ROOK>(pos, mlist, us, dc, ksq);
mlist = generate_piece_checks<QUEEN>(pos, mlist, us, dc, ksq);
mlist = generate_piece_checks<KING>(pos, mlist, us, dc, ksq);
// Castling moves that give check. Very rare but nice to have! while (b)
if ( pos.can_castle_queenside(us) {
&& (square_rank(ksq) == square_rank(pos.king_square(us)) || square_file(ksq) == FILE_D) from = pop_1st_bit(&b);
&& castling_is_check(pos, QUEEN_SIDE)) switch (pos.type_of_piece_on(from))
mlist = generate_castle_moves<QUEEN_SIDE>(pos, mlist); {
case PAWN: /* Will be generated togheter with pawns direct checks */ break;
case KNIGHT: mlist = generate_discovered_checks<KNIGHT>(pos, mlist, from); break;
case BISHOP: mlist = generate_discovered_checks<BISHOP>(pos, mlist, from); break;
case ROOK: mlist = generate_discovered_checks<ROOK>(pos, mlist, from); break;
case KING: mlist = generate_discovered_checks<KING>(pos, mlist, from); break;
default: assert(false); break;
}
}
if ( pos.can_castle_kingside(us) // Direct non-capture checks
&& (square_rank(ksq) == square_rank(pos.king_square(us)) || square_file(ksq) == FILE_F) mlist = generate_direct_checks<PAWN>(pos, mlist, us, dc, ksq);
&& castling_is_check(pos, KING_SIDE)) mlist = generate_direct_checks<KNIGHT>(pos, mlist, us, dc, ksq);
mlist = generate_castle_moves<KING_SIDE>(pos, mlist); mlist = generate_direct_checks<BISHOP>(pos, mlist, us, dc, ksq);
mlist = generate_direct_checks<ROOK>(pos, mlist, us, dc, ksq);
return mlist; return generate_direct_checks<QUEEN>(pos, mlist, us, dc, ksq);
} }
/// generate_evasions() generates all check evasions when the side to move is /// generate_evasions() generates all pseudo-legal check evasions when
/// in check. Unlike the other move generation functions, this one generates /// the side to move is in check. Returns a pointer to the end of the move list.
/// only legal moves. Returns a pointer to the end of the move list.
MoveStack* generate_evasions(const Position& pos, MoveStack* mlist, Bitboard pinned) { MoveStack* generate_evasions(const Position& pos, MoveStack* mlist) {
assert(pos.is_ok()); assert(pos.is_ok());
assert(pos.is_check()); assert(pos.is_check());
Square from, to; Bitboard b, target;
Square from, checksq;
int checkersCnt = 0;
Color us = pos.side_to_move(); Color us = pos.side_to_move();
Color them = opposite_color(us);
Square ksq = pos.king_square(us); Square ksq = pos.king_square(us);
Bitboard sliderAttacks = EmptyBoardBB;
Bitboard checkers = pos.checkers(); Bitboard checkers = pos.checkers();
Bitboard sliderAttacks = EmptyBoardBB;
assert(pos.piece_on(ksq) == piece_of_color_and_type(us, KING)); assert(pos.piece_on(ksq) == piece_of_color_and_type(us, KING));
assert(checkers);
// The bitboard of occupied pieces without our king
Bitboard b_noKing = pos.occupied_squares();
clear_bit(&b_noKing, ksq);
// Find squares attacked by slider checkers, we will remove // Find squares attacked by slider checkers, we will remove
// them from the king evasions set so to avoid a couple // them from the king evasions set so to early skip known
// of cycles in the slow king evasions legality check loop // illegal moves and avoid an useless legality check later.
// and to be able to use attackers_to(). b = checkers;
Bitboard b = checkers & pos.pieces(BISHOP, QUEEN); do
while (b)
{ {
from = pop_1st_bit(&b); checkersCnt++;
sliderAttacks |= bishop_attacks_bb(from, b_noKing); checksq = pop_1st_bit(&b);
}
b = checkers & pos.pieces(ROOK, QUEEN); assert(pos.color_of_piece_on(checksq) == opposite_color(us));
while (b)
{ switch (pos.type_of_piece_on(checksq))
from = pop_1st_bit(&b); {
sliderAttacks |= rook_attacks_bb(from, b_noKing); case BISHOP: sliderAttacks |= BishopPseudoAttacks[checksq]; break;
} case ROOK: sliderAttacks |= RookPseudoAttacks[checksq]; break;
case QUEEN:
// In case of a queen remove also squares attacked in the other direction to
// avoid possible illegal moves when queen and king are on adjacent squares.
if (direction_is_straight(checksq, ksq))
sliderAttacks |= RookPseudoAttacks[checksq] | pos.attacks_from<BISHOP>(checksq);
else
sliderAttacks |= BishopPseudoAttacks[checksq] | pos.attacks_from<ROOK>(checksq);
default:
break;
}
} while (b);
// Generate evasions for king, capture and non capture moves // Generate evasions for king, capture and non capture moves
Bitboard enemy = pos.pieces_of_color(them); b = pos.attacks_from<KING>(ksq) & ~pos.pieces_of_color(us) & ~sliderAttacks;
Bitboard b1 = pos.attacks_from<KING>(ksq) & ~pos.pieces_of_color(us) & ~sliderAttacks; from = ksq;
while (b1) SERIALIZE_MOVES(b);
{
// Note that we can use attackers_to() only because we have already
// removed from b1 the squares attacked by slider checkers.
to = pop_1st_bit(&b1);
if (!(pos.attackers_to(to) & enemy))
(*mlist++).move = make_move(ksq, to);
}
// Generate evasions for other pieces only if not double check. We use a // Generate evasions for other pieces only if not double check
// simple bit twiddling hack here rather than calling count_1s in order to if (checkersCnt > 1)
// save some time (we know that pos.checkers() has at most two nonzero bits). return mlist;
if (!(checkers & (checkers - 1))) // Only one bit set?
{
Square checksq = first_1(checkers);
assert(pos.color_of_piece_on(checksq) == them); // Find squares where a blocking evasion or a capture of the
// checker piece is possible.
target = squares_between(checksq, ksq) | checkers;
// Generate captures of the checking piece mlist = generate_piece_moves<PAWN, EVASION>(pos, mlist, us, target);
mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target);
// Pawn captures mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target);
b1 = pos.attacks_from<PAWN>(checksq, them) & pos.pieces(PAWN, us) & ~pinned; mlist = generate_piece_moves<ROOK>(pos, mlist, us, target);
while (b1) return generate_piece_moves<QUEEN>(pos, mlist, us, target);
{
from = pop_1st_bit(&b1);
if (relative_rank(us, checksq) == RANK_8)
{
(*mlist++).move = make_promotion_move(from, checksq, QUEEN);
(*mlist++).move = make_promotion_move(from, checksq, ROOK);
(*mlist++).move = make_promotion_move(from, checksq, BISHOP);
(*mlist++).move = make_promotion_move(from, checksq, KNIGHT);
} else
(*mlist++).move = make_move(from, checksq);
}
// Pieces captures
b1 = ( (pos.attacks_from<KNIGHT>(checksq) & pos.pieces(KNIGHT, us))
| (pos.attacks_from<BISHOP>(checksq) & pos.pieces(BISHOP, QUEEN, us))
| (pos.attacks_from<ROOK>(checksq) & pos.pieces(ROOK, QUEEN, us)) ) & ~pinned;
while (b1)
{
from = pop_1st_bit(&b1);
(*mlist++).move = make_move(from, checksq);
}
// Blocking check evasions are possible only if the checking piece is a slider
if (sliderAttacks)
{
Bitboard blockSquares = squares_between(checksq, ksq);
assert((pos.occupied_squares() & blockSquares) == EmptyBoardBB);
if (blockSquares)
{
mlist = generate_piece_evasions<PAWN>(pos, mlist, us, blockSquares, pinned);
mlist = generate_piece_evasions<KNIGHT>(pos, mlist, us, blockSquares, pinned);
mlist = generate_piece_evasions<BISHOP>(pos, mlist, us, blockSquares, pinned);
mlist = generate_piece_evasions<ROOK>(pos, mlist, us, blockSquares, pinned);
mlist = generate_piece_evasions<QUEEN>(pos, mlist, us, blockSquares, pinned);
}
}
// Finally, the special case of en passant captures. An en passant
// capture can only be a check evasion if the check is not a discovered
// check. If pos.ep_square() is set, the last move made must have been
// a double pawn push. If, furthermore, the checking piece is a pawn,
// an en passant check evasion may be possible.
if (pos.ep_square() != SQ_NONE && (checkers & pos.pieces(PAWN, them)))
{
to = pos.ep_square();
b1 = pos.attacks_from<PAWN>(to, them) & pos.pieces(PAWN, us);
// The checking pawn cannot be a discovered (bishop) check candidate
// otherwise we were in check also before last double push move.
assert(!bit_is_set(pos.discovered_check_candidates(them), checksq));
assert(count_1s(b1) == 1 || count_1s(b1) == 2);
b1 &= ~pinned;
while (b1)
{
from = pop_1st_bit(&b1);
// Move is always legal because checking pawn is not a discovered
// check candidate and our capturing pawn has been already tested
// against pinned pieces.
(*mlist++).move = make_ep_move(from, to);
}
}
}
return mlist;
} }
@@ -332,24 +256,25 @@ MoveStack* generate_moves(const Position& pos, MoveStack* mlist, bool pseudoLega
assert(pos.is_ok()); assert(pos.is_ok());
MoveStack *last, *cur = mlist;
Bitboard pinned = pos.pinned_pieces(pos.side_to_move()); Bitboard pinned = pos.pinned_pieces(pos.side_to_move());
if (pos.is_check())
return generate_evasions(pos, mlist, pinned);
// Generate pseudo-legal moves // Generate pseudo-legal moves
MoveStack* last = generate_captures(pos, mlist); if (pos.is_check())
last = generate_noncaptures(pos, last); last = generate_evasions(pos, mlist);
else
last = generate_noncaptures(pos, generate_captures(pos, mlist));
if (pseudoLegal) if (pseudoLegal)
return last; return last;
// Remove illegal moves from the list // Remove illegal moves from the list
for (MoveStack* cur = mlist; cur != last; cur++) while (cur != last)
if (!pos.pl_move_is_legal(cur->move, pinned)) if (pos.pl_move_is_legal(cur->move, pinned))
{ cur++;
else
cur->move = (--last)->move; cur->move = (--last)->move;
cur--;
}
return last; return last;
} }
@@ -361,10 +286,11 @@ MoveStack* generate_moves(const Position& pos, MoveStack* mlist, bool pseudoLega
bool move_is_legal(const Position& pos, const Move m) { bool move_is_legal(const Position& pos, const Move m) {
MoveStack mlist[256]; MoveStack mlist[256];
MoveStack* last = generate_moves(pos, mlist, true); MoveStack *cur, *last = generate_moves(pos, mlist, true);
for (MoveStack* cur = mlist; cur != last; cur++)
for (cur = mlist; cur != last; cur++)
if (cur->move == m) if (cur->move == m)
return pos.pl_move_is_legal(m); return pos.pl_move_is_legal(m, pos.pinned_pieces(pos.side_to_move()));
return false; return false;
} }
@@ -372,25 +298,23 @@ bool move_is_legal(const Position& pos, const Move m) {
/// Fast version of move_is_legal() that takes a position a move and a /// Fast version of move_is_legal() that takes a position a move and a
/// bitboard of pinned pieces as input, and tests whether the move is legal. /// bitboard of pinned pieces as input, and tests whether the move is legal.
/// This version must only be used when the side to move is not in check.
bool move_is_legal(const Position& pos, const Move m, Bitboard pinned) { bool move_is_legal(const Position& pos, const Move m, Bitboard pinned) {
assert(pos.is_ok()); assert(pos.is_ok());
assert(!pos.is_check());
assert(move_is_ok(m)); assert(move_is_ok(m));
assert(pinned == pos.pinned_pieces(pos.side_to_move())); assert(pinned == pos.pinned_pieces(pos.side_to_move()));
// Use a slower but simpler function for uncommon cases
if (move_is_ep(m) || move_is_castle(m))
return move_is_legal(pos, m);
Color us = pos.side_to_move(); Color us = pos.side_to_move();
Color them = opposite_color(us); Color them = opposite_color(us);
Square from = move_from(m); Square from = move_from(m);
Square to = move_to(m); Square to = move_to(m);
Piece pc = pos.piece_on(from); Piece pc = pos.piece_on(from);
// Use a slower but simpler function for uncommon cases
if (move_is_ep(m) || move_is_castle(m))
return move_is_legal(pos, m);
// If the from square is not occupied by a piece belonging to the side to // If the from square is not occupied by a piece belonging to the side to
// move, the move is obviously not legal. // move, the move is obviously not legal.
if (color_of_piece(pc) != us) if (color_of_piece(pc) != us)
@@ -459,13 +383,13 @@ bool move_is_legal(const Position& pos, const Move m, Bitboard pinned) {
return false; return false;
} }
// The move is pseudo-legal, check if it is also legal // The move is pseudo-legal, check if it is also legal
return pos.pl_move_is_legal(m, pinned); return pos.is_check() ? pos.pl_move_is_evasion(m, pinned) : pos.pl_move_is_legal(m, pinned);
} }
// Luckly we can handle all the other pieces in one go // Luckly we can handle all the other pieces in one go
return ( bit_is_set(pos.attacks_from(pc, from), to) return bit_is_set(pos.attacks_from(pc, from), to)
&& pos.pl_move_is_legal(m, pinned) && (pos.is_check() ? pos.pl_move_is_evasion(m, pinned) : pos.pl_move_is_legal(m, pinned))
&& !move_is_promotion(m)); && !move_is_promotion(m);
} }
@@ -474,8 +398,8 @@ namespace {
template<PieceType Piece> template<PieceType Piece>
MoveStack* generate_piece_moves(const Position& pos, MoveStack* mlist, Color us, Bitboard target) { MoveStack* generate_piece_moves(const Position& pos, MoveStack* mlist, Color us, Bitboard target) {
Square from;
Bitboard b; Bitboard b;
Square from;
const Square* ptr = pos.piece_list_begin(us, Piece); const Square* ptr = pos.piece_list_begin(us, Piece);
while ((from = *ptr++) != SQ_NONE) while ((from = *ptr++) != SQ_NONE)
@@ -497,24 +421,6 @@ namespace {
return mlist; return mlist;
} }
template<PieceType Piece>
MoveStack* generate_piece_evasions(const Position& pos, MoveStack* mlist,
Color us, Bitboard target, Bitboard pinned) {
Square from;
Bitboard b;
const Square* ptr = pos.piece_list_begin(us, Piece);
while ((from = *ptr++) != SQ_NONE)
{
if (pinned && bit_is_set(pinned, from))
continue;
b = pos.attacks_from<Piece>(from) & target;
SERIALIZE_MOVES(b);
}
return mlist;
}
template<Color Us, SquareDelta Direction> template<Color Us, SquareDelta Direction>
inline Bitboard move_pawns(Bitboard p) { inline Bitboard move_pawns(Bitboard p) {
@@ -528,8 +434,8 @@ namespace {
return p; return p;
} }
template<Color Us, SquareDelta Diagonal> template<Color Us, MoveType Type, SquareDelta Diagonal>
MoveStack* generate_pawn_diagonal_captures(MoveStack* mlist, Bitboard pawns, Bitboard enemyPieces, bool promotion) { inline MoveStack* generate_pawn_captures(MoveStack* mlist, Bitboard pawns, Bitboard enemyPieces) {
// Calculate our parametrized parameters at compile time // Calculate our parametrized parameters at compile time
const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB);
@@ -538,136 +444,152 @@ namespace {
const SquareDelta TDELTA_NW = (Us == WHITE ? DELTA_NW : DELTA_SW); const SquareDelta TDELTA_NW = (Us == WHITE ? DELTA_NW : DELTA_SW);
const SquareDelta TTDELTA_NE = (Diagonal == DELTA_NE ? TDELTA_NE : TDELTA_NW); const SquareDelta TTDELTA_NE = (Diagonal == DELTA_NE ? TDELTA_NE : TDELTA_NW);
Bitboard b1, b2;
Square to; Square to;
// Captures in the a1-h8 (a8-h1 for black) diagonal or in the h1-a8 (h8-a1 for black) // Captures in the a1-h8 (a8-h1 for black) diagonal or in the h1-a8 (h8-a1 for black)
Bitboard b1 = move_pawns<Us, Diagonal>(pawns) & ~TFileABB & enemyPieces; b1 = move_pawns<Us, Diagonal>(pawns) & ~TFileABB & enemyPieces;
// Capturing promotions // Capturing promotions and under-promotions
if (promotion) if (b1 & TRank8BB)
{ {
Bitboard b2 = b1 & TRank8BB; b2 = b1 & TRank8BB;
b1 &= ~TRank8BB; b1 &= ~TRank8BB;
while (b2) while (b2)
{ {
to = pop_1st_bit(&b2); to = pop_1st_bit(&b2);
(*mlist++).move = make_promotion_move(to - TTDELTA_NE, to, QUEEN);
if (Type == CAPTURE || Type == EVASION)
(*mlist++).move = make_promotion_move(to - TTDELTA_NE, to, QUEEN);
if (Type == NON_CAPTURE || Type == EVASION)
{
(*mlist++).move = make_promotion_move(to - TTDELTA_NE, to, ROOK);
(*mlist++).move = make_promotion_move(to - TTDELTA_NE, to, BISHOP);
(*mlist++).move = make_promotion_move(to - TTDELTA_NE, to, KNIGHT);
}
// This is the only possible under promotion that can give a check
// not already included in the queen-promotion. It is not sure that
// the promoted knight will give check, but it doesn't worth to verify.
if (Type == CHECK)
(*mlist++).move = make_promotion_move(to - TTDELTA_NE, to, KNIGHT);
} }
} }
// Capturing non-promotions // Serialize standard captures
SERIALIZE_MOVES_D(b1, -TTDELTA_NE); if (Type == CAPTURE || Type == EVASION)
SERIALIZE_MOVES_D(b1, -TTDELTA_NE);
return mlist; return mlist;
} }
template<Color Us, MoveType Type> template<Color Us, MoveType Type>
MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist, Bitboard dcp, MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist, Bitboard target, Square ksq) {
Square ksq, Bitboard blockSquares) {
// Calculate our parametrized parameters at compile time // Calculate our parametrized parameters at compile time
const Color Them = (Us == WHITE ? BLACK : WHITE); const Color Them = (Us == WHITE ? BLACK : WHITE);
const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB);
const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
const SquareDelta TDELTA_NE = (Us == WHITE ? DELTA_NE : DELTA_SE);
const SquareDelta TDELTA_NW = (Us == WHITE ? DELTA_NW : DELTA_SW);
const SquareDelta TDELTA_N = (Us == WHITE ? DELTA_N : DELTA_S); const SquareDelta TDELTA_N = (Us == WHITE ? DELTA_N : DELTA_S);
Bitboard b1, b2, dcPawns1, dcPawns2;
Square to; Square to;
Bitboard pawns = (Type == EVASION ? pos.pieces(PAWN, Us) & ~dcp : pos.pieces(PAWN, Us)); Bitboard b1, b2, enemyPieces, emptySquares;
bool possiblePromotion = pawns & TRank7BB; Bitboard pawns = pos.pieces(PAWN, Us);
if (Type == CAPTURE) // Standard captures and capturing promotions and underpromotions
if (Type == CAPTURE || Type == EVASION || (pawns & TRank7BB))
{ {
// Standard captures and capturing promotions in both directions enemyPieces = (Type == CAPTURE ? target : pos.pieces_of_color(opposite_color(Us)));
Bitboard enemyPieces = pos.pieces_of_color(opposite_color(Us));
mlist = generate_pawn_diagonal_captures<Us, DELTA_NE>(mlist, pawns, enemyPieces, possiblePromotion); if (Type == EVASION)
mlist = generate_pawn_diagonal_captures<Us, DELTA_NW>(mlist, pawns, enemyPieces, possiblePromotion); enemyPieces &= target; // Capture only the checker piece
mlist = generate_pawn_captures<Us, Type, DELTA_NE>(mlist, pawns, enemyPieces);
mlist = generate_pawn_captures<Us, Type, DELTA_NW>(mlist, pawns, enemyPieces);
} }
if (possiblePromotion) // Non-capturing promotions and underpromotions
if (pawns & TRank7BB)
{ {
// When generating checks consider under-promotion moves (both captures b1 = move_pawns<Us, DELTA_N>(pawns) & TRank8BB & pos.empty_squares();
// and non captures) only if can give a discovery check. Note that dcp
// is dc bitboard or pinned bitboard when Type == EVASION.
Bitboard pp = (Type == CHECK ? pawns & dcp : pawns);
if (Type != EVASION && Type != CAPTURE) if (Type == EVASION)
{ b1 &= target; // Only blocking promotion pushes
Bitboard enemyPieces = pos.pieces_of_color(opposite_color(Us));
// Underpromotion captures in the a1-h8 (a8-h1 for black) direction
b1 = move_pawns<Us, DELTA_NE>(pp) & ~FileABB & enemyPieces & TRank8BB;
while (b1)
{
to = pop_1st_bit(&b1);
(*mlist++).move = make_promotion_move(to - TDELTA_NE, to, ROOK);
(*mlist++).move = make_promotion_move(to - TDELTA_NE, to, BISHOP);
(*mlist++).move = make_promotion_move(to - TDELTA_NE, to, KNIGHT);
}
// Underpromotion captures in the h1-a8 (h8-a1 for black) direction
b1 = move_pawns<Us, DELTA_NW>(pp) & ~FileHBB & enemyPieces & TRank8BB;
while (b1)
{
to = pop_1st_bit(&b1);
(*mlist++).move = make_promotion_move(to - TDELTA_NW, to, ROOK);
(*mlist++).move = make_promotion_move(to - TDELTA_NW, to, BISHOP);
(*mlist++).move = make_promotion_move(to - TDELTA_NW, to, KNIGHT);
}
}
// Underpromotion pawn pushes. Also queen promotions for evasions and captures.
b1 = move_pawns<Us, DELTA_N>(pp) & TRank8BB;
b1 &= (Type == EVASION ? blockSquares : pos.empty_squares());
while (b1) while (b1)
{ {
to = pop_1st_bit(&b1); to = pop_1st_bit(&b1);
if (Type == EVASION || Type == CAPTURE)
if (Type == CAPTURE || Type == EVASION)
(*mlist++).move = make_promotion_move(to - TDELTA_N, to, QUEEN); (*mlist++).move = make_promotion_move(to - TDELTA_N, to, QUEEN);
if (Type != CAPTURE) if (Type == NON_CAPTURE || Type == EVASION)
{ {
(*mlist++).move = make_promotion_move(to - TDELTA_N, to, ROOK); (*mlist++).move = make_promotion_move(to - TDELTA_N, to, ROOK);
(*mlist++).move = make_promotion_move(to - TDELTA_N, to, BISHOP); (*mlist++).move = make_promotion_move(to - TDELTA_N, to, BISHOP);
(*mlist++).move = make_promotion_move(to - TDELTA_N, to, KNIGHT); (*mlist++).move = make_promotion_move(to - TDELTA_N, to, KNIGHT);
} }
// This is the only possible under promotion that can give a check
// not already included in the queen-promotion.
if (Type == CHECK && bit_is_set(pos.attacks_from<KNIGHT>(to), pos.king_square(Them)))
(*mlist++).move = make_promotion_move(to - TDELTA_N, to, KNIGHT);
} }
} }
// Standard pawn pushes and double pushes
if (Type != CAPTURE) if (Type != CAPTURE)
{ {
Bitboard emptySquares = pos.empty_squares(); emptySquares = (Type == NON_CAPTURE ? target : pos.empty_squares());
dcPawns1 = dcPawns2 = EmptyBoardBB;
if (Type == CHECK && (pawns & dcp))
{
// Pawn moves which gives discovered check. This is possible only if the
// pawn is not on the same file as the enemy king, because we don't
// generate captures.
dcPawns1 = move_pawns<Us, DELTA_N>(pawns & dcp & ~file_bb(ksq)) & emptySquares & ~TRank8BB;
dcPawns2 = move_pawns<Us, DELTA_N>(dcPawns1 & TRank3BB) & emptySquares;
}
// Single pawn pushes // Single and double pawn pushes
b1 = move_pawns<Us, DELTA_N>(pawns) & emptySquares & ~TRank8BB; b1 = move_pawns<Us, DELTA_N>(pawns) & emptySquares & ~TRank8BB;
b2 = (Type == CHECK ? (b1 & pos.attacks_from<PAWN>(ksq, Them)) | dcPawns1 : b2 = move_pawns<Us, DELTA_N>(b1 & TRank3BB) & emptySquares;
(Type == EVASION ? b1 & blockSquares : b1));
SERIALIZE_MOVES_D(b2, -TDELTA_N);
// Double pawn pushes // Filter out unwanted pushes according to the move type
b1 = move_pawns<Us, DELTA_N>(b1 & TRank3BB) & emptySquares; if (Type == EVASION)
b2 = (Type == CHECK ? (b1 & pos.attacks_from<PAWN>(ksq, Them)) | dcPawns2 : {
(Type == EVASION ? b1 & blockSquares : b1)); b1 &= target;
b2 &= target;
}
else if (Type == CHECK)
{
// Pawn moves which give direct cheks
b1 &= pos.attacks_from<PAWN>(ksq, Them);
b2 &= pos.attacks_from<PAWN>(ksq, Them);
// Pawn moves which gives discovered check. This is possible only if
// the pawn is not on the same file as the enemy king, because we
// don't generate captures.
if (pawns & target) // For CHECK type target is dc bitboard
{
Bitboard dc1 = move_pawns<Us, DELTA_N>(pawns & target & ~file_bb(ksq)) & emptySquares & ~TRank8BB;
Bitboard dc2 = move_pawns<Us, DELTA_N>(dc1 & TRank3BB) & emptySquares;
b1 |= dc1;
b2 |= dc2;
}
}
SERIALIZE_MOVES_D(b1, -TDELTA_N);
SERIALIZE_MOVES_D(b2, -TDELTA_N -TDELTA_N); SERIALIZE_MOVES_D(b2, -TDELTA_N -TDELTA_N);
} }
else if (pos.ep_square() != SQ_NONE) // En passant captures
// En passant captures
if ((Type == CAPTURE || Type == EVASION) && pos.ep_square() != SQ_NONE)
{ {
assert(Us != WHITE || square_rank(pos.ep_square()) == RANK_6); assert(Us != WHITE || square_rank(pos.ep_square()) == RANK_6);
assert(Us != BLACK || square_rank(pos.ep_square()) == RANK_3); assert(Us != BLACK || square_rank(pos.ep_square()) == RANK_3);
// An en passant capture can be an evasion only if the checking piece
// is the double pushed pawn and so is in the target. Otherwise this
// is a discovery check and we are forced to do otherwise.
if (Type == EVASION && !bit_is_set(target, pos.ep_square() - TDELTA_N))
return mlist;
b1 = pawns & pos.attacks_from<PAWN>(pos.ep_square(), Them); b1 = pawns & pos.attacks_from<PAWN>(pos.ep_square(), Them);
assert(b1 != EmptyBoardBB); assert(b1 != EmptyBoardBB);
while (b1) while (b1)
@@ -680,43 +602,49 @@ namespace {
} }
template<PieceType Piece> template<PieceType Piece>
MoveStack* generate_piece_checks(const Position& pos, MoveStack* mlist, Color us, MoveStack* generate_discovered_checks(const Position& pos, MoveStack* mlist, Square from) {
assert(Piece != QUEEN);
Bitboard b = pos.attacks_from<Piece>(from) & pos.empty_squares();
if (Piece == KING)
{
Square ksq = pos.king_square(opposite_color(pos.side_to_move()));
b &= ~QueenPseudoAttacks[ksq];
}
SERIALIZE_MOVES(b);
return mlist;
}
template<PieceType Piece>
MoveStack* generate_direct_checks(const Position& pos, MoveStack* mlist, Color us,
Bitboard dc, Square ksq) { Bitboard dc, Square ksq) {
assert(Piece != KING);
Bitboard target = pos.pieces(Piece, us); Bitboard checkSqs, b;
Square from;
const Square* ptr = pos.piece_list_begin(us, Piece);
// Discovered non-capture checks if ((from = *ptr++) == SQ_NONE)
Bitboard b = target & dc; return mlist;
assert(Piece != QUEEN || !b); checkSqs = pos.attacks_from<Piece>(ksq) & pos.empty_squares();
while (b) do
{ {
Square from = pop_1st_bit(&b); if ( (Piece == QUEEN && !(QueenPseudoAttacks[from] & checkSqs))
Bitboard bb = pos.attacks_from<Piece>(from) & pos.empty_squares(); || (Piece == ROOK && !(RookPseudoAttacks[from] & checkSqs))
if (Piece == KING) || (Piece == BISHOP && !(BishopPseudoAttacks[from] & checkSqs)))
bb &= ~QueenPseudoAttacks[ksq]; continue;
SERIALIZE_MOVES(bb); if (dc && bit_is_set(dc, from))
} continue;
// Direct non-capture checks b = pos.attacks_from<Piece>(from) & checkSqs;
b = target & ~dc; SERIALIZE_MOVES(b);
Bitboard checkSqs = pos.attacks_from<Piece>(ksq) & pos.empty_squares();
if (Piece != KING && checkSqs) } while ((from = *ptr++) != SQ_NONE);
{
while (b)
{
Square from = pop_1st_bit(&b);
if ( (Piece == QUEEN && !(QueenPseudoAttacks[from] & checkSqs))
|| (Piece == ROOK && !(RookPseudoAttacks[from] & checkSqs))
|| (Piece == BISHOP && !(BishopPseudoAttacks[from] & checkSqs)))
continue;
Bitboard bb = pos.attacks_from<Piece>(from) & checkSqs;
SERIALIZE_MOVES(bb);
}
}
return mlist; return mlist;
} }
@@ -762,17 +690,4 @@ namespace {
} }
return mlist; return mlist;
} }
bool castling_is_check(const Position& pos, CastlingSide side) {
// After castling opponent king is attacked by the castled rook?
File rookFile = (side == QUEEN_SIDE ? FILE_D : FILE_F);
Color us = pos.side_to_move();
Square ksq = pos.king_square(us);
Bitboard occ = pos.occupied_squares();
clear_bit(&occ, ksq); // Remove our king from the board
Square rsq = make_square(rookFile, square_rank(ksq));
return bit_is_set(rook_attacks_bb(rsq, occ), pos.king_square(opposite_color(us)));
}
} }
+2 -2
View File
@@ -34,8 +34,8 @@
extern MoveStack* generate_captures(const Position& pos, MoveStack* mlist); extern MoveStack* generate_captures(const Position& pos, MoveStack* mlist);
extern MoveStack* generate_noncaptures(const Position& pos, MoveStack* mlist); extern MoveStack* generate_noncaptures(const Position& pos, MoveStack* mlist);
extern MoveStack* generate_non_capture_checks(const Position& pos, MoveStack* mlist, Bitboard dc); extern MoveStack* generate_non_capture_checks(const Position& pos, MoveStack* mlist);
extern MoveStack* generate_evasions(const Position& pos, MoveStack* mlist, Bitboard pinned); extern MoveStack* generate_evasions(const Position& pos, MoveStack* mlist);
extern MoveStack* generate_moves(const Position& pos, MoveStack* mlist, bool pseudoLegal = false); extern MoveStack* generate_moves(const Position& pos, MoveStack* mlist, bool pseudoLegal = false);
extern bool move_is_legal(const Position& pos, const Move m, Bitboard pinned); extern bool move_is_legal(const Position& pos, const Move m, Bitboard pinned);
extern bool move_is_legal(const Position& pos, const Move m); extern bool move_is_legal(const Position& pos, const Move m);
+38 -37
View File
@@ -23,7 +23,6 @@
//// Includes //// Includes
//// ////
#include <algorithm>
#include <cassert> #include <cassert>
#include "history.h" #include "history.h"
@@ -53,7 +52,7 @@ namespace {
CACHE_LINE_ALIGNMENT CACHE_LINE_ALIGNMENT
const uint8_t MainSearchPhaseTable[] = { PH_TT_MOVES, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES, PH_BAD_CAPTURES, PH_STOP}; const uint8_t MainSearchPhaseTable[] = { PH_TT_MOVES, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES, PH_BAD_CAPTURES, PH_STOP};
const uint8_t EvasionsPhaseTable[] = { PH_EVASIONS, PH_STOP}; const uint8_t EvasionsPhaseTable[] = { PH_TT_MOVES, PH_EVASIONS, PH_STOP};
const uint8_t QsearchWithChecksPhaseTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_QCHECKS, PH_STOP}; const uint8_t QsearchWithChecksPhaseTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_QCHECKS, PH_STOP};
const uint8_t QsearchWithoutChecksPhaseTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_STOP}; const uint8_t QsearchWithoutChecksPhaseTable[] = { PH_TT_MOVES, PH_QCAPTURES, PH_STOP};
} }
@@ -78,7 +77,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
finished = false; finished = false;
lastBadCapture = badCaptures; lastBadCapture = badCaptures;
if (ss) pinned = p.pinned_pieces(pos.side_to_move());
if (ss && !p.is_check())
{ {
ttMoves[1].move = (ss->mateKiller == ttm)? MOVE_NONE : ss->mateKiller; ttMoves[1].move = (ss->mateKiller == ttm)? MOVE_NONE : ss->mateKiller;
searchTT |= ttMoves[1].move; searchTT |= ttMoves[1].move;
@@ -87,21 +88,16 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d,
} else } else
ttMoves[1].move = killers[0].move = killers[1].move = MOVE_NONE; ttMoves[1].move = killers[0].move = killers[1].move = MOVE_NONE;
Color us = pos.side_to_move();
dc = p.discovered_check_candidates(us);
pinned = p.pinned_pieces(us);
if (p.is_check()) if (p.is_check())
phasePtr = EvasionsPhaseTable; phasePtr = EvasionsPhaseTable;
else if (d > Depth(0)) else if (d > Depth(0))
phasePtr = MainSearchPhaseTable + !searchTT; phasePtr = MainSearchPhaseTable;
else if (d == Depth(0)) else if (d == Depth(0))
phasePtr = QsearchWithChecksPhaseTable + !searchTT; phasePtr = QsearchWithChecksPhaseTable;
else else
phasePtr = QsearchWithoutChecksPhaseTable + !searchTT; phasePtr = QsearchWithoutChecksPhaseTable;
phasePtr--; phasePtr += !searchTT - 1;
go_next_phase(); go_next_phase();
} }
@@ -123,7 +119,6 @@ void MovePicker::go_next_phase() {
case PH_GOOD_CAPTURES: case PH_GOOD_CAPTURES:
lastMove = generate_captures(pos, moves); lastMove = generate_captures(pos, moves);
score_captures(); score_captures();
std::sort(moves, lastMove);
return; return;
case PH_KILLERS: case PH_KILLERS:
@@ -134,7 +129,7 @@ void MovePicker::go_next_phase() {
case PH_NONCAPTURES: case PH_NONCAPTURES:
lastMove = generate_noncaptures(pos, moves); lastMove = generate_noncaptures(pos, moves);
score_noncaptures(); score_noncaptures();
std::sort(moves, lastMove); sort_moves(moves, lastMove);
return; return;
case PH_BAD_CAPTURES: case PH_BAD_CAPTURES:
@@ -142,25 +137,22 @@ void MovePicker::go_next_phase() {
// to get SEE move ordering. // to get SEE move ordering.
curMove = badCaptures; curMove = badCaptures;
lastMove = lastBadCapture; lastMove = lastBadCapture;
std::sort(badCaptures, lastMove);
return; return;
case PH_EVASIONS: case PH_EVASIONS:
assert(pos.is_check()); assert(pos.is_check());
lastMove = generate_evasions(pos, moves, pinned); lastMove = generate_evasions(pos, moves);
score_evasions(); score_evasions();
std::sort(moves, lastMove);
return; return;
case PH_QCAPTURES: case PH_QCAPTURES:
lastMove = generate_captures(pos, moves); lastMove = generate_captures(pos, moves);
score_captures(); score_captures();
std::sort(moves, lastMove);
return; return;
case PH_QCHECKS: case PH_QCHECKS:
// Perhaps we should order moves move here? FIXME // Perhaps we should order moves move here? FIXME
lastMove = generate_non_capture_checks(pos, moves, dc); lastMove = generate_non_capture_checks(pos, moves);
return; return;
case PH_STOP: case PH_STOP:
@@ -202,8 +194,8 @@ void MovePicker::score_captures() {
if (move_is_promotion(m)) if (move_is_promotion(m))
cur->score = QueenValueMidgame; cur->score = QueenValueMidgame;
else else
cur->score = int(pos.midgame_value_of_piece_on(move_to(m))) cur->score = pos.midgame_value_of_piece_on(move_to(m))
-int(pos.type_of_piece_on(move_from(m))); - pos.type_of_piece_on(move_from(m));
} }
} }
@@ -229,24 +221,27 @@ void MovePicker::score_noncaptures() {
hs += 1000; hs += 1000;
// pst based scoring // pst based scoring
cur->score = hs + pos.pst_delta<Position::MidGame>(piece, from, to); cur->score = hs + mg_value(pos.pst_delta(piece, from, to));
} }
} }
void MovePicker::score_evasions() { void MovePicker::score_evasions() {
// Try good captures ordered by MVV/LVA, then non-captures if
// destination square is not under attack, ordered by history
// value, and at the end bad-captures and non-captures with a
// negative SEE. This last group is ordered by the SEE score.
Move m; Move m;
int seeScore;
for (MoveStack* cur = moves; cur != lastMove; cur++) for (MoveStack* cur = moves; cur != lastMove; cur++)
{ {
m = cur->move; m = cur->move;
if (m == ttMoves[0].move) if ((seeScore = pos.see_sign(m)) < 0)
cur->score = 2 * HistoryMax; cur->score = seeScore;
else if (!pos.square_is_empty(move_to(m))) else if (pos.move_is_capture(m))
{ cur->score = pos.midgame_value_of_piece_on(move_to(m))
int seeScore = pos.see(m); - pos.type_of_piece_on(move_from(m)) + HistoryMax;
cur->score = seeScore + (seeScore >= 0 ? HistoryMax : 0); else
} else
cur->score = H.move_ordering_score(pos.piece_on(move_from(m)), move_to(m)); cur->score = H.move_ordering_score(pos.piece_on(move_from(m)), move_to(m));
} }
} }
@@ -259,26 +254,23 @@ void MovePicker::score_evasions() {
Move MovePicker::get_next_move() { Move MovePicker::get_next_move() {
assert(!pos.is_check() || *phasePtr == PH_EVASIONS || *phasePtr == PH_STOP);
assert( pos.is_check() || *phasePtr != PH_EVASIONS);
Move move; Move move;
while (true) while (true)
{ {
while (curMove != lastMove) while (curMove != lastMove)
{ {
move = (curMove++)->move;
switch (phase) { switch (phase) {
case PH_TT_MOVES: case PH_TT_MOVES:
move = (curMove++)->move;
if ( move != MOVE_NONE if ( move != MOVE_NONE
&& move_is_legal(pos, move, pinned)) && move_is_legal(pos, move, pinned))
return move; return move;
break; break;
case PH_GOOD_CAPTURES: case PH_GOOD_CAPTURES:
move = pick_best(curMove++, lastMove).move;
if ( move != ttMoves[0].move if ( move != ttMoves[0].move
&& move != ttMoves[1].move && move != ttMoves[1].move
&& pos.pl_move_is_legal(move, pinned)) && pos.pl_move_is_legal(move, pinned))
@@ -298,6 +290,7 @@ Move MovePicker::get_next_move() {
break; break;
case PH_KILLERS: case PH_KILLERS:
move = (curMove++)->move;
if ( move != MOVE_NONE if ( move != MOVE_NONE
&& move != ttMoves[0].move && move != ttMoves[0].move
&& move != ttMoves[1].move && move != ttMoves[1].move
@@ -307,6 +300,7 @@ Move MovePicker::get_next_move() {
break; break;
case PH_NONCAPTURES: case PH_NONCAPTURES:
move = (curMove++)->move;
if ( move != ttMoves[0].move if ( move != ttMoves[0].move
&& move != ttMoves[1].move && move != ttMoves[1].move
&& move != killers[0].move && move != killers[0].move
@@ -315,13 +309,20 @@ Move MovePicker::get_next_move() {
return move; return move;
break; break;
case PH_EVASIONS:
case PH_BAD_CAPTURES: case PH_BAD_CAPTURES:
move = pick_best(curMove++, lastMove).move;
return move; return move;
case PH_EVASIONS:
case PH_QCAPTURES: case PH_QCAPTURES:
move = pick_best(curMove++, lastMove).move;
if ( move != ttMoves[0].move
&& pos.pl_move_is_legal(move, pinned))
return move;
break;
case PH_QCHECKS: case PH_QCHECKS:
// Maybe postpone the legality check until after futility pruning? move = (curMove++)->move;
if ( move != ttMoves[0].move if ( move != ttMoves[0].move
&& pos.pl_move_is_legal(move, pinned)) && pos.pl_move_is_legal(move, pinned))
return move; return move;
+1 -10
View File
@@ -54,7 +54,6 @@ public:
Move get_next_move(); Move get_next_move();
Move get_next_move(Lock& lock); Move get_next_move(Lock& lock);
int number_of_evasions() const; int number_of_evasions() const;
Bitboard discovered_check_candidates() const;
private: private:
void score_captures(); void score_captures();
@@ -69,7 +68,7 @@ private:
int phase; int phase;
const uint8_t* phasePtr; const uint8_t* phasePtr;
MoveStack *curMove, *lastMove, *lastBadCapture; MoveStack *curMove, *lastMove, *lastBadCapture;
Bitboard dc, pinned; Bitboard pinned;
MoveStack moves[256], badCaptures[64]; MoveStack moves[256], badCaptures[64];
}; };
@@ -88,12 +87,4 @@ inline int MovePicker::number_of_evasions() const {
return int(lastMove - moves); return int(lastMove - moves);
} }
/// MovePicker::discovered_check_candidates() returns a bitboard containing
/// all pieces which can possibly give discovered check. This bitboard is
/// computed by the constructor function.
inline Bitboard MovePicker::discovered_check_candidates() const {
return dc;
}
#endif // !defined(MOVEPICK_H_INCLUDED) #endif // !defined(MOVEPICK_H_INCLUDED)
+204 -245
View File
@@ -38,64 +38,36 @@ namespace {
/// Constants and variables /// Constants and variables
// Doubled pawn penalty by file, middle game #define S(mg, eg) make_score(mg, eg)
const Value DoubledPawnMidgamePenalty[8] = {
Value(13), Value(20), Value(23), Value(23), // Doubled pawn penalty by file
Value(23), Value(23), Value(20), Value(13) const Score DoubledPawnPenalty[8] = {
S(13, 43), S(20, 48), S(23, 48), S(23, 48),
S(23, 48), S(23, 48), S(20, 48), S(13, 43)
}; };
// Doubled pawn penalty by file, endgame // Isolated pawn penalty by file
const Value DoubledPawnEndgamePenalty[8] = { const Score IsolatedPawnPenalty[8] = {
Value(43), Value(48), Value(48), Value(48), S(25, 30), S(36, 35), S(40, 35), S(40, 35),
Value(48), Value(48), Value(48), Value(43) S(40, 35), S(40, 35), S(36, 35), S(25, 30)
}; };
// Isolated pawn penalty by file, middle game // Backward pawn penalty by file
const Value IsolatedPawnMidgamePenalty[8] = { const Score BackwardPawnPenalty[8] = {
Value(25), Value(36), Value(40), Value(40), S(20, 28), S(29, 31), S(33, 31), S(33, 31),
Value(40), Value(40), Value(36), Value(25) S(33, 31), S(33, 31), S(29, 31), S(20, 28)
}; };
// Isolated pawn penalty by file, endgame // Pawn chain membership bonus by file
const Value IsolatedPawnEndgamePenalty[8] = { const Score ChainBonus[8] = {
Value(30), Value(35), Value(35), Value(35), S(11,-1), S(13,-1), S(13,-1), S(14,-1),
Value(35), Value(35), Value(35), Value(30) S(14,-1), S(13,-1), S(13,-1), S(11,-1)
}; };
// Backward pawn penalty by file, middle game // Candidate passed pawn bonus by rank
const Value BackwardPawnMidgamePenalty[8] = { const Score CandidateBonus[8] = {
Value(20), Value(29), Value(33), Value(33), S( 0, 0), S( 6, 13), S(6,13), S(14,29),
Value(33), Value(33), Value(29), Value(20) S(34,68), S(83,166), S(0, 0), S( 0, 0)
};
// Backward pawn penalty by file, endgame
const Value BackwardPawnEndgamePenalty[8] = {
Value(28), Value(31), Value(31), Value(31),
Value(31), Value(31), Value(31), Value(28)
};
// Pawn chain membership bonus by file, middle game
const Value ChainMidgameBonus[8] = {
Value(11), Value(13), Value(13), Value(14),
Value(14), Value(13), Value(13), Value(11)
};
// Pawn chain membership bonus by file, endgame
const Value ChainEndgameBonus[8] = {
Value(-1), Value(-1), Value(-1), Value(-1),
Value(-1), Value(-1), Value(-1), Value(-1)
};
// Candidate passed pawn bonus by rank, middle game
const Value CandidateMidgameBonus[8] = {
Value( 0), Value( 6), Value(6), Value(14),
Value(34), Value(83), Value(0), Value( 0)
};
// Candidate passed pawn bonus by rank, endgame
const Value CandidateEndgameBonus[8] = {
Value( 0), Value( 13), Value(13), Value(29),
Value(68), Value(166), Value( 0), Value( 0)
}; };
// Pawn storm tables for positions with opposite castling // Pawn storm tables for positions with opposite castling
@@ -190,207 +162,194 @@ PawnInfo* PawnInfoTable::get_pawn_info(const Position& pos) {
pi->clear(); pi->clear();
pi->key = key; pi->key = key;
Value mgValue[2] = {Value(0), Value(0)};
Value egValue[2] = {Value(0), Value(0)};
// Calculate pawn attacks // Calculate pawn attacks
pi->pawnAttacks[WHITE] = ((pos.pieces(PAWN, WHITE) << 9) & ~FileABB) | ((pos.pieces(PAWN, WHITE) << 7) & ~FileHBB); Bitboard whitePawns = pos.pieces(PAWN, WHITE);
pi->pawnAttacks[BLACK] = ((pos.pieces(PAWN, BLACK) >> 7) & ~FileABB) | ((pos.pieces(PAWN, BLACK) >> 9) & ~FileHBB); Bitboard blackPawns = pos.pieces(PAWN, BLACK);
pi->pawnAttacks[WHITE] = ((whitePawns << 9) & ~FileABB) | ((whitePawns << 7) & ~FileHBB);
pi->pawnAttacks[BLACK] = ((blackPawns >> 7) & ~FileABB) | ((blackPawns >> 9) & ~FileHBB);
// Loop through the pawns for both colors // Evaluate pawns for both colors
for (Color us = WHITE; us <= BLACK; us++) pi->value = evaluate_pawns<WHITE>(pos, whitePawns, blackPawns, pi)
{ - evaluate_pawns<BLACK>(pos, blackPawns, whitePawns, pi);
Color them = opposite_color(us);
Bitboard ourPawns = pos.pieces(PAWN, us);
Bitboard theirPawns = pos.pieces(PAWN, them);
Bitboard pawns = ourPawns;
// Initialize pawn storm scores by giving bonuses for open files
for (File f = FILE_A; f <= FILE_H; f++)
if (!(pawns & file_bb(f)))
{
pi->ksStormValue[us] += KStormOpenFileBonus[f];
pi->qsStormValue[us] += QStormOpenFileBonus[f];
pi->halfOpenFiles[us] |= (1 << f);
}
// Loop through all pawns of the current color and score each pawn
while (pawns)
{
Square s = pop_1st_bit(&pawns);
File f = square_file(s);
Rank r = square_rank(s);
assert(pos.piece_on(s) == piece_of_color_and_type(us, PAWN));
// Passed, isolated or doubled pawn?
bool passed = Position::pawn_is_passed(theirPawns, us, s);
bool isolated = Position::pawn_is_isolated(ourPawns, s);
bool doubled = Position::pawn_is_doubled(ourPawns, us, s);
// We calculate kingside and queenside pawn storm
// scores for both colors. These are used when evaluating
// middle game positions with opposite side castling.
//
// Each pawn is given a base score given by a piece square table
// (KStormTable[] or QStormTable[]). Pawns which seem to have good
// chances of creating an open file by exchanging itself against an
// enemy pawn on an adjacent file gets an additional bonus.
// Kingside pawn storms
int bonus = KStormTable[relative_square(us, s)];
if (f >= FILE_F)
{
Bitboard b = outpost_mask(us, s) & theirPawns & (FileFBB | FileGBB | FileHBB);
while (b)
{
Square s2 = pop_1st_bit(&b);
if (!(theirPawns & neighboring_files_bb(s2) & rank_bb(s2)))
{
// The enemy pawn has no pawn beside itself, which makes it
// particularly vulnerable. Big bonus, especially against a
// weakness on the rook file.
if (square_file(s2) == FILE_H)
bonus += 4*StormLeverBonus[f] - 8*square_distance(s, s2);
else
bonus += 2*StormLeverBonus[f] - 4*square_distance(s, s2);
} else
// There is at least one enemy pawn beside the enemy pawn we look
// at, which means that the pawn has somewhat better chances of
// defending itself by advancing. Smaller bonus.
bonus += StormLeverBonus[f] - 2*square_distance(s, s2);
}
}
pi->ksStormValue[us] += bonus;
// Queenside pawn storms
bonus = QStormTable[relative_square(us, s)];
if (f <= FILE_C)
{
Bitboard b = outpost_mask(us, s) & theirPawns & (FileABB | FileBBB | FileCBB);
while (b)
{
Square s2 = pop_1st_bit(&b);
if (!(theirPawns & neighboring_files_bb(s2) & rank_bb(s2)))
{
// The enemy pawn has no pawn beside itself, which makes it
// particularly vulnerable. Big bonus, especially against a
// weakness on the rook file.
if (square_file(s2) == FILE_A)
bonus += 4*StormLeverBonus[f] - 16*square_distance(s, s2);
else
bonus += 2*StormLeverBonus[f] - 8*square_distance(s, s2);
} else
// There is at least one enemy pawn beside the enemy pawn we look
// at, which means that the pawn has somewhat better chances of
// defending itself by advancing. Smaller bonus.
bonus += StormLeverBonus[f] - 4*square_distance(s, s2);
}
}
pi->qsStormValue[us] += bonus;
// Member of a pawn chain (but not the backward one)? We could speed up
// the test a little by introducing an array of masks indexed by color
// and square for doing the test, but because everything is hashed,
// it probably won't make any noticable difference.
bool chain = ourPawns
& neighboring_files_bb(f)
& (rank_bb(r) | rank_bb(r - (us == WHITE ? 1 : -1)));
// Test for backward pawn
//
// If the pawn is passed, isolated, or member of a pawn chain
// it cannot be backward. If can capture an enemy pawn or if
// there are friendly pawns behind on neighboring files it cannot
// be backward either.
bool backward;
if ( passed
|| isolated
|| chain
|| (pos.attacks_from<PAWN>(s, us) & theirPawns)
|| (ourPawns & behind_bb(us, r) & neighboring_files_bb(f)))
backward = false;
else
{
// We now know that there are no friendly pawns beside or behind this
// pawn on neighboring files. We now check whether the pawn is
// backward by looking in the forward direction on the neighboring
// files, and seeing whether we meet a friendly or an enemy pawn first.
Bitboard b = pos.attacks_from<PAWN>(s, us);
if (us == WHITE)
{
for ( ; !(b & (ourPawns | theirPawns)); b <<= 8);
backward = (b | (b << 8)) & theirPawns;
}
else
{
for ( ; !(b & (ourPawns | theirPawns)); b >>= 8);
backward = (b | (b >> 8)) & theirPawns;
}
}
// Test for candidate passed pawn
bool candidate;
candidate = !passed
&& !(theirPawns & file_bb(f))
&& ( count_1s_max_15(neighboring_files_bb(f) & (behind_bb(us, r) | rank_bb(r)) & ourPawns)
- count_1s_max_15(neighboring_files_bb(f) & in_front_bb(us, r) & theirPawns)
>= 0);
// In order to prevent doubled passed pawns from receiving a too big
// bonus, only the frontmost passed pawn on each file is considered as
// a true passed pawn.
if (passed && (ourPawns & squares_in_front_of(us, s)))
passed = false;
// Score this pawn
if (passed)
set_bit(&(pi->passedPawns), s);
if (isolated)
{
mgValue[us] -= IsolatedPawnMidgamePenalty[f];
egValue[us] -= IsolatedPawnEndgamePenalty[f];
if (!(theirPawns & file_bb(f)))
{
mgValue[us] -= IsolatedPawnMidgamePenalty[f] / 2;
egValue[us] -= IsolatedPawnEndgamePenalty[f] / 2;
}
}
if (doubled)
{
mgValue[us] -= DoubledPawnMidgamePenalty[f];
egValue[us] -= DoubledPawnEndgamePenalty[f];
}
if (backward)
{
mgValue[us] -= BackwardPawnMidgamePenalty[f];
egValue[us] -= BackwardPawnEndgamePenalty[f];
if (!(theirPawns & file_bb(f)))
{
mgValue[us] -= BackwardPawnMidgamePenalty[f] / 2;
egValue[us] -= BackwardPawnEndgamePenalty[f] / 2;
}
}
if (chain)
{
mgValue[us] += ChainMidgameBonus[f];
egValue[us] += ChainEndgameBonus[f];
}
if (candidate)
{
mgValue[us] += CandidateMidgameBonus[relative_rank(us, s)];
egValue[us] += CandidateEndgameBonus[relative_rank(us, s)];
}
} // while(pawns)
} // for(colors)
pi->mgValue = int16_t(mgValue[WHITE] - mgValue[BLACK]);
pi->egValue = int16_t(egValue[WHITE] - egValue[BLACK]);
return pi; return pi;
} }
/// PawnInfoTable::evaluate_pawns() evaluates each pawn of the given color
template<Color Us>
Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns,
Bitboard theirPawns, PawnInfo* pi) {
Square s;
File f;
Rank r;
bool passed, isolated, doubled, chain, backward, candidate;
int bonus;
Score value = make_score(0, 0);
const Square* ptr = pos.piece_list_begin(Us, PAWN);
// Initialize pawn storm scores by giving bonuses for open files
for (f = FILE_A; f <= FILE_H; f++)
if (!(ourPawns & file_bb(f)))
{
pi->ksStormValue[Us] += KStormOpenFileBonus[f];
pi->qsStormValue[Us] += QStormOpenFileBonus[f];
pi->halfOpenFiles[Us] |= (1 << f);
}
// Loop through all pawns of the current color and score each pawn
while ((s = *ptr++) != SQ_NONE)
{
f = square_file(s);
r = square_rank(s);
assert(pos.piece_on(s) == piece_of_color_and_type(Us, PAWN));
// Passed, isolated or doubled pawn?
passed = Position::pawn_is_passed(theirPawns, Us, s);
isolated = Position::pawn_is_isolated(ourPawns, s);
doubled = Position::pawn_is_doubled(ourPawns, Us, s);
// We calculate kingside and queenside pawn storm
// scores for both colors. These are used when evaluating
// middle game positions with opposite side castling.
//
// Each pawn is given a base score given by a piece square table
// (KStormTable[] or QStormTable[]). Pawns which seem to have good
// chances of creating an open file by exchanging itself against an
// enemy pawn on an adjacent file gets an additional bonus.
// Kingside pawn storms
bonus = KStormTable[relative_square(Us, s)];
if (f >= FILE_F)
{
Bitboard b = outpost_mask(Us, s) & theirPawns & (FileFBB | FileGBB | FileHBB);
while (b)
{
Square s2 = pop_1st_bit(&b);
if (!(theirPawns & neighboring_files_bb(s2) & rank_bb(s2)))
{
// The enemy pawn has no pawn beside itself, which makes it
// particularly vulnerable. Big bonus, especially against a
// weakness on the rook file.
if (square_file(s2) == FILE_H)
bonus += 4*StormLeverBonus[f] - 8*square_distance(s, s2);
else
bonus += 2*StormLeverBonus[f] - 4*square_distance(s, s2);
} else
// There is at least one enemy pawn beside the enemy pawn we look
// at, which means that the pawn has somewhat better chances of
// defending itself by advancing. Smaller bonus.
bonus += StormLeverBonus[f] - 2*square_distance(s, s2);
}
}
pi->ksStormValue[Us] += bonus;
// Queenside pawn storms
bonus = QStormTable[relative_square(Us, s)];
if (f <= FILE_C)
{
Bitboard b = outpost_mask(Us, s) & theirPawns & (FileABB | FileBBB | FileCBB);
while (b)
{
Square s2 = pop_1st_bit(&b);
if (!(theirPawns & neighboring_files_bb(s2) & rank_bb(s2)))
{
// The enemy pawn has no pawn beside itself, which makes it
// particularly vulnerable. Big bonus, especially against a
// weakness on the rook file.
if (square_file(s2) == FILE_A)
bonus += 4*StormLeverBonus[f] - 16*square_distance(s, s2);
else
bonus += 2*StormLeverBonus[f] - 8*square_distance(s, s2);
} else
// There is at least one enemy pawn beside the enemy pawn we look
// at, which means that the pawn has somewhat better chances of
// defending itself by advancing. Smaller bonus.
bonus += StormLeverBonus[f] - 4*square_distance(s, s2);
}
}
pi->qsStormValue[Us] += bonus;
// Member of a pawn chain (but not the backward one)? We could speed up
// the test a little by introducing an array of masks indexed by color
// and square for doing the test, but because everything is hashed,
// it probably won't make any noticable difference.
chain = ourPawns
& neighboring_files_bb(f)
& (rank_bb(r) | rank_bb(r - (Us == WHITE ? 1 : -1)));
// Test for backward pawn
//
// If the pawn is passed, isolated, or member of a pawn chain
// it cannot be backward. If can capture an enemy pawn or if
// there are friendly pawns behind on neighboring files it cannot
// be backward either.
if ( (passed | isolated | chain)
|| (ourPawns & behind_bb(Us, r) & neighboring_files_bb(f))
|| (pos.attacks_from<PAWN>(s, Us) & theirPawns))
backward = false;
else
{
// We now know that there are no friendly pawns beside or behind this
// pawn on neighboring files. We now check whether the pawn is
// backward by looking in the forward direction on the neighboring
// files, and seeing whether we meet a friendly or an enemy pawn first.
Bitboard b = pos.attacks_from<PAWN>(s, Us);
// Note that we are sure to find something because pawn is not passed
// nor isolated, so loop is potentially infinite, but it isn't.
while (!(b & (ourPawns | theirPawns)))
Us == WHITE ? b <<= 8 : b >>= 8;
// The friendly pawn needs to be at least two ranks closer than the enemy
// pawn in order to help the potentially backward pawn advance.
backward = (b | (Us == WHITE ? b << 8 : b >> 8)) & theirPawns;
}
// Test for candidate passed pawn
candidate = !passed
&& !(theirPawns & file_bb(f))
&& ( count_1s_max_15(neighboring_files_bb(f) & (behind_bb(Us, r) | rank_bb(r)) & ourPawns)
- count_1s_max_15(neighboring_files_bb(f) & in_front_bb(Us, r) & theirPawns)
>= 0);
// In order to prevent doubled passed pawns from receiving a too big
// bonus, only the frontmost passed pawn on each file is considered as
// a true passed pawn.
if (passed && (ourPawns & squares_in_front_of(Us, s)))
passed = false;
// Score this pawn
if (passed)
set_bit(&(pi->passedPawns), s);
if (isolated)
{
value -= IsolatedPawnPenalty[f];
if (!(theirPawns & file_bb(f)))
value -= IsolatedPawnPenalty[f] / 2;
}
if (doubled)
value -= DoubledPawnPenalty[f];
if (backward)
{
value -= BackwardPawnPenalty[f];
if (!(theirPawns & file_bb(f)))
value -= BackwardPawnPenalty[f] / 2;
}
if (chain)
value += ChainBonus[f];
if (candidate)
value += CandidateBonus[relative_rank(Us, s)];
}
return value;
}
/// PawnInfo::updateShelter calculates and caches king shelter. It is called /// PawnInfo::updateShelter calculates and caches king shelter. It is called
/// only when king square changes, about 20% of total get_king_shelter() calls. /// only when king square changes, about 20% of total get_king_shelter() calls.
int PawnInfo::updateShelter(const Position& pos, Color c, Square ksq) { int PawnInfo::updateShelter(const Position& pos, Color c, Square ksq) {
+8 -10
View File
@@ -45,10 +45,9 @@ class PawnInfo {
friend class PawnInfoTable; friend class PawnInfoTable;
public: public:
PawnInfo() : key(0) { clear(); } PawnInfo() { clear(); }
Value mg_value() const; Score pawns_value() const;
Value eg_value() const;
Value kingside_storm_value(Color c) const; Value kingside_storm_value(Color c) const;
Value queenside_storm_value(Color c) const; Value queenside_storm_value(Color c) const;
Bitboard pawn_attacks(Color c) const; Bitboard pawn_attacks(Color c) const;
@@ -65,7 +64,7 @@ private:
Key key; Key key;
Bitboard passedPawns; Bitboard passedPawns;
Bitboard pawnAttacks[2]; Bitboard pawnAttacks[2];
int16_t mgValue, egValue; Score value;
int16_t ksStormValue[2], qsStormValue[2]; int16_t ksStormValue[2], qsStormValue[2];
uint8_t halfOpenFiles[2]; uint8_t halfOpenFiles[2];
Square kingSquares[2]; Square kingSquares[2];
@@ -85,6 +84,9 @@ public:
PawnInfo* get_pawn_info(const Position& pos); PawnInfo* get_pawn_info(const Position& pos);
private: private:
template<Color Us>
Score evaluate_pawns(const Position& pos, Bitboard ourPawns, Bitboard theirPawns, PawnInfo* pi);
unsigned size; unsigned size;
PawnInfo* entries; PawnInfo* entries;
}; };
@@ -94,12 +96,8 @@ private:
//// Inline functions //// Inline functions
//// ////
inline Value PawnInfo::mg_value() const { inline Score PawnInfo::pawns_value() const {
return Value(mgValue); return value;
}
inline Value PawnInfo::eg_value() const {
return Value(egValue);
} }
inline Bitboard PawnInfo::passed_pawns() const { inline Bitboard PawnInfo::passed_pawns() const {
+328 -323
View File
@@ -51,18 +51,31 @@ Key Position::zobEp[64];
Key Position::zobCastle[16]; Key Position::zobCastle[16];
Key Position::zobMaterial[2][8][16]; Key Position::zobMaterial[2][8][16];
Key Position::zobSideToMove; Key Position::zobSideToMove;
Key Position::zobExclusion;
Value Position::MgPieceSquareTable[16][64]; Score Position::PieceSquareTable[16][64];
Value Position::EgPieceSquareTable[16][64];
static bool RequestPending = false; static bool RequestPending = false;
////
//// Functions
////
/// Constructors /// Constructors
CheckInfo::CheckInfo(const Position& pos) {
Color us = pos.side_to_move();
Color them = opposite_color(us);
ksq = pos.king_square(them);
dcCandidates = pos.discovered_check_candidates(us);
checkSq[PAWN] = pos.attacks_from<PAWN>(ksq, them);
checkSq[KNIGHT] = pos.attacks_from<KNIGHT>(ksq);
checkSq[BISHOP] = pos.attacks_from<BISHOP>(ksq);
checkSq[ROOK] = pos.attacks_from<ROOK>(ksq);
checkSq[QUEEN] = checkSq[BISHOP] | checkSq[ROOK];
checkSq[KING] = EmptyBoardBB;
}
Position::Position(const Position& pos) { Position::Position(const Position& pos) {
copy(pos); copy(pos);
} }
@@ -130,72 +143,77 @@ void Position::from_fen(const string& fen) {
} }
i++; i++;
while(strchr("KQkqabcdefghABCDEFGH-", fen[i])) { while (strchr("KQkqabcdefghABCDEFGH-", fen[i])) {
if (fen[i] == '-') if (fen[i] == '-')
{ {
i++;
break;
}
else if (fen[i] == 'K') allow_oo(WHITE);
else if (fen[i] == 'Q') allow_ooo(WHITE);
else if (fen[i] == 'k') allow_oo(BLACK);
else if (fen[i] == 'q') allow_ooo(BLACK);
else if (fen[i] >= 'A' && fen[i] <= 'H') {
File rookFile, kingFile = FILE_NONE;
for (Square square = SQ_B1; square <= SQ_G1; square++)
if (piece_on(square) == WK)
kingFile = square_file(square);
if (kingFile == FILE_NONE) {
std::cout << "Error in FEN at character " << i << std::endl;
return;
}
initialKFile = kingFile;
rookFile = File(fen[i] - 'A') + FILE_A;
if (rookFile < initialKFile) {
allow_ooo(WHITE);
initialQRFile = rookFile;
}
else {
allow_oo(WHITE);
initialKRFile = rookFile;
}
}
else if (fen[i] >= 'a' && fen[i] <= 'h') {
File rookFile, kingFile = FILE_NONE;
for (Square square = SQ_B8; square <= SQ_G8; square++)
if (piece_on(square) == BK)
kingFile = square_file(square);
if (kingFile == FILE_NONE) {
std::cout << "Error in FEN at character " << i << std::endl;
return;
}
initialKFile = kingFile;
rookFile = File(fen[i] - 'a') + FILE_A;
if (rookFile < initialKFile) {
allow_ooo(BLACK);
initialQRFile = rookFile;
}
else {
allow_oo(BLACK);
initialKRFile = rookFile;
}
}
else {
std::cout << "Error in FEN at character " << i << std::endl;
return;
}
i++; i++;
break;
}
else if(fen[i] == 'K') allow_oo(WHITE);
else if(fen[i] == 'Q') allow_ooo(WHITE);
else if(fen[i] == 'k') allow_oo(BLACK);
else if(fen[i] == 'q') allow_ooo(BLACK);
else if(fen[i] >= 'A' && fen[i] <= 'H') {
File rookFile, kingFile = FILE_NONE;
for(Square square = SQ_B1; square <= SQ_G1; square++)
if(piece_on(square) == WK)
kingFile = square_file(square);
if(kingFile == FILE_NONE) {
std::cout << "Error in FEN at character " << i << std::endl;
return;
}
initialKFile = kingFile;
rookFile = File(fen[i] - 'A') + FILE_A;
if(rookFile < initialKFile) {
allow_ooo(WHITE);
initialQRFile = rookFile;
}
else {
allow_oo(WHITE);
initialKRFile = rookFile;
}
}
else if(fen[i] >= 'a' && fen[i] <= 'h') {
File rookFile, kingFile = FILE_NONE;
for(Square square = SQ_B8; square <= SQ_G8; square++)
if(piece_on(square) == BK)
kingFile = square_file(square);
if(kingFile == FILE_NONE) {
std::cout << "Error in FEN at character " << i << std::endl;
return;
}
initialKFile = kingFile;
rookFile = File(fen[i] - 'a') + FILE_A;
if(rookFile < initialKFile) {
allow_ooo(BLACK);
initialQRFile = rookFile;
}
else {
allow_oo(BLACK);
initialKRFile = rookFile;
}
}
else {
std::cout << "Error in FEN at character " << i << std::endl;
return;
}
i++;
} }
// Skip blanks // Skip blanks
while (fen[i] == ' ') while (fen[i] == ' ')
i++; i++;
// En passant square // En passant square -- ignore if no capture is possible
if ( i <= fen.length() - 2 if ( i <= fen.length() - 2
&& (fen[i] >= 'a' && fen[i] <= 'h') && (fen[i] >= 'a' && fen[i] <= 'h')
&& (fen[i+1] == '3' || fen[i+1] == '6')) && (fen[i+1] == '3' || fen[i+1] == '6'))
st->epSquare = square_from_string(fen.substr(i, 2)); {
Square fenEpSquare = square_from_string(fen.substr(i, 2));
Color them = opposite_color(sideToMove);
if (attacks_from<PAWN>(fenEpSquare, them) & this->pieces(PAWN, sideToMove))
st->epSquare = square_from_string(fen.substr(i, 2));
}
// Various initialisation // Various initialisation
for (Square sq = SQ_A1; sq <= SQ_H8; sq++) for (Square sq = SQ_A1; sq <= SQ_H8; sq++)
@@ -213,8 +231,7 @@ void Position::from_fen(const string& fen) {
st->key = compute_key(); st->key = compute_key();
st->pawnKey = compute_pawn_key(); st->pawnKey = compute_pawn_key();
st->materialKey = compute_material_key(); st->materialKey = compute_material_key();
st->mgValue = compute_value<MidGame>(); st->value = compute_value();
st->egValue = compute_value<EndGame>();
st->npMaterial[WHITE] = compute_non_pawn_material(WHITE); st->npMaterial[WHITE] = compute_non_pawn_material(WHITE);
st->npMaterial[BLACK] = compute_non_pawn_material(BLACK); st->npMaterial[BLACK] = compute_non_pawn_material(BLACK);
} }
@@ -254,10 +271,24 @@ const string Position::to_fen() const {
fen += (sideToMove == WHITE ? "w " : "b "); fen += (sideToMove == WHITE ? "w " : "b ");
if (st->castleRights != NO_CASTLES) if (st->castleRights != NO_CASTLES)
{ {
if (can_castle_kingside(WHITE)) fen += 'K'; if (initialKFile == FILE_E && initialQRFile == FILE_A && initialKRFile == FILE_H)
if (can_castle_queenside(WHITE)) fen += 'Q'; {
if (can_castle_kingside(BLACK)) fen += 'k'; if (can_castle_kingside(WHITE)) fen += 'K';
if (can_castle_queenside(BLACK)) fen += 'q'; if (can_castle_queenside(WHITE)) fen += 'Q';
if (can_castle_kingside(BLACK)) fen += 'k';
if (can_castle_queenside(BLACK)) fen += 'q';
}
else
{
if (can_castle_kingside(WHITE))
fen += char(toupper(file_to_char(initialKRFile)));
if (can_castle_queenside(WHITE))
fen += char(toupper(file_to_char(initialQRFile)));
if (can_castle_kingside(BLACK))
fen += file_to_char(initialKRFile);
if (can_castle_queenside(BLACK))
fen += file_to_char(initialQRFile);
}
} else } else
fen += '-'; fen += '-';
@@ -332,16 +363,15 @@ void Position::copy(const Position& pos) {
template<bool FindPinned> template<bool FindPinned>
Bitboard Position::hidden_checkers(Color c) const { Bitboard Position::hidden_checkers(Color c) const {
Bitboard pinners, result = EmptyBoardBB; Bitboard result = EmptyBoardBB;
Bitboard pinners = pieces_of_color(FindPinned ? opposite_color(c) : c);
// Pinned pieces protect our king, dicovery checks attack // Pinned pieces protect our king, dicovery checks attack
// the enemy king. // the enemy king.
Square ksq = king_square(FindPinned ? c : opposite_color(c)); Square ksq = king_square(FindPinned ? c : opposite_color(c));
// Pinners are sliders, not checkers, that give check when // Pinners are sliders, not checkers, that give check when candidate pinned is removed
// candidate pinned is removed. pinners &= (pieces(ROOK, QUEEN) & RookPseudoAttacks[ksq]) | (pieces(BISHOP, QUEEN) & BishopPseudoAttacks[ksq]);
pinners = (pieces(ROOK, QUEEN, FindPinned ? opposite_color(c) : c) & RookPseudoAttacks[ksq])
| (pieces(BISHOP, QUEEN, FindPinned ? opposite_color(c) : c) & BishopPseudoAttacks[ksq]);
if (FindPinned && pinners) if (FindPinned && pinners)
pinners &= ~st->checkersBB; pinners &= ~st->checkersBB;
@@ -458,19 +488,11 @@ void Position::find_checkers() {
/// Position::pl_move_is_legal() tests whether a pseudo-legal move is legal /// Position::pl_move_is_legal() tests whether a pseudo-legal move is legal
bool Position::pl_move_is_legal(Move m) const {
// If we're in check, all pseudo-legal moves are legal, because our
// check evasion generator only generates true legal moves.
return is_check() || pl_move_is_legal(m, pinned_pieces(side_to_move()));
}
bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
assert(is_ok()); assert(is_ok());
assert(move_is_ok(m)); assert(move_is_ok(m));
assert(pinned == pinned_pieces(side_to_move())); assert(pinned == pinned_pieces(side_to_move()));
assert(!is_check());
// Castling moves are checked for legality during move generation. // Castling moves are checked for legality during move generation.
if (move_is_castle(m)) if (move_is_castle(m))
@@ -482,7 +504,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
assert(color_of_piece_on(from) == us); assert(color_of_piece_on(from) == us);
assert(piece_on(king_square(us)) == piece_of_color_and_type(us, KING)); assert(piece_on(king_square(us)) == piece_of_color_and_type(us, KING));
// En passant captures are a tricky special case. Because they are // En passant captures are a tricky special case. Because they are
// rather uncommon, we do it simply by testing whether the king is attacked // rather uncommon, we do it simply by testing whether the king is attacked
// after the move is made // after the move is made
if (move_is_ep(m)) if (move_is_ep(m))
@@ -519,179 +541,142 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const {
} }
/// Position::pl_move_is_evasion() tests whether a pseudo-legal move is a legal evasion
bool Position::pl_move_is_evasion(Move m, Bitboard pinned) const
{
assert(is_check());
Color us = side_to_move();
Square from = move_from(m);
Square to = move_to(m);
// King moves and en-passant captures are verified in pl_move_is_legal()
if (type_of_piece_on(from) == KING || move_is_ep(m))
return pl_move_is_legal(m, pinned);
Bitboard target = checkers();
Square checksq = pop_1st_bit(&target);
if (target) // double check ?
return false;
// Our move must be a blocking evasion or a capture of the checking piece
target = squares_between(checksq, king_square(us)) | checkers();
return bit_is_set(target, to) && pl_move_is_legal(m, pinned);
}
/// Position::move_is_check() tests whether a pseudo-legal move is a check /// Position::move_is_check() tests whether a pseudo-legal move is a check
bool Position::move_is_check(Move m) const { bool Position::move_is_check(Move m) const {
Bitboard dc = discovered_check_candidates(side_to_move()); return move_is_check(m, CheckInfo(*this));
return move_is_check(m, dc);
} }
bool Position::move_is_check(Move m, Bitboard dcCandidates) const { bool Position::move_is_check(Move m, const CheckInfo& ci) const {
assert(is_ok()); assert(is_ok());
assert(move_is_ok(m)); assert(move_is_ok(m));
assert(dcCandidates == discovered_check_candidates(side_to_move())); assert(ci.dcCandidates == discovered_check_candidates(side_to_move()));
assert(color_of_piece_on(move_from(m)) == side_to_move());
assert(piece_on(ci.ksq) == piece_of_color_and_type(opposite_color(side_to_move()), KING));
Color us = side_to_move();
Color them = opposite_color(us);
Square from = move_from(m); Square from = move_from(m);
Square to = move_to(m); Square to = move_to(m);
Square ksq = king_square(them); PieceType pt = type_of_piece_on(from);
assert(color_of_piece_on(from) == us); // Direct check ?
assert(piece_on(ksq) == piece_of_color_and_type(them, KING)); if (bit_is_set(ci.checkSq[pt], to))
return true;
// Proceed according to the type of the moving piece // Discovery check ?
switch (type_of_piece_on(from)) if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from))
{ {
case PAWN: // For pawn and king moves we need to verify also direction
if ( (pt != PAWN && pt != KING)
if (bit_is_set(attacks_from<PAWN>(ksq, them), to)) // Normal check? ||(direction_between_squares(from, ci.ksq) != direction_between_squares(to, ci.ksq)))
return true; return true;
if ( dcCandidates // Discovered check?
&& bit_is_set(dcCandidates, from)
&& (direction_between_squares(from, ksq) != direction_between_squares(to, ksq)))
return true;
if (move_is_promotion(m)) // Promotion with check?
{
Bitboard b = occupied_squares();
clear_bit(&b, from);
switch (move_promotion_piece(m))
{
case KNIGHT:
return bit_is_set(attacks_from<KNIGHT>(to), ksq);
case BISHOP:
return bit_is_set(bishop_attacks_bb(to, b), ksq);
case ROOK:
return bit_is_set(rook_attacks_bb(to, b), ksq);
case QUEEN:
return bit_is_set(queen_attacks_bb(to, b), ksq);
default:
assert(false);
}
}
// En passant capture with check? We have already handled the case
// of direct checks and ordinary discovered check, the only case we
// need to handle is the unusual case of a discovered check through the
// captured pawn.
else if (move_is_ep(m))
{
Square capsq = make_square(square_file(to), square_rank(from));
Bitboard b = occupied_squares();
clear_bit(&b, from);
clear_bit(&b, capsq);
set_bit(&b, to);
return (rook_attacks_bb(ksq, b) & pieces(ROOK, QUEEN, us))
||(bishop_attacks_bb(ksq, b) & pieces(BISHOP, QUEEN, us));
}
return false;
// Test discovered check and normal check according to piece type
case KNIGHT:
return (dcCandidates && bit_is_set(dcCandidates, from))
|| bit_is_set(attacks_from<KNIGHT>(ksq), to);
case BISHOP:
return (dcCandidates && bit_is_set(dcCandidates, from))
|| (direction_is_diagonal(ksq, to) && bit_is_set(attacks_from<BISHOP>(ksq), to));
case ROOK:
return (dcCandidates && bit_is_set(dcCandidates, from))
|| (direction_is_straight(ksq, to) && bit_is_set(attacks_from<ROOK>(ksq), to));
case QUEEN:
// Discovered checks are impossible!
assert(!bit_is_set(dcCandidates, from));
return ( (direction_is_straight(ksq, to) && bit_is_set(attacks_from<ROOK>(ksq), to))
|| (direction_is_diagonal(ksq, to) && bit_is_set(attacks_from<BISHOP>(ksq), to)));
case KING:
// Discovered check?
if ( bit_is_set(dcCandidates, from)
&& (direction_between_squares(from, ksq) != direction_between_squares(to, ksq)))
return true;
// Castling with check?
if (move_is_castle(m))
{
Square kfrom, kto, rfrom, rto;
Bitboard b = occupied_squares();
kfrom = from;
rfrom = to;
if (rfrom > kfrom)
{
kto = relative_square(us, SQ_G1);
rto = relative_square(us, SQ_F1);
} else {
kto = relative_square(us, SQ_C1);
rto = relative_square(us, SQ_D1);
}
clear_bit(&b, kfrom);
clear_bit(&b, rfrom);
set_bit(&b, rto);
set_bit(&b, kto);
return bit_is_set(rook_attacks_bb(rto, b), ksq);
}
return false;
default: // NO_PIECE_TYPE
break;
} }
assert(false);
// Can we skip the ugly special cases ?
if (!move_is_special(m))
return false;
Color us = side_to_move();
Bitboard b = occupied_squares();
// Promotion with check ?
if (move_is_promotion(m))
{
clear_bit(&b, from);
switch (move_promotion_piece(m))
{
case KNIGHT:
return bit_is_set(attacks_from<KNIGHT>(to), ci.ksq);
case BISHOP:
return bit_is_set(bishop_attacks_bb(to, b), ci.ksq);
case ROOK:
return bit_is_set(rook_attacks_bb(to, b), ci.ksq);
case QUEEN:
return bit_is_set(queen_attacks_bb(to, b), ci.ksq);
default:
assert(false);
}
}
// En passant capture with check? We have already handled the case
// of direct checks and ordinary discovered check, the only case we
// need to handle is the unusual case of a discovered check through the
// captured pawn.
if (move_is_ep(m))
{
Square capsq = make_square(square_file(to), square_rank(from));
clear_bit(&b, from);
clear_bit(&b, capsq);
set_bit(&b, to);
return (rook_attacks_bb(ci.ksq, b) & pieces(ROOK, QUEEN, us))
||(bishop_attacks_bb(ci.ksq, b) & pieces(BISHOP, QUEEN, us));
}
// Castling with check ?
if (move_is_castle(m))
{
Square kfrom, kto, rfrom, rto;
kfrom = from;
rfrom = to;
if (rfrom > kfrom)
{
kto = relative_square(us, SQ_G1);
rto = relative_square(us, SQ_F1);
} else {
kto = relative_square(us, SQ_C1);
rto = relative_square(us, SQ_D1);
}
clear_bit(&b, kfrom);
clear_bit(&b, rfrom);
set_bit(&b, rto);
set_bit(&b, kto);
return bit_is_set(rook_attacks_bb(rto, b), ci.ksq);
}
return false; return false;
} }
/// Position::update_checkers() udpates chekers info given the move. It is called
/// in do_move() and is faster then find_checkers().
template<PieceType Piece>
inline void Position::update_checkers(Bitboard* pCheckersBB, Square ksq, Square from,
Square to, Bitboard dcCandidates) {
const bool Bishop = (Piece == QUEEN || Piece == BISHOP);
const bool Rook = (Piece == QUEEN || Piece == ROOK);
const bool Slider = Bishop || Rook;
// Direct checks
if ( ( (Bishop && bit_is_set(BishopPseudoAttacks[ksq], to))
|| (Rook && bit_is_set(RookPseudoAttacks[ksq], to)))
&& bit_is_set(attacks_from<Piece>(ksq), to)) // slow, try to early skip
set_bit(pCheckersBB, to);
else if ( Piece != KING
&& !Slider
&& bit_is_set(Piece == PAWN ? attacks_from<PAWN>(ksq, opposite_color(sideToMove))
: attacks_from<Piece>(ksq), to))
set_bit(pCheckersBB, to);
// Discovery checks
if (Piece != QUEEN && bit_is_set(dcCandidates, from))
{
if (Piece != ROOK)
(*pCheckersBB) |= (attacks_from<ROOK>(ksq) & pieces(ROOK, QUEEN, side_to_move()));
if (Piece != BISHOP)
(*pCheckersBB) |= (attacks_from<BISHOP>(ksq) & pieces(BISHOP, QUEEN, side_to_move()));
}
}
/// Position::do_move() makes a move, and saves all information necessary /// Position::do_move() makes a move, and saves all information necessary
/// to a StateInfo object. The move is assumed to be legal. /// to a StateInfo object. The move is assumed to be legal.
/// Pseudo-legal moves should be filtered out before this function is called. /// Pseudo-legal moves should be filtered out before this function is called.
void Position::do_move(Move m, StateInfo& newSt) { void Position::do_move(Move m, StateInfo& newSt) {
do_move(m, newSt, discovered_check_candidates(side_to_move())); CheckInfo ci(*this);
do_move(m, newSt, ci, move_is_check(m, ci));
} }
void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) { void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveIsCheck) {
assert(is_ok()); assert(is_ok());
assert(move_is_ok(m)); assert(move_is_ok(m));
@@ -702,10 +687,10 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
// ones which are recalculated from scratch anyway, then switch our state // ones which are recalculated from scratch anyway, then switch our state
// pointer to point to the new, ready to be updated, state. // pointer to point to the new, ready to be updated, state.
struct ReducedStateInfo { struct ReducedStateInfo {
Key key, pawnKey, materialKey; Key pawnKey, materialKey;
int castleRights, rule50; int castleRights, rule50, pliesFromNull;
Square epSquare; Square epSquare;
Value mgValue, egValue; Score value;
Value npMaterial[2]; Value npMaterial[2];
}; };
@@ -724,6 +709,7 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
// Increment the 50 moves rule draw counter. Resetting it to zero in the // Increment the 50 moves rule draw counter. Resetting it to zero in the
// case of non-reversible moves is taken care of later. // case of non-reversible moves is taken care of later.
st->rule50++; st->rule50++;
st->pliesFromNull++;
if (move_is_castle(m)) if (move_is_castle(m))
{ {
@@ -741,16 +727,15 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
Piece piece = piece_on(from); Piece piece = piece_on(from);
PieceType pt = type_of_piece(piece); PieceType pt = type_of_piece(piece);
PieceType capture = ep ? PAWN : type_of_piece_on(to);
assert(color_of_piece_on(from) == us); assert(color_of_piece_on(from) == us);
assert(color_of_piece_on(to) == them || square_is_empty(to)); assert(color_of_piece_on(to) == them || square_is_empty(to));
assert(!(ep || pm) || piece == piece_of_color_and_type(us, PAWN)); assert(!(ep || pm) || piece == piece_of_color_and_type(us, PAWN));
assert(!pm || relative_rank(us, to) == RANK_8); assert(!pm || relative_rank(us, to) == RANK_8);
st->capture = ep ? PAWN : type_of_piece_on(to); if (capture)
do_capture_move(key, capture, them, to, ep);
if (st->capture)
do_capture_move(key, st->capture, them, to, ep);
// Update hash key // Update hash key
key ^= zobrist[us][pt][from] ^ zobrist[us][pt][to]; key ^= zobrist[us][pt][from] ^ zobrist[us][pt][to];
@@ -800,7 +785,7 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
st->pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to]; st->pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to];
// Set en passant square, only if moved pawn can be captured // Set en passant square, only if moved pawn can be captured
if (abs(int(to) - int(from)) == 16) if ((to ^ from) == 16)
{ {
if (attacks_from<PAWN>(from + (us == WHITE ? DELTA_N : DELTA_S), us) & pieces(PAWN, them)) if (attacks_from<PAWN>(from + (us == WHITE ? DELTA_N : DELTA_S), us) & pieces(PAWN, them))
{ {
@@ -811,8 +796,10 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
} }
// Update incremental scores // Update incremental scores
st->mgValue += pst_delta<MidGame>(piece, from, to); st->value += pst_delta(piece, from, to);
st->egValue += pst_delta<EndGame>(piece, from, to);
// Set capture piece
st->capture = capture;
if (pm) // promotion ? if (pm) // promotion ?
{ {
@@ -847,10 +834,8 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
st->pawnKey ^= zobrist[us][PAWN][to]; st->pawnKey ^= zobrist[us][PAWN][to];
// Partially revert and update incremental scores // Partially revert and update incremental scores
st->mgValue -= pst<MidGame>(us, PAWN, to); st->value -= pst(us, PAWN, to);
st->mgValue += pst<MidGame>(us, promotion, to); st->value += pst(us, promotion, to);
st->egValue -= pst<EndGame>(us, PAWN, to);
st->egValue += pst<EndGame>(us, promotion, to);
// Update material // Update material
st->npMaterial[us] += piece_value_midgame(promotion); st->npMaterial[us] += piece_value_midgame(promotion);
@@ -860,29 +845,33 @@ void Position::do_move(Move m, StateInfo& newSt, Bitboard dcCandidates) {
st->key = key; st->key = key;
// Update checkers bitboard, piece must be already moved // Update checkers bitboard, piece must be already moved
if (ep | pm) st->checkersBB = EmptyBoardBB;
st->checkersBB = attackers_to(king_square(them)) & pieces_of_color(us);
else if (moveIsCheck)
{ {
st->checkersBB = EmptyBoardBB; if (ep | pm)
Square ksq = king_square(them); st->checkersBB = attackers_to(king_square(them)) & pieces_of_color(us);
switch (pt) else
{ {
case PAWN: update_checkers<PAWN>(&(st->checkersBB), ksq, from, to, dcCandidates); break; // Direct checks
case KNIGHT: update_checkers<KNIGHT>(&(st->checkersBB), ksq, from, to, dcCandidates); break; if (bit_is_set(ci.checkSq[pt], to))
case BISHOP: update_checkers<BISHOP>(&(st->checkersBB), ksq, from, to, dcCandidates); break; st->checkersBB = SetMaskBB[to];
case ROOK: update_checkers<ROOK>(&(st->checkersBB), ksq, from, to, dcCandidates); break;
case QUEEN: update_checkers<QUEEN>(&(st->checkersBB), ksq, from, to, dcCandidates); break; // Discovery checks
case KING: update_checkers<KING>(&(st->checkersBB), ksq, from, to, dcCandidates); break; if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from))
default: assert(false); break; {
if (pt != ROOK)
st->checkersBB |= (attacks_from<ROOK>(ci.ksq) & pieces(ROOK, QUEEN, us));
if (pt != BISHOP)
st->checkersBB |= (attacks_from<BISHOP>(ci.ksq) & pieces(BISHOP, QUEEN, us));
}
} }
} }
// Finish // Finish
sideToMove = opposite_color(sideToMove); sideToMove = opposite_color(sideToMove);
st->value += (sideToMove == WHITE ? TempoValue : -TempoValue);
st->mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame;
st->egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame;
assert(is_ok()); assert(is_ok());
} }
@@ -918,8 +907,7 @@ void Position::do_capture_move(Bitboard& key, PieceType capture, Color them, Squ
key ^= zobrist[them][capture][capsq]; key ^= zobrist[them][capture][capsq];
// Update incremental scores // Update incremental scores
st->mgValue -= pst<MidGame>(them, capture, capsq); st->value -= pst(them, capture, capsq);
st->egValue -= pst<EndGame>(them, capture, capsq);
// If the captured piece was a pawn, update pawn hash key, // If the captured piece was a pawn, update pawn hash key,
// otherwise update non-pawn material. // otherwise update non-pawn material.
@@ -985,16 +973,21 @@ void Position::do_castle_move(Move m) {
rto = relative_square(us, SQ_D1); rto = relative_square(us, SQ_D1);
} }
// Move the pieces // Remove pieces from source squares:
Bitboard kmove_bb = make_move_bb(kfrom, kto); clear_bit(&(byColorBB[us]), kfrom);
do_move_bb(&(byColorBB[us]), kmove_bb); clear_bit(&(byTypeBB[KING]), kfrom);
do_move_bb(&(byTypeBB[KING]), kmove_bb); clear_bit(&(byTypeBB[0]), kfrom); // HACK: byTypeBB[0] == occupied squares
do_move_bb(&(byTypeBB[0]), kmove_bb); // HACK: byTypeBB[0] == occupied squares clear_bit(&(byColorBB[us]), rfrom);
clear_bit(&(byTypeBB[ROOK]), rfrom);
clear_bit(&(byTypeBB[0]), rfrom); // HACK: byTypeBB[0] == occupied squares
Bitboard rmove_bb = make_move_bb(rfrom, rto); // Put pieces on destination squares:
do_move_bb(&(byColorBB[us]), rmove_bb); set_bit(&(byColorBB[us]), kto);
do_move_bb(&(byTypeBB[ROOK]), rmove_bb); set_bit(&(byTypeBB[KING]), kto);
do_move_bb(&(byTypeBB[0]), rmove_bb); // HACK: byTypeBB[0] == occupied squares set_bit(&(byTypeBB[0]), kto); // HACK: byTypeBB[0] == occupied squares
set_bit(&(byColorBB[us]), rto);
set_bit(&(byTypeBB[ROOK]), rto);
set_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares
// Update board array // Update board array
Piece king = piece_of_color_and_type(us, KING); Piece king = piece_of_color_and_type(us, KING);
@@ -1011,10 +1004,8 @@ void Position::do_castle_move(Move m) {
index[rto] = tmp; index[rto] = tmp;
// Update incremental scores // Update incremental scores
st->mgValue += pst_delta<MidGame>(king, kfrom, kto); st->value += pst_delta(king, kfrom, kto);
st->egValue += pst_delta<EndGame>(king, kfrom, kto); st->value += pst_delta(rook, rfrom, rto);
st->mgValue += pst_delta<MidGame>(rook, rfrom, rto);
st->egValue += pst_delta<EndGame>(rook, rfrom, rto);
// Update hash key // Update hash key
st->key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto]; st->key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto];
@@ -1040,9 +1031,7 @@ void Position::do_castle_move(Move m) {
// Finish // Finish
sideToMove = opposite_color(sideToMove); sideToMove = opposite_color(sideToMove);
st->value += (sideToMove == WHITE ? TempoValue : -TempoValue);
st->mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame;
st->egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame;
assert(is_ok()); assert(is_ok());
} }
@@ -1106,6 +1095,7 @@ void Position::undo_move(Move m) {
pieceList[us][PAWN][index[to]] = to; pieceList[us][PAWN][index[to]] = to;
} }
// Put the piece back at the source square // Put the piece back at the source square
Bitboard move_bb = make_move_bb(to, from); Bitboard move_bb = make_move_bb(to, from);
do_move_bb(&(byColorBB[us]), move_bb); do_move_bb(&(byColorBB[us]), move_bb);
@@ -1184,16 +1174,21 @@ void Position::undo_castle_move(Move m) {
assert(piece_on(kto) == piece_of_color_and_type(us, KING)); assert(piece_on(kto) == piece_of_color_and_type(us, KING));
assert(piece_on(rto) == piece_of_color_and_type(us, ROOK)); assert(piece_on(rto) == piece_of_color_and_type(us, ROOK));
// Put the pieces back at the source square // Remove pieces from destination squares:
Bitboard kmove_bb = make_move_bb(kto, kfrom); clear_bit(&(byColorBB[us]), kto);
do_move_bb(&(byColorBB[us]), kmove_bb); clear_bit(&(byTypeBB[KING]), kto);
do_move_bb(&(byTypeBB[KING]), kmove_bb); clear_bit(&(byTypeBB[0]), kto); // HACK: byTypeBB[0] == occupied squares
do_move_bb(&(byTypeBB[0]), kmove_bb); // HACK: byTypeBB[0] == occupied squares clear_bit(&(byColorBB[us]), rto);
clear_bit(&(byTypeBB[ROOK]), rto);
clear_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares
Bitboard rmove_bb = make_move_bb(rto, rfrom); // Put pieces on source squares:
do_move_bb(&(byColorBB[us]), rmove_bb); set_bit(&(byColorBB[us]), kfrom);
do_move_bb(&(byTypeBB[ROOK]), rmove_bb); set_bit(&(byTypeBB[KING]), kfrom);
do_move_bb(&(byTypeBB[0]), rmove_bb); // HACK: byTypeBB[0] == occupied squares set_bit(&(byTypeBB[0]), kfrom); // HACK: byTypeBB[0] == occupied squares
set_bit(&(byColorBB[us]), rfrom);
set_bit(&(byTypeBB[ROOK]), rfrom);
set_bit(&(byTypeBB[0]), rfrom); // HACK: byTypeBB[0] == occupied squares
// Update board // Update board
board[rto] = board[kto] = EMPTY; board[rto] = board[kto] = EMPTY;
@@ -1228,9 +1223,9 @@ void Position::do_null_move(StateInfo& backupSt) {
// a backup storage not as a new state to be used. // a backup storage not as a new state to be used.
backupSt.key = st->key; backupSt.key = st->key;
backupSt.epSquare = st->epSquare; backupSt.epSquare = st->epSquare;
backupSt.mgValue = st->mgValue; backupSt.value = st->value;
backupSt.egValue = st->egValue;
backupSt.previous = st->previous; backupSt.previous = st->previous;
backupSt.pliesFromNull = st->pliesFromNull;
st->previous = &backupSt; st->previous = &backupSt;
// Save the current key to the history[] array, in order to be able to // Save the current key to the history[] array, in order to be able to
@@ -1247,10 +1242,9 @@ void Position::do_null_move(StateInfo& backupSt) {
sideToMove = opposite_color(sideToMove); sideToMove = opposite_color(sideToMove);
st->epSquare = SQ_NONE; st->epSquare = SQ_NONE;
st->rule50++; st->rule50++;
st->pliesFromNull = 0;
st->value += (sideToMove == WHITE) ? TempoValue : -TempoValue;
gamePly++; gamePly++;
st->mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame;
st->egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame;
} }
@@ -1265,9 +1259,9 @@ void Position::undo_null_move() {
StateInfo* backupSt = st->previous; StateInfo* backupSt = st->previous;
st->key = backupSt->key; st->key = backupSt->key;
st->epSquare = backupSt->epSquare; st->epSquare = backupSt->epSquare;
st->mgValue = backupSt->mgValue; st->value = backupSt->value;
st->egValue = backupSt->egValue;
st->previous = backupSt->previous; st->previous = backupSt->previous;
st->pliesFromNull = backupSt->pliesFromNull;
// Update the necessary information // Update the necessary information
sideToMove = opposite_color(sideToMove); sideToMove = opposite_color(sideToMove);
@@ -1335,6 +1329,10 @@ int Position::see(Square from, Square to) const {
Piece capture = piece_on(to); Piece capture = piece_on(to);
Bitboard occ = occupied_squares(); Bitboard occ = occupied_squares();
// King cannot be recaptured
if (type_of_piece(piece) == KING)
return seeValues[capture];
// Handle en passant moves // Handle en passant moves
if (st->epSquare == to && type_of_piece_on(from) == PAWN) if (st->epSquare == to && type_of_piece_on(from) == PAWN)
{ {
@@ -1473,8 +1471,8 @@ void Position::clear() {
for (int i = 0; i < 64; i++) for (int i = 0; i < 64; i++)
board[i] = EMPTY; board[i] = EMPTY;
for (int i = 0; i < 7; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) for (int j = 0; j < 16; j++)
pieceList[0][i][j] = pieceList[1][i][j] = SQ_NONE; pieceList[0][i][j] = pieceList[1][i][j] = SQ_NONE;
sideToMove = WHITE; sideToMove = WHITE;
@@ -1574,7 +1572,7 @@ Key Position::compute_pawn_key() const {
for (Color c = WHITE; c <= BLACK; c++) for (Color c = WHITE; c <= BLACK; c++)
{ {
b = pieces(PAWN, c); b = pieces(PAWN, c);
while(b) while (b)
{ {
s = pop_1st_bit(&b); s = pop_1st_bit(&b);
result ^= zobrist[c][PAWN][s]; result ^= zobrist[c][PAWN][s];
@@ -1608,10 +1606,9 @@ Key Position::compute_material_key() const {
/// game and the endgame. These functions are used to initialize the incremental /// game and the endgame. These functions are used to initialize the incremental
/// scores when a new position is set up, and to verify that the scores are correctly /// scores when a new position is set up, and to verify that the scores are correctly
/// updated by do_move and undo_move when the program is running in debug mode. /// updated by do_move and undo_move when the program is running in debug mode.
template<Position::GamePhase Phase> Score Position::compute_value() const {
Value Position::compute_value() const {
Value result = Value(0); Score result = make_score(0, 0);
Bitboard b; Bitboard b;
Square s; Square s;
@@ -1619,16 +1616,15 @@ Value Position::compute_value() const {
for (PieceType pt = PAWN; pt <= KING; pt++) for (PieceType pt = PAWN; pt <= KING; pt++)
{ {
b = pieces(pt, c); b = pieces(pt, c);
while(b) while (b)
{ {
s = pop_1st_bit(&b); s = pop_1st_bit(&b);
assert(piece_on(s) == piece_of_color_and_type(c, pt)); assert(piece_on(s) == piece_of_color_and_type(c, pt));
result += pst<Phase>(c, pt, s); result += pst(c, pt, s);
} }
} }
const Value TempoValue = (Phase == MidGame ? TempoValueMidgame : TempoValueEndgame); result += (side_to_move() == WHITE ? TempoValue / 2 : -TempoValue / 2);
result += (side_to_move() == WHITE)? TempoValue / 2 : -TempoValue / 2;
return result; return result;
} }
@@ -1672,7 +1668,7 @@ bool Position::is_draw() const {
return true; return true;
// Draw by repetition? // Draw by repetition?
for (int i = 2; i < Min(gamePly, st->rule50); i += 2) for (int i = 2; i < Min(Min(gamePly, st->rule50), st->pliesFromNull); i += 2)
if (history[gamePly - i] == st->key) if (history[gamePly - i] == st->key)
return true; return true;
@@ -1686,8 +1682,7 @@ bool Position::is_draw() const {
bool Position::is_mate() const { bool Position::is_mate() const {
MoveStack moves[256]; MoveStack moves[256];
return is_check() && (generate_moves(*this, moves, false) == moves);
return is_check() && (generate_evasions(*this, moves, pinned_pieces(sideToMove)) == moves);
} }
@@ -1708,11 +1703,10 @@ bool Position::has_mate_threat(Color c) {
MoveStack mlist[120]; MoveStack mlist[120];
bool result = false; bool result = false;
Bitboard dc = discovered_check_candidates(sideToMove);
Bitboard pinned = pinned_pieces(sideToMove); Bitboard pinned = pinned_pieces(sideToMove);
// Generate pseudo-legal non-capture and capture check moves // Generate pseudo-legal non-capture and capture check moves
MoveStack* last = generate_non_capture_checks(*this, mlist, dc); MoveStack* last = generate_non_capture_checks(*this, mlist);
last = generate_captures(*this, last); last = generate_captures(*this, last);
// Loop through the moves, and see if one of them is mate // Loop through the moves, and see if one of them is mate
@@ -1762,6 +1756,8 @@ void Position::init_zobrist() {
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
zobMaterial[0][KING][i] = zobMaterial[1][KING][i] = Key(0ULL); zobMaterial[0][KING][i] = zobMaterial[1][KING][i] = Key(0ULL);
zobExclusion = genrand_int64();
} }
@@ -1779,16 +1775,12 @@ void Position::init_piece_square_tables() {
for (Piece p = WP; p <= WK; p++) for (Piece p = WP; p <= WK; p++)
{ {
i = (r == 0)? 0 : (genrand_int32() % (r*2) - r); i = (r == 0)? 0 : (genrand_int32() % (r*2) - r);
MgPieceSquareTable[p][s] = Value(MgPST[p][s] + i); PieceSquareTable[p][s] = make_score(MgPST[p][s] + i, EgPST[p][s] + i);
EgPieceSquareTable[p][s] = Value(EgPST[p][s] + i);
} }
for (Square s = SQ_A1; s <= SQ_H8; s++) for (Square s = SQ_A1; s <= SQ_H8; s++)
for (Piece p = BP; p <= BK; p++) for (Piece p = BP; p <= BK; p++)
{ PieceSquareTable[p][s] = -PieceSquareTable[p-8][flip_square(s)];
MgPieceSquareTable[p][s] = -MgPieceSquareTable[p-8][flip_square(s)];
EgPieceSquareTable[p][s] = -EgPieceSquareTable[p-8][flip_square(s)];
}
} }
@@ -1843,8 +1835,7 @@ void Position::flipped_copy(const Position& pos) {
st->materialKey = compute_material_key(); st->materialKey = compute_material_key();
// Incremental scores // Incremental scores
st->mgValue = compute_value<MidGame>(); st->value = compute_value();
st->egValue = compute_value<EndGame>();
// Material // Material
st->npMaterial[WHITE] = compute_non_pawn_material(WHITE); st->npMaterial[WHITE] = compute_non_pawn_material(WHITE);
@@ -1871,6 +1862,7 @@ bool Position::is_ok(int* failedStep) const {
static const bool debugNonPawnMaterial = false; static const bool debugNonPawnMaterial = false;
static const bool debugPieceCounts = false; static const bool debugPieceCounts = false;
static const bool debugPieceList = false; static const bool debugPieceList = false;
static const bool debugCastleSquares = false;
if (failedStep) *failedStep = 1; if (failedStep) *failedStep = 1;
@@ -1971,14 +1963,8 @@ bool Position::is_ok(int* failedStep) const {
// Incremental eval OK? // Incremental eval OK?
if (failedStep) (*failedStep)++; if (failedStep) (*failedStep)++;
if (debugIncrementalEval) if (debugIncrementalEval && st->value != compute_value())
{ return false;
if (st->mgValue != compute_value<MidGame>())
return false;
if (st->egValue != compute_value<EndGame>())
return false;
}
// Non-pawn material OK? // Non-pawn material OK?
if (failedStep) (*failedStep)++; if (failedStep) (*failedStep)++;
@@ -2002,9 +1988,9 @@ bool Position::is_ok(int* failedStep) const {
if (failedStep) (*failedStep)++; if (failedStep) (*failedStep)++;
if (debugPieceList) if (debugPieceList)
{ {
for(Color c = WHITE; c <= BLACK; c++) for (Color c = WHITE; c <= BLACK; c++)
for(PieceType pt = PAWN; pt <= KING; pt++) for (PieceType pt = PAWN; pt <= KING; pt++)
for(int i = 0; i < pieceCount[c][pt]; i++) for (int i = 0; i < pieceCount[c][pt]; i++)
{ {
if (piece_on(piece_list(c, pt, i)) != piece_of_color_and_type(c, pt)) if (piece_on(piece_list(c, pt, i)) != piece_of_color_and_type(c, pt))
return false; return false;
@@ -2013,6 +1999,25 @@ bool Position::is_ok(int* failedStep) const {
return false; return false;
} }
} }
if (failedStep) (*failedStep)++;
if (debugCastleSquares) {
for (Color c = WHITE; c <= BLACK; c++) {
if (can_castle_kingside(c) && piece_on(initial_kr_square(c)) != piece_of_color_and_type(c, ROOK))
return false;
if (can_castle_queenside(c) && piece_on(initial_qr_square(c)) != piece_of_color_and_type(c, ROOK))
return false;
}
if (castleRightsMask[initial_kr_square(WHITE)] != (ALL_CASTLES ^ WHITE_OO))
return false;
if (castleRightsMask[initial_qr_square(WHITE)] != (ALL_CASTLES ^ WHITE_OOO))
return false;
if (castleRightsMask[initial_kr_square(BLACK)] != (ALL_CASTLES ^ BLACK_OO))
return false;
if (castleRightsMask[initial_qr_square(BLACK)] != (ALL_CASTLES ^ BLACK_OOO))
return false;
}
if (failedStep) *failedStep = 0; if (failedStep) *failedStep = 0;
return true; return true;
} }
+43 -48
View File
@@ -63,6 +63,18 @@ const int MaxGameLength = 220;
//// Types //// Types
//// ////
/// struct checkInfo is initialized at c'tor time and keeps
/// info used to detect if a move gives check.
struct CheckInfo {
CheckInfo(const Position&);
Square ksq;
Bitboard dcCandidates;
Bitboard checkSq[8];
};
/// Castle rights, encoded as bit fields /// Castle rights, encoded as bit fields
enum CastleRights { enum CastleRights {
@@ -87,12 +99,13 @@ enum Phase {
/// must be passed as a parameter. /// must be passed as a parameter.
struct StateInfo { struct StateInfo {
Key key, pawnKey, materialKey; Key pawnKey, materialKey;
int castleRights, rule50; int castleRights, rule50, pliesFromNull;
Square epSquare; Square epSquare;
Value mgValue, egValue; Score value;
Value npMaterial[2]; Value npMaterial[2];
Key key;
PieceType capture; PieceType capture;
Bitboard checkersBB; Bitboard checkersBB;
StateInfo* previous; StateInfo* previous;
@@ -202,11 +215,12 @@ public:
template<PieceType> Bitboard attacks_from(Square s, Color c) const; template<PieceType> Bitboard attacks_from(Square s, Color c) const;
// Properties of moves // Properties of moves
bool pl_move_is_legal(Move m) const;
bool pl_move_is_legal(Move m, Bitboard pinned) const; bool pl_move_is_legal(Move m, Bitboard pinned) const;
bool pl_move_is_evasion(Move m, Bitboard pinned) const;
bool move_is_check(Move m) const; bool move_is_check(Move m) const;
bool move_is_check(Move m, Bitboard dcCandidates) const; bool move_is_check(Move m, const CheckInfo& ci) const;
bool move_is_capture(Move m) const; bool move_is_capture(Move m) const;
bool move_is_capture_or_promotion(Move m) const;
bool move_is_passed_pawn_push(Move m) const; bool move_is_passed_pawn_push(Move m) const;
bool move_attacks_square(Move m, Square s) const; bool move_attacks_square(Move m, Square s) const;
@@ -222,7 +236,7 @@ public:
// Doing and undoing moves // Doing and undoing moves
void saveState(); void saveState();
void do_move(Move m, StateInfo& st); void do_move(Move m, StateInfo& st);
void do_move(Move m, StateInfo& st, Bitboard dcCandidates); void do_move(Move m, StateInfo& st, const CheckInfo& ci, bool moveIsCheck);
void undo_move(Move m); void undo_move(Move m);
void do_null_move(StateInfo& st); void do_null_move(StateInfo& st);
void undo_null_move(); void undo_null_move();
@@ -235,15 +249,14 @@ public:
// Accessing hash keys // Accessing hash keys
Key get_key() const; Key get_key() const;
Key get_exclusion_key() const;
Key get_pawn_key() const; Key get_pawn_key() const;
Key get_material_key() const; Key get_material_key() const;
// Incremental evaluation // Incremental evaluation
Value mg_value() const; Score value() const;
Value eg_value() const;
Value non_pawn_material(Color c) const; Value non_pawn_material(Color c) const;
Phase game_phase() const; Score pst_delta(Piece piece, Square from, Square to) const;
template<GamePhase> Value pst_delta(Piece piece, Square from, Square to) const;
// Game termination checks // Game termination checks
bool is_mate() const; bool is_mate() const;
@@ -283,9 +296,6 @@ private:
void undo_castle_move(Move m); void undo_castle_move(Move m);
void find_checkers(); void find_checkers();
template<PieceType Piece>
void update_checkers(Bitboard* pCheckersBB, Square ksq, Square from, Square to, Bitboard dcCandidates);
template<bool FindPinned> template<bool FindPinned>
Bitboard hidden_checkers(Color c) const; Bitboard hidden_checkers(Color c) const;
@@ -295,8 +305,8 @@ private:
Key compute_material_key() const; Key compute_material_key() const;
// Computing incremental evaluation scores and material counts // Computing incremental evaluation scores and material counts
template<GamePhase> Value pst(Color c, PieceType pt, Square s) const; Score pst(Color c, PieceType pt, Square s) const;
template<GamePhase> Value compute_value() const; Score compute_value() const;
Value compute_non_pawn_material(Color c) const; Value compute_non_pawn_material(Color c) const;
// Board // Board
@@ -327,8 +337,8 @@ private:
static Key zobCastle[16]; static Key zobCastle[16];
static Key zobMaterial[2][8][16]; static Key zobMaterial[2][8][16];
static Key zobSideToMove; static Key zobSideToMove;
static Value MgPieceSquareTable[16][64]; static Score PieceSquareTable[16][64];
static Value EgPieceSquareTable[16][64]; static Key zobExclusion;
}; };
@@ -493,6 +503,10 @@ inline Key Position::get_key() const {
return st->key; return st->key;
} }
inline Key Position::get_exclusion_key() const {
return st->key ^ zobExclusion;
}
inline Key Position::get_pawn_key() const { inline Key Position::get_pawn_key() const {
return st->pawnKey; return st->pawnKey;
} }
@@ -501,46 +515,22 @@ inline Key Position::get_material_key() const {
return st->materialKey; return st->materialKey;
} }
template<Position::GamePhase Ph> inline Score Position::pst(Color c, PieceType pt, Square s) const {
inline Value Position::pst(Color c, PieceType pt, Square s) const { return PieceSquareTable[piece_of_color_and_type(c, pt)][s];
return (Ph == MidGame ? MgPieceSquareTable[piece_of_color_and_type(c, pt)][s]
: EgPieceSquareTable[piece_of_color_and_type(c, pt)][s]);
} }
template<Position::GamePhase Ph> inline Score Position::pst_delta(Piece piece, Square from, Square to) const {
inline Value Position::pst_delta(Piece piece, Square from, Square to) const { return PieceSquareTable[piece][to] - PieceSquareTable[piece][from];
return (Ph == MidGame ? MgPieceSquareTable[piece][to] - MgPieceSquareTable[piece][from]
: EgPieceSquareTable[piece][to] - EgPieceSquareTable[piece][from]);
} }
inline Value Position::mg_value() const { inline Score Position::value() const {
return st->mgValue; return st->value;
}
inline Value Position::eg_value() const {
return st->egValue;
} }
inline Value Position::non_pawn_material(Color c) const { inline Value Position::non_pawn_material(Color c) const {
return st->npMaterial[c]; return st->npMaterial[c];
} }
inline Phase Position::game_phase() const {
// Values modified by Joona Kiiski
static const Value MidgameLimit = Value(15581);
static const Value EndgameLimit = Value(3998);
Value npm = non_pawn_material(WHITE) + non_pawn_material(BLACK);
if (npm >= MidgameLimit)
return PHASE_MIDGAME;
else if(npm <= EndgameLimit)
return PHASE_ENDGAME;
else
return Phase(((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit));
}
inline bool Position::move_is_passed_pawn_push(Move m) const { inline bool Position::move_is_passed_pawn_push(Move m) const {
Color c = side_to_move(); Color c = side_to_move();
@@ -568,8 +558,13 @@ inline bool Position::has_pawn_on_7th(Color c) const {
inline bool Position::move_is_capture(Move m) const { inline bool Position::move_is_capture(Move m) const {
// Move must not be MOVE_NONE ! // Move must not be MOVE_NONE !
return (m & (3 << 15)) ? !move_is_castle(m) : !square_is_empty(move_to(m));
}
return (!square_is_empty(move_to(m)) && !move_is_castle(m)) || move_is_ep(m); inline bool Position::move_is_capture_or_promotion(Move m) const {
// Move must not be MOVE_NONE !
return (m & (0x1F << 12)) ? !move_is_castle(m) : !square_is_empty(move_to(m));
} }
#endif // !defined(POSITION_H_INCLUDED) #endif // !defined(POSITION_H_INCLUDED)
+8 -5
View File
@@ -142,13 +142,14 @@ Move move_from_san(const Position& pos, const string& movestr) {
assert(pos.is_ok()); assert(pos.is_ok());
MovePicker mp = MovePicker(pos, MOVE_NONE, OnePly, H); MovePicker mp = MovePicker(pos, MOVE_NONE, OnePly, H);
Bitboard pinned = pos.pinned_pieces(pos.side_to_move());
// Castling moves // Castling moves
if (movestr == "O-O-O" || movestr == "O-O-O+") if (movestr == "O-O-O" || movestr == "O-O-O+")
{ {
Move m; Move m;
while ((m = mp.get_next_move()) != MOVE_NONE) while ((m = mp.get_next_move()) != MOVE_NONE)
if (move_is_long_castle(m) && pos.pl_move_is_legal(m)) if (move_is_long_castle(m) && pos.pl_move_is_legal(m, pinned))
return m; return m;
return MOVE_NONE; return MOVE_NONE;
@@ -157,7 +158,7 @@ Move move_from_san(const Position& pos, const string& movestr) {
{ {
Move m; Move m;
while ((m = mp.get_next_move()) != MOVE_NONE) while ((m = mp.get_next_move()) != MOVE_NONE)
if (move_is_short_castle(m) && pos.pl_move_is_legal(m)) if (move_is_short_castle(m) && pos.pl_move_is_legal(m, pinned))
return m; return m;
return MOVE_NONE; return MOVE_NONE;
@@ -325,14 +326,15 @@ const string line_to_san(const Position& pos, Move line[], int startColumn, bool
/// when the UCI parameter "Use Search Log" is "true"). /// when the UCI parameter "Use Search Log" is "true").
const string pretty_pv(const Position& pos, int time, int depth, const string pretty_pv(const Position& pos, int time, int depth,
uint64_t nodes, Value score, Move pv[]) { uint64_t nodes, Value score, ValueType type, Move pv[]) {
std::stringstream s; std::stringstream s;
// Depth // Depth
s << std::setw(2) << depth << " "; s << std::setw(2) << depth << " ";
// Score // Score
s << std::setw(8) << score_string(score); s << ((type == VALUE_TYPE_LOWER)? ">" : ((type == VALUE_TYPE_UPPER)? "<" : " "));
s << std::setw(7) << score_string(score);
// Time // Time
s << std::setw(8) << time_string(time) << " "; s << std::setw(8) << time_string(time) << " ";
@@ -366,11 +368,12 @@ namespace {
return AMBIGUITY_NONE; return AMBIGUITY_NONE;
MovePicker mp = MovePicker(pos, MOVE_NONE, OnePly, H); MovePicker mp = MovePicker(pos, MOVE_NONE, OnePly, H);
Bitboard pinned = pos.pinned_pieces(pos.side_to_move());
Move mv, moveList[8]; Move mv, moveList[8];
int n = 0; int n = 0;
while ((mv = mp.get_next_move()) != MOVE_NONE) while ((mv = mp.get_next_move()) != MOVE_NONE)
if (move_to(mv) == to && pos.piece_on(move_from(mv)) == pc && pos.pl_move_is_legal(mv)) if (move_to(mv) == to && pos.piece_on(move_from(mv)) == pc && pos.pl_move_is_legal(mv, pinned))
moveList[n++] = mv; moveList[n++] = mv;
if (n == 1) if (n == 1)
+1 -1
View File
@@ -39,6 +39,6 @@
extern const std::string move_to_san(const Position& pos, Move m); extern const std::string move_to_san(const Position& pos, Move m);
extern Move move_from_san(const Position& pos, const std::string& str); extern Move move_from_san(const Position& pos, const std::string& str);
extern const std::string line_to_san(const Position& pos, Move line[], int startColumn, bool breakLines); extern const std::string line_to_san(const Position& pos, Move line[], int startColumn, bool breakLines);
extern const std::string pretty_pv(const Position& pos, int time, int depth, uint64_t nodes, Value score, Move pv[]); extern const std::string pretty_pv(const Position& pos, int time, int depth, uint64_t nodes, Value score, ValueType type, Move pv[]);
#endif // !defined(SAN_H_INCLUDED) #endif // !defined(SAN_H_INCLUDED)
+545 -342
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -48,7 +48,7 @@ const int KILLER_MAX = 2;
/// current ply. /// current ply.
struct SearchStack { struct SearchStack {
Move pv[PLY_MAX]; Move pv[PLY_MAX_PLUS_2];
Move currentMove; Move currentMove;
Move mateKiller; Move mateKiller;
Move threatMove; Move threatMove;
@@ -69,6 +69,7 @@ extern void stop_threads();
extern bool think(const Position &pos, bool infinite, bool ponder, int side_to_move, extern bool think(const Position &pos, bool infinite, bool ponder, int side_to_move,
int time[], int increment[], int movesToGo, int maxDepth, int time[], int increment[], int movesToGo, int maxDepth,
int maxNodes, int maxTime, Move searchMoves[]); int maxNodes, int maxTime, Move searchMoves[]);
extern int perft(Position &pos, Depth depth);
extern int64_t nodes_searched(); extern int64_t nodes_searched();
+4 -4
View File
@@ -46,13 +46,13 @@ const int THREAD_MAX = 8;
struct SplitPoint { struct SplitPoint {
SplitPoint *parent; SplitPoint *parent;
Position pos; Position pos;
SearchStack sstack[THREAD_MAX][PLY_MAX]; SearchStack sstack[THREAD_MAX][PLY_MAX_PLUS_2];
SearchStack *parentSstack; SearchStack *parentSstack;
int ply; int ply;
Depth depth; Depth depth;
volatile Value alpha, beta, bestValue; volatile Value alpha, beta, bestValue, futilityValue;
Value approximateEval;
bool pvNode; bool pvNode;
Bitboard dcCandidates;
int master, slaves[THREAD_MAX]; int master, slaves[THREAD_MAX];
Lock lock; Lock lock;
MovePicker *mp; MovePicker *mp;
@@ -64,7 +64,7 @@ struct SplitPoint {
struct Thread { struct Thread {
SplitPoint *splitPoint; SplitPoint *splitPoint;
int activeSplitPoints; volatile int activeSplitPoints;
uint64_t nodes; uint64_t nodes;
uint64_t betaCutOffs[2]; uint64_t betaCutOffs[2];
bool failHighPly1; bool failHighPly1;
+22 -21
View File
@@ -53,11 +53,11 @@ TranspositionTable::~TranspositionTable() {
/// TranspositionTable::set_size sets the size of the transposition table, /// TranspositionTable::set_size sets the size of the transposition table,
/// measured in megabytes. /// measured in megabytes.
void TranspositionTable::set_size(unsigned mbSize) { void TranspositionTable::set_size(size_t mbSize) {
assert(mbSize >= 4 && mbSize <= 4096); assert(mbSize >= 4 && mbSize <= 8192);
unsigned newSize = 1024; size_t newSize = 1024;
// We store a cluster of ClusterSize number of TTEntry for each position // We store a cluster of ClusterSize number of TTEntry for each position
// and newSize is the maximum number of storable positions. // and newSize is the maximum number of storable positions.
@@ -208,7 +208,9 @@ void TranspositionTable::insert_pv(const Position& pos, Move pv[]) {
for (int i = 0; pv[i] != MOVE_NONE; i++) for (int i = 0; pv[i] != MOVE_NONE; i++)
{ {
store(p.get_key(), VALUE_NONE, VALUE_TYPE_NONE, Depth(-127*OnePly), pv[i]); TTEntry *tte = retrieve(p.get_key());
if (!tte || tte->move() != pv[i])
store(p.get_key(), VALUE_NONE, VALUE_TYPE_NONE, Depth(-127*OnePly), pv[i]);
p.do_move(pv[i], st); p.do_move(pv[i], st);
} }
} }
@@ -220,27 +222,26 @@ void TranspositionTable::insert_pv(const Position& pos, Move pv[]) {
/// will often get single-move PVs when the search stops while failing high, /// will often get single-move PVs when the search stops while failing high,
/// and a single-move PV means that we don't have a ponder move. /// and a single-move PV means that we don't have a ponder move.
void TranspositionTable::extract_pv(const Position& pos, Move pv[]) { void TranspositionTable::extract_pv(const Position& pos, Move pv[], const int PLY_MAX) {
int ply;
Position p(pos);
StateInfo st[100];
for (ply = 0; pv[ply] != MOVE_NONE; ply++)
p.do_move(pv[ply], st[ply]);
bool stop;
const TTEntry* tte; const TTEntry* tte;
for (stop = false, tte = retrieve(p.get_key()); StateInfo st;
tte && tte->move() != MOVE_NONE && !stop; Position p(pos);
tte = retrieve(p.get_key()), ply++) int ply = 0;
// Update position to the end of current PV
while (pv[ply] != MOVE_NONE)
p.do_move(pv[ply++], st);
// Try to add moves from TT while possible
while ( (tte = retrieve(p.get_key())) != NULL
&& tte->move() != MOVE_NONE
&& move_is_legal(p, tte->move())
&& (!p.is_draw() || ply < 2)
&& ply < PLY_MAX)
{ {
if (!move_is_legal(p, tte->move()))
break;
pv[ply] = tte->move(); pv[ply] = tte->move();
p.do_move(pv[ply], st[ply]); p.do_move(pv[ply++], st);
for (int j = 0; j < ply; j++)
if (st[j].key == p.get_key()) stop = true;
} }
pv[ply] = MOVE_NONE; pv[ply] = MOVE_NONE;
} }
+3 -3
View File
@@ -95,14 +95,14 @@ class TranspositionTable {
public: public:
TranspositionTable(); TranspositionTable();
~TranspositionTable(); ~TranspositionTable();
void set_size(unsigned mbSize); void set_size(size_t mbSize);
void clear(); void clear();
void store(const Key posKey, Value v, ValueType type, Depth d, Move m); void store(const Key posKey, Value v, ValueType type, Depth d, Move m);
TTEntry* retrieve(const Key posKey) const; TTEntry* retrieve(const Key posKey) const;
void prefetch(const Key posKey) const; void prefetch(const Key posKey) const;
void new_search(); void new_search();
void insert_pv(const Position& pos, Move pv[]); void insert_pv(const Position& pos, Move pv[]);
void extract_pv(const Position& pos, Move pv[]); void extract_pv(const Position& pos, Move pv[], const int PLY_MAX);
int full() const; int full() const;
private: private:
@@ -114,7 +114,7 @@ private:
unsigned writes; // heavy SMP read/write access here unsigned writes; // heavy SMP read/write access here
unsigned char pad_after[64]; unsigned char pad_after[64];
unsigned size; size_t size;
TTCluster* entries; TTCluster* entries;
uint8_t generation; uint8_t generation;
}; };
+1 -1
View File
@@ -33,7 +33,7 @@ typedef __int16 int16;
typedef unsigned __int16 uint16_t; typedef unsigned __int16 uint16_t;
typedef __int32 int32_t; typedef __int32 int32_t;
typedef unsigned __int32 uint32_t; typedef unsigned __int32 uint32_t;
typedef __int64 int64; typedef __int64 int64_t;
typedef unsigned __int64 uint64_t; typedef unsigned __int64 uint64_t;
typedef __int16 int16_t; typedef __int16 int16_t;
+48 -44
View File
@@ -61,6 +61,7 @@ namespace {
void set_option(UCIInputParser& uip); void set_option(UCIInputParser& uip);
void set_position(UCIInputParser& uip); void set_position(UCIInputParser& uip);
bool go(UCIInputParser& uip); bool go(UCIInputParser& uip);
void perft(UCIInputParser& uip);
} }
@@ -106,7 +107,8 @@ namespace {
UCIInputParser uip(command); UCIInputParser uip(command);
string token; string token;
uip >> token; // operator>>() skips any whitespace if (!(uip >> token)) // operator>>() skips any whitespace
return true;
if (token == "quit") if (token == "quit")
return false; return false;
@@ -123,7 +125,7 @@ namespace {
} }
else if (token == "ucinewgame") else if (token == "ucinewgame")
{ {
push_button("Clear Hash"); push_button("New Game");
Position::init_piece_square_tables(); Position::init_piece_square_tables();
RootPosition.from_fen(StartPosition); RootPosition.from_fen(StartPosition);
} }
@@ -147,23 +149,19 @@ namespace {
else if (token == "eval") else if (token == "eval")
{ {
EvalInfo ei; EvalInfo ei;
cout << "Incremental mg: " << RootPosition.mg_value() cout << "Incremental mg: " << mg_value(RootPosition.value())
<< "\nIncremental eg: " << RootPosition.eg_value() << "\nIncremental eg: " << eg_value(RootPosition.value())
<< "\nFull eval: " << evaluate(RootPosition, ei, 0) << endl; << "\nFull eval: " << evaluate(RootPosition, ei, 0) << endl;
} }
else if (token == "key") else if (token == "key")
cout << "key: " << hex << RootPosition.get_key() cout << "key: " << hex << RootPosition.get_key()
<< "\nmaterial key: " << RootPosition.get_material_key() << "\nmaterial key: " << RootPosition.get_material_key()
<< "\npawn key: " << RootPosition.get_pawn_key() << endl; << "\npawn key: " << RootPosition.get_pawn_key() << endl;
else if (token == "perft")
perft(uip);
else else
{
cout << "Unknown command: " << command << endl; cout << "Unknown command: " << command << endl;
while (!uip.eof())
{
uip >> token;
cout << token << endl;
}
}
return true; return true;
} }
@@ -178,33 +176,33 @@ namespace {
string token; string token;
uip >> token; // operator>>() skips any whitespace if (!(uip >> token)) // operator>>() skips any whitespace
return;
if (token == "startpos") if (token == "startpos")
RootPosition.from_fen(StartPosition); RootPosition.from_fen(StartPosition);
else if (token == "fen") else if (token == "fen")
{ {
string fen; string fen;
while (token != "moves" && !uip.eof()) while (uip >> token && token != "moves")
{ {
uip >> token;
fen += token; fen += token;
fen += ' '; fen += ' ';
} }
RootPosition.from_fen(fen); RootPosition.from_fen(fen);
} }
if (!uip.eof()) if (uip.good())
{ {
if (token != "moves") if (token != "moves")
uip >> token; uip >> token;
if (token == "moves") if (token == "moves")
{ {
Move move; Move move;
StateInfo st; StateInfo st;
while (!uip.eof()) while (uip >> token)
{ {
uip >> token;
move = move_from_string(RootPosition, token); move = move_from_string(RootPosition, token);
RootPosition.do_move(move, st); RootPosition.do_move(move, st);
if (RootPosition.rule_50_counter() == 0) if (RootPosition.rule_50_counter() == 0)
@@ -226,27 +224,22 @@ namespace {
void set_option(UCIInputParser& uip) { void set_option(UCIInputParser& uip) {
string token, name; string token, name, value;
uip >> token; if (!(uip >> token)) // operator>>() skips any whitespace
if (token == "name") return;
if (token == "name" && uip >> name)
{ {
uip >> name; while (uip >> token && token != "value")
while (!uip.eof())
{
uip >> token;
if (token == "value")
break;
name += (" " + token); name += (" " + token);
}
if (token == "value")
{
// Reads until end of line and left trim white space
getline(uip, token);
token.erase(0, token.find_first_not_of(" \n\r\t"));
set_option_value(name, token); if (token == "value" && uip >> value)
{
while (uip >> token)
value += (" " + token);
set_option_value(name, value);
} else } else
push_button(name); push_button(name);
} }
@@ -273,10 +266,8 @@ namespace {
searchMoves[0] = MOVE_NONE; searchMoves[0] = MOVE_NONE;
while (!uip.eof()) while (uip >> token)
{ {
uip >> token;
if (token == "infinite") if (token == "infinite")
infinite = true; infinite = true;
else if (token == "ponder") else if (token == "ponder")
@@ -300,22 +291,35 @@ namespace {
else if (token == "searchmoves") else if (token == "searchmoves")
{ {
int numOfMoves = 0; int numOfMoves = 0;
while (!uip.eof()) while (uip >> token)
{
uip >> token;
searchMoves[numOfMoves++] = move_from_string(RootPosition, token); searchMoves[numOfMoves++] = move_from_string(RootPosition, token);
}
searchMoves[numOfMoves] = MOVE_NONE; searchMoves[numOfMoves] = MOVE_NONE;
} }
} }
if (moveTime)
infinite = true; // HACK
assert(RootPosition.is_ok()); assert(RootPosition.is_ok());
return think(RootPosition, infinite, ponder, RootPosition.side_to_move(), return think(RootPosition, infinite, ponder, RootPosition.side_to_move(),
time, inc, movesToGo, depth, nodes, moveTime, searchMoves); time, inc, movesToGo, depth, nodes, moveTime, searchMoves);
} }
void perft(UCIInputParser& uip) {
string token;
int depth, tm, n;
Position pos = RootPosition;
if (!(uip >> depth))
return;
tm = get_system_time();
n = perft(pos, depth * OnePly);
tm = get_system_time() - tm;
std::cout << "\nNodes " << n
<< "\nTime (ms) " << tm
<< "\nNodes/second " << (int)(n/(tm/1000.0)) << std::endl;
}
} }
+45 -11
View File
@@ -122,9 +122,10 @@ namespace {
o["Randomness"] = Option(0, 0, 10); o["Randomness"] = Option(0, 0, 10);
o["Minimum Split Depth"] = Option(4, 4, 7); o["Minimum Split Depth"] = Option(4, 4, 7);
o["Maximum Number of Threads per Split Point"] = Option(5, 4, 8); o["Maximum Number of Threads per Split Point"] = Option(5, 4, 8);
o["Threads"] = Option(1, 1, 8); o["Threads"] = Option(1, 1, THREAD_MAX);
o["Hash"] = Option(32, 4, 4096); o["Hash"] = Option(32, 4, 8192);
o["Clear Hash"] = Option(false, BUTTON); o["Clear Hash"] = Option(false, BUTTON);
o["New Game"] = Option(false, BUTTON);
o["Ponder"] = Option(true); o["Ponder"] = Option(true);
o["OwnBook"] = Option(true); o["OwnBook"] = Option(true);
o["MultiPV"] = Option(1, 1, 500); o["MultiPV"] = Option(1, 1, 500);
@@ -169,6 +170,18 @@ namespace {
return ret; return ret;
} }
// Specialization for std::string where instruction 'ss >> ret;'
// would erroneusly tokenize a string with spaces.
template<>
string get_option_value<string>(const string& optionName) {
if (options.find(optionName) == options.end())
return string();
return options[optionName].currentValue;
}
} }
//// ////
@@ -183,15 +196,18 @@ void init_uci_options() {
load_defaults(options); load_defaults(options);
// Limit the default value of "Threads" to 7 even if we have 8 CPU cores. // Set optimal value for parameter "Minimum Split Depth"
// According to Ken Dail's tests, Glaurung plays much better with 7 than // according to number of available cores.
// with 8 threads. This is weird, but it is probably difficult to find out
// why before I have a 8-core computer to experiment with myself.
assert(options.find("Threads") != options.end()); assert(options.find("Threads") != options.end());
assert(options.find("Minimum Split Depth") != options.end()); assert(options.find("Minimum Split Depth") != options.end());
options["Threads"].defaultValue = stringify(Min(cpu_count(), 7)); Option& thr = options["Threads"];
options["Threads"].currentValue = stringify(Min(cpu_count(), 7)); Option& msd = options["Minimum Split Depth"];
thr.defaultValue = thr.currentValue = stringify(cpu_count());
if (cpu_count() >= 8)
msd.defaultValue = msd.currentValue = stringify(7);
} }
@@ -279,10 +295,28 @@ void set_option_value(const string& name, const string& value) {
else if (v == "false") else if (v == "false")
v = "0"; v = "0";
if (options.find(name) != options.end()) if (options.find(name) == options.end())
options[name].currentValue = v; {
else
std::cout << "No such option: " << name << std::endl; std::cout << "No such option: " << name << std::endl;
return;
}
// Normally it's up to the GUI to check for option's limits,
// but we could receive the new value directly from the user
// by teminal window. So let's check the bounds anyway.
Option& opt = options[name];
if (opt.type == CHECK && v != "0" && v != "1")
return;
else if (opt.type == SPIN)
{
int val = atoi(v.c_str());
if (val < opt.minValue || val > opt.maxValue)
return;
}
opt.currentValue = v;
} }
+41 -3
View File
@@ -48,10 +48,49 @@ enum Value {
VALUE_KNOWN_WIN = 15000, VALUE_KNOWN_WIN = 15000,
VALUE_MATE = 30000, VALUE_MATE = 30000,
VALUE_INFINITE = 30001, VALUE_INFINITE = 30001,
VALUE_NONE = 30002 VALUE_NONE = 30002,
VALUE_ENSURE_SIGNED = -1
}; };
/// Score enum keeps a midgame and an endgame value in a single
/// integer (enum), first LSB 16 bits are used to store endgame
/// value, while upper bits are used for midgame value.
// Compiler is free to choose the enum type as long as can keep
// its data, so ensure Score to be an integer type.
enum Score { ENSURE_32_BITS_SIZE_P = (1 << 16), ENSURE_32_BITS_SIZE_N = -(1 << 16)};
// Extracting the _signed_ lower and upper 16 bits it not so trivial
// because according to the standard a simple cast to short is
// implementation defined and so is a right shift of a signed integer.
inline Value mg_value(Score s) { return Value(((int(s) + 32768) & ~0xffff) / 0x10000); }
// Unfortunatly on Intel 64 bit we have a small speed regression, so use a faster code in
// this case, although not 100% standard compliant it seems to work for Intel and MSVC.
#if defined(IS_64BIT) && (!defined(__GNUC__) || defined(__INTEL_COMPILER))
inline Value eg_value(Score s) { return Value(int16_t(s & 0xffff)); }
#else
inline Value eg_value(Score s) { return Value((int)(unsigned(s) & 0x7fffu) - (int)(unsigned(s) & 0x8000u)); }
#endif
inline Score make_score(int mg, int eg) { return Score((mg << 16) + eg); }
inline Score operator-(Score s) { return Score(-int(s)); }
inline Score operator+(Score s1, Score s2) { return Score(int(s1) + int(s2)); }
inline Score operator-(Score s1, Score s2) { return Score(int(s1) - int(s2)); }
inline void operator+=(Score& s1, Score s2) { s1 = Score(int(s1) + int(s2)); }
inline void operator-=(Score& s1, Score s2) { s1 = Score(int(s1) - int(s2)); }
inline Score operator*(int i, Score s) { return Score(i * int(s)); }
// Division must be handled separately for each term
inline Score operator/(Score s, int i) { return make_score(mg_value(s) / i, eg_value(s) / i); }
// Only declared but not defined. We don't want to multiply two scores due to
// a very high risk of overflow. So user should explicitly convert to integer.
inline Score operator*(Score s1, Score s2);
//// ////
//// Constants and variables //// Constants and variables
//// ////
@@ -97,8 +136,7 @@ const Value PieceValueEndgame[17] = {
/// Bonus for having the side to move (modified by Joona Kiiski) /// Bonus for having the side to move (modified by Joona Kiiski)
const Value TempoValueMidgame = Value(48); const Score TempoValue = make_score(48, 22);
const Value TempoValueEndgame = Value(22);
//// ////