@@ -339,7 +339,7 @@ static int search_solve_4(Search *search, int alpha)
339339#endif
340340
341341/**
342- * @brief Evaluate a position using a shallow NWS.
342+ * @brief Evaluate a position using a shallow NWS. (5..6 empties)
343343 *
344344 * This function is used when there are few empty squares on the board. Here,
345345 * optimizations are in favour of speed instead of efficiency.
@@ -444,7 +444,7 @@ static int search_shallow(Search *search, const int alpha, bool pass1)
444444}
445445
446446/**
447- * @brief Evaluate an endgame position with a Null Window Search algorithm.
447+ * @brief Evaluate an endgame position with a Null Window Search algorithm. (7..9 empties)
448448 *
449449 * This function is used when there are still many empty squares on the board. Move
450450 * ordering, hash table cutoff, enhanced transposition cutoff, etc. are used in
@@ -455,7 +455,8 @@ static int search_shallow(Search *search, const int alpha, bool pass1)
455455 * @param alpha Alpha bound.
456456 * @return The final score, as a disc difference.
457457 */
458- int NWS_endgame (Search * search , const int alpha )
458+
459+ static int NWS_endgame_local (Search * search , const int alpha )
459460{
460461 int score , ofssolid , bestscore ;
461462 unsigned long long hash_code , solid_opp ;
@@ -491,34 +492,32 @@ int NWS_endgame(Search *search, const int alpha)
491492 // Improvement of Serch by Reducing Redundant Information in a Position of Othello
492493 // Hidekazu Matsuo, Shuji Narazaki
493494 // http://id.nii.ac.jp/1001/00156359/
494- if (search -> eval .n_empties <= MASK_SOLID_DEPTH ) { // (99%)
495- solid_opp = full [4 ] & hashboard .opponent ; // full[4] = all full
495+ solid_opp = full [4 ] & hashboard .opponent ; // full[4] = all full
496496#ifndef POPCOUNT
497- if (solid_opp ) // (72%)
497+ if (solid_opp ) // (72%)
498498#endif
499- {
500- hashboard .player ^= solid_opp ; // normalize solid to player
501- hashboard .opponent ^= solid_opp ;
502- ofssolid = bit_count (solid_opp ) * 2 ; // hash score is ofssolid grater than real
503- }
499+ {
500+ hashboard .player ^= solid_opp ; // normalize solid to player
501+ hashboard .opponent ^= solid_opp ;
502+ ofssolid = bit_count (solid_opp ) * 2 ; // hash score is ofssolid grater than real
504503 }
505504 }
506505
507506 hash_code = board_get_hash_code (& hashboard );
508- hash_prefetch ( & search -> hash_table , hash_code );
507+ PREFETCH ( search -> thread_hash . hash + ( hash_code & search -> thread_hash . hash_mask ) );
509508
510509 search_get_movelist (search , & movelist );
511510
512511 if (movelist .n_moves > 1 ) { // (96%)
513512 // transposition cutoff
514- if (hash_get (& search -> hash_table , & hashboard , hash_code , & hash_data .data )) { // (6%)
513+ if (hash_get_local (& search -> thread_hash , & hashboard , hash_code , & hash_data .data )) { // (6%)
515514 hash_data .data .lower -= ofssolid ;
516515 hash_data .data .upper -= ofssolid ;
517516 if (search_TC_NWS (& hash_data .data , search -> eval .n_empties , NO_SELECTIVITY , alpha , & score )) // (6%)
518517 return score ;
519518 }
520519 // else if (ofssolid) // slows down
521- // hash_get_from_board(&search->hash_table , HBOARD_V(board0), &hash_data.data);
520+ // hash_get_from_board(&search->thread_hash , HBOARD_V(board0), &hash_data.data);
522521
523522 movelist_evaluate_fast (& movelist , search , & hash_data .data );
524523
@@ -547,7 +546,7 @@ int NWS_endgame(Search *search, const int alpha)
547546 search -> eval .parity = parity0 ^ QUADRANT_ID [move -> x ];
548547 empty_remove (search -> empties , move -> x );
549548 vboard_update (& search -> board , board0 , move );
550- score = - NWS_endgame (search , ~alpha );
549+ score = - NWS_endgame_local (search , ~alpha );
551550 empty_restore (search -> empties , move -> x );
552551 search -> board = board0 .board ;
553552
@@ -565,12 +564,12 @@ int NWS_endgame(Search *search, const int alpha)
565564
566565 hash_data .data .wl .c .depth = search -> eval .n_empties ;
567566 hash_data .data .wl .c .selectivity = NO_SELECTIVITY ;
568- hash_data .data .wl .c .cost = last_bit (search -> n_nodes - nodes_org );
567+ // hash_data.data.wl.c.cost = last_bit(search->n_nodes - nodes_org);
569568 // hash_data.data.move[0] = bestmove;
570569 hash_data .alpha = alpha + ofssolid ;
571570 hash_data .beta = alpha + ofssolid + 1 ;
572571 hash_data .score = bestscore + ofssolid ;
573- hash_store (& search -> hash_table , & hashboard , hash_code , & hash_data );
572+ hash_store_local (& search -> thread_hash , & hashboard , hash_code , & hash_data );
574573
575574 // special cases
576575 } else if (movelist .n_moves == 1 ) { // (3%)
@@ -581,12 +580,130 @@ int NWS_endgame(Search *search, const int alpha)
581580 vboard_update (& search -> board , board0 , move );
582581 if (-- search -> eval .n_empties <= DEPTH_TO_SHALLOW_SEARCH ) // (56%)
583582 bestscore = - search_shallow (search , ~alpha , false);
584- else bestscore = - NWS_endgame (search , ~alpha );
583+ else bestscore = - NWS_endgame_local (search , ~alpha );
585584 ++ search -> eval .n_empties ;
586585 empty_restore (search -> empties , move -> x );
587586 search -> eval .parity = parity0 ;
588587 search -> board = board0 .board ;
589588
589+ } else { // (1%)
590+ if (can_move (search -> board .opponent , search -> board .player )) { // pass
591+ search_pass (search );
592+ bestscore = - NWS_endgame_local (search , ~alpha );
593+ search_pass (search );
594+ } else { // game over
595+ bestscore = search_solve (search );
596+ }
597+ }
598+
599+ if (SQUARE_STATS (1 ) + 0 ) {
600+ foreach_move (move , movelist )
601+ ++ statistics .n_played_square [search -> eval .n_empties ][SQUARE_TYPE [move -> x ]];
602+ if (bestscore > alpha )
603+ ++ statistics .n_good_square [search -> eval .n_empties ][SQUARE_TYPE [bestscore ]];
604+ }
605+ assert (SCORE_MIN <= bestscore && bestscore <= SCORE_MAX );
606+ assert ((bestscore & 1 ) == 0 );
607+ return bestscore ;
608+ }
609+
610+ /**
611+ * @brief Evaluate an endgame position with a Null Window Search algorithm. (10..15 empties)
612+ *
613+ * This function is used when there are still many empty squares on the board. Move
614+ * ordering, hash table cutoff, enhanced transposition cutoff, etc. are used in
615+ * order to diminish the size of the tree to analyse, but at the expense of a
616+ * slower speed.
617+ *
618+ * @param search Search.
619+ * @param alpha Alpha bound.
620+ * @return The final score, as a disc difference.
621+ */
622+ int NWS_endgame (Search * search , const int alpha )
623+ {
624+ int score , bestscore ;
625+ unsigned long long hash_code ;
626+ // const int beta = alpha + 1;
627+ HashStoreData hash_data ;
628+ Move * move ;
629+ long long nodes_org ;
630+ V2DI board0 ;
631+ Board hashboard ;
632+ unsigned int parity0 ;
633+ unsigned long long full [5 ];
634+ MoveList movelist ;
635+
636+ assert (bit_count (~(search -> board .player |search -> board .opponent )) < DEPTH_MIDGAME_TO_ENDGAME );
637+ assert (SCORE_MIN <= alpha && alpha <= SCORE_MAX );
638+
639+ if (search -> eval .n_empties <= DEPTH_TO_USE_LOCAL_HASH )
640+ return NWS_endgame_local (search , alpha );
641+
642+ if (search -> stop ) return alpha ;
643+
644+ SEARCH_STATS (++ statistics .n_NWS_endgame );
645+ SEARCH_UPDATE_INTERNAL_NODES (search -> n_nodes );
646+
647+ // stability cutoff
648+ hashboard = board0 .board = search -> board ;
649+ if (USE_SC && alpha >= NWS_STABILITY_THRESHOLD [search -> eval .n_empties ]) { // (7%)
650+ CUTOFF_STATS (++ statistics .n_stability_try ;)
651+ score = SCORE_MAX - 2 * get_stability (search -> board .opponent , search -> board .player );
652+ if (score <= alpha ) { // (3%)
653+ CUTOFF_STATS (++ statistics .n_stability_low_cutoff ;)
654+ return score ;
655+ }
656+ }
657+
658+ hash_code = board_get_hash_code (& hashboard );
659+ hash_prefetch (& search -> hash_table , hash_code );
660+
661+ search_get_movelist (search , & movelist );
662+
663+ if (movelist .n_moves > 0 ) { // (96%)
664+ // transposition cutoff
665+ if (hash_get (& search -> hash_table , & hashboard , hash_code , & hash_data .data )) { // (6%)
666+ if (search_TC_NWS (& hash_data .data , search -> eval .n_empties , NO_SELECTIVITY , alpha , & score )) // (6%)
667+ return score ;
668+ }
669+ if (movelist .n_moves > 1 )
670+ movelist_evaluate_fast (& movelist , search , & hash_data .data );
671+
672+ nodes_org = search -> n_nodes ;
673+ parity0 = search -> eval .parity ;
674+ bestscore = - SCORE_INF ;
675+ // loop over all moves
676+ move = & movelist .move [0 ];
677+ -- search -> eval .n_empties ; // for next move
678+ while ((move = move_next_best (move ))) { // (76%)
679+ search -> eval .parity = parity0 ^ QUADRANT_ID [move -> x ];
680+ empty_remove (search -> empties , move -> x );
681+ vboard_update (& search -> board , board0 , move );
682+ score = - NWS_endgame (search , ~alpha );
683+ empty_restore (search -> empties , move -> x );
684+ search -> board = board0 .board ;
685+
686+ if (score > bestscore ) { // (63%)
687+ bestscore = score ;
688+ hash_data .data .move [0 ] = move -> x ;
689+ if (bestscore > alpha ) break ; // (39%)
690+ }
691+ }
692+ ++ search -> eval .n_empties ;
693+ search -> eval .parity = parity0 ;
694+
695+ if (search -> stop ) // (1%)
696+ return alpha ;
697+
698+ hash_data .data .wl .c .depth = search -> eval .n_empties ;
699+ hash_data .data .wl .c .selectivity = NO_SELECTIVITY ;
700+ hash_data .data .wl .c .cost = last_bit (search -> n_nodes - nodes_org );
701+ // hash_data.data.move[0] = bestmove;
702+ hash_data .alpha = alpha ;
703+ hash_data .beta = alpha + 1 ;
704+ hash_data .score = bestscore ;
705+ hash_store (& search -> hash_table , & hashboard , hash_code , & hash_data );
706+
590707 } else { // (1%)
591708 if (can_move (search -> board .opponent , search -> board .player )) { // pass
592709 search_pass (search );
0 commit comments