I'm not going to give the whole C code at once, but just paste it in as I go. Here is the ь code translated
Code: Select all
* PV_NODE(LOWER,UPPER,depth,check)^HISTORY#
< [ depth <= 1 ] *%PV_QSEARCH#[check](LOWER,UPPER,depth)! |
HASH[PV]_READ&TRANS&hashdepth |
[ [ !TRANS ] + [ depth >= 6] ]
*%PV[ depth >= 10](LOWER-depth,UPPER+depth,depth-8,check)^TRANS ^
*%PV(LOWER-depth,UPPER+depth,depth-4,check)^TRANS
\ [ [ depth >= 10 ] + [ depth >= hashdepth + 8] ]
*%PV(LOWER-depth,UPPER+depth,depth-8,check)^TRANS ^
*%PV(LOWER-depth,UPPER+depth,depth-4,check)^TRANS |
[ check ] [ @EVADE() | [ #moves < 2 ] SINGULAR*[ #moves ] ] |
[ depth >= 16 ] + [ OK_MOVE(TRANS) ] + [ SINGULAR < 2 ]
[ || EVAL ! %PV(-UPPER,-LOWER,depth-10,&check) @!
*%EXCLUDE[check]#((&)-depth/2,depth-MINIMUM(12,depth/2),TRANS)^SINGULAR ^
*%EXCLUDE[check]#((&)-depth,depth-MINIMUM(12,depth/2),TRANS)^SINGULAR ] |
<< [ REPETITION# ] %MAXIMUM(%%,0)% + count! ||
EVAL() !
[PASSED_PAWN_PUSH(6th)] EXTENSION + EXTENSION
\ [ !CAPTURE ] EXTENSION
\ [ & check ] EXTENSION
\ [ check ] + [ EARLYGAME ] EXTENSION
\ [ PASSED_PAWN_PUSH(4th) ] EXTENSION |
[ TRANS ] + [ depth-2+MAXIMUM(EXTENSION,SINGULAR) > 1 ]
%CUT/LOW#(-LOWER,&LEFT_VALUE)^%PV(-UPPER,-LOWER,&LEFT_VALUE,&check)
\ %PV(-UPPER,-LOWER,depth-2+MAXIMUM(EXTENSION,SINGULAR),&check) >>
[ !%% ] [ [ check ] (0)! \ HEIGHT! ]
>
I reformatted this to make it more clear somewheres.
The initial line names the function (I called it PV_NODE here, but PV below), and has some flags
Code: Select all
* PV_NODE(LOWER,UPPER,depth,check)^HISTORY#
The first is that the function takes LOWER and UPPER rather than just one value (SCORE), and also that is has a flag for in-check. There is also the HISTORY flag after the caret, and the # makes it use LOWER rather than SCORE (seen below).
The next line and expression calls QSEARCH when depth is small (not more than 1)
Code: Select all
< [ depth <= 1 ] *%PV_QSEARCH#[check](LOWER,UPPER,depth)! |
The % is a usual function call, but *% makes it a self-call (white to white, rather than white to black). The LOWER and UPPER and depth and passed. The # on PV_QSEARCH plus the [check] seems redundant to me, as I though the # itself did a check splitting. The final ! demands to return the value obtained from PV_QSEARCH. Also the first pipe (|) actuates the 50-move rule and repetition checks
Code: Select all
if (above < -30000)
return (-30000);
if (below > 30000)
return (30000);
if (deepness <= 1)
{
if (shah)
return pv_white_qsearching_check (below, above, 1);
else
return pv_white_qsearching (below, above, 1);
}
if (tower_dynamics->non_permanent >= 100)
return (0);
for (i = 4; i <= tower_dynamics->non_permanent && i <= elevation_stack; i += 2)
if (stack[elevation_stack - i] == tower_dynamics->hash_64bit)
return (0);
The very first to check is that LOWER and UPPER are in range, and then the depth <= 1 split, with the 50 moves and repetition trailing thereof.
The hash code should be familiar, but has many flags too
The READ and TRANS are with the same as before. The [PV] ensures that "exact" hash entries have care, and the hashdepth tracks this. I think the &VARIABLE construction means to save VARIABLE
Code: Select all
S->transpositional_movement = 0;
hash_deepness = 0;
S->move = 0;
S->wrong_capturings_enumerator = 0;
k = tower_dynamics->hash_64bit & hash_mask;
(tower_structure + 1)->move = 0;
for (i = 0; i < 4; i++)
{
hash = table_hash + (k + i);
if ((hash->hash_64bit ^ (tower_dynamics->hash_64bit >> 32)) == 0)
{
transpositional_deepness = hash->deepness_below;
move = hash->move;
if (move && transpositional_deepness > move_deepness)
{
move_deepness = transpositional_deepness;
(tower_structure + 1)->move = transpositional_movement = move;
}
if (hash->deepness_below > hash->deepness_above)
{
transpositional_deepness = hash->deepness_below;
score = (hash->score_below);
}
else
{
transpositional_deepness = hash->deepness_above;
score = (hash->score_above);
}
if (transpositional_deepness > hash_deepness)
hash_deepness = transpositional_deepness;
if (((hash)->flag & 16) && transpositional_deepness >= deepness)
{
hash->ancientness = AGEDNESS;
if (!analysis_mode)
return (score);
}
}
}
The "hash->flag & 16" is the direct [PV] part. The score is given as whichever for the below/above hash depths is greater. If analysis_mode is ON then a hash score from the PV is ignored.
The next lines are the iterative calls to find a quality hash move when none is apparent.
Code: Select all
[ [ !TRANS ] + [ depth >= 6] ]
*%PV[ depth >= 10](LOWER-depth,UPPER+depth,depth-8,check)^TRANS ^
*%PV(LOWER-depth,UPPER+depth,depth-4,check)^TRANS
\ [ [ depth >= 10 ] + [ depth >= hashdepth + 8] ]
*%PV(LOWER-depth,UPPER+depth,depth-8,check)^TRANS ^
*%PV(LOWER-depth,UPPER+depth,depth-4,check)^TRANS |
This is six lines, but rather formulaic. First the three beginning lines. The first condition demands that the TRANS move not exist, and that the depth is at least 6 halves-ply. When this is true, there is a splitting. The [depth>=10] conditional on the self-call of PV (from *%) is the first condition. When it is true, PV is iterated at four ply lower, and if this works for a hash move (not failing low), at two ply (4 halves-ply) again. The TRANS after the caret means to append the TRANS move when found. The caret (^) after that is the conjunctive that says to search again when the TRANS appears at the outset.
Code: Select all
if (!transpositional_movement && deepness >= 6)
{
itog = below;
if (deepness >= 10)
{
itog = white_pv (below - deepness, above + deepness, deepness - 8, shah);
if (itog > below - deepness)
transpositional_movement = (tower_structure + 1)->move;
}
if (itog > below - deepness)
itog = white_pv (below - deepness, above + deepness, deepness - 4, shah);
if (itog > below - deepness)
transpositional_movement = (tower_structure + 1)->move;
}
From the code the condition for the disjunctive is that the returned value from white_pv must not fail low (with a margin of deepness). When depth is less 10 (five total ply), only the 2 ply reduction is tested, but otherwise the 4 ply is tested first, and must work for the later iteration.
In the other case, in the else conditional with the backslash (\), the condition is that depth be at least 10 halves-ply and the searching depth be 8 halves-ply more than the depth from the hash entry.
Code: Select all
\ [ [ depth >= 10 ] + [ depth >= hashdepth + 8] ]
*%PV(LOWER-depth,UPPER+depth,depth-8,check)^TRANS ^
*%PV(LOWER-depth,UPPER+depth,depth-4,check)^TRANS |
This is the case where a TRANS move exists, but is not very deep. The iterative PV call is as before, with the 8 halves-ply reduction first, and again at 2 full ply when this works.
Code: Select all
else if (deepness >= 10 && deepness > hash_deepness + 8)
{
itog = white_pv (below - deepness, above + deepness, deepness - 8, shah);
if (itog > below - deepness)
transpositional_movement = (tower_structure + 1)->move;
if (itog > below - deepness)
{
itog = white_pv (below - deepness, above + deepness, deepness - 4, shah);
if (itog > below - deepness)
transpositional_movement = (tower_structure + 1)->move;
}
}
Again the condition for benefice with a TRANS move is for it not to fail low. I do not understand why the C code has "deepness > hash_deepness + 8" but the ь code has "depth >= hashdepth + 8" for the comparison should be similar.
I'm not sure I agree with the logic here for the first place. Why do I need check at 2 ply again after it works at 4 ply?
The next ь code condition discerns based upon whether the side is in check
Code: Select all
[ check ] [ @EVADE() | [ #moves < 2 ] SINGULAR*[ #moves ] ] |
The condition of the if-else is simply [check], and when true the EVADE generator (@) is called, with no TARGET element (other than 0xffffffffffffffff). If the #moves is < 2 there is an extension (singular), with one half-ply when there are two moves, and one whole ply on a forced move. I don't see why the condition is "#moves < 2" rather than "#moves <= 2" here, really. The syntax is obscure.
Code: Select all
S->transpositional_movement = transpositional_movement;
S->phase = transpositional;
stretches = 0;
S->target = position_fixed.bitboard[enumerated_occupation_black];
if (shah)
{
move_list = white_escape (S->movement_listing, 0xffffffffffffffff);
S->phase = rebut_check;
for (p = move_list - 1; p >= S->movement_listing; p--)
{
if ((p->move & 0x7fff) == transpositional_movement)
p->move |= 0xffff0000;
else if (p->move <= (0x80 << 24))
{
if ((p->move & 0x7fff) == tower_structure->murderer_1)
p->move |= 0x7fff8000;
else if ((p->move & 0x7fff) == tower_structure->murderer_2)
p->move |= 0x7fff0000;
else
p->move |= (p->move & 0x7fff) | (history_tabular[position_fixed.square[(((p->move) >> 6) & 077)]][((p->move) & 077)] << 15);
}
move = p->move;
for (q = p + 1; q < move_list; q++)
{
if (move < q->move)
(q - 1)->move = q->move;
else
break;
}
q--;
q->move = move;
}
if ((move_list - S->movement_listing) <= 1)
singular = 2;
if ((move_list - S->movement_listing) == 2)
singular = 1;
if ((move_list - S->movement_listing) > 2)
singular = 0;
}
The magic expansion happens again here, as the singular condition appears only at the end, after the sorting phase is included after the EVADE. This includes the TRANS move, and killer moves (murderers), and then proceeds to use history values.
There is one more expression before the iterative move loop
Code: Select all
[ depth >= 16 ] + [ OK_MOVE(TRANS) ] + [ SINGULAR < 2 ]
[ || EVAL ! %PV(-UPPER,-LOWER,depth-10,&check) @!
*%EXCLUDE[check]#((&)-depth/2,depth-MINIMUM(12,depth/2),TRANS)^SINGULAR ^
*%EXCLUDE[check]#((&)-depth,depth-MINIMUM(12,depth/2),TRANS)^SINGULAR ] |
This is another singular extension check. The first line is the conditional (agglutinative via +), and demands the depth be at least 8 full ply, the TRANS move be valid, and the move not already be maximal SINGULAR. When true, the double pipe (||) makes the TRANS move, and EVAL is called with no lazy margin. Below I see EVAL(), and the difference is unclear.
The point of the code is to see if only the TRANS move is useful. This accomplishes by first evaluating the TRANS move at depth-10, or five ply less. This is done via %PV(-UPPER,-LOWER,depth-10,&check) where the &check comes from the result of EVAL I think. The final @! undoes the move, and saves the value of the iterative call in "(&)" it seems. The exclusion search is called, with a [check] splitting, first with a margin of "depth/2" around the "(&)" value and depth reduced by 12 halves-ply (in general), with the excluded move being TRANS. If this fails low then SINGULAR is increased and another call is made for the same with the margin now as "depth" around the "(&)" value, with again SINGULAR increased if it fails low.
Code: Select all
if (deepness >= 16 && S->transpositional_movement && singular < 2 &&
white_ok (S->transpositional_movement))
{
move = S->transpositional_movement;
kk = ((move) & 077);
ot = (((move) >> 6) & 077);
white_spell (move);
evaluation_scoring (-0x7fff0000, 0x7fff0000, move);
if (((tower_structure + 1)->white_king_check))
{
white_unspell (move);
goto zab;
}
score = -black_pv (-above, -below, deepness - 10, (((tower_structure + 1)->black_king_check)) != 0);
white_unspell (move);
if (shah)
itog = white_exclustion_check (score - deepness / 2,
deepness - (((12) <= (deepness / 2)) ? (12) : (deepness / 2)), move & 0x7fff);
else
itog = white_exclustion (score - deepness / 2,
deepness - (((12) <= (deepness / 2)) ? (12) : (deepness / 2)), move & 0x7fff);
if (itog < score - deepness / 2)
{
singular = 1;
if (shah)
itog = white_exclustion_check (score - deepness,
deepness - (((12) <= (deepness / 2)) ? (12) : (deepness / 2)), move & 0x7fff);
else
itog = white_exclustion (score - deepness,
deepness - (((12) <= (deepness / 2)) ? (12) : (deepness / 2)), move & 0x7fff);
if (itog < score - deepness)
singular = 2;
}
}
The idea is that if all other moves than TRANS are bad then the TRANS move is singular. The legality check of TRANS is invisible in the ь code, except maybe for the pipe (|) after EVAL.
The final part of the PV function is the move iterator
Code: Select all
<< [ REPETITION# ] %MAXIMUM(%%,0)% + ct! ||
EVAL() !
[PASSED_PAWN_PUSH(6th)] EXTENSION + EXTENSION
\ [ !CAPTURE ] EXTENSION
\ [ & check ] EXTENSION
\ [ check ] + [ EARLYGAME ] EXTENSION
\ [ PASSED_PAWN_PUSH(4th) ] EXTENSION |
[ TRANS ] + [ depth-2+MAXIMUM(EXTENSION,SINGULAR) > 1 ]
%CUT/LOW#(-LOWER,&LEFT_VALUE)^%PV(-UPPER,-LOWER,&LEFT_VALUE,&check)
\ %PV(-UPPER,-LOWER,depth-2+MAXIMUM(EXTENSION,SINGULAR),&check) >>
The first line is a repetition check, then after the EVAL call there are five lines of EXTENSION concerns. The last three lines determine which search function to call.
The REPETITION check is similar to the one in LowDepth, except that LOWER is used as a base rather than SCORE, which is the # effect I think.
Code: Select all
<< [ REPETITION# ] %MAXIMUM(%%,0)% + count! ||
The determination is made whether the LOWER bound is at least 0, and if so and the move made is invertible, then the move is skipped, with the best value (%%) updated to 0 if not so already via the %...% operation, and finally the move count is increased, and the loop continued (count! with exclam continuing).
Code: Select all
while ((move = white_next (S)))
{
kk = ((move) & 077);
ot = (((move) >> 6) & 077);
if (below > 0 &&
tower_structure->non_permanent >= 2 &&
((((move) & 077) << 6) | (((move) >> 6) & 077)) == (tower_structure - 1)->move &&
position_fixed.square[((move) & 077)] == 0)
{
best_valuation = (((0) >= (best_valuation)) ? (0) : (best_valuation));
continue;
}
Everything follows as in LowDepth except for "below" other than "notch" in there.
The EVAL() is called, with various conditions for extending added
Code: Select all
EVAL() !
[PASSED_PAWN_PUSH(6th)] EXTENSION + EXTENSION
\ [ !CAPTURE ] EXTENSION
\ [ & check ] EXTENSION
\ [ check ] + [ EARLYGAME ] EXTENSION
\ [ PASSED_PAWN_PUSH(4th) ] EXTENSION |
If a PASSED_PAWN_PUSH to the 6th rank happens, then EXTENSION is incremented twice. I get lost in the next phase, and the C code seems to differ. At the end, a PASSED_PAWN_PUSH to the 4th rank gets a single EXTENSION (half-ply).
Code: Select all
move &= 0x7fff;
white_spell (move);
evaluation_scoring (-0x7fff0000, 0x7fff0000, move);
if (((tower_structure + 1)->white_king_check))
{
white_unspell (move);
continue;
}
yes_check = (((tower_structure + 1)->black_king_check) != 0);
This is just the EVAL part, with the legality check. Then there is
Code: Select all
stretches = 0;
if (stretches < 2)
{
if ((position_fixed.square[kk] == enumerated_white_pawns && ((kk) >= A6) &&
(position_fixed.bitboard[enumerated_black_pawns] & passed_pawn_white[kk]) == 0))
stretches = 2;
}
if (stretches < 2)
{
if ((tower_structure + 1)->captured != 0 ||
yes_check ||
(shah && ((tower_dynamics->material_64bit & 0xff) >= 18)))
stretches = 1;
else if ((position_fixed.square[kk] == enumerated_white_pawns && ((kk) >= A4) &&
(position_fixed.bitboard[enumerated_black_pawns] & passed_pawn_white[kk]) == 0))
stretches = 1;
}
if (S->transpositional_movement != move)
singular = 0;
The "stretches" are EXTENSIONS, and the first check is for the PASSED_PAWN_PUSH to the 6th (or 7th), with a double EXTENSION added if so. The next condition is to demand that a move is a capture, or is a check, or the position is already check and the game is "early" (18/32 of force), then make an extension. The final extension addition is when the PASSED_PAWN_PUSH is the 4th or more. The final bit here, an "invisible" line again, sets SINGULAR to zero if the move is not the TRANS move, for bookkeeping.
My guess is that [ !CAPTURE ] should be [ CAPTURE ], as the confusion is that "CAPTURE" involves "!= 0"
I think the original ь code had the three conditions on one line, so maybe that matters too
Code: Select all
\ [ !CAPTURE ] EXTENSION \ [ & check ] EXTENSION \ [ check ] + [ EARLYGAME ] EXTENSION
Finally other search functions are called
Code: Select all
[ TRANS ] + [ depth-2+MAXIMUM(EXTENSION,SINGULAR) > 1 ]
%CUT/LOW#(-LOWER,&LEFT_VALUE)^%PV(-UPPER,-LOWER,&LEFT_VALUE,&check)
\ %PV(-UPPER,-LOWER,depth-2+MAXIMUM(EXTENSION,SINGULAR),&check) >>
The condition is whether a move is the TRANS move, and if the following depth is at least 1. If so, then either CUT or LOW is called, with an invisible splitting based upon whether the new depth is 8 halves-ply or more. Again I think it should be [ !TRANS ] here, but I don't know. The "LEFT_VALUE" is the left-value from the above condition, and so is "depth-2+MAX(EXT,SING)" in shorthand. The caret (^) in the first call here is triggered when CUT or LOW returns a fail high, and then PV is called. The # after the CUT/LOW is a splitting for if the move is check, and this is in the &check for PV calls. When the condition fails, so the move is TRANS or the following depth is less than one whole ply, then PV is called directly. I don't see why LEFT_VALUE could not be used again, and the C code does so, in gratitude.
Code: Select all
novel_deepness = deepness - 2 + (((stretches) >= (singular)) ? (stretches) : (singular));
if (S->transpositional_movement != move && novel_deepness > 1)
{
if (novel_deepness <= 7)
{
if (yes_check)
itog = -black_slide_check (-below, novel_deepness);
else
itog = -black_slide (-below, novel_deepness);
}
else
{
if (yes_check)
itog = -black_cut_check (-below, novel_deepness);
else
itog = -black_cut (-below, novel_deepness);
}
if (itog > below)
itog = -black_pv (-above, -below, novel_deepness, yes_check);
}
else
itog = -black_pv (-above, -below, novel_deepness, yes_check);
white_unspell (move);
As can be seen, the TRANS condition is that when the move is the TRANS move (the first move) then PV is called without CUT/LOW first. I don't know what happens if there is no TRANS move. The "itog > below" condition demands that PV be called when CUT/LOW returns a high value.
Next there is the large "magic" block with bookkeeping and history, that is invisible in ь code.
Code: Select all
if (itog <= below && position_fixed.square[((move) & 077)] == 0 && ((move & 060000) == 0))
{
int ist = history_tabular[position_fixed.square[(((move) >> 6) & 077)]][((move) & 077)];
if (tower_structure->score_value > below - 50)
history_tabular[position_fixed.square[(((move) >> 6) & 077)]][((move) & 077)] =
ist - ((ist * deepness) >> 8);
}
if (itog <= best_valuation)
continue;
best_valuation = itog;
if (itog <= below)
continue;
below = itog;
pleasant_move = move;
hash_below (tower_dynamics->hash_64bit, move, deepness, itog);
When a move fails low, and is not a capture or promotion, and the evaluation is within 50 of the LOWER value, then the history is reduced. The # flag on the original HISTORY caret (^) in the function definition probably uses LOWER rather than SCORE as with LowDepth. The next lines update best_value and LOWER if necessary. If the move beats LOWER, then it is "pleasant", and has its hash updated for that.
Code: Select all
if (itog >= above)
{
if (position_fixed.square[((move) & 077)] == 0 && ((move & 060000) == 0))
{
int ist = history_tabular[position_fixed.square[(((move) >> 6) & 077)]][((move) & 077)];
history_tabular[position_fixed.square[(((move) >> 6) & 077)]][((move) & 077)] =
ist + (((0xff00 - ist) * deepness) >> 8);
if (move != tower_dynamics->murderer_1)
{
tower_dynamics->murderer_2 = tower_dynamics->murderer_1;
tower_dynamics->murderer_1 = move;
}
}
return (itog);
}
}
If a move fails high (itog >= above), then the update of history is again, with killer moves also. Again any ь parser "knows" all this.
Finally we get out of the move iteration loop.
Code: Select all
[ !%% ] [ [ check ] (0)! \ HEIGHT! ]
The !%% determines if best_value has been set (no pruning other than repetition, so no moves if not), and if not, looks if check is set, and returns either 0 or MATE-HEIGHT. The logic again seems reversed?
Code: Select all
move = pleasant_move;
(tower_structure + 1)->move = pleasant_move & 0x7fff;
if (best_valuation == -32750)
{
if (!shah)
return (0);
return ((tower_structure - (table_dynamics + 1)) - 30000);
}
The C code also invisibly sets the "good move" at this level (tower+1) to be the pleasant move.
The last thing is to record the hash information when a move exists.
Code: Select all
if (move)
{
if (position_fixed.square[((move) & 077)] == 0 && ((move & 060000) == 0))
{
int ist = history_tabular[position_fixed.square[(((move) >> 6) & 077)]][((move) & 077)];
history_tabular[position_fixed.square[(((move) >> 6) & 077)]][((move) & 077)] =
ist + (((0xff00 - ist) * deepness) >> 8);
if (move != tower_dynamics->murderer_1)
{
tower_dynamics->murderer_2 = tower_dynamics->murderer_1;
tower_dynamics->murderer_1 = move;
}
}
hash_exactly (move, deepness, best_valuation, 16);
return (best_valuation);
}
hash_above (tower_dynamics->hash_64bit, deepness, best_valuation);
return (best_valuation);
When a pleasant move exists, its history is upgraded, and killers set, and hash_exactly is called as the value is between LOWER and UPPER. If no move exists, hash_above is called.
This ends the PV code. It seems filled with backwards logic, but the skeleton of it exists well.