Rafs-an09002 commited on
Commit
5950ee5
·
verified ·
1 Parent(s): e3e5e3d

Create engine/time_manager.py

Browse files
Files changed (1) hide show
  1. engine/time_manager.py +113 -0
engine/time_manager.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Adaptive Time Management
3
+ Research: Stockfish time management algorithm
4
+
5
+ Key Concepts:
6
+ - Time allocation based on game phase
7
+ - Emergency time reserve
8
+ - Increment handling
9
+ - Move overhead compensation
10
+ """
11
+
12
+ import time
13
+ from typing import Optional
14
+
15
+
16
+ class TimeManager:
17
+ """
18
+ Smart time allocation for searches
19
+ Adapts based on position complexity and game phase
20
+ """
21
+
22
+ def __init__(self):
23
+ self.start_time = 0.0
24
+ self.allocated_time = 0.0
25
+ self.hard_limit = 0.0
26
+ self.move_overhead = 0.050 # 50ms network/processing overhead
27
+
28
+ def allocate_time(
29
+ self,
30
+ time_left: float,
31
+ increment: float = 0.0,
32
+ moves_to_go: Optional[int] = None,
33
+ move_number: int = 1
34
+ ) -> tuple[float, float]:
35
+ """
36
+ Calculate time allocation for this move
37
+
38
+ Args:
39
+ time_left: Remaining time in seconds
40
+ increment: Time increment per move
41
+ moves_to_go: Expected moves until time control
42
+ move_number: Current move number
43
+
44
+ Returns:
45
+ (soft_limit, hard_limit) in seconds
46
+ """
47
+ # Emergency time reserve (never go below 2 seconds)
48
+ emergency_reserve = min(2.0, time_left * 0.1)
49
+ available_time = max(0, time_left - emergency_reserve - self.move_overhead)
50
+
51
+ # Estimate moves remaining
52
+ if moves_to_go:
53
+ expected_moves = moves_to_go
54
+ else:
55
+ # Typical game length: 40 moves per side
56
+ expected_moves = max(20, 40 - move_number // 2)
57
+
58
+ # Base allocation with increment
59
+ base_time = available_time / expected_moves
60
+ increment_bonus = increment * 0.8 # Use 80% of increment
61
+
62
+ # Soft limit (normal search time)
63
+ soft_limit = base_time + increment_bonus
64
+
65
+ # Hard limit (absolute maximum, emergency situations)
66
+ hard_limit = min(
67
+ soft_limit * 3.0, # Up to 3x soft limit
68
+ available_time * 0.5 # But never more than 50% of remaining time
69
+ )
70
+
71
+ # Ensure minimums
72
+ soft_limit = max(soft_limit, 0.1) # At least 100ms
73
+ hard_limit = max(hard_limit, soft_limit)
74
+
75
+ return soft_limit, hard_limit
76
+
77
+ def start_search(self, allocated_time: float, hard_limit: float):
78
+ """Initialize search timer"""
79
+ self.start_time = time.time()
80
+ self.allocated_time = allocated_time
81
+ self.hard_limit = hard_limit
82
+
83
+ def should_stop(self, depth: int, best_move_stable: bool = False) -> bool:
84
+ """
85
+ Check if search should stop
86
+
87
+ Args:
88
+ depth: Current search depth
89
+ best_move_stable: True if best move hasn't changed recently
90
+
91
+ Returns:
92
+ True if should stop search
93
+ """
94
+ elapsed = time.time() - self.start_time
95
+
96
+ # Hard limit exceeded
97
+ if elapsed >= self.hard_limit:
98
+ return True
99
+
100
+ # Soft limit exceeded with stable best move
101
+ if elapsed >= self.allocated_time:
102
+ if best_move_stable or depth >= 4:
103
+ return True
104
+
105
+ return False
106
+
107
+ def elapsed(self) -> float:
108
+ """Get elapsed search time"""
109
+ return time.time() - self.start_time
110
+
111
+ def remaining(self) -> float:
112
+ """Get remaining time in soft limit"""
113
+ return max(0, self.allocated_time - self.elapsed())