|
|
""" |
|
|
Adaptive Time Management |
|
|
Research: Stockfish time management algorithm |
|
|
|
|
|
Key Concepts: |
|
|
- Time allocation based on game phase |
|
|
- Emergency time reserve |
|
|
- Increment handling |
|
|
- Move overhead compensation |
|
|
""" |
|
|
|
|
|
import time |
|
|
from typing import Optional |
|
|
|
|
|
|
|
|
class TimeManager: |
|
|
""" |
|
|
Smart time allocation for searches |
|
|
Adapts based on position complexity and game phase |
|
|
""" |
|
|
|
|
|
def __init__(self): |
|
|
self.start_time = 0.0 |
|
|
self.allocated_time = 0.0 |
|
|
self.hard_limit = 0.0 |
|
|
self.move_overhead = 0.050 |
|
|
|
|
|
def allocate_time( |
|
|
self, |
|
|
time_left: float, |
|
|
increment: float = 0.0, |
|
|
moves_to_go: Optional[int] = None, |
|
|
move_number: int = 1 |
|
|
) -> tuple[float, float]: |
|
|
""" |
|
|
Calculate time allocation for this move |
|
|
|
|
|
Args: |
|
|
time_left: Remaining time in seconds |
|
|
increment: Time increment per move |
|
|
moves_to_go: Expected moves until time control |
|
|
move_number: Current move number |
|
|
|
|
|
Returns: |
|
|
(soft_limit, hard_limit) in seconds |
|
|
""" |
|
|
|
|
|
emergency_reserve = min(2.0, time_left * 0.1) |
|
|
available_time = max(0, time_left - emergency_reserve - self.move_overhead) |
|
|
|
|
|
|
|
|
if moves_to_go: |
|
|
expected_moves = moves_to_go |
|
|
else: |
|
|
|
|
|
expected_moves = max(20, 40 - move_number // 2) |
|
|
|
|
|
|
|
|
base_time = available_time / expected_moves |
|
|
increment_bonus = increment * 0.8 |
|
|
|
|
|
|
|
|
soft_limit = base_time + increment_bonus |
|
|
|
|
|
|
|
|
hard_limit = min( |
|
|
soft_limit * 3.0, |
|
|
available_time * 0.5 |
|
|
) |
|
|
|
|
|
|
|
|
soft_limit = max(soft_limit, 0.1) |
|
|
hard_limit = max(hard_limit, soft_limit) |
|
|
|
|
|
return soft_limit, hard_limit |
|
|
|
|
|
def start_search(self, allocated_time: float, hard_limit: float): |
|
|
"""Initialize search timer""" |
|
|
self.start_time = time.time() |
|
|
self.allocated_time = allocated_time |
|
|
self.hard_limit = hard_limit |
|
|
|
|
|
def should_stop(self, depth: int, best_move_stable: bool = False) -> bool: |
|
|
""" |
|
|
Check if search should stop |
|
|
|
|
|
Args: |
|
|
depth: Current search depth |
|
|
best_move_stable: True if best move hasn't changed recently |
|
|
|
|
|
Returns: |
|
|
True if should stop search |
|
|
""" |
|
|
elapsed = time.time() - self.start_time |
|
|
|
|
|
|
|
|
if elapsed >= self.hard_limit: |
|
|
return True |
|
|
|
|
|
|
|
|
if elapsed >= self.allocated_time: |
|
|
if best_move_stable or depth >= 4: |
|
|
return True |
|
|
|
|
|
return False |
|
|
|
|
|
def elapsed(self) -> float: |
|
|
"""Get elapsed search time""" |
|
|
return time.time() - self.start_time |
|
|
|
|
|
def remaining(self) -> float: |
|
|
"""Get remaining time in soft limit""" |
|
|
return max(0, self.allocated_time - self.elapsed()) |