-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.py
218 lines (187 loc) · 8.05 KB
/
search.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
"""
In this script, we implement the first version of evaluation and search !
"""
import time
from chess import Move
from maxoul_chess.abstract_board import AbstractBoard
from maxoul_chess.python_chess_board import PythonChessBoard
from maxoul_chess.evaluation import evaluate
from maxoul_chess.legal_moves_generation import order_moves, get_quiescence_moves
from maxoul_chess.utils import zobrist_hash, LimitedHashTable
def quiescence_search(board: AbstractBoard,
depth: int,
alpha: float,
beta: float,
maximizing_player: bool,
z_hash: str,
use_eval_cache: bool):
"""
Performs a quiescence search on the given board state.
"""
stand_pat = evaluate(board, z_hash=z_hash, use_cache=use_eval_cache)
if depth == 0:
return stand_pat
if stand_pat == 0: # There may be a draw here ! caution:
if board.is_stalemate() or board.is_insufficient_material() \
or board.is_fifty_moves() or board.board.is_repetition():
return 0.
moves = get_quiescence_moves(board)
if len(moves) == 0:
return stand_pat
if maximizing_player:
best_score = -float('inf')
if stand_pat >= beta:
return stand_pat
if alpha < stand_pat:
alpha = stand_pat
for move in moves:
board.push(move)
score = quiescence_search(board=board,
alpha=alpha,
beta=beta,
depth=depth - 1,
maximizing_player=False,
z_hash=z_hash,
use_eval_cache=use_eval_cache)
board.pop()
best_score = max(best_score, score)
if best_score >= beta:
return best_score
alpha = max(best_score, alpha)
return best_score
else:
best_score = float('inf')
if stand_pat <= alpha:
return stand_pat
if stand_pat < beta:
beta = stand_pat
for move in moves:
board.push(move)
score = quiescence_search(board=board,
alpha=alpha,
beta=beta,
depth=depth - 1,
maximizing_player=True,
z_hash=z_hash,
use_eval_cache=use_eval_cache)
board.pop()
best_score = min(best_score, score)
if best_score <= alpha:
return best_score
beta = min(best_score, beta)
return best_score
pv_cache = LimitedHashTable(max_size=1e7)
search_cache = LimitedHashTable(max_size=1e7)
n_calls = 0
def min_max_search(board: PythonChessBoard,
depth: int = 2,
maximizing_player: bool = True,
alpha: float = -float('inf'),
beta: float = float('inf'),
capture_max_depth: int = 4,
pruning: bool = True,
candidate_best_move: Move = None,
max_end_time: float = None,
use_search_cache: bool = False,
use_eval_cache: bool = False,
use_pv_cache: bool = False):
"""
alpha: The best lower bound on the score for the maximizing player (White in chess). beta is the worst possible score for black
beta: Best upper bound on the score of black
"""
global n_calls
n_calls += 1
z_hash = None
if use_search_cache or use_eval_cache or use_pv_cache:
z_hash = str(zobrist_hash(board.board))
hash_key = None
if use_search_cache:
hash_key = z_hash + str(depth)
out = search_cache.get(hash_key)
if out is not None:
return out
if use_pv_cache and candidate_best_move is None:
candidate_best_move = pv_cache.get(z_hash) # only z_hash this time !
if depth == 0:
out = quiescence_search(board=board,
depth=capture_max_depth,
alpha=alpha,
beta=beta,
maximizing_player=maximizing_player,
z_hash=z_hash,
use_eval_cache=use_eval_cache)
if use_search_cache:
search_cache.insert(hash_key, (out, None))
return out, None, False
legal_moves = board.generate_legal_moves()
ordered_moves = order_moves(legal_moves, board, candidate_best_move=candidate_best_move)
search_cancelled = False
if maximizing_player:
best_evaluation = -float('inf')
best_move = None
for move in ordered_moves:
if max_end_time is not None and time.time() > max_end_time:
print('Stopping during current search, time is ellapsed')
search_cancelled = True
break # Still wanna do the end routine !
board.push(move)
move_evaluation = min_max_search(board=board,
depth=depth - 1,
maximizing_player=False,
alpha=alpha,
beta=beta,
pruning=pruning,
capture_max_depth=capture_max_depth,
use_search_cache=use_search_cache,
use_eval_cache=use_eval_cache,
use_pv_cache=use_pv_cache)[0]
if move_evaluation > best_evaluation:
best_evaluation = move_evaluation
best_move = move
alpha = max(alpha, best_evaluation)
board.pop()
if pruning:
if beta <= alpha:
break
else:
best_evaluation = float('inf')
best_move = None
for move in ordered_moves:
# Check time is not over:
if max_end_time is not None and time.time() > max_end_time:
print('Stopping during current search, time is ellapsed')
search_cancelled = True
break # Still wanna do the end routine !
board.push(move)
move_evaluation = min_max_search(board=board,
depth=depth - 1,
maximizing_player=True,
alpha=alpha,
beta=beta,
pruning=pruning,
capture_max_depth=capture_max_depth,
use_search_cache=use_search_cache,
use_eval_cache=use_eval_cache,
use_pv_cache=use_pv_cache)[0]
if move_evaluation < best_evaluation:
best_evaluation = move_evaluation
best_move = move
beta = min(beta, best_evaluation)
board.pop()
if pruning:
if beta <= alpha:
break
out = best_evaluation, best_move, search_cancelled
if search_cancelled:
return out
# If there were no legal moves (checkmate or stalemate), we still need to evaluate the position
if best_evaluation == float('inf') or best_evaluation == - float('inf'):
out = evaluate(board=board,
z_hash=z_hash,
use_cache=use_eval_cache), None, True
if use_search_cache:
if not search_cancelled:
search_cache.insert(hash_key, out)
if use_pv_cache:
pv_cache.insert(z_hash, out[1])
return out