Rafs-an09002 commited on
Commit
de8be59
·
verified ·
1 Parent(s): 2d2e215

Create engine/move_ordering.py

Browse files
Files changed (1) hide show
  1. engine/move_ordering.py +195 -0
engine/move_ordering.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Move Ordering for Nexus-Core
3
+ Simplified but effective heuristics
4
+
5
+ Research:
6
+ - Schaeffer (1989) - History heuristic
7
+ - Akl & Newborn (1977) - Killer moves
8
+ """
9
+
10
+ import chess
11
+ from typing import List, Optional, Dict
12
+ import numpy as np
13
+
14
+
15
+ class MoveOrderer:
16
+ """Move ordering with killer + history heuristics"""
17
+
18
+ PIECE_VALUES = {
19
+ chess.PAWN: 100,
20
+ chess.KNIGHT: 320,
21
+ chess.BISHOP: 330,
22
+ chess.ROOK: 500,
23
+ chess.QUEEN: 900,
24
+ chess.KING: 20000
25
+ }
26
+
27
+ def __init__(self):
28
+ """Initialize move ordering structures"""
29
+
30
+ # Killer moves (2 per depth)
31
+ self.killer_moves: Dict[int, List[Optional[chess.Move]]] = {}
32
+ self.max_killers = 2
33
+
34
+ # History table [from_square][to_square]
35
+ self.history = np.zeros((64, 64), dtype=np.int32)
36
+
37
+ # Counter moves
38
+ self.counter_moves: Dict[chess.Move, Optional[chess.Move]] = {}
39
+
40
+ # Stats
41
+ self.killer_hits = 0
42
+ self.history_hits = 0
43
+
44
+ def order_moves(
45
+ self,
46
+ board: chess.Board,
47
+ moves: List[chess.Move],
48
+ depth: int,
49
+ tt_move: Optional[chess.Move] = None,
50
+ previous_move: Optional[chess.Move] = None
51
+ ) -> List[chess.Move]:
52
+ """
53
+ Order moves for optimal pruning
54
+
55
+ Priority:
56
+ 1. TT move
57
+ 2. Winning captures (MVV-LVA)
58
+ 3. Killer moves
59
+ 4. Counter moves
60
+ 5. History heuristic
61
+ 6. Quiet moves
62
+ """
63
+
64
+ scored_moves = []
65
+
66
+ for move in moves:
67
+ score = 0
68
+
69
+ # TT move (highest priority)
70
+ if tt_move and move == tt_move:
71
+ score += 1000000
72
+
73
+ # Captures
74
+ elif board.is_capture(move):
75
+ score += self._score_capture(board, move)
76
+
77
+ # Quiet moves
78
+ else:
79
+ # Killer moves
80
+ if self._is_killer_move(move, depth):
81
+ score += 9000
82
+ self.killer_hits += 1
83
+
84
+ # Counter moves
85
+ if previous_move and move == self.counter_moves.get(previous_move):
86
+ score += 8000
87
+
88
+ # History heuristic
89
+ history_score = self.history[move.from_square, move.to_square]
90
+ score += min(history_score, 7000)
91
+
92
+ # Promotions
93
+ if move.promotion == chess.QUEEN:
94
+ score += 10000
95
+
96
+ # Checks
97
+ board.push(move)
98
+ if board.is_check():
99
+ score += 6000
100
+ board.pop()
101
+
102
+ # Castling
103
+ if board.is_castling(move):
104
+ score += 3000
105
+
106
+ # Center control
107
+ center = [chess.D4, chess.D5, chess.E4, chess.E5]
108
+ if move.to_square in center:
109
+ score += 50
110
+
111
+ scored_moves.append((score, move))
112
+
113
+ # Sort descending
114
+ scored_moves.sort(key=lambda x: x[0], reverse=True)
115
+
116
+ return [move for _, move in scored_moves]
117
+
118
+ def _score_capture(self, board: chess.Board, move: chess.Move) -> int:
119
+ """MVV-LVA capture scoring"""
120
+
121
+ captured = board.piece_at(move.to_square)
122
+ attacker = board.piece_at(move.from_square)
123
+
124
+ if not captured or not attacker:
125
+ return 0
126
+
127
+ victim_value = self.PIECE_VALUES.get(captured.piece_type, 0)
128
+ attacker_value = self.PIECE_VALUES.get(attacker.piece_type, 1)
129
+
130
+ # MVV-LVA formula
131
+ mvv_lva = (victim_value * 10 - attacker_value) * 100
132
+
133
+ # En passant bonus
134
+ if board.is_en_passant(move):
135
+ mvv_lva += 10500
136
+
137
+ # Penalty for hanging captures
138
+ if board.is_attacked_by(not board.turn, move.to_square):
139
+ if victim_value < attacker_value:
140
+ mvv_lva -= 5000
141
+
142
+ return mvv_lva
143
+
144
+ def _is_killer_move(self, move: chess.Move, depth: int) -> bool:
145
+ """Check if killer move"""
146
+ killers = self.killer_moves.get(depth, [])
147
+ return move in killers
148
+
149
+ def update_killer_move(self, move: chess.Move, depth: int):
150
+ """Update killer moves"""
151
+ if depth not in self.killer_moves:
152
+ self.killer_moves[depth] = []
153
+
154
+ killers = self.killer_moves[depth]
155
+
156
+ if move not in killers:
157
+ killers.insert(0, move)
158
+ self.killer_moves[depth] = killers[:self.max_killers]
159
+
160
+ def update_history(self, move: chess.Move, depth: int, success: bool):
161
+ """Update history heuristic"""
162
+ if success:
163
+ bonus = depth * depth
164
+ self.history[move.from_square, move.to_square] += bonus
165
+ self.history_hits += 1
166
+ else:
167
+ self.history[move.from_square, move.to_square] -= 1
168
+
169
+ # Clip to prevent overflow
170
+ self.history = np.clip(self.history, -10000, 10000)
171
+
172
+ def update_counter_move(self, previous_move: chess.Move, refutation: chess.Move):
173
+ """Update counter move"""
174
+ self.counter_moves[previous_move] = refutation
175
+
176
+ def clear_history(self):
177
+ """Clear history"""
178
+ self.history.fill(0)
179
+ self.killer_moves.clear()
180
+ self.killer_hits = 0
181
+ self.history_hits = 0
182
+
183
+ def age_history(self, factor: float = 0.9):
184
+ """Age history table"""
185
+ self.history = (self.history * factor).astype(np.int32)
186
+
187
+ def get_stats(self) -> Dict:
188
+ """Get statistics"""
189
+ return {
190
+ 'killer_hits': self.killer_hits,
191
+ 'history_hits': self.history_hits,
192
+ 'history_max': int(np.max(self.history)),
193
+ 'killer_depths': len(self.killer_moves),
194
+ 'counter_moves': len(self.counter_moves)
195
+ }