Strelka Reverse Engineered from Rybka: Details Examined
Moderators: hgm, chrisw, Rebel
Re: Strelka Reverse Engineered from Rybka: Details Examined
I don't know how others do it, but I assign them a score depending on the promoted piece, which makes them end up at the correct place during movesorting. This way I can generate the promotions together with the other pawn moves.
-
- Posts: 12672
- Joined: Wed Mar 08, 2006 8:57 pm
- Location: Redmond, WA USA
Re: Strelka Reverse Engineered from Rybka: Details Examined
I want to see it. I am particularly interested in:rfadden wrote:The answer is: a few others were saying essentially "Show us the Details" and so as I wrote above I have started doing what these guys are asking.Aleks Peshkov wrote:It is a known fact, that was confirmed by Strelka's author from the very beginning about a year ago. Why should we waste time reading your long posts about reverse engineering of open source code?rfadden wrote:In this thread I start by giving examples that show how Strelka is reverse engineered from Rybka.
In your case you don't have to read this.
Doesn't it seem that you have the choice to read or not to read...
Also, people have different interests.
I agree that you don't need to look at this at all. This is not a problem.
Thanks.
I'm continuing... I take the point about gen_captures not being the perfect example, but keep in mind that if we make it to example 89 then by that time you will see so many different routines all with this same pattern. All constants, all function arguments, the order that functions are called, the numeric constants passed to routines, they all come from the Rybka binary... I will continue showing this...
I'll be back
Things that are in Rybka and in Strelka but not in Fruit and which are unusual.
-
- Posts: 10554
- Joined: Thu Mar 09, 2006 12:37 am
- Location: Tel-Aviv Israel
Re: Strelka Reverse Engineered from Rybka: Details Examined
I think that captures by pawns should be generated before captures by other pieces because later captures by pawns have better chances to be ordered first.Guetti wrote:I don't know how others do it, but I assign them a score depending on the promoted piece, which makes them end up at the correct place during movesorting. This way I can generate the promotions together with the other pawn moves.
The order of generating is also important because correct order of generating moves can save sorting later so the logical order is first promotions and after it captures by pawn and only after it captures by knights and other pieces.
Note that I did not test if changing the move ordering in this way cause strelka to become better and it is possible that things are counter intuitive.
Uri
-
- Posts: 6401
- Joined: Thu Mar 09, 2006 8:30 pm
- Location: Chicago, Illinois, USA
Re: Strelka Reverse Engineered from Rybka: Details Examined
What you say is true if you have the moves in a list and you loop increasing the index to find the best. If you loop decreasing the index (from the end to the start), you will be better off generating promotions last.Uri Blass wrote:I did not look much at Crafty's code so I did not check the order that it generates captures but I see no reason to generate promotion after generating capture by small pieces.Guetti wrote:Sorry, but Crafty generates the moves in exactly the same order.Uri Blass wrote: The move generator of strelka generates captures in the following order:
1)Captures by knights
2)Captures by bishops
3)Captures by rooks
4)Captures by Queens
5)Captures by the king
6)promotions
7)promotions that are also captures
8)Captures by pawns.
I do not think that this order is the most logical order and if rybka has the same order then it is clearly an evidence for reverse engineering.
Uri
I do it the same in my engine. Why? The order can effect search efficiency, and since I read once Bob tested this, I simply chose the same order than him. Why invent the wheel from scratch?
Now, it is not my goal to defend a cloner, but I think you have to come up with better things for prove.
You want the best move to be generated first because it can save you time later when you do not need to change order of moves in the move list when you pick first move to search.
A Capture that is a promotion is the best candidate to be generated first because it is probably better than other captures.
Uri
Miguel
Re: Strelka Reverse Engineered from Rybka: Details Examined
Maybe you're right. I simply did not care when I wrote this part, because I assumed the whole move list gets rearranged anyway when I sort with SEE later on.Uri Blass wrote:I think that captures by pawns should be generated before captures by other pieces because later captures by pawns have better chances to be ordered first.Guetti wrote:I don't know how others do it, but I assign them a score depending on the promoted piece, which makes them end up at the correct place during movesorting. This way I can generate the promotions together with the other pawn moves.
The order of generating is also important because correct order of generating moves can save sorting later so the logical order is first promotions and after it captures by pawn and only after it captures by knights and other pieces.
Note that I did not test if changing the move ordering in this way cause strelka to become better and it is possible that things are counter intuitive.
Uri
So, to be precise I have to make a correction. The reason why I generate the captures in this order is because I simply copied the non-captures function and made the changes to generate captures. The functions are almost identical anyway with the exception of pawns.
And the reason why I generate Non-captures in this order is because Crafty does it in this order. Maybe Vas took a look at crafty too, and finally the same ended up in Strelka.
Re: Strelka Reverse Engineered from Rybka: Details Examined
Here is some further high level information that seemed amazing to me as I was discovering this.
I had tracked through functions in Rybka 1.0 just before I got the source to Strelka and I was looking for the information related to Node Count, since I was curious about that. I invented these function names based upon a few simple guesses such as "follow the call tree invoked by the UCI go command" and also, "functions that call themselves are likely the key recursive search routines."
I traced out the call sequence starting from the UCI "go" command and using my whacky names where I really was not "seeing" what was going on, here is some information from my notes that was later transformed surprisingly by Strlka Source:
So that's the day I found the Strelka 2.0 source. So with the above as my status at that moment I went to the Strelka "go_command" and I started following the C functions that it was calling and as I walked along I used the above Rybka call sequence and at every step along the way I ran into Strelka source code that matched the Rybka binary code!
In other words I would reach a search routine in Strelka, look at it, and then look at the binary routine that is called next in Rybka and I would then see that all of the constants were the same and the code was the same, and so with that information I would then rename the Rybka function to match the Strelka name and here is what the above list turned into (actually below there are three different ways of looking at this exact matching between Rybka functions and Strelka functions).
The above is my translation chart as I discovered that my "unknown" functions exactly matched up with Strelka functions, with the binary code in these functions doing the same thing as the Strelka source code!
With all of this exacty match from Strelka my call tree with the names updated looks like this:
I need to be clear, using the above technique I found each of the mostly unknown Rybka functions in Strelka. I read the assembly language and it exactly matched the code in each of these Strelka functions in turn.
Caveat: Everything was matching so beautifully of course I could easily have missed a few modified lines of code. I'm just saying this perfect correlation was amazing to me! It became so clear to me that Strelka is a pure Reverse Engineer of Rybka. All of the above exactly matches.
So then there were a few of my "unknown" functions left (important search related functions) so I then decided to finish finding each of my unknown functions in the Strelka source by matching up the following.
I made a list of each key function and named subroutines that are called from each of these functions. Much to my amazement these lists exactly matched between Rybka and Strelka. This then completely filled out all of Rybkas search, eval, and chess support functions. I found every single one of these in Strelka, and also by the way there were no Strelka functions that were not in Rybka.
Here is the list of the key Chess functions in Rybka, and this diagram also is the diagram of function calls in Strelka, and note there is only one exception to this, and I will write about this one difference, next.
Skip the first two lines, there is a difference there and it's not too important so I can mention the details of this in a later posting.
Here is a difference between Rybka and Strelka that I found: look at the lines above for Start_Search -> Full_Root_Base -> Full_Root. This is Rybka, and Full_Root_Base just had a handful of lines of C in it apparently, and this is where the massive (or one of the large) lookup tables is accessed in Rybka. Recall that we heard reports from Vasic that the earlier Strelka used his large tables with a minor obfuscation of the table contents, and then later Jury Osipov removed this use of the large table and he replaced this with a function that fills the table (a function in place of a large data table, and this function technique is what is now in Strelka 2.0).
So it looks like in the later version of Strelka, Jury cut out the small piece of logic that is in "Full_Root_Base" (the code that Vasic uses to access his huge table).
I was a bit curious about this function since it is one of the key rare differences in this pure reverse engineering effort so I started converting this function to C for my own viewing (I'll can write more about this below).
So this is the one difference in the way that Strelka/Rybka functions call the other functions. In Strelka, the call to Full_Root_Base is deleted, so instead you simply see "Start_Search -> Full_Root"
Everything else exactly matches. By the way, all of these functions have the exact same arguments and they are called in exactly the same order and with the same arguments, they return the same values and all of the other code matches also ("all" meaning all you see as you compare for hours is nothing but exact match of Rybka Binary to Strelka C).
So I will stop this post now, send it, and I'm pretty excited about the work I did for the next post since the first core part of chess search is an exact match between Rybka and Strelka with all constants matching up, and I will show you a comparison of this code next.
I had tracked through functions in Rybka 1.0 just before I got the source to Strelka and I was looking for the information related to Node Count, since I was curious about that. I invented these function names based upon a few simple guesses such as "follow the call tree invoked by the UCI go command" and also, "functions that call themselves are likely the key recursive search routines."
I traced out the call sequence starting from the UCI "go" command and using my whacky names where I really was not "seeing" what was going on, here is some information from my notes that was later transformed surprisingly by Strlka Source:
Code: Select all
Calling Sequence
The_Go_Function
Main_Search_Routine
Search_Call_Lev1B
Byte_Mask_Proc
Long_Interesting_MaskingA
Bi_Search_Lev1C
Byte_Mask_Proc
Bi_Search_Lev1D
Byte_Mask_Proc
Medium_MaskingA
Sub_Something_FEN_Lev2C
Search_Call_Lev1B
Byte_Mask_Proc
Long_Interesting_MaskingA
Block_of_MaskingA
Sub_Something_FEN_Lev2C
Medium_MaskingA
Sub_Something_FEN_Lev2C
Search_Call_Lev1B
Byte_Mask_Proc
Long_Interesting_MaskingA
Block_of_MaskingA
Sub_Something_FEN_Lev2C
Medium_MaskingA
Sub_Something_FEN_Lev2C
Search_Call_Lev1B
Byte_Mask_Proc
Long_Interesting_MaskingA
Block_of_MaskingA
Sub_Something_FEN_Lev2C
Some_Search_Init
ds:GetTickCount
Per_Depth_Calc
Per_Depth_Info_Disp
Medium_MaskingA
Sub_Something_FEN_Lev2C
Search_Call_Lev1B
Byte_Mask_Proc
Long_Interesting_MaskingA
Recurse_SearchC
Recurse_SearchD
Bi_Search_Lev1C
Byte_Mask_Proc
Major_MaskingA
Byte_Mask_Proc
Block_of_MaskingA
Sub_Something_FEN_Lev2C
Medium_MaskingA
Sub_Something_FEN_Lev2C
Search_Call_Lev1B
Byte_Mask_Proc
Long_Interesting_MaskingA
Recurse_SearchC
Recurse_SearchD
Bi_Search_Lev1C
Byte_Mask_Proc
Major_MaskingD
Major_MaskingA
Byte_Mask_Proc
Recurse_SearchD
Bi_Search_Lev1C
Byte_Mask_Proc
Recurse_SearchA
Search_call_Lev1A
Byte_Mask_Proc
Recurse_SearchD
In other words I would reach a search routine in Strelka, look at it, and then look at the binary routine that is called next in Rybka and I would then see that all of the constants were the same and the code was the same, and so with that information I would then rename the Rybka function to match the Strelka name and here is what the above list turned into (actually below there are three different ways of looking at this exact matching between Rybka functions and Strelka functions).
Code: Select all
Strelka Naming
Main_Search_Routine -> Start_Search
Some_Search_init -> Setjmp
Search_call_Lev1B -> Evaluate
Long_MaskingA -> Eval_Mob <Evaluate Mobility>
It looks like Rybka's Eval calls Eval_Mob and Strelka includes Eval_Mob but it includes this code within its Evaluate function to avoid the call or to obfuscate.
Recurse_SearchD -> Qu_Search
Long_Interesting_MaskingA -> Pawn_Get_Info
The_Go_Function -> Start_Go
Search_Call_Lev1A -> Gen_Evasions
Bi_Search_Lev1C -> Gen_Captures
Bi_Search_Lev1D -> Gen_Quiet_Moves
Medium_MaskingA -> Move_Do
Block_of_MaskingA -> Move_Undo
Per_Depth_Calc -> Full_Root_Base <This code is not in Strelka!>
<< Important. Full Root Base accesses the massive 2 Megabyte Lookup Table>>
Blob_of_MaskingA -> Move_Do_Castle
Sub_Something_FEN_Lev2C -> Shift_Left_Double
Blob_of_MaskingB -> Move_Undo_Castle
Per_Depth_Info_Disp -> Full_Root
Byte_Mask_Proc -> Shift_Right_Double
Major_MaskingD -> SEE_Move
Recurse_SearchC -> Full_PV_Search
Recurse_Major_NodeTick -> Full_Search
Recurse_SearchB - > Full_Check_Search
Small_MaskingA -> Move_Do_Null
Med_LogicA -> Trans_Min_Store
Major_MaskingC -> Next_Move
Major_MaskingB -> Move_Is_Legal
nodeTickLow -> check_nb << check_nb-- >>
Medium_LogicB -> Trans_Max_Store
Test_Proc_Command -> Search_Check
Block_of_LogicA -> Trans_Move_Store
Recurse_SearchA -> Qu_Check_Search
Major_MaskingA -> Gen_Checks
Sub_Something_FEN_Lev1E -> Strstr
Something_FEN -> Parse_Position
Sub_Something_FEN_Lev1A -> Board_From_Fen
Sub_Something_FEN_Lev1B -> Board_Init
Sub_Something_FEN_Lev2A -> Mem_Fill_X
Sub_Something_FEN_Lev2B -> Mem_Fill_Y
Sub_Something_FEN_Lev1C -> Unknown_X
Sub_Something_FEN_Lev1D -> Unknown_Y
Small_LogicA -> History_Store
Blob_of_LogicA -> Trans_Store
Blob_of_LogicC -> Trans_Clear
Init_for_Commands -> Trans_Alloc
Unknown_Heap_Alloc -> Malloc
Big_Glob_of_MaskingA -> Board_Init_MaskingA
Big_Glob_of_MaskingB -> Board_Init_MaskingB
With all of this exacty match from Strelka my call tree with the names updated looks like this:
Code: Select all
Calling Sequence
Start_Go
Start_Search
Evaluate
Shift_Right_Double
Pawn_Get_Info
Gen_Captures
Shift_Right_Double
Gen_Quiet_Moves
Shift_Right_Double
Move_Do
Shift_Left_Double
Evaluate
Shift_Right_Double
Pawn_Get_Info
Move_Undo
Shift_Left_Double
Move_Do
Shift_Left_Double
Evaluate
Shift_Right_Double
Pawn_Get_Info
Move_Undo
Shift_Left_Double
Move_Do
Shift_Left_Double
Evaluate
Shift_Right_Double
Pawn_Get_Info
Move_Undo
Shift_Left_Double
Setjmp
ds:GetTickCount
Full_Root_Base
Full_Root
Move_Do
Shift_Left_Double
Evaluate
Shift_Right_Double
Pawn_Get_Info
Full_PV_Search
Qu_Search
Gen_Captures
Shift_Right_Double
Major_MaskingA
Shift_Right_Double
Move_Undo
Shift_Left_Double
Move_Do
Shift_Left_Double
Evaluate
Shift_Right_Double
Pawn_Get_Info
Full_PV_Search
Qu_Search
Gen_Captures
Shift_Right_Double
SEE_Move
Major_MaskingA
Shift_Right_Double
Qu_Search
Gen_Captures
Shift_Right_Double
Recurse_SearchA
Gen_Evasions
Shift_Right_Double
Qu_Search
Caveat: Everything was matching so beautifully of course I could easily have missed a few modified lines of code. I'm just saying this perfect correlation was amazing to me! It became so clear to me that Strelka is a pure Reverse Engineer of Rybka. All of the above exactly matches.
So then there were a few of my "unknown" functions left (important search related functions) so I then decided to finish finding each of my unknown functions in the Strelka source by matching up the following.
I made a list of each key function and named subroutines that are called from each of these functions. Much to my amazement these lists exactly matched between Rybka and Strelka. This then completely filled out all of Rybkas search, eval, and chess support functions. I found every single one of these in Strelka, and also by the way there were no Strelka functions that were not in Rybka.
Here is the list of the key Chess functions in Rybka, and this diagram also is the diagram of function calls in Strelka, and note there is only one exception to this, and I will write about this one difference, next.
Code: Select all
Start ->
Init_for_Commands ->
Command_Interpretter
Command_Interpretter ->
Start_Go ->
Start_Search
Start_Search ->
Full_Root_Base ->
Full_Root
Full_Root ->
*Full_Search
*Full_Check_Search
*Full_PV_Search
Move_Do
Evaluate
SEE_Move
Move_Undo
GetTickCount
fprintf
*Full_Search ->
*Full_Search
*Full_Check_Search
*Full_PV_Search
*Qu_Search
GetTickCount
Move_Do_Null
Move_Do
Next_Move
Evaluate
Trans_Move_Store
Trans_Max_Store
Move_Undo
SetJump_Return
Search_Check
*Full_Check_Search ->
*Full_Check_Search
*Full_Search
*Qu_Search
Gen_Evasions
Move_Do
Evaluate
Trans_Min_Store
Trans_Move_Store
Trans_Max_Store
Move_Undo
*Full_PV_Search ->
*Full_PV_Search
*Full_Search
*Full_Check_Search
*Qu_Search
Gen_Evasions
Move_Do
Next_Move
Evaluate
SEE_Move
Trans_Move_Store
Trans_Max_Store
Trans_Min_Store
History_Store
Trans_Store
Move_Undo
*Qu_Search ->
*Qu_Search
*Qu_Check_Search
Gen_Captures
Gen_Checks
Move_Do
Evaluate
Eval_Mobility
SEE_Move
Move_Undo
*Qu_Check_Search ->
*Qu_Check_Search
*Qu_Search
Gen_Evasions
Move_Do
Evaluate
Move_Undo
Here is a difference between Rybka and Strelka that I found: look at the lines above for Start_Search -> Full_Root_Base -> Full_Root. This is Rybka, and Full_Root_Base just had a handful of lines of C in it apparently, and this is where the massive (or one of the large) lookup tables is accessed in Rybka. Recall that we heard reports from Vasic that the earlier Strelka used his large tables with a minor obfuscation of the table contents, and then later Jury Osipov removed this use of the large table and he replaced this with a function that fills the table (a function in place of a large data table, and this function technique is what is now in Strelka 2.0).
So it looks like in the later version of Strelka, Jury cut out the small piece of logic that is in "Full_Root_Base" (the code that Vasic uses to access his huge table).
I was a bit curious about this function since it is one of the key rare differences in this pure reverse engineering effort so I started converting this function to C for my own viewing (I'll can write more about this below).
So this is the one difference in the way that Strelka/Rybka functions call the other functions. In Strelka, the call to Full_Root_Base is deleted, so instead you simply see "Start_Search -> Full_Root"
Everything else exactly matches. By the way, all of these functions have the exact same arguments and they are called in exactly the same order and with the same arguments, they return the same values and all of the other code matches also ("all" meaning all you see as you compare for hours is nothing but exact match of Rybka Binary to Strelka C).
So I will stop this post now, send it, and I'm pretty excited about the work I did for the next post since the first core part of chess search is an exact match between Rybka and Strelka with all constants matching up, and I will show you a comparison of this code next.
-
- Posts: 12672
- Joined: Wed Mar 08, 2006 8:57 pm
- Location: Redmond, WA USA
Re: Strelka Reverse Engineered from Rybka: Details Examined
I think that the most pertinant question is:Guetti wrote:Maybe you're right. I simply did not care when I wrote this part, because I assumed the whole move list gets rearranged anyway when I sort with SEE later on.Uri Blass wrote:I think that captures by pawns should be generated before captures by other pieces because later captures by pawns have better chances to be ordered first.Guetti wrote:I don't know how others do it, but I assign them a score depending on the promoted piece, which makes them end up at the correct place during movesorting. This way I can generate the promotions together with the other pawn moves.
The order of generating is also important because correct order of generating moves can save sorting later so the logical order is first promotions and after it captures by pawn and only after it captures by knights and other pieces.
Note that I did not test if changing the move ordering in this way cause strelka to become better and it is possible that things are counter intuitive.
Uri
So, to be precise I have to make a correction. The reason why I generate the captures in this order is because I simply copied the non-captures function and made the changes to generate captures. The functions are almost identical anyway with the exception of pawns.
And the reason why I generate Non-captures in this order is because Crafty does it in this order. Maybe Vas took a look at crafty too, and finally the same ended up in Strelka.
Is the move order generation of Stelka both the same as Rybka and also different from fruit.
Anything that is the same as Fruit is not evidence that Strelka copied from Rybka. We already know that Strelka was derived from Fruit ideas. So anything that is the same as Fruit is explained from that origin. However, anything that is the same as Rybka but is *different* from how Fruit does it will be interesting in showing that Strelka copied from Rybka.
Re: Strelka Reverse Engineered from Rybka: Details Examined
Well, and there is your problem if you look at move generation, because Rybka/Strelka is bitboard and Fruit is not. That probably implies that not much Fruit ideas are in the move generation of Strelka/Rybka.Dann Corbit wrote:I think that the most pertinant question is:Guetti wrote:Maybe you're right. I simply did not care when I wrote this part, because I assumed the whole move list gets rearranged anyway when I sort with SEE later on.Uri Blass wrote:I think that captures by pawns should be generated before captures by other pieces because later captures by pawns have better chances to be ordered first.Guetti wrote:I don't know how others do it, but I assign them a score depending on the promoted piece, which makes them end up at the correct place during movesorting. This way I can generate the promotions together with the other pawn moves.
The order of generating is also important because correct order of generating moves can save sorting later so the logical order is first promotions and after it captures by pawn and only after it captures by knights and other pieces.
Note that I did not test if changing the move ordering in this way cause strelka to become better and it is possible that things are counter intuitive.
Uri
So, to be precise I have to make a correction. The reason why I generate the captures in this order is because I simply copied the non-captures function and made the changes to generate captures. The functions are almost identical anyway with the exception of pawns.
And the reason why I generate Non-captures in this order is because Crafty does it in this order. Maybe Vas took a look at crafty too, and finally the same ended up in Strelka.
Is the move order generation of Stelka both the same as Rybka and also different from fruit.
Anything that is the same as Fruit is not evidence that Strelka copied from Rybka. We already know that Strelka was derived from Fruit ideas. So anything that is the same as Fruit is explained from that origin. However, anything that is the same as Rybka but is *different* from how Fruit does it will be interesting in showing that Strelka copied from Rybka.
Since Vas mentioned once he uses rotated bitboards a la (former) crafty, I think the move generation routines are probably more influenced from crafty. And not much from Fruit could help Strelka there. And the similar order of the functions in generate_captures as in crafty would support that, as I pointed out.
Now if we look at evaluation, search and TT implementation it's probably different and I would search for Fruit influence there.
Re: Strelka Reverse Engineered from Rybka: Details Examined
To trace the Chess engine in Rybka or other UCI engines, start at the Command Interpretter, the function which waits for and then processes UCI commands. Then follow the function that is called when the UCI "go" command is entered. All chess computation will be traversed by following the program from this point.
Since the UCI "go" command includes optional arguments, most chess programs parse these optional arguments in a function that is called when the go command is detected. This is the technique in Rybka/Strelka.
The function that is called is named: "start_go" and it initializes the chess move search process, it parses any optional commands that were supplied with the UCI "go" command and then it sets up and invokes "start_search ()".
I previously mentioned preparing the material to present Rybka and Strelka's "start_search ()" function, and I mentioned that this is core chess search logic with Strelka essentially identical to Rybka here. In preparing this material (coming up next) I decided I had better first show the comparison of "start_go ()".
(Instead of jumping in to the second function in sequence we might as well start with the first function.)
I just spent about 4+ hours walking through this assembly language and producing a C version of all of the details. Now that this work is done I can show the unmodified Rybka function in C and show the Strelka version which has a few lines of code moved to different locations and which has a modified "time limit" calculation.
First, here is the Rybka C version:
Here is the Strelka version:
Note the modest Modifications in Strelka 2.0. I believe if we look at the source code to Strelka 1.0 we will see Strelka 1.0 to be an exact match with Rybka 1.0 Beta (we can find out by looking inside the Strelka 1.0 binary)
Notice that Strelka 2.0 is a reverse engineered version of Rybka. So many details match that there is no way that all of this identical code could be coincidental.
Looking at the Strelka 2.0 changes I actually like the Rybka 1.0 code better. Strelka's modifications look haphazard to me.
----------------
Some folks could question the Rybka C, so here is the material that I translated into C.
[/code]
Since the UCI "go" command includes optional arguments, most chess programs parse these optional arguments in a function that is called when the go command is detected. This is the technique in Rybka/Strelka.
The function that is called is named: "start_go" and it initializes the chess move search process, it parses any optional commands that were supplied with the UCI "go" command and then it sets up and invokes "start_search ()".
I previously mentioned preparing the material to present Rybka and Strelka's "start_search ()" function, and I mentioned that this is core chess search logic with Strelka essentially identical to Rybka here. In preparing this material (coming up next) I decided I had better first show the comparison of "start_go ()".
(Instead of jumping in to the second function in sequence we might as well start with the first function.)
I just spent about 4+ hours walking through this assembly language and producing a C version of all of the details. Now that this work is done I can show the unmodified Rybka function in C and show the Strelka version which has a few lines of code moved to different locations and which has a modified "time limit" calculation.
First, here is the Rybka C version:
Code: Select all
double gTimeDouble;
int time_limit_1, time_limit_2;
int depthLimit;
int nodeTickHigh, nodeTickLow;
int bestMove, moveScore, depthScore;
bool bad_1, bad2, change, easy, clockFlag;
bool StopSearch, IgnoreClockFlag, Searching, Infinite, Delay;
// start_go.c - Process the UCI go command
void start_go (char string [])
{
const char *ptr;
bool ponder, infinite;
int movestogo, movetime, btime, wtime, binc, winc;
// Initialize global search variables
// Initialize a global floating point time related variable
gTimeDouble = 0.0;
time_limit_1 = -1;
time_limit_2 = -1;
ignoreClockFlag = 0;
depthLimit = 0x7FFFFFFF;
stopSearch = 0;
nodeTickLow = 1024;
bestMove = 0;
moveScore = 0;
depthScore = 0;
bad_1 = 0;
bad_2 = 0;
change = 0;
easy = 0;
clackFlag = 0;
nodeTickHigh = 1;
// Initialize local variables
infinite = 0;
ponder = 0;
movestogo = 25;
winc = 0;
wtime = 0;
binc = 0;
btime = 0;
movetime = 0;
ptr = strtok (string," ");
for (ptr = strtok (NULL," "); ptr != NULL; ptr = strtok (NULL," ")) {
if (!strcmp (ptr,"winc" )) { ptr = strtok (NULL," "); winc = atoi (ptr); }
if (!strcmp (ptr,"wtime")) { ptr = strtok (NULL," "); wtime = atoi (ptr); }
if (!strcmp (ptr,"binc" )) { ptr = strtok (NULL," "); binc = atoi (ptr); }
if (!strcmp (ptr,"btime")) { ptr = strtok (NULL," "); btime = atoi (ptr); }
if (!strcmp (ptr,"depth")) {
ptr = strtok (NULL," ");
// This is where Rybka adds 2 to the depth that is entered
// Rybka uses "atoi()" twice, and so the following matches the binary.
if ((atoi (ptr) + 2) < -1)
depthLimit = -1;
} else {
depthLimit = atoi (ptr) + 2;
}
}
if (!strcmp (ptr,"infinite")) { infinite = 1; }
if (!strcmp (ptr,"movestogo")) {
ptr = strtok (NULL," ");
movestogo = atoi (ptr);
if (movestogo > 30) movestogo = 30;
if (movestogo < 1) movestogo = 1;
}
else if (!strcmp (ptr,"movetime")) { ptr = strtok (NULL," "); movetime = atoi (ptr); }
else if (!strcmp (ptr,"ponder" )) { ponder = 1; }
}
// Temporary variables just used in the following calc of time_limt_1, time_limit_2
int time, inc, time_max, alloc;
if ((Board->turn) == 0) { time = wtime; inc = winc; }
else { time = btime; inc = binc; }
// Rybka compares movetime with a double precision value: 0.0
if (movetime >= 0.0) {
time_limit_1 = 5 * movetime;
time_limit_2 = 1000 * movetime;
} else if (time > 0) {
time_max = time - 5000;
alloc = (time_max + inc * (movestogo - 1)) / movestogo;
if (alloc >= time_max) alloc = time_max;
time_limit_1 = alloc;
alloc = (time_max + inc * (movestogo - 1)) / 2;
if (alloc < time_limit_1) alloc = time_limit_1;
if (alloc > time_max) alloc = time_max;
time_limit_2 = alloc;
}
if (infinite || ponder) ignoreClockFlag = 1;
Searching = 1;
Infinite = 0;
if (infinite || ponder) Infinite = 1;
Delay = 0;
start_search ();
Searching = 0;
Delay = Infinite;
if (!Infinite) Send_Best_Move ();
}
Here is the Strelka version:
Code: Select all
void start_go(char string[])
{ const char * ptr;
int infinite, ponder;
int depth, mate, movestogo, zapas;
__int64 nodes;
int binc, btime, movetime, winc, wtime;
int time, inc, alloc;
struct board_t BoardSave[1];
infinite = 0;
ponder = 0;
depth = -1;
mate = -1;
movestogo = -1;
nodes = -1;
binc = -1;
btime = -1;
movetime = -1;
winc = -1;
wtime = -1;
ptr = strtok(string," ");
for (ptr = strtok(NULL," "); ptr != NULL; ptr = strtok(NULL," ")) {
if (!strcmp(ptr,"binc")) { ptr = strtok(NULL," "); binc = atoi(ptr); }
else if (!strcmp(ptr,"btime")) { ptr = strtok(NULL," "); btime = atoi(ptr); }
else if (!strcmp(ptr,"depth")) { ptr = strtok(NULL," "); depth = atoi(ptr); }
else if (!strcmp(ptr,"infinite")) { infinite = 1; }
else if (!strcmp(ptr,"mate")) { ptr = strtok(NULL," "); mate = atoi(ptr); }
else if (!strcmp(ptr,"movestogo")) { ptr = strtok(NULL," "); movestogo = atoi(ptr); }
else if (!strcmp(ptr,"movetime")) { ptr = strtok(NULL," "); movetime = atoi(ptr); }
else if (!strcmp(ptr,"nodes")) { ptr = strtok(NULL," "); sscanf(ptr,"%I64d",&nodes); }
else if (!strcmp(ptr,"ponder")) { ponder = 1; }
else if (!strcmp(ptr,"winc")) { ptr = strtok(NULL," "); winc = atoi(ptr); }
else if (!strcmp(ptr,"wtime")) { ptr = strtok(NULL," "); wtime = atoi(ptr); }
}
best_move = 0;
best_score = 0;
depth_score = 0;
check_nb = 1024;
start_time = GetTickCount();
can_stop = 0;
bad_1 = bad_2 = change = easy = flag = 0;
*pv_str = 0;
stop_search = 0;
depth_is_limited = time_is_limited = 0;
if (depth >= 0) {
depth_is_limited = 1;
depth_limit = depth;
}
else if (mate >= 0) {
depth_is_limited = 1;
depth_limit = mate * 2 - 1;
}
if ((Board->turn) == 0) { time = wtime; inc = winc; }
else { time = btime; inc = binc; }
zapas = 0;
if (movestogo <= 0) movestogo = 30;
if (movestogo > 30) { movestogo = 30; zapas = 1; }
time_max = 100000;
if (inc < 0) inc = 0;
if (movetime >= 0) {
time_is_limited = 1;
time_max = movetime;
time_limit_1 = movetime * 5;
time_limit_2 = movetime;
}
else if (time >= 0) { // êîíòðîëü âðåìåíè
time_is_limited = 1;
if (zapas) time_max = ((time / 10) * 9) - 5000; // ìàêñèìàëüíîå âðåìÿ
else time_max = time - 2000;
if (time_max < 0) time_max = 0;
alloc = (time_max + inc * (movestogo - 1)) / movestogo;
if (alloc > time_max) alloc = time_max;
time_limit_1 = alloc; // íèæíÿÿ ãðàíèöà êîíòðîëÿ âðåìåíè
alloc = (time_max + inc * (movestogo - 1)) / 2; // ïîëîâèíà îò îñòàâøåãîñÿ
if (alloc < time_limit_1) alloc = time_limit_1;
if (alloc > time_max) alloc = time_max;
time_limit_2 = alloc; // âåðõíÿÿ ãðàíèöà êîíòðîëÿ
// Åñòü ìûñëü âåðõíþþ ãðàíèöó áðàòü íå 1/2 à 1/3 îò îñòàâøåãîñÿ,
// à òî áûâàåò äîëãî äóìàåò â òàêèõ ñëó÷àÿõ, è ïîòîì ïîïàäàåò â öåéòíîò.
}
Infinite = 0;
if (infinite || ponder) Infinite = 1;
Searching = 1;
Delay = 0;
memcpy(BoardSave, Board, sizeof(struct board_t));
start_search();
memcpy(Board, BoardSave, sizeof(struct board_t));
search_time = GetTickCount() - start_time;
if (search_time < 0) search_time = 0;
Searching = 0;
Delay = Infinite;
if (!Delay) send_best_move();
}
Note the modest Modifications in Strelka 2.0. I believe if we look at the source code to Strelka 1.0 we will see Strelka 1.0 to be an exact match with Rybka 1.0 Beta (we can find out by looking inside the Strelka 1.0 binary)
Notice that Strelka 2.0 is a reverse engineered version of Rybka. So many details match that there is no way that all of this identical code could be coincidental.
Looking at the Strelka 2.0 changes I actually like the Rybka 1.0 code better. Strelka's modifications look haphazard to me.
----------------
Some folks could question the Rybka C, so here is the material that I translated into C.
Code: Select all
.text:00409520 Start_Go proc near ; CODE XREF: Command_Interpretter+2FFp
.text:00409520
.text:00409520 ponder = byte ptr -1Ah
.text:00409520 infinite = byte ptr -19h
.text:00409520 movestogo = dword ptr -18h
.text:00409520 movetime = dword ptr -14h
.text:00409520 btime = dword ptr -10h
.text:00409520 wtime = dword ptr -0Ch
.text:00409520 binc = dword ptr -8
.text:00409520 winc = dword ptr -4
.text:00409520 arg_pString = dword ptr 4
.text:00409520
.text:00409520 sub esp, 1Ch
.text:00409523 fldz
.text:00409525 push ebx
.text:00409526 xor ebx, ebx
.text:00409528 fstp gTimeDouble
.text:0040952E push ebp
.text:0040952F push esi
.text:00409530 or eax, 0FFFFFFFFh
.text:00409533 push edi
.text:00409534 mov time_limit_1, eax
.text:00409539 mov time_limit_2, eax
.text:0040953E mov eax, [esp+2Ch+arg_pString]
.text:00409542 push offset asc_661F8C ; " "
.text:00409547 mov edi, 19h ; 19h = 25 Decimal
.text:0040954C xor esi, esi
.text:0040954E push eax
.text:0040954F mov IgnoreClockFlag, bl
.text:00409555 mov depthLimit, 7FFFFFFFh
.text:0040955F mov StopSearch, bl
.text:00409565 mov nodeTickLow, 400h
.text:0040956F mov bestMove, ebx
.text:00409575 mov moveScore, ebx
.text:0040957B mov depthScore, ebx
.text:00409581 mov bad_1, bl
.text:00409587 mov bad_2, bl
.text:0040958D mov change, bl
.text:00409593 mov easy, bl
.text:00409599 mov clockFlag, bl
.text:0040959F mov nodeTickHigh, 1
.text:004095A9 mov [esp+34h+infinite], bl
.text:004095AD mov [esp+34h+ponder], bl
.text:004095B1 mov [esp+34h+movestogo], edi
.text:004095B5 mov [esp+34h+winc], ebx
.text:004095B9 mov [esp+34h+wtime], ebx
.text:004095BD mov [esp+34h+binc], ebx
.text:004095C1 mov [esp+34h+btime], ebx
.text:004095C5 mov [esp+34h+movetime], esi
.text:004095C9 call strtok
.text:004095CE push offset asc_661F8C ; " "
.text:004095D3 push ebx
.text:004095D4 call strtok
.text:004095D9 mov ebp, eax
.text:004095DB add esp, 10h
.text:004095DE cmp ebp, ebx
.text:004095E0 jz loc_4097CC
.text:004095E6 jmp short loc_4095F0
.text:004095E6 ; ---------------------------------------------------------------------------
.text:004095E8 align 10h
.text:004095F0
.text:004095F0 loc_4095F0: ; CODE XREF: Start_Go+C6j
.text:004095F0 ; Start_Go+29Ej
.text:004095F0 mov edi, offset aWinc ; "winc"
.text:004095F5 mov esi, ebp
.text:004095F7 mov ecx, 5
.text:004095FC xor edx, edx
.text:004095FE repe cmpsb
.text:00409600 jnz short loc_40961F
.text:00409602 push offset asc_661F8C ; " "
.text:00409607 push ebx
.text:00409608 call strtok
.text:0040960D push eax
.text:0040960E call atoi
.text:00409613 add esp, 0Ch
.text:00409616 mov [esp+2Ch+winc], eax
.text:0040961A jmp loc_4097AC
.text:0040961F ; ---------------------------------------------------------------------------
.text:0040961F
.text:0040961F loc_40961F: ; CODE XREF: Start_Go+E0j
.text:0040961F mov edi, offset aWtime ; "wtime"
.text:00409624 mov esi, ebp
.text:00409626 mov ecx, 6
.text:0040962B xor eax, eax
.text:0040962D repe cmpsb
.text:0040962F jnz short loc_409653
.text:00409631 push offset asc_661F8C ; " "
.text:00409636 push ebx
.text:00409637 call strtok
.text:0040963C push eax
.text:0040963D call atoi
.text:00409642 add esp, 0Ch
.text:00409645 sub eax, 1388h
.text:0040964A mov [esp+2Ch+wtime], eax
.text:0040964E jmp loc_4097AC
.text:00409653 ; ---------------------------------------------------------------------------
.text:00409653
.text:00409653 loc_409653: ; CODE XREF: Start_Go+10Fj
.text:00409653 mov edi, offset aBinc ; "binc"
.text:00409658 mov esi, ebp
.text:0040965A mov ecx, 5
.text:0040965F xor edx, edx
.text:00409661 repe cmpsb
.text:00409663 jnz short loc_409682
.text:00409665 push offset asc_661F8C ; " "
.text:0040966A push ebx
.text:0040966B call strtok
.text:00409670 push eax
.text:00409671 call atoi
.text:00409676 add esp, 0Ch
.text:00409679 mov [esp+2Ch+binc], eax
.text:0040967D jmp loc_4097AC
.text:00409682 ; ---------------------------------------------------------------------------
.text:00409682
.text:00409682 loc_409682: ; CODE XREF: Start_Go+143j
.text:00409682 mov edi, offset aBtime ; "btime"
.text:00409687 mov esi, ebp
.text:00409689 mov ecx, 6
.text:0040968E xor eax, eax
.text:00409690 repe cmpsb
.text:00409692 jnz short loc_4096B6
.text:00409694 push offset asc_661F8C ; " "
.text:00409699 push ebx
.text:0040969A call strtok
.text:0040969F push eax
.text:004096A0 call atoi
.text:004096A5 add esp, 0Ch
.text:004096A8 sub eax, 1388h
.text:004096AD mov [esp+2Ch+btime], eax
.text:004096B1 jmp loc_4097AC
.text:004096B6 ; ---------------------------------------------------------------------------
.text:004096B6
.text:004096B6 loc_4096B6: ; CODE XREF: Start_Go+172j
.text:004096B6 mov edi, offset aDepth ; "depth"
.text:004096BB mov esi, ebp
.text:004096BD mov ecx, 6
.text:004096C2 xor edx, edx
.text:004096C4 repe cmpsb
.text:004096C6 jnz short loc_40970B
.text:004096C8 push offset asc_661F8C ; " "
.text:004096CD push ebx
.text:004096CE call strtok
.text:004096D3 mov esi, eax
.text:004096D5 push esi
.text:004096D6 call atoi
.text:004096DB add eax, 2
.text:004096DE add esp, 0Ch
.text:004096E1 cmp eax, 0FFFFFFFFh
.text:004096E4 jge short loc_4096F5
.text:004096E6 mov depthLimit, 0FFFFFFFFh
.text:004096F0 jmp loc_4097AC
.text:004096F5 ; ---------------------------------------------------------------------------
.text:004096F5
.text:004096F5 loc_4096F5: ; CODE XREF: Start_Go+1C4j
.text:004096F5 push esi
.text:004096F6 call atoi
.text:004096FB add esp, 4
.text:004096FE add eax, 2
.text:00409701 mov depthLimit, eax
.text:00409706 jmp loc_4097AC
.text:0040970B ; ---------------------------------------------------------------------------
.text:0040970B
.text:0040970B loc_40970B: ; CODE XREF: Start_Go+1A6j
.text:0040970B mov edi, offset aIs_infinite ; "is_infinite"
.text:00409710 mov esi, ebp
.text:00409712 mov ecx, 0Ch
.text:00409717 xor eax, eax
.text:00409719 repe cmpsb
.text:0040971B jnz short loc_409727
.text:0040971D mov [esp+2Ch+infinite], 1
.text:00409722 jmp loc_4097AC
.text:00409727 ; ---------------------------------------------------------------------------
.text:00409727
.text:00409727 loc_409727: ; CODE XREF: Start_Go+1FBj
.text:00409727 mov ecx, offset aMovestogo ; "movestogo"
.text:0040972C mov eax, ebp
.text:0040972E call StringCompare
.text:00409733 test al, al
.text:00409735 jz short loc_40976D
.text:00409737 push offset asc_661F8C ; " "
.text:0040973C push ebx
.text:0040973D call strtok
.text:00409742 push eax
.text:00409743 call atoi
.text:00409748 add esp, 0Ch
.text:0040974B cmp eax, 1Eh ; 1Eh = 30 Decimal
.text:0040974E mov [esp+2Ch+movestogo], eax
.text:00409752 jle short loc_40975E
.text:00409754 mov [esp+2Ch+movestogo], 1Eh
.text:0040975C jmp short loc_4097AC
.text:0040975E ; ---------------------------------------------------------------------------
.text:0040975E
.text:0040975E loc_40975E: ; CODE XREF: Start_Go+232j
.text:0040975E cmp eax, 1
.text:00409761 jge short loc_4097AC
.text:00409763 mov [esp+2Ch+movestogo], 1
.text:0040976B jmp short loc_4097AC
.text:0040976D ; ---------------------------------------------------------------------------
.text:0040976D
.text:0040976D loc_40976D: ; CODE XREF: Start_Go+215j
.text:0040976D mov ecx, offset aMovetime ; "movetime"
.text:00409772 mov eax, ebp
.text:00409774 call StringCompare
.text:00409779 test al, al
.text:0040977B jz short loc_409797
.text:0040977D push offset asc_661F8C ; " "
.text:00409782 push ebx
.text:00409783 call strtok
.text:00409788 push eax
.text:00409789 call atoi
.text:0040978E add esp, 0Ch
.text:00409791 mov [esp+2Ch+movetime], eax
.text:00409795 jmp short loc_4097AC
.text:00409797 ; ---------------------------------------------------------------------------
.text:00409797
.text:00409797 loc_409797: ; CODE XREF: Start_Go+25Bj
.text:00409797 mov ecx, offset aPonder ; "ponder"
.text:0040979C mov eax, ebp
.text:0040979E call StringCompare
.text:004097A3 test al, al
.text:004097A5 jz short loc_4097AC
.text:004097A7 mov [esp+2Ch+ponder], 1
.text:004097AC
.text:004097AC loc_4097AC: ; CODE XREF: Start_Go+FAj
.text:004097AC ; Start_Go+12Ej ...
.text:004097AC push offset asc_661F8C ; " "
.text:004097B1 push ebx
.text:004097B2 call strtok
.text:004097B7 mov ebp, eax
.text:004097B9 add esp, 8
.text:004097BC cmp ebp, ebx
.text:004097BE jnz loc_4095F0
.text:004097C4 mov edi, [esp+2Ch+movestogo]
.text:004097C8 mov esi, [esp+2Ch+movetime]
.text:004097CC
.text:004097CC loc_4097CC: ; CODE XREF: Start_Go+C0j
.text:004097CC cmp Board_turn, ebx
.text:004097D2 jz short loc_4097DE
.text:004097D4 mov ecx, [esp+2Ch+btime]
.text:004097D8 mov edx, [esp+2Ch+binc]
.text:004097DC jmp short loc_4097E6
.text:004097DE ; ---------------------------------------------------------------------------
.text:004097DE
.text:004097DE loc_4097DE: ; CODE XREF: Start_Go+2B2j
.text:004097DE mov ecx, [esp+2Ch+wtime]
.text:004097E2 mov edx, [esp+2Ch+winc]
.text:004097E6
.text:004097E6 loc_4097E6: ; CODE XREF: Start_Go+2BCj
.text:004097E6 fild [esp+2Ch+movetime]
.text:004097EA fcomp ds:dbl_6623D0
.text:004097F0 fnstsw ax
.text:004097F2 test ah, 41h ; 41h = 65 Decimal
.text:004097F5 jnz short loc_40980E
.text:004097F7 lea ecx, [esi+esi*4]
.text:004097FA imul esi, 3E8h ; 3E8h = 1000 Decimal
.text:00409800 mov time_limit_1, ecx
.text:00409806 mov time_limit_2, esi
.text:0040980C jmp short loc_409846
.text:0040980E ; ---------------------------------------------------------------------------
.text:0040980E
.text:0040980E loc_40980E: ; CODE XREF: Start_Go+2D5j
.text:0040980E cmp ecx, ebx
.text:00409810 jle short loc_409846
.text:00409812 lea eax, [edi-1]
.text:00409815 imul eax, edx
.text:00409818 lea esi, [ecx-1388h] ; 1388h = 5000 Decimal
.text:0040981E lea ecx, [eax+esi]
.text:00409821 xor edx, edx
.text:00409823 mov eax, ecx
.text:00409825 div edi
.text:00409827 cmp eax, esi
.text:00409829 jb short loc_40982D
.text:0040982B mov eax, esi
.text:0040982D
.text:0040982D loc_40982D: ; CODE XREF: Start_Go+309j
.text:0040982D shr ecx, 1
.text:0040982F cmp ecx, eax
.text:00409831 mov time_limit_1, eax
.text:00409836 ja short loc_40983A
.text:00409838 mov ecx, eax
.text:0040983A
.text:0040983A loc_40983A: ; CODE XREF: Start_Go+316j
.text:0040983A cmp ecx, esi
.text:0040983C jb short loc_409840
.text:0040983E mov ecx, esi
.text:00409840
.text:00409840 loc_409840: ; CODE XREF: Start_Go+31Cj
.text:00409840 mov time_limit_2, ecx
.text:00409846
.text:00409846 loc_409846: ; CODE XREF: Start_Go+2ECj
.text:00409846 ; Start_Go+2F0j
.text:00409846 mov al, [esp+2Ch+infinite]
.text:0040984A cmp al, bl
.text:0040984C jnz short loc_409854
.text:0040984E cmp [esp+2Ch+ponder], bl
.text:00409852 jz short loc_40985B
.text:00409854
.text:00409854 loc_409854: ; CODE XREF: Start_Go+32Cj
.text:00409854 mov IgnoreClockFlag, 1
.text:0040985B
.text:0040985B loc_40985B: ; CODE XREF: Start_Go+332j
.text:0040985B cmp al, bl
.text:0040985D mov Searching, 1
.text:00409864 jnz short loc_409872
.text:00409866 cmp [esp+2Ch+ponder], bl
.text:0040986A mov Infinite, bl
.text:00409870 jz short loc_409879
.text:00409872
.text:00409872 loc_409872: ; CODE XREF: Start_Go+344j
.text:00409872 mov Infinite, 1
.text:00409879
.text:00409879 loc_409879: ; CODE XREF: Start_Go+350j
.text:00409879 mov Delay, bl
.text:0040987F call Start_Search
.text:00409884 mov al, Infinite
.text:00409889 pop edi
.text:0040988A pop esi
.text:0040988B pop ebp
.text:0040988C mov Searching, bl
.text:00409892 cmp al, bl
.text:00409894 mov Delay, al
.text:00409899 pop ebx
.text:0040989A jnz short loc_4098A4
.text:0040989C add esp, 1Ch
.text:0040989F jmp Send_Best_Move
.text:004098A4 ; ---------------------------------------------------------------------------
.text:004098A4
.text:004098A4 loc_4098A4: ; CODE XREF: Start_Go+37Aj
.text:004098A4 add esp, 1Ch
.text:004098A7 retn
.text:004098A7 Start_Go endp
-
- Posts: 880
- Joined: Thu Mar 09, 2006 11:21 pm
- Location: Nederland
Re: Strelka Reverse Engineered from Rybka: Details Examined
looks almost exactly like the fruit/toga code to me
protocol.cpp if i remember well
strelka using integers instead of (slower) double
Best
Fonzy
protocol.cpp if i remember well
strelka using integers instead of (slower) double
Best
Fonzy