evaluation without special white/black code

Discussion of chess software programming and technical issues.

Moderator: Ras

Uri Blass
Posts: 10798
Joined: Thu Mar 09, 2006 12:37 am
Location: Tel-Aviv Israel

Re: evaluation without special white/black code

Post by Uri Blass »

bob wrote:
Uri Blass wrote:
bob wrote:
Uri Blass wrote:I wonder how you write your evaluation without special code for white/black.

The original code of strelka has code like the following

endgame += cnt * mobility_knight_endgame;//black minus
opening += cnt * mobility_knight_opening;//black minus

I can simply have array of 2 numbers 1 and -1 and have

endgame += cnt * mobility_knight_endgame*mult[side]
opening += cnt * mobility_knight_opening*mult[side]//black minus

Second alternative is to have special arrays and have
endgame[side]+=cnt*mobility_knight_endgame


In this case you need in the end to substract in order to find the score.
Third alternative is to have

Code: Select all

#define eval(a,side) ((side)==White)?(a):(0-(a)))
endgame[side]+=cnt*eval(mobility_knight_endgame,side)
I wonder which alternative is better.
I decided to go this way:

1. a procedure that evaluates the pieces for just one side, and returns a score +=good for that side, -=bad for that side...

then

value += EvaluateKnight(white) - EvaluateKnight(black)

does the trick. Somewhere you obviously have to have special-case code, since white and black are different, the above minimizes the differences pretty nicely and now you never have to look at the above line, you just modify EvaluateKnights() and you are done...
This mean that you call EvaluateKnight twice in your code and the same for some other functions.

I wonder what is the advantage relative to value+=Evaluate(white)-Evaluate(black) when Evaluate(side) calls only to Evaluateknight(side)

In the case of Strelka the program has no EvaluateKnight function but
even if I change it and add EvaluateKnight function then
value += EvaluateKnight(white) - EvaluateKnight(black) is not a solution

The problem is that EvaluateKnight not only change the evaluation by calculating mobility but also updates some variables at the same time(controling squares near the king) and these variables are used later in the evaluation.

Uri
Almost right. Since EvaluateKnights() is defined in the same source file as Evaluate() where it is called, the two calls get inlined and there is no function call overhead. But it makes it easier to read...

As far as the latter problem, I deal with that in Crafty. You just have a variable such as xxx[2] where xxx[0] is the stuff black knights do, and xxx[1] is the stuff white knights do (I have those for mobility, and any sort of other information that needs to be passed around (white candidate pawns, black candidates becomes candidates[2] for example...)
You can give evaluateknight a pointer to array xxx and it solves the problem but I still do not think that it is simpler than having:

value += Evaluate(White)-Evaluate(Black) when Evaluate(side) calculates Evaluateknights(side)

If you do it in this way then you still have the same evaluation but you have only in one case of your program White or Black and if you use
Evaluateknights(White)-Evaluateknights(Black) then you may also use EvaluateRooks(White)-EvaluateRooks(Black) and
EvaluateQueens(White)-EvaluateQueens(Black) so you have more code to write.

It may be the same from speed point of view but I think that a solution
with smaller code is more elegant.

I am also not sure if it is the same from speed point of view.

If you have EvaluateKnight(white)-EvaluateKnight(Black) then
EvaluateKnight(white) updates xxx[White] when
EvaluateKnight(black) updates xxx[Black]

It mean that practically EvaluateKnight get an array of 2 numbers and not pointer to a single variable.

If you have Evaluate(white)-Evaluate(Black) for independent parts of the evaluation then you do not need to give array xxx to your functions and you can give them only a pointer to variable.

Note that Evaluate(White)-Evaluate(Black) is not the final evaluation but only most of it.

It clearly include more than one function like EvaluateKnights but you still need to do steps like changing the score to be closer to draw for opposite color bishops endgame.

Uri
Dann Corbit
Posts: 12777
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: evaluation without special white/black code

Post by Dann Corbit »

Uri Blass wrote:
bob wrote:
Uri Blass wrote:
bob wrote:
Uri Blass wrote:I wonder how you write your evaluation without special code for white/black.

The original code of strelka has code like the following

endgame += cnt * mobility_knight_endgame;//black minus
opening += cnt * mobility_knight_opening;//black minus

I can simply have array of 2 numbers 1 and -1 and have

endgame += cnt * mobility_knight_endgame*mult[side]
opening += cnt * mobility_knight_opening*mult[side]//black minus

Second alternative is to have special arrays and have
endgame[side]+=cnt*mobility_knight_endgame


In this case you need in the end to substract in order to find the score.
Third alternative is to have

Code: Select all

#define eval(a,side) ((side)==White)?(a):(0-(a)))
endgame[side]+=cnt*eval(mobility_knight_endgame,side)
I wonder which alternative is better.
I decided to go this way:

1. a procedure that evaluates the pieces for just one side, and returns a score +=good for that side, -=bad for that side...

then

value += EvaluateKnight(white) - EvaluateKnight(black)

does the trick. Somewhere you obviously have to have special-case code, since white and black are different, the above minimizes the differences pretty nicely and now you never have to look at the above line, you just modify EvaluateKnights() and you are done...
This mean that you call EvaluateKnight twice in your code and the same for some other functions.

I wonder what is the advantage relative to value+=Evaluate(white)-Evaluate(black) when Evaluate(side) calls only to Evaluateknight(side)

In the case of Strelka the program has no EvaluateKnight function but
even if I change it and add EvaluateKnight function then
value += EvaluateKnight(white) - EvaluateKnight(black) is not a solution

The problem is that EvaluateKnight not only change the evaluation by calculating mobility but also updates some variables at the same time(controling squares near the king) and these variables are used later in the evaluation.

Uri
Almost right. Since EvaluateKnights() is defined in the same source file as Evaluate() where it is called, the two calls get inlined and there is no function call overhead. But it makes it easier to read...

As far as the latter problem, I deal with that in Crafty. You just have a variable such as xxx[2] where xxx[0] is the stuff black knights do, and xxx[1] is the stuff white knights do (I have those for mobility, and any sort of other information that needs to be passed around (white candidate pawns, black candidates becomes candidates[2] for example...)
You can give evaluateknight a pointer to array xxx and it solves the problem but I still do not think that it is simpler than having:

value += Evaluate(White)-Evaluate(Black) when Evaluate(side) calculates Evaluateknights(side)

If you do it in this way then you still have the same evaluation but you have only in one case of your program White or Black and if you use
Evaluateknights(White)-Evaluateknights(Black) then you may also use EvaluateRooks(White)-EvaluateRooks(Black) and
EvaluateQueens(White)-EvaluateQueens(Black) so you have more code to write.

It may be the same from speed point of view but I think that a solution
with smaller code is more elegant.

I am also not sure if it is the same from speed point of view.

If you have EvaluateKnight(white)-EvaluateKnight(Black) then
EvaluateKnight(white) updates xxx[White] when
EvaluateKnight(black) updates xxx[Black]

It mean that practically EvaluateKnight get an array of 2 numbers and not pointer to a single variable.

If you have Evaluate(white)-Evaluate(Black) for independent parts of the evaluation then you do not need to give array xxx to your functions and you can give them only a pointer to variable.

Note that Evaluate(White)-Evaluate(Black) is not the final evaluation but only most of it.

It clearly include more than one function like EvaluateKnights but you still need to do steps like changing the score to be closer to draw for opposite color bishops endgame.

Uri
The smaller code is very important for another reason: less bugs.

If you have 1/2 as much code, then you have 1/2 as many bugs in it, if the defect rate is constant. Also, having to remember to fix things in two places every time you make a correction is a problem waiting to happen.

I think that bug-free code is extremely important.

Another reason is that smaller code is easier to understand than larger code. If the code is clearly understood, then it is much easier to improve it than if it is hard to understand.

Basically, the smaller the better, but we should keep in mind what Einstein said:
"Make it as simple as possible, but no simpler."
;-)
wgarvin
Posts: 838
Joined: Thu Jul 05, 2007 5:03 pm
Location: British Columbia, Canada

Re: evaluation without special white/black code

Post by wgarvin »

Dann Corbit wrote:If you have 1/2 as much code, then you have 1/2 as many bugs in it, if the defect rate is constant. Also, having to remember to fix things in two places every time you make a correction is a problem waiting to happen.
c2 wiki says... Once And Only Once.
Dann Corbit wrote:I think that bug-free code is extremely important.

Another reason is that smaller code is easier to understand than larger code. If the code is clearly understood, then it is much easier to improve it than if it is hard to understand.

Basically, the smaller the better, but we should keep in mind what Einstein said:
"Make it as simple as possible, but no simpler."
;-)
c2 wiki says... Do The Simplest Thing That Could Possibly Work.

The c2 wiki is a treasure trove of interesting programming ideas (and miscellaneous other stuff). Its worth browsing around in if you've never come across it before. :P

Some interesting pages to start at are:
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: evaluation without special white/black code

Post by bob »

Uri Blass wrote:
bob wrote:
Uri Blass wrote:
bob wrote:
Uri Blass wrote:I wonder how you write your evaluation without special code for white/black.

The original code of strelka has code like the following

endgame += cnt * mobility_knight_endgame;//black minus
opening += cnt * mobility_knight_opening;//black minus

I can simply have array of 2 numbers 1 and -1 and have

endgame += cnt * mobility_knight_endgame*mult[side]
opening += cnt * mobility_knight_opening*mult[side]//black minus

Second alternative is to have special arrays and have
endgame[side]+=cnt*mobility_knight_endgame


In this case you need in the end to substract in order to find the score.
Third alternative is to have

Code: Select all

#define eval(a,side) ((side)==White)?(a):(0-(a)))
endgame[side]+=cnt*eval(mobility_knight_endgame,side)
I wonder which alternative is better.
I decided to go this way:

1. a procedure that evaluates the pieces for just one side, and returns a score +=good for that side, -=bad for that side...

then

value += EvaluateKnight(white) - EvaluateKnight(black)

does the trick. Somewhere you obviously have to have special-case code, since white and black are different, the above minimizes the differences pretty nicely and now you never have to look at the above line, you just modify EvaluateKnights() and you are done...
This mean that you call EvaluateKnight twice in your code and the same for some other functions.

I wonder what is the advantage relative to value+=Evaluate(white)-Evaluate(black) when Evaluate(side) calls only to Evaluateknight(side)

In the case of Strelka the program has no EvaluateKnight function but
even if I change it and add EvaluateKnight function then
value += EvaluateKnight(white) - EvaluateKnight(black) is not a solution

The problem is that EvaluateKnight not only change the evaluation by calculating mobility but also updates some variables at the same time(controling squares near the king) and these variables are used later in the evaluation.

Uri
Almost right. Since EvaluateKnights() is defined in the same source file as Evaluate() where it is called, the two calls get inlined and there is no function call overhead. But it makes it easier to read...

As far as the latter problem, I deal with that in Crafty. You just have a variable such as xxx[2] where xxx[0] is the stuff black knights do, and xxx[1] is the stuff white knights do (I have those for mobility, and any sort of other information that needs to be passed around (white candidate pawns, black candidates becomes candidates[2] for example...)
You can give evaluateknight a pointer to array xxx and it solves the problem but I still do not think that it is simpler than having:

value += Evaluate(White)-Evaluate(Black) when Evaluate(side) calculates Evaluateknights(side)

If you do it in this way then you still have the same evaluation but you have only in one case of your program White or Black and if you use
Evaluateknights(White)-Evaluateknights(Black) then you may also use EvaluateRooks(White)-EvaluateRooks(Black) and
EvaluateQueens(White)-EvaluateQueens(Black) so you have more code to write.

It may be the same from speed point of view but I think that a solution
with smaller code is more elegant.

I am also not sure if it is the same from speed point of view.

If you have EvaluateKnight(white)-EvaluateKnight(Black) then
EvaluateKnight(white) updates xxx[White] when
EvaluateKnight(black) updates xxx[Black]

It mean that practically EvaluateKnight get an array of 2 numbers and not pointer to a single variable.

If you have Evaluate(white)-Evaluate(Black) for independent parts of the evaluation then you do not need to give array xxx to your functions and you can give them only a pointer to variable.

Note that Evaluate(White)-Evaluate(Black) is not the final evaluation but only most of it.

It clearly include more than one function like EvaluateKnights but you still need to do steps like changing the score to be closer to draw for opposite color bishops endgame.

Uri
I'm a bit lost here. We first talked about evaluate for one side only, to get rid of duplicate black/white code. Then you mentioned that sometimes a procedure like EvaluateKnights() needs to compute values used in other procedures. That is the case I was handling...

If you look at EvaluateKnights() in Crafty, it evaluates with respect to the side passed in, and also computes some "global" values that are used elsewhere (king safety tropism for example). All I have done is make all that global state stuff that used to be white_tropism and black_tropism, into an array tropism[2] where tropism[0] is for black, tropism[1] is for white. Now I still have just one procedure that is called twice, once for black and once for white, yet it stikll sets the same global state information needed by other evaluation procedures...
Uri Blass
Posts: 10798
Joined: Thu Mar 09, 2006 12:37 am
Location: Tel-Aviv Israel

Re: evaluation without special white/black code

Post by Uri Blass »

bob wrote:
Uri Blass wrote:
bob wrote:
Uri Blass wrote:
bob wrote:
Uri Blass wrote:I wonder how you write your evaluation without special code for white/black.

The original code of strelka has code like the following

endgame += cnt * mobility_knight_endgame;//black minus
opening += cnt * mobility_knight_opening;//black minus

I can simply have array of 2 numbers 1 and -1 and have

endgame += cnt * mobility_knight_endgame*mult[side]
opening += cnt * mobility_knight_opening*mult[side]//black minus

Second alternative is to have special arrays and have
endgame[side]+=cnt*mobility_knight_endgame


In this case you need in the end to substract in order to find the score.
Third alternative is to have

Code: Select all

#define eval(a,side) ((side)==White)?(a):(0-(a)))
endgame[side]+=cnt*eval(mobility_knight_endgame,side)
I wonder which alternative is better.
I decided to go this way:

1. a procedure that evaluates the pieces for just one side, and returns a score +=good for that side, -=bad for that side...

then

value += EvaluateKnight(white) - EvaluateKnight(black)

does the trick. Somewhere you obviously have to have special-case code, since white and black are different, the above minimizes the differences pretty nicely and now you never have to look at the above line, you just modify EvaluateKnights() and you are done...
This mean that you call EvaluateKnight twice in your code and the same for some other functions.

I wonder what is the advantage relative to value+=Evaluate(white)-Evaluate(black) when Evaluate(side) calls only to Evaluateknight(side)

In the case of Strelka the program has no EvaluateKnight function but
even if I change it and add EvaluateKnight function then
value += EvaluateKnight(white) - EvaluateKnight(black) is not a solution

The problem is that EvaluateKnight not only change the evaluation by calculating mobility but also updates some variables at the same time(controling squares near the king) and these variables are used later in the evaluation.

Uri
Almost right. Since EvaluateKnights() is defined in the same source file as Evaluate() where it is called, the two calls get inlined and there is no function call overhead. But it makes it easier to read...

As far as the latter problem, I deal with that in Crafty. You just have a variable such as xxx[2] where xxx[0] is the stuff black knights do, and xxx[1] is the stuff white knights do (I have those for mobility, and any sort of other information that needs to be passed around (white candidate pawns, black candidates becomes candidates[2] for example...)
You can give evaluateknight a pointer to array xxx and it solves the problem but I still do not think that it is simpler than having:

value += Evaluate(White)-Evaluate(Black) when Evaluate(side) calculates Evaluateknights(side)

If you do it in this way then you still have the same evaluation but you have only in one case of your program White or Black and if you use
Evaluateknights(White)-Evaluateknights(Black) then you may also use EvaluateRooks(White)-EvaluateRooks(Black) and
EvaluateQueens(White)-EvaluateQueens(Black) so you have more code to write.

It may be the same from speed point of view but I think that a solution
with smaller code is more elegant.

I am also not sure if it is the same from speed point of view.

If you have EvaluateKnight(white)-EvaluateKnight(Black) then
EvaluateKnight(white) updates xxx[White] when
EvaluateKnight(black) updates xxx[Black]

It mean that practically EvaluateKnight get an array of 2 numbers and not pointer to a single variable.

If you have Evaluate(white)-Evaluate(Black) for independent parts of the evaluation then you do not need to give array xxx to your functions and you can give them only a pointer to variable.

Note that Evaluate(White)-Evaluate(Black) is not the final evaluation but only most of it.

It clearly include more than one function like EvaluateKnights but you still need to do steps like changing the score to be closer to draw for opposite color bishops endgame.

Uri
I'm a bit lost here. We first talked about evaluate for one side only, to get rid of duplicate black/white code. Then you mentioned that sometimes a procedure like EvaluateKnights() needs to compute values used in other procedures. That is the case I was handling...

If you look at EvaluateKnights() in Crafty, it evaluates with respect to the side passed in, and also computes some "global" values that are used elsewhere (king safety tropism for example). All I have done is make all that global state stuff that used to be white_tropism and black_tropism, into an array tropism[2] where tropism[0] is for black, tropism[1] is for white. Now I still have just one procedure that is called twice, once for black and once for white, yet it stikll sets the same global state information needed by other evaluation procedures...
I have no problem with one procedure that is called twice.

The point is that it seems to me that my solution is better.
It is better simply to have
Evaluate=Evaluate(white)-Evaluate(black) and not to have Evaluateknights(white,xxx)-Evaluateknights(Black,xxx) inside the code
when xxx is an array of 2 variables.

of course Evaluate(white) calls Evaluateknight(White,xxx) and
Evaluate(Black) call EvaluateKnight(Black,xxx) but the advantages are

1)in the code there is only one EvaluateKnight(side)
2)EvaluateKnight get only a pointer to single variable to update and not a pointer to array of 2 variables.

Uri
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: evaluation without special white/black code

Post by bob »

OK, I see what you are talking about. two procedures, one for white, one for black, rather than N for white and N for black. Here's my take:

(1) smaller procedures are better from a readability perspective. It always has been easier to test, debug and modify small blocks of code as opposed to large blocks.

(2) They are no less efficient because the compiler can inline everything, so there is no cost there.

(3) Isolating knight (for an example) scoring away from the rest of the evaluation tends to further reduce bugs because of how the local variables are used to prevent unexpected interactions with one or two large procedures that have everything in one large block of code...

I looked at doing it both ways, and even just having one EvaluateKnights() with a loop over both sides... But the current approach produced nicer-looking code when I was playing around with it...