#! /bin/false

# Copyright (C) 2021-2026 Guido Flohr <guido.flohr@cantanea.com>,
# all rights reserved.

# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What the Fuck You Want
# to Public License, Version 2, as published by Sam Hocevar. See
# http://www.wtfpl.net/ for more details.

package Chess::Plisco::Engine::Tree;
$Chess::Plisco::Engine::Tree::VERSION = 'v1.0.3';
use strict;
use integer;

use Locale::TextDomain ('Chess-Plisco');
use List::Util qw(min);

use Chess::Plisco qw(:all);
# Macros from Chess::Plisco::Macro are already expanded here!
use Chess::Plisco::Engine::Position qw(CP_POS_REVERSIBLE_CLOCK CP_POS_POPCOUNT);
use Chess::Plisco::Engine::Constants;

use Time::HiRes qw(tv_interval ualarm gettimeofday);

use constant DEBUG => $ENV{DEBUG_PLISCO_TREE};

use constant CP_POS_SIGNATURE => Chess::Plisco::Engine::Position::CP_POS_SIGNATURE();
use constant CP_POS_REVERSIBLE_CLOCK => Chess::Plisco::Engine::Position::CP_POS_REVERSIBLE_CLOCK();

use Chess::Plisco::Engine::TranspositionTable;

# These values get stored in the upper 32 bits of a moves so that they are
# searched first.
use constant MOVE_ORDERING_PV => 1 << 62;
use constant MOVE_ORDERING_TT => 1 << 61;

use constant SAFETY_MARGIN => 300;
use constant UALARM_INTERVAL => 500 * SAFETY_MARGIN;

use constant WDL_LOSS => -2;
use constant WDL_BLESSED_LOSS => -1;
use constant WDL_DRAW => 0;
use constant WDL_CURSED_WIN => 1;
use constant WDL_WIN => 2;

# For all combinations of promotion piece and captured piece, calculate a
# value suitable for sorting.  We choose the raw material balance minus the
# piece that moves.  That way, captures that the queen makes are less
# "attractive" than captures that the rook makes.
my @move_values = (0) x 369;


# MVV-LVA values. Looked up via the captured and moving piece ($move & 0x3f).
my @mvv_lva;

# Usually only queen promotions and sometimes promotions to a knight are
# interesting. This mask is used to filter them out.
use constant GOOD_PROMO_MASK => (1 << (CP_QUEEN)) | (1 << (CP_KNIGHT));

my ($self_tb_cardinality, $self_tb_probe_depth);

sub new {
	my ($class, %options) = @_;

	my $position = $options{position};
	my $signatures = $options{signatures};

	# Make sure that the reversible clock does not look beyond the known
	# positions.  This will simplify the detection of a draw by repetition.
	if ($position->[CP_POS_REVERSIBLE_CLOCK] >= @$signatures) {
		$position->[CP_POS_REVERSIBLE_CLOCK] = @$signatures - 1;
	}

	no integer;

	my @killers = map { [] } 0 .. MAX_PLY - 1;
	my $self = {
		position => $position,
		signatures => $signatures,
		history_length => -1 + scalar @$signatures,
		tt => $options{tt},
		watcher => $options{watcher},
		info => $options{info} || sub {},
		book => $options{book},
		book_depth => $options{book_depth},
		killers => \@killers,
		cutoff_moves => [[], []], # History heuristic, one slot for each side.
		average_score => -INF,
		previous_average_score => INF,
		iter_scores => [],
		previous_time_reduction => 0.85,
		root_moves => {},
		total_best_move_changes => 0,
		tb => $options{tb},
		tb_cardinality => $options{tb_cardinality},
		tb_probe_depth => $options{tb_probe_depth},
		tb_probe_limit => $options{tb_probe_limit},
		tb_50_move_rule => $options{tb_50_move_rule},
		tb_7 => $options{tb_7},
		tb_3 => $options{tb_3},
		tb_hits => 0,
		tb_root_hit => 0,
	};

	$self_tb_cardinality = $options{tb_cardinality};
	$self_tb_probe_depth = $options{tb_probe_depth};

	bless $self, $class;
}

sub checkTime {
	my ($self) = @_;

	no integer;

	# Remember whether we are currently pondering. If a "ponderhit" was
	# returned, the engine object will reset the ponder flag and also reset
	# the start time to now.  If that happens, we should skip this
	# recalibration of the nodes to the next time control because the ETA will
	# be zero or close to 0.
	my $was_ponder = $self->{ponder};

	# It is important to check for input before checking the time control. If
	# another "go" command has been received, the engine object will request
	# us to stop immediately.  When this search terminates, the engine will
	# immediately resume with the next search.
	$self->{watcher}->check($self);
	if ($self->{stop_requested}) {
		die "PLISCO_ABORTED\n";
	}

	my $elapsed = 1000 * tv_interval($self->{start_time});

	# Taken from Stockfish: Start printing the current move after 0.5 s.
	# Otherwise the output is getting messy in the beginning.  Stockfish is
	# using 3 s but we are slower.
	if ($elapsed > 500) {
		$self->{print_current_move} = 1;
	}

	my $nodes = $self->{nodes};

	# FIXME! It is probably better to look at the current nps, not the
	# overall nps.
	my $nps = $elapsed ? (1000 * $nodes / $elapsed) : 10000;

	my $max_nodes_to_tc = $nps >> 2;

	if ($self->{ponder}) {
		# We have to be quick enough to stop.  On the other hand, pondering
		# is not effective, if we invoke the time control function too often.
		$self->{nodes_to_tc} = $nodes + $nps >> 4;
	} elsif ($self->{max_depth}) {
		$self->{nodes_to_tc} = $nodes + $max_nodes_to_tc;
	} elsif ($self->{max_nodes}) {
		if ($nodes + $max_nodes_to_tc < $self->{max_nodes}) {
			$self->{nodes_to_tc} = $nodes + $max_nodes_to_tc;
		} else {
			$self->{nodes_to_tc} = $self->{max_nodes};
		}
	} else {
		use integer;

		my $allocated = $self->{maximum};
		my $eta = $allocated - $elapsed;
		if ($eta < SAFETY_MARGIN) {
			die "PLISCO_ABORTED\n";
		}

		# How many nodes should we check until the next time control?
		# We want to roughly do at least 4 time checks per second to keep the
		# application responsive.
		my $max_nodes = $nps >> 2;

		# Re-calibrate the number of nodes to the next time control.
		#
		# But do not do this when the uci engine object has just received
		# a "ponderhit" command. It has then reset our start time to the
		# current time and has removed the ponder flag. That is why we have
		# remembered it at the start of this routine.
		#
		# If we have less than about one second left, we gradually reduce
		# the batch size so that we do not overuse the allocated time.
		#
		# We can expect to process $eta * $nps / 1000 nodes in the remaining
		# time. In order to play safe, should the performance suddenly drop,
		# we divide that number by 8. A division by 1000 is roughly a
		# right shift of 10, a division by 4 a right-shift by 2. We can
		# therefore just right-shift the product by 13.
		if (!($was_ponder && !$self->{ponder})) {
			my $dyn_nodes = ($eta * $nps) >> 13;

			my $nodes_to_go = ($max_nodes < $dyn_nodes) ? $max_nodes : $dyn_nodes;
			$self->{nodes_to_tc} = $nodes + $nodes_to_go;
		}
	}
}

sub debug {
	my ($self, $msg) = @_;

	chomp $msg;
	print STDERR "DEBUG $msg\n";

	return 1;
}

sub indent {
	my ($self, $ply, $msg) = @_;

	chomp $msg;
	my $indent = '..' x ($ply - 1);
	$self->debug("[$ply/$self->{depth}] $indent$msg");
}

sub printPV {
	my ($self, $pline) = @_;

	no integer;
	my $position = $self->{start};
	my $score = $self->{score} // 0;
	my $mate_in;
	if ($score >= MATE - MAX_PLY) {
		use integer;
		$mate_in = (MATE - $score) >> 1;
	} elsif ($score <= (-MATE + MAX_PLY)) {
		use integer;
		$mate_in = -((-(-MATE - $score)) >> 1);
	}

	my $nodes = $self->{nodes} // 1;
	my $elapsed = tv_interval($self->{start_time});
	my $nps = $elapsed ? (int(0.5 + $nodes / $elapsed)) : 0;
	if ($self->{tb_root_hit} && !$mate_in) {
		$score = $self->{tb_outcome};
	}
	my $scorestr = $mate_in ? "mate $mate_in" : "cp $score";
	my $pv = join ' ', $position->movesCoordinateNotation(@$pline);
	my $time = int(0.5 + (1000 * $elapsed));
	my $hashfull = $self->{tt}->hashfull // 0;
	my $depth = $self->{depth} // 1;
	my $seldepth = $self->{seldepth} // $depth;
	my $tbhits = $self->{tb_hits} // 0;
	$self->{info}->("depth $depth seldepth $seldepth"
			. " score $scorestr nodes $nodes nps $nps hashfull $hashfull"
			. " tbhits $self->{tb_hits} time $time pv $pv");
	if ($self->{__debug}) {
		$self->{info}->("tt_hits $self->{tt_hits}") if $self->{__debug};
	}
}

# Make them invokable without a method call.
sub quiesce;



sub alphabeta {
	my ($self, $ply, $depth, $alpha, $beta, $pline, $node_type, $cut_node,
		$tt_pvs, $min_probe_ply) = @_;

	if ($self->{nodes} >= $self->{nodes_to_tc}) {
		$self->checkTime;
	}

	my $pv_node = $node_type != NON_PV_NODE;

	if ($depth <= 0) {
		return quiesce($self, $ply, $alpha, $beta, $pv_node ? PV_NODE : NON_PV_NODE, $min_probe_ply);
	}

	$depth = ((($depth) < (MAX_PLY - 1)) ? ($depth) : (MAX_PLY - 1));

	my $root_node = $node_type == ROOT_NODE;
	my $all_node = !($pv_node || $cut_node);

	my $position = $self->{position};

	if (DEBUG) {
		my $hex_signature = sprintf '%016x', $position->[CP_POS_SIGNATURE];
		my $line = join ' ', @{$self->{line}};
		my $fen = $position->toFEN;
		my $node_type_name = NODE_TYPES->[$node_type];
		$self->indent($ply, "alphabeta<$node_type_name>: alpha = $alpha, beta = $beta, line: $line,"
			. " depth: $depth, sig: $hex_signature $fen");
	}

	if ($position->[CP_POS_HALFMOVE_CLOCK] >= 100) {
		if (DEBUG) {
			$self->indent($ply, "draw detected");
		}
		return DRAW;
	}

	# Check draw by repetition.  FIXME! Try to find near repetitions with
	# cuckoo tables.
	#
	# We know that the reversible clock is never pointing beyond the known
	# positions/signatures because that gets adjusted in the constructor.
	my $signatures = $self->{signatures};
	my $signature = $position->[CP_POS_SIGNATURE];
	if ($ply > 1) {
		my $rc = $position->[CP_POS_REVERSIBLE_CLOCK];
		my $history_length = $self->{history_length};
		my $signature_slot = $history_length + $ply;
		my $max_back = $signature_slot - $rc - 1;
		for (my $n = $signature_slot - 5; $n >= $max_back; $n -= 2) {
			if ($signatures->[$n] == $signature) {
				if (DEBUG) {
					$self->indent($ply, "3-fold repetition");
				}
				return DRAW;
			}
		}
	}

	# Transposition table lookup.
	my $tt = $self->{tt};
	if (DEBUG) {
		my $hex_sig = sprintf '%016x', $signature;
		$self->indent($ply, "TT probe $hex_sig \@depth $depth, alpha = $alpha, beta = $beta");
	}
	my ($tt_hit, $tt_depth, $tt_bound, $tt_move, $tt_value, $tt_eval,
		$tt_pv, @tt_address) = $tt->probe($signature);

	$tt_move = 0 if ($root_node || !$tt_hit);
	$tt_value = (do {	if ($tt_value >= VALUE_TB_WIN_IN_MAX_PLY) {		if ($tt_value >= MATE_IN_MAX_PLY && MATE - $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_WIN_IN_MAX_PLY - 1		} elsif (VALUE_TB_WIN_IN_MAX_PLY - $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_WIN_IN_MAX_PLY - 1		} else {			$tt_value - $ply;		}	} elsif ($tt_value <= VALUE_TB_LOSS_IN_MAX_PLY) {		if ($tt_value <= MATED_IN_MAX_PLY && MATE + $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_LOSS_IN_MAX_PLY + 1;		} elsif (VALUE_TB_LOSS_IN_MAX_PLY + $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_LOSS_IN_MAX_PLY + 1;		} else {			$tt_value + $ply;		}	} else {		$tt_value;	}})
		if $tt_hit && defined $tt_value;
	$tt_pvs->[$ply] = $pv_node || ($tt_hit && $tt_pv);
	my $pv_hit = $tt_hit && $tt_pv;

	if ($tt_hit && defined $tt_value && $tt_depth >= $depth) {
		# FIXME! Actually, there is no need for a TT move.
		if ($tt_move
		    && ($tt_bound == BOUND_EXACT
		        || ($tt_bound == BOUND_LOWER && $tt_value >= $beta)
		        || ($tt_bound == BOUND_UPPER && $tt_value <= $alpha))) {
			++$self->{tt_hits};
			if ($ply == 1) {
				@$pline = ($tt_move);
				$self->{score} = $tt_value;
			}

			if (DEBUG) {
				my $hex_sig = sprintf '%016x', $signature;
				my $cn = $position->moveCoordinateNotation($tt_move);
				my $type = BOUND_TYPES->[$tt_bound];
				my $is_pv_hit = $pv_hit ? 'true' : 'false';
				$self->indent($ply, "TT hit for $hex_sig, value $tt_value ($type), TT depth $tt_depth, PV hit: $is_pv_hit, best move $cn");
			}

			return $tt_value;
		}
	}

	# Probe endgame tablebase.
	my $best_value = -INF;
	if ($ply >= $min_probe_ply) {
		my $pieces_count = $position->[CP_POS_POPCOUNT];
		if ($min_probe_ply > 1) {
			if ($pieces_count <= $self_tb_cardinality) {
				$min_probe_ply = 1;
			} else {
				$min_probe_ply = $ply + $pieces_count - $self_tb_cardinality;
				goto TB_PROBE_DONE;
			}
		}

		if ($position->[CP_POS_HALFMOVE_CLOCK] == 0
			&& !$position->[CP_POS_CASTLING_RIGHTS]
			&& ($pieces_count < $self_tb_cardinality
			   || $depth >= $self_tb_probe_depth)) {
			my $tb = $self->{tb};
			my $wdl;
			
			eval {
				local $SIG{ALRM} = sub { $self->checkTime };

				ualarm UALARM_INTERVAL, UALARM_INTERVAL;
				$wdl = $tb->safeProbeWdl($position);
				ualarm 0;
			};
			if ($@) {
				# Time used up. Disable all tablebase lookups, and go on with
				# a regular search.
				$self_tb_cardinality = 0;
				ualarm 0;
			}

			if (defined $wdl) {
				if (DEBUG) {
					$self->indent($ply, "tablebase hit, WDL: $wdl\n");
				}

				++$self->{tb_hits};

				my $draw_score = $self->{tb_50_move_rule} ? 1 : 0;
				my $tb_value = VALUE_TB - $ply;

				my ($value, $tt_type);
				if ($wdl < -$draw_score) {
					$value = -$tb_value;
					$tt_type = BOUND_UPPER;
				} elsif ($wdl > $draw_score) {
					$value = $tb_value;
					$tt_type = BOUND_LOWER;
				} else {
					$value = DRAW + 2 * $wdl * $draw_score;
					$tt_type = BOUND_EXACT;
				}

				if ($tt_type == BOUND_EXACT
					|| ($tt_type == BOUND_LOWER ? $value >= $beta : $value <= $alpha)) {
					if (DEBUG) {
						my $tt_type_name = BOUND_TYPES->[$tt_type];
						my $tt_value = (do {	(($value >= VALUE_TB_WIN_IN_MAX_PLY) ? $value + $ply : ($value <= VALUE_TB_LOSS_IN_MAX_PLY) ? $value - $ply : $value);});
						$self->indent($ply, "store value $ply \@depth $depth with bound type $tt_type_name");
					}
					$tt->store(
						@tt_address, # Address.
						$signature, # Zobrist key.
						(do {	(($value >= VALUE_TB_WIN_IN_MAX_PLY) ? $value + $ply : ($value <= VALUE_TB_LOSS_IN_MAX_PLY) ? $value - $ply : $value);}), # score, mate distance adjusted.
						$tt_pvs->[-1], # PV flag.
						$tt_type, # BOUND_EXACT/TT_SCORE_ALPHA/TT_SCORE_LOWER.
						$depth, # Depth searched.
						0, # Best move at this node.
					);

					return $value;
				}

				if ($pv_node && $tt_type == BOUND_LOWER) {
					$best_value = $value;
					if (DEBUG && $best_value > $alpha) {
						$self->indent($ply, "raise alpha to tablebase value $best_value");
					}
					$alpha = ((($alpha) > ($best_value)) ? ($alpha) : ($best_value));
				}
			}
		}
	}
TB_PROBE_DONE:

	my @moves;
	my $pv_move;
	my $cutoff_moves = $self->{cutoff_moves}->[$position->[CP_POS_TO_MOVE]];
	if ($root_node) {
		@moves = @{$self->{ordered_root_moves}};
		$pv_move = $moves[0];
	} elsif ($depth >= 5) {
		# Use the same move ordering as for root moves.
		$pv_move = $pline->[$ply - 1] if @$pline >= $ply;
		@moves = $self->orderRootMoves($position, $depth, $ply, $pv_move, $position->pseudoLegalMoves);
	} else {
		@moves = $position->pseudoLegalMoves;

		$pv_move = $pline->[$ply - 1] if @$pline >= $ply;
		my (@pv, @tt, @promotions, @checks, @good_captures, @k1, @k2, @k3, @quiet, @bad_captures);
		my $killers = $self->{killers}->[$ply];
		my $k1 = $killers->[0];
		my $k2 = $killers->[1];
		my $k3 = $ply > 1 ? $self->{killers}->[$ply - 2]->[0] : 0;
		if ($depth >= 4) {
			# Light sorting.
			my %good_captures;
			foreach my $move (@moves) {
				if (((($move) & 0x1fffc0) == (($pv_move) & 0x1fffc0))) {
					push @pv, $move;
				} elsif (((($move) & 0x1fffc0) == (($tt_move) & 0x1fffc0))) {
					push @tt, $move;
				} elsif (my $promote = ((($move) >> 6) & 0x7)) {
					if ((GOOD_PROMO_MASK >> $promote) & 1) {
						push @promotions, $move;
					} else {
						push @quiet, $move;
					}
				} elsif (((($move) >> 3) & 0x7)) {
					my $see = $position->SEE($move);
					if ($see >= 0) {
						$good_captures{$move} = $position->SEE($move);
					} else {
						push @bad_captures, $move;
					}
				} elsif ($move == $k1) {
					$k1[0] = $move;
				} elsif ($move == $k2) {
					$k2[0] = $move;
				} elsif ($move == $k3) {
					$k3[0] = $move;
				} else {
					push @quiet, $move;
				}
			}
			@good_captures = sort { $good_captures{$b} <=> $good_captures{$a} || $b <=> $a } keys %good_captures;
			@bad_captures = sort { $mvv_lva[$b] <=> $mvv_lva[$a] || $b <=> $a } @bad_captures;
		} else {
			# Minimal sorting.
			foreach my $move (@moves) {
				if (((($move) & 0x1fffc0) == (($pv_move) & 0x1fffc0))) {
					push @pv, $move;
				} elsif (((($move) & 0x1fffc0) == (($tt_move) & 0x1fffc0))) {
					push @tt, $move;
				} elsif (my $promote = ((($move) >> 6) & 0x7)) {
					if ((GOOD_PROMO_MASK >> $promote) & 1) {
						push @promotions, $move;
					} else {
						push @quiet, $move;
					}
				} elsif (((($move) >> 3) & 0x7)) {
					push @good_captures, $move;
				} elsif ($move == $k1) {
					$k1[0] = $move;
				} elsif ($move == $k2) {
					$k2[0] = $move;
				} elsif ($move == $k3) {
					$k3[0] = $move;
				} else {
					push @quiet, $move;
				}
			}
			@good_captures = sort { $mvv_lva[$b & 0x3f] <=> $mvv_lva[$a & 0x3f] } @good_captures;
		}

		# Apply history bonus and malus to all quiet moves. We store the bonuses
		# in the upper 32 bits so that we can do a simple integer sort.
		foreach my $move (@quiet) {
			$move |= (($cutoff_moves->[($move & 0x1ffe00) >> 9]) << 32);
		}
		@quiet = sort { $b <=> $a } @quiet;

		@moves = (@pv, @tt, @promotions, @checks, @good_captures, @k1, @k2, @k3, @quiet, @bad_captures);
	}

	my $legal = 0;
	my $moveno = 0;
	my $pv_found;
	my $is_null_window = $beta - $alpha == 1;
	my $best_move = 0;
	my $print_current_move = $ply == 1 && $self->{print_current_move};
	my $signature_slot = $self->{history_length} + $ply;
	my @check_info = $position->inCheck;
	my @backup = @$position;
	foreach my $move (@moves) {
		next if !$position->checkPseudoLegalMove($move, @check_info);
		my @line;
		$position->move($move, 1);
		$signatures->[$signature_slot] = $position->[CP_POS_SIGNATURE];
		my $nodes_before = $self->{nodes}++;
		$self->printCurrentMove($move, $legal) if $print_current_move;
		my $score;
		if (DEBUG) {
			my $cn = $position->moveCoordinateNotation($move);
			$self->indent($ply, "move $cn: start search");
			push @{$self->{line}}, $cn;
		}
		if ($pv_found) {
			if (DEBUG) {
				$self->indent($ply, "null window search");
			}
			$score = -alphabeta($self, $ply + 1, $depth - 1,
				-$alpha - 1, -$alpha, \@line, NON_PV_NODE, 1, $tt_pvs, $min_probe_ply);
			if (($score > $alpha) && ($score < $beta)) {
				if (DEBUG) {
					$self->indent($ply, "value $score outside null window, re-search");
				}
				undef @line;
				$score = -alphabeta($self, $ply + 1, $depth - 1,
					-$beta, -$alpha, \@line, NON_PV_NODE, 0, $tt_pvs, $min_probe_ply);
			}
		} else {
			if (DEBUG) {
				$self->indent($ply, "recurse normal search");
			}
			$score = -alphabeta($self, $ply + 1, $depth - 1, -$beta, -$alpha,
				\@line, $pv_node ? PV_NODE : NON_PV_NODE, 0, $tt_pvs,
				$min_probe_ply);
		}

		++$legal;
		++$moveno;
		if (DEBUG) {
			my $cn = $position->moveCoordinateNotation($move);
			$self->indent($ply, "move $cn: value $score");
		}
		@$position = @backup;
		if (DEBUG) {
			pop @{$self->{line}};
		}
		my $root_move;
		if ($node_type == ROOT_NODE) {
			$root_move = $self->{root_moves}->{$move & 0xffff_ffff};
			# The constants for the time management are taken from Stockfish
			# and they use their own units which seem to be 3.5-4.0 centipawns.
			$root_move->{average_score} =
				$root_move->{average_score} != -INF ? ($score + $root_move->{average_score}) >> 1 : $score;
			$root_move->{mean_squared_score} = $root_move->{mean_squared_score} != -INF * INF
									? ($score * abs($score) + $root_move->{mean_squared_score}) / 2
									: $score * abs($score);
			$root_move->{move_effort} += $self->{nodes} - $nodes_before;
			++$self->{total_best_move_changes} if $score > $best_value && $score > $alpha && $legal > 1;
		}
		if ($score > $best_value) {
			$best_value = $score;

			if ($score > $alpha) {
				$best_move = $move;

				$pv_found = 1;
				@$pline = ($move, @line);
				if ($node_type == ROOT_NODE) {
					$self->{score} = $score;
					$self->printPV($pline);
				}

				if ($score < $beta) {
					$alpha = $score;
					if (DEBUG) {
						$self->indent($ply, "raise alpha to $alpha");
					}
				} else {
					if (DEBUG) {
						my $hex_sig = sprintf '%016x', $signature;
						my $cn = $position->moveCoordinateNotation($move);
						$self->indent($ply, "$cn fail high ($score >= $beta), store $score(BOUND_LOWER) \@depth $depth for $hex_sig");
					}

					# Quiet move failing high?
					if (!((($move) >> 3) & 0x7)
					    && !((($move) & 0x1fffc0) == (($pv_move) & 0x1fffc0))
						&& !((($move) & 0x1fffc0) == (($tt_move) & 0x1fffc0))) {
						if (DEBUG) {
							my $cn = $position->moveCoordinateNotation($move);
							$self->indent($ply, "$cn is quiet and becomes new killer move");
						}

						my $killers = $self->{killers}->[$ply];
						($killers->[0], $killers->[1]) = ($move, $killers->[0]);

						# The history bonus should only be given to real quiet
						# moves, not bad captures. Later, when we also give
						# maluses, we still want to give the malus to all
						# previously searched quiet moves.

						# This is the from and to square as one single integer.
						my $from_to = ($move & 0x1ffe00) >> 9;

						$cutoff_moves->[$from_to] += $depth * $depth;
						if (DEBUG) {
							my $bonus = $depth * $depth;
							my $cn = $position->moveCoordinateNotation($move);
							$self->indent($ply, "$cn is quiet and gets history bonus $bonus");
						}
					}

					last;
				}
			}
		}
	}

	my $tt_type;
	if (!$legal) {
		# Mate or stalemate.
		if ($position->inCheck) {
			$best_value = -MATE + $ply;
		} else {
			$best_value = DRAW;
		}
		$tt_type = BOUND_EXACT;
		$depth = (((MAX_PLY - 1) < ($depth + 6)) ? (MAX_PLY - 1) : ($depth + 6));
		if (DEBUG) {
			$self->indent($ply, "mate/stalemate, score: $best_value");
		}
	} elsif ($best_value >= $beta) {
		$tt_type = BOUND_LOWER;
	} elsif ($best_move && $pv_node) {
		$tt_type = BOUND_EXACT;
	} else {
		$tt_type = BOUND_UPPER;
	}

	if (DEBUG) {
		my $hex_sig = sprintf '%016x', $signature;
		if ($is_null_window && $tt_type == BOUND_EXACT) {
			$self->indent($ply, "returning best value $best_value without tt store for $hex_sig");
		} else {
			my $type;
			if ($tt_type == BOUND_UPPER) {
				$type = 'BOUND_UPPER';
			} elsif ($tt_type == BOUND_LOWER) {
				$type = 'BOUND_LOWER';
			} else {
				$type = 'BOUND_EXACT';
			}
			my $value = (do {	(($best_value >= VALUE_TB_WIN_IN_MAX_PLY) ? $best_value + $ply : ($best_value <= VALUE_TB_LOSS_IN_MAX_PLY) ? $best_value - $ply : $best_value);});
			$self->indent($ply, "returning best value $best_value, store $value ($type) \@depth $depth for $hex_sig");
		}
	}

	# Taken from Stockfish: If no good move is found and the previous position
	# was ttPv, then the previous opponent move is probably good and the new
	# position is added to the search tree.
	if ($best_value <= $alpha) {
		$tt_pvs->[-1] = $tt_pvs->[-1] || $tt_pvs->[-2];
	}

	$tt->store(
		@tt_address, # Address.
		$signature, # Zobrist key.
		(do {	(($best_value >= VALUE_TB_WIN_IN_MAX_PLY) ? $best_value + $ply : ($best_value <= VALUE_TB_LOSS_IN_MAX_PLY) ? $best_value - $ply : $best_value);}), # score, mate distance adjusted.
		$tt_pvs->[-1], # PV flag.
		$tt_type, # BOUND_EXACT/TT_SCORE_ALPHA/TT_SCORE_LOWER.
		$depth, # Depth searched.
		$best_move # Best move at this node.
	) if !($is_null_window && $tt_type == BOUND_EXACT);

	return $best_value;
}

sub quiesce {
	my ($self, $ply, $alpha, $beta, $node_type, $min_probe_ply) = @_;

	if ($self->{nodes} >= $self->{nodes_to_tc}) {
		$self->checkTime;
	}

	my $pv_node = $node_type != NON_PV_NODE;

	$self->{seldepth} = ((($ply) > ($self->{seldepth})) ? ($ply) : ($self->{seldepth}));

	my $position = $self->{position};

	if (DEBUG) {
		my $hex_signature = sprintf '%016x', $position->[CP_POS_SIGNATURE];
		my $line = join ' ', @{$self->{line}};
		my $fen = $position->fen;
		$self->indent($ply, "quiescence: alpha = $alpha, beta = $beta, line: $line,"
			. " sig: $hex_signature $fen");
	}

	# Expand the search, when in check.
	if ($position->inCheck) {
		if (DEBUG) {
			$self->indent($ply, "quiescence check extension");
		}

		return alphabeta($self, $ply, 1, $alpha, $beta, [], $node_type,
			!$pv_node && $beta == $alpha + 1, [$pv_node], $min_probe_ply);
	}

	my $tt = $self->{tt};
	my $signature = $position->[CP_POS_SIGNATURE];
	my ($tt_hit, $tt_depth, $tt_bound, $tt_move, $tt_value, $tt_eval,
		$tt_pv, @tt_address) = $tt->probe($signature);

	$tt_value = (do {	if ($tt_value >= VALUE_TB_WIN_IN_MAX_PLY) {		if ($tt_value >= MATE_IN_MAX_PLY && MATE - $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_WIN_IN_MAX_PLY - 1		} elsif (VALUE_TB_WIN_IN_MAX_PLY - $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_WIN_IN_MAX_PLY - 1		} else {			$tt_value - $ply;		}	} elsif ($tt_value <= VALUE_TB_LOSS_IN_MAX_PLY) {		if ($tt_value <= MATED_IN_MAX_PLY && MATE + $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_LOSS_IN_MAX_PLY + 1;		} elsif (VALUE_TB_LOSS_IN_MAX_PLY + $tt_value > 100 - $position->[CP_POS_HALFMOVE_CLOCK]) {			VALUE_TB_LOSS_IN_MAX_PLY + 1;		} else {			$tt_value + $ply;		}	} else {		$tt_value;	}})
		if $tt_hit && defined $tt_value;

	# Early TT cutoff.
	if (!$pv_node && $tt_depth >= DEPTH_QUIESCENCE
	    && defined $tt_value
	    && ($tt_bound & ($tt_value >= $beta ? BOUND_LOWER : BOUND_UPPER))) {
		return $tt_value;
	}

	# Compute best available stand-pat value
	my $best_value = $position->evaluate;
	if (DEBUG) {
		$self->indent($ply, "static evaluation: $best_value");
	}

	if ($best_value >= $beta) {
		# FIXME! Reduce branching here!
		if (DEBUG) {
			my $hex_sig = sprintf '%016x', $signature;
			if ($tt_hit) {
				$self->indent($ply, "quiescence standing pat ($best_value >= $beta) without tt store for $hex_sig");
			} else {
				$self->indent($ply, "quiescence standing pat ($best_value >= $beta), store no move value $best_value (BOUND_LOWER) \@depth 0 for $hex_sig");
			}
		}

		if (!$tt_hit) {
			$tt->store(
				@tt_address,
				$signature,
				(do {	(($best_value >= VALUE_TB_WIN_IN_MAX_PLY) ? $best_value + $ply : ($best_value <= VALUE_TB_LOSS_IN_MAX_PLY) ? $best_value - $ply : $best_value);}),
				0,
				BOUND_LOWER,
				DEPTH_UNSEARCHED,
				0,
			);
		} else {
			$best_value = $tt_value;
		}

		return $best_value;
	}

	if ($best_value > $alpha) {
		$alpha = $best_value;
	}

	# FIXME! Jump over all the expensive stuff following if there are no
	# moves.
	my @moves = $position->pseudoLegalAttacks; # or goto SKIP_MOVE_LOOP ...

	my (@tt, @promotions, @checks, %captures);
	foreach my $move (@moves) {
		if (((($move) & 0x1fffc0) == (($tt_move) & 0x1fffc0))) {
			push @tt, $move;
		} elsif ((GOOD_PROMO_MASK >> (((($move) >> 6) & 0x7))) & 1) {
			# Skip underpromotions in quiescence.
			push @promotions, $move;
		} elsif ($position->moveGivesCheck($move)) { # FIXME! Too expensive?
			push @checks, $move;
		} else {
			# info depth 10 seldepth 37 score cp 6 nodes 46676164 nps 39461 hashfull 500 tbhits 0 time 1182841 pv e2e4 e7e5 c2c3 g8f6 g1f3 f8e7 f3e5 f6e4 d1g4 e4g5
			# bestmove e2e4 ponder e7e5

			my $see = $position->SEE($move);
			$captures{$move} = $see if $see >= -80;
		}
	}

	my @captures = sort { $captures{$b} <=> $captures{$a} || $b <=> $a } keys %captures;
	@moves = (@tt, @promotions, @checks, @captures);

	my $signatures = $self->{signatures};
	my $signature_slot = $self->{history_length} + $ply;
	my @check_info = $position->inCheck;
	my @backup = @$position;

	my $legal = 0;
	my $best_move = 0;
	foreach my $move (@moves) {
		next if !$position->checkPseudoLegalMove($move, @check_info);
		$position->move($move, 1);
		$signatures->[$signature_slot] = $position->[CP_POS_SIGNATURE];
		if (DEBUG) {
			my $cn = $position->moveCoordinateNotation($move);
			push @{$self->{line}}, $cn;
			$self->indent($ply, "move $cn: start quiescence search");
		}
		++$self->{nodes};
		++$legal;
		if (DEBUG) {
			$self->indent($ply, "recurse quiescence search");
		}
		my $score = -quiesce($self, $ply + 1, -$beta, -$alpha, $node_type, $min_probe_ply);
		if (DEBUG) {
			my $cn = $position->moveCoordinateNotation($move);
			$self->indent($ply, "move $cn: value: $score");
			pop @{$self->{line}};
		}
		@$position = @backup;
		if ($score > $best_value) {
			$best_value = $score;

			if ($score > $alpha) {
				$best_move = $move;

				if ($score < $beta) {
					if (DEBUG) {
						$self->indent($ply, "raise quiescence alpha to $alpha");
					}
					$alpha = $score;
				} else {
					if (DEBUG) {
						my $hex_sig = sprintf '%016x', $signature;
						my $cn = $position->moveCoordinateNotation($move);
						$self->indent($ply, "$cn quiescence fail high ($score >= $beta), store $score (BOUND_LOWER) \@depth 0 for $hex_sig");
					}

					last;
				}
			}
		}
	}

	my $tt_type = $best_value >= $beta ? BOUND_LOWER : BOUND_UPPER;
	$tt->store(
		@tt_address, # Address.
		$signature, # Zobrist key.
		(do {	(($best_value >= VALUE_TB_WIN_IN_MAX_PLY) ? $best_value + $ply : ($best_value <= VALUE_TB_LOSS_IN_MAX_PLY) ? $best_value - $ply : $best_value);}), # score, mate distance adjusted.
		$tt_hit && $tt_pv, # PV flag.
		$tt_type, # BOUND_EXACT/BOUND_UPPER/BOUND_LOWER.
		DEPTH_QUIESCENCE, # Depth searched.
		$best_move # Best move at this node.
	);
	if (DEBUG) {
		my $hex_sig = sprintf '%016x', $signature;
		my $type = BOUND_TYPES->[$tt_type];
		$self->indent($ply, "returning best value (quiescence) $best_value, store ($type) for $hex_sig");
	}

	return $best_value;
}

sub rootSearch {
	my ($self, $pline) = @_;

	$self->{nodes} = 0;

	my $position = $self->{position};

	my $max_depth = $self->{max_depth} || (MAX_PLY - 1);
	my $depth = $self->{depth} = 0;
	$self->{seldepth} = 0;
	my $score = $self->{score} = 0;

	my @line = @$pline;

	my $last_best_move;
	my $last_best_move_depth = 0;
	my $search_again_counter = 0;

	# Checking whether a tablebase probe is elegible is relatively expensive.
	# But we know that the popcount of the position can only decrease by
	# one per ply. So we pre-calculate the minimum ply, where a probe makes
	# sense. If we hit that limit during the search, we re-evaluate.  That
	# reduces the number of checks to a minimum. If we have a cardinality
	# of 7, then we start even thinking a about probing at ply 26.
	my $min_probe_ply = INF;
	if ($self_tb_cardinality > 2) {
		my $popcount = $position->[CP_POS_POPCOUNT];
		if ($popcount <= $self_tb_cardinality) {
			$min_probe_ply = 2;
		} else {
			$min_probe_ply = 1 + $popcount - $self_tb_cardinality;
		}
	}

	if (DEBUG) {
		my $fen = $position->toFEN;
		$self->debug("Searching $fen");
	}
	eval {
		DEEPENING: while (++$depth <= $max_depth) {
			no integer;

			# When the aspiration window changes, the PV move should be
			# brought to the front and the order of all other moves must
			# remain unchanged. Therefore, we order the moves just once.
			$self->{ordered_root_moves} = [$self->orderRootMoves($position, 1, $depth, 0, keys %{$self->{root_moves}})];

			# Age out instability.
			$self->{total_best_move_changes} /= 2;

			$self->{depth} = $depth;
			if (DEBUG) {
				$self->debug("Deepening to depth $depth");
				$self->{line} = [];
			}

			# The upper 32 bits of the move can be abused by the move ordering.
			my $pv_move = $line[0] & 0xffff_ffff;
			my $delta = 15 + abs $self->{root_moves}->{$pv_move}->{mean_squared_score} / 8000;
			my $avg = $self->{root_moves}->{$pv_move}->{average_score};
#stop "pv mean squared score: $self->{root_moves}->{$pv_move}->{mean_squared_score}\n";
#warn "pv average score: $self->{root_moves}->{$pv_move}->{average_score}\n";
			my $alpha = int(((($avg - $delta) > (-INF)) ? ($avg - $delta) : (-INF)));
			my $beta = int(((($avg + $delta) < (+INF)) ? ($avg + $delta) : (+INF)));

			my $failed_high_count = 0;
			while (1) {
				my @tt_pvs;
#warn "DEPTH $depth: delta: $delta avg: $avg alpha: $alpha beta: $beta\n";
				$score = $self->alphabeta(1, $depth, $alpha, $beta, \@line,
					ROOT_NODE, 0, \@tt_pvs, $min_probe_ply);
#warn "  best value: $score\n";
				if (DEBUG) {
					$self->debug("Score at depth $depth: $score");
				}

				# Mate found?
				if (($score >= MATE - $depth) || ($score <= -(MATE - $depth))) {
					$self->printPV(\@line);
					last DEEPENING;
				}

				if ($score <= $alpha) {
					$beta = $alpha;
					$alpha = int(((($score - $delta) > (-INF)) ? ($score - $delta) : (-INF)));
					if (DEBUG) {
						$self->debug("Failed low, must re-search with alpha=$alpha and beta=$beta");
					}
					$failed_high_count = 0;
				} elsif ($score >= $beta) {
					$alpha = int(((($beta - $delta) > ($alpha)) ? ($beta - $delta) : ($alpha)));
					$beta = int(((($score + $delta) < (+INF)) ? ($score + $delta) : (+INF)));
					if (DEBUG) {
						$self->debug("Failed high, must re-search with alpha=$alpha and beta=$beta");
					}
					++$failed_high_count;
				} else {
					last;
				}
				
				$delta *= 5;

				# Bring the PV move to the front and keep the rest of the
				# move ordering unchanged.
				my $pv_move = $line[0];
				$self->{ordered_root_moves} = [
					$pv_move,
					grep { $_ ne $pv_move } @{$self->{ordered_root_moves}},
				];
			}

			$self->printPV(\@line);

			if ($self->{use_time_management}) {
				my $iter_idx = ($depth - 1) & 3;

				no integer;

				my $root_move = $self->{root_moves}->{$line[0] & 0xffff_ffff};
				my $best_value = $root_move->{score};

				my $falling_eval = (11.85 + 2.24 * ($root_move->{previous_average_score} - $best_value)
					+ 0.93 * ($self->{iter_scores}->[$iter_idx] - $best_value)) / 100.0;
				$falling_eval = ($falling_eval) < (0.57) ? (0.57) : ($falling_eval) > (1.70) ? (1.70) : ($falling_eval);
				if ($line[0] != $last_best_move) {
					$last_best_move_depth = $depth;
				}

				# Idea: Try to move the center based on the average completed
				# search depth. But the average search depth should probably
				# be aged out.
				# my $depth_scale = $self->{avg_completed_depth} / 40.0; # SF baseline
				# my $center = $last_best_move_depth + 12.15 * $depth_scale;
				my $k = 0.51; # FIXME! Lower that to 0.25?
				my $center = $last_best_move_depth + 12.15; # Divide by 4.25?

				my $time_reduction = 0.66 + 0.85 / (0.98 + exp(-$k * ($depth - $center)));
				my $reduction = (1.43 + $self->{previous_time_reduction}) / (2.28 * $time_reduction);
				my $best_move_instability = 1.02 + 2.14 * $self->{total_best_move_changes};
				my $nodes_effort = $root_move->{move_effort} * 100000 / (((1) > ($self->{nodes})) ? (1) : ($self->{nodes}));

				# The original value is 93340. But we will not reach that
				# with only safe prunings.
				my $high_best_move_effort = $nodes_effort >= 30000 ? 0.76 : 1.0;

				my $total_time = $self->{optimum} * $falling_eval * $reduction
					* $best_move_instability * $high_best_move_effort;

				my $elapsed = 1000 * tv_interval($self->{start_time});

				if ($elapsed > ((($total_time) < ($self->{maximum})) ? ($total_time) : ($self->{maximum}))) {
					if (!$self->{ponder}) {
						last;
					}
				} elsif (!$self->{ponder}) {
					# Stockfish sets the threshold to half of the total time.
					# We use less.
					#
					# Current best: 0.25
					# Values 0.5, 0.375, 0.3125 all failed SPRTs.
					if ($elapsed > $total_time >> 2) {
						last;
					}
				}

				$root_move->{previous_average_score} = $root_move->{average_score};
				$self->{previous_time_reduction} = $time_reduction;
				$last_best_move = $line[0];
				$self->{iter_scores}->[$iter_idx] = $best_value;
			}
		}
	};
	if ($@) {
		if ($@ ne "PLISCO_ABORTED\n") {
			$self->{info}->(__"Error: exception raised: $@");
		}
		$self->printPV(\@line);
	}

	if ($self->{maximum} && !$self->{ponderhit}) {
		my $elapsed = 1000 * tv_interval($self->{start_time});
		if ($elapsed > $self->{maximum}) {
			$self->{info}->(__"Error: used $elapsed ms instead of $self->{maximum} ms.");
		}
	}

	@$pline = @line;
}

sub orderRootMoves {
	my ($self, $position, $depth, $ply, $pv_move, @moves) = @_;

	if ($self->{tb_hit} && $depth == 1) {
		my $root_moves = $self->{root_moves};
		@moves = sort { $root_moves->{$a} <=> $root_moves->{$b} } @moves;
	}

	my $signature = $self->{position}->[CP_POS_SIGNATURE];

	my $tt = $self->{tt};
	my ($tt_hit, $tt_depth, $tt_bound, $tt_move, $tt_value, $tt_eval,
		$tt_pv, @tt_address) = $tt->probe($signature);
	$tt_move = 0 if !$tt_hit;

	my (@pv, @tt, @promotions, @checks, @good_captures, @k1, @k2, @k3, @quiet, @bad_captures);
	my $killers = $self->{killers}->[$ply];
	my $k1 = $killers->[0];
	my $k2 = $killers->[1];
	my $k3 = $ply > 1 ? $self->{killers}->[$ply - 2]->[0] : 0;

	my %good_captures;
	foreach my $move (@moves) {
		if (((($move) & 0x1fffc0) == (($pv_move) & 0x1fffc0))) {
			push @tt, $move;
		} elsif (((($move) & 0x1fffc0) == (($tt_move) & 0x1fffc0))) {
			push @tt, $move;
		} elsif (my $promote = ((($move) >> 6) & 0x7)) {
			if ((GOOD_PROMO_MASK >> $promote) & 1) {
				push @promotions, $move;
			} else {
				push @quiet, $move;
			}
		} elsif ($position->moveGivesCheck($move)) {
			push @checks, $move;
		} elsif (((($move) >> 3) & 0x7)) {
			my $see = $position->SEE($move);
			if ($see >= 0) {
				$good_captures{$move} = $position->SEE($move);
			} else {
				push @bad_captures, $move;
			}
		} elsif ($move == $k1) {
			$k1[0] = $move;
		} elsif ($move == $k2) {
			$k2[0] = $move;
		} elsif ($move == $k3) {
			$k3[0] = $move;
		} else {
			push @quiet, $move;
		}
	}
	@good_captures = sort { $good_captures{$b} <=> $good_captures{$a} || $b <=> $a } keys %good_captures;
	@bad_captures = sort { $mvv_lva[$b] <=> $mvv_lva[$a] || $b <=> $a } @bad_captures;

	# Apply history bonus and malus to all quiet moves. We store the bonuses
	# in the upper 32 bits so that we can do a simple integer sort.
	my $cutoff_moves = $self->{cutoff_moves}->[$position->[CP_POS_TO_MOVE]];
	foreach my $move (@quiet) {
		$move |= (($cutoff_moves->[($move & 0x1ffe00) >> 9]) << 32);
	}
	@quiet = sort { $b <=> $a } @quiet;

	return @pv, @tt, @promotions, @checks, @good_captures, @k1, @k2, @k3, @quiet, @bad_captures;
}



sub outputMoveEfforts {
	my ($self) = @_;

	my $sum = 0;
	my $all_nodes = $self->{nodes} || 1;
	foreach my $move (sort { $self->{root_moves}->{$b}->{move_effort} <=> $self->{root_moves}->{$a}->{move_effort} } keys %{$self->{root_moves}}) {
		my $san = $self->{position}->SAN($move);
		my $nodes = $self->{root_moves}->{$move}->{move_effort};
		$sum += $nodes;
		my $effort = int(0.5 + (100000 * $nodes / $all_nodes));
		$self->{info}->("Move effort $san: $effort ($nodes nodes)");
	}

	$self->{info}->("Move effort sum: $sum");
}

sub printCurrentMove {
	my ($self, $move, $moveno) = @_;

	no integer;

	my $position = $self->{position};
	my $cn = $position->moveCoordinateNotation($move);
	my $elapsed = int(1000 * tv_interval($self->{start_time}));

	$moveno += 1;
	$self->{info}->("depth $self->{depth} currmove $cn currmovenumber $moveno"
		. " time $elapsed");
}

sub think {
	my ($self) = @_;

	my $position = $self->{position};
	$self->{start} = $position->copy;
	my @legal = $position->legalMoves;
	if (!@legal) {
		my $state = $position->gameOver;
		if ($state & (CP_GAME_WHITE_WINS | CP_GAME_BLACK_WINS)) {
			$self->{info}->("depth 0 score mate 0");
		} else {
			$self->{info}->("depth 0 score cp 0");
		}

		return;
	} elsif (1 == @legal) {
		# This is not good if in ponder mode because there will be no move to
		# ponder on. Try to at least get a ponder move from the transposition
		# table.
		my $move = $legal[0];
		$self->{depth} = 1;
		$self->printCurrentMove($move, 1);
		my $ponder_move = $self->getPonderMove($move);
		$self->printPV([$move]);

		if (defined $ponder_move) {
			return $move, $ponder_move;
		} else {
			return $move;
		}
	}

	# Initialize root moves.
	foreach my $move (@legal) {
		$self->{root_moves}->{$move} = {
			move_effort => 0,
			score => -INF,
			previous_score => -INF,
			average_score => -INF,
			previous_average_score => -INF,
			mean_squared_score => -INF * INF,
		};
	}

	if ($self->{book} && $self->{history_length} < $self->{book_depth}) {
		my $notation = $self->{book}->pickMove($position);
		if ($notation) {
			my $move = eval { $position->parseMove($notation) };
			return $move if $move;
		}
	}

	# Check for tablebase hit.
	eval {
		local $SIG{ALRM} = sub { $self->checkTime };
		ualarm UALARM_INTERVAL, UALARM_INTERVAL;
		$self->tbRankRootMoves;
		ualarm 0;
	};
	if ($@) {
		ualarm 0;
	}

	# There must always be a valid move in the line.
	my @line = ($legal[0]);

	$self->{thinking} = 1;
	$self->{tt_hits} = 0;
	$self->{tt}->newSearch;

	if ($self->{debug}) {
		$self->{info}->("allocated time: $self->{optimum} .. $self->{maximum}");
	}

	$self->rootSearch(\@line);

	delete $self->{thinking};

	# Avoid printing the PV or the best move if the search was cancelled.
	# Otherwise, the GUI may be confused.  This can actually not happen
	# when everybody follows the convention that a "stop" is sent to
	# cancel a ponder but we reset the flag in the "stop" handler and are
	# good for both cases.
	return if $self->{cancelled};

	if (!@line) {
		# Search has returned no move.
		$self->{info}->("Error: pick a random move because of search failure.");
		$line[0] = $legal[int rand @legal];
	}

	my $ponder_move = $self->getPonderMove(@line);

	if (defined $ponder_move) {
		return $line[0], $line[1];
	} else {
		return $line[0];
	}
}

sub getPonderMove {
	my ($self, @line) = @_;

	return if !@line;

	return $line[1] if @line > 1;

	my $pos = $self->{position}->copy;

	# Play our move.
	$pos->move($line[0]);

	# And now try to find an entry in the transposition table.
	my $signature = $pos->[CP_POS_SIGNATURE];
	my $tt = $self->{tt};
	my $tt_move;
	if (DEBUG) {
		$self->debug("probing transposition table for ponder move");
	}
	# We're not interested in the value.
	$tt->probe($signature, 1, MAX_PLY + 1, 0, 0, \$tt_move);

	if (DEBUG) {
		if ($tt_move) {
			my $cn = $pos->moveCoordinateNotation($tt_move);
			#$self->debug("best move: $cn");
		}
	}

	return $tt_move if $tt_move;
}

sub tbRankRootMoves {
	my ($self) = @_;

	my $tb = $self->{tb};

	$self->{tb_root_hit} = 0;

	# Limit probe depth to 0 if table piece count too small.
	if ($self->{tb_probe_limit} > $self->{tb_cardinality}) {
		$self->{tb_probe_limit} = $self->{tb_cardinality};
		$self->{tb_probe_depth} = 0;
	}

	my $pos = $self->{position};
	
	if (!$pos->[CP_POS_CASTLING_RIGHTS]
	    && $pos->[CP_POS_POPCOUNT] <= $self->{tb_probe_limit}) {
		if ($self->{use_time_management}) {
			my $min_time = $self->{tb_7} / (5 ^ (7 - $pos->[CP_POS_POPCOUNT]));
			if ($min_time < $self->{tb_3}) {
				$min_time = $self->{tb_3};
			}
			if ($self->{maximum} < $min_time) {
				return;
			}
		}
		$self->{tb_root_hit} = 1 if $self->tbRootProbe;
	}
}

sub tbRootProbe {
	my ($self) = @_;

	my $pos = $self->{position}->copy;

	my $tb = $self->{tb};

	# Probe for the outcome of the game.
	my $wdl = $tb->safeProbeWdl($pos);
	return if !defined $wdl;

	my $wdl_sign = $wdl <=> 0;

	# First determine whether we are winning or losing.
	my $winning = $wdl > 0;
	if ($wdl == 0) {
		# In case of a draw, we want to escape there, if we are behind.
		my $static_eval = $pos->evaluate;
		$winning = $static_eval > 0;
	}

	# Pass 1. Probe all root moves.
	my $root_moves = $self->{root_moves};
	my @backup = @$pos;

	# Pass 2.
	# Discard all moves that change the outcome of the game.  They cannot be
	# part of the PV.
	#
	# We want to order the moves by the DTZ of the position after the move
	# has been made.
	foreach my $move (keys %{$root_moves}) {
my $san = $pos->SAN($move);
		$pos->move($move);

		$root_moves->{$move}->{tb_wdl} = $tb->safeProbeWdl($pos);
		# We consider a missing WDL table a configuration error.
		return if !defined $root_moves->{$move}->{tb_wdl};

		if (($root_moves->{$move}->{tb_wdl} <=> 0) != -$wdl_sign) {
			delete $root_moves->{$move};
			goto UNDO_MOVE;
		}

		# First, get mate, stalemate out of the way.
		my @legal = $pos->legalMoves;
		if (!@legal) {
			if ($pos->inCheck || !$winning) {
				# We have found a mate or stalemate. Our single-threaded
				# engine cannot be used for multiPV analysys. We can just as
				# well bypass the search altogether and return the winning
				# move.
				$self->{root_moves} = { $move => $self->{root_moves}->{$move} };
				return;
			} else {
				# A stalemate but we are winning.
				delete $root_moves->{$move};
				goto UNDO_MOVE;
			}
		}

		# Does the move result in a draw by insufficient material?
		if ($pos->insufficientMaterial) {
			if ($winning) {
				# Don't even consider that move.
				delete $root_moves->{$move};
				goto UNDO_MOVE;
			} else {
				# Force playing this move.
				$self->{root_moves} = { $move => $self->{root_moves}->{$move} };
				return;
			}
		}

		# If the probe fails, treat all moves equally.
		my $dtz = -$tb->safeProbeDtz($pos) // 0;

		# We will later try to filter out moves that will result in an
		# unwanted draw by the 50-move-rule.
		my $hmc = $pos->[CP_POS_HALFMOVE_CLOCK];

		if ($hmc == 0) {
			# The current move is a pawn push or capture. The DTZ is that
			# of the next endgame phase. Compared to the DTZ before that, it will
			# make a leap and is useless for move ordering. Therefore, we
			# use the DTZ from the position before which was -1, -101, 0, 1, or
			# 101. However, the position before hasn't been probed. But we can
			# deduce the value from the WDL:
			$dtz = (-67 * $wdl * $wdl * $wdl + 269 * $wdl) >> 1;
		} else {
			# We have already handled stalemate and draw because of
			# insufficient material. Now check the draws that have to be
			# claimed.

			# Check for draw by 3-fold repetition. Unlike the normal
			# search, the root search has to check that this is really
			# the 3rd repetition.
			my $signature = $pos->[CP_POS_SIGNATURE];
			my @repetitions = grep { $_ == $signature } @{$self->{signatures}};
			my $is_draw = @repetitions > 1;

			$root_moves->{$move}->{tb_draw} = $is_draw;

			# Correct the DTZ by 1 and apply the offset for the first move.
			if ($dtz > 0) {
				++$dtz;
			} elsif ($dtz < 0) {
				--$dtz;
			}

			if ($is_draw) {
				$root_moves->{$move}->{tb_rank} = 0;
			} else {
				$root_moves->{$move}->{tb_rank} = $dtz;
			}
		}

		$root_moves->{$move}->{tb_abs_dtz} = $hmc - 1 + abs $dtz;

		UNDO_MOVE: @$pos = @backup; # Undo move.
	}

	if ($self->{tb_50_move_rule}) {
		my (@drawing, @rule50, @other);
		foreach my $move (keys %{$root_moves}) {
			my $root_move = $root_moves->{$move};

			if ($root_move->{tb_rank} == 0) {
				push @drawing, $move;
			} elsif ($root_move->{tb_abs_dtz} > 100) {
				push @rule50, $move;
			} else {
				push @other, $move;
			}
		}

		if ($winning) {
			if (@other) {
				delete @{$root_moves}{@drawing};
				delete @{$root_moves}{@rule50};
			} else {
				my $min_dtz = min map { $root_moves->{$_}->{tb_abs_dtz }} keys %$root_moves;
				foreach my $move (keys %$root_moves) {
					delete $root_moves->{$move} if $root_moves->{$move}->{tb_abs_dtz} != $min_dtz;
				}
			}
		} else {
			if (@drawing) {
				delete @{$root_moves}{@other};
			}
		}
		$self->{tb_outcome} = $wdl == WDL_WIN ? 20000 : $wdl == WDL_LOSS ? -20000 : 0;
	} else {
		my (@drawing, @other) = @_;

		foreach my $move (keys %{$root_moves}) {
			my $root_move = $root_moves->{$move};
			if ($root_move->{tb_rank} == 0) {
				push @drawing, $move;
			} else {
				push @other, $move;
			}
		}

		if ($winning) {
			if (@other) {
				delete @{$root_moves}{@drawing};
			}
		} else {
			if (@drawing) {
				delete @{$root_moves}{@other};
			}
		}
		$self->{tb_outcome} = 20000 * $wdl_sign;
	}

	return $self;
}

# Fill the lookup table for the move values.
foreach my $mover (CP_PAWN .. CP_KING) {
	my @piece_values = (
		0,
		CP_PAWN_VALUE,
		CP_KNIGHT_VALUE,
		CP_BISHOP_VALUE,
		CP_ROOK_VALUE,
		CP_QUEEN_VALUE,
	);

	foreach my $victim (CP_NO_PIECE, CP_PAWN .. CP_QUEEN) {
		my $index = ($victim << 6) | ($mover << 3);
		my $value = $victim ? ($piece_values[$victim] - $mover) : 0;
		$move_values[$index] = $value;
		my $key = (CP_PIECE_CHARS->[0]->[$victim] || ' ') . CP_PIECE_CHARS->[0]->[$mover];
		if ($mover == CP_PAWN) {
			foreach my $promote (CP_KNIGHT .. CP_QUEEN) {
				$move_values[$index | $promote]
					= $value + $piece_values[$promote] - CP_PAWN_VALUE;
				my $pc = CP_PIECE_CHARS->[0]->[$promote];
				my $pvalue = $value + $piece_values[$promote] - CP_PAWN_VALUE;
			}
		}
	}
}

my @mvv_lva_values = (
	0, CP_PAWN_VALUE, CP_KNIGHT_VALUE, CP_BISHOP_VALUE,
	CP_ROOK_VALUE, CP_QUEEN_VALUE, 2 * CP_QUEEN_VALUE,
);
foreach my $victim (CP_PAWN .. CP_QUEEN) {
	foreach my $attacker (CP_PAWN .. CP_KING) {
		$mvv_lva[($victim << 3) | $attacker] =
			100 * $mvv_lva_values[$victim] - $mvv_lva_values[$attacker];
		my $idx = ($victim << 3) | $attacker;
	}
}

1;
