SHOW:
|
|
- or go back to the newest paste.
1 | #include <bits/stdc++.h> | |
2 | using namespace std; | |
3 | ||
4 | // Constants | |
5 | const int MAX_VALUE = 10000; | |
6 | const int DEPTH = 13; | |
7 | const int N = 4; // 4x4 board size | |
8 | ||
9 | // Player constants | |
10 | const int WHITE = 0; | |
11 | const int BLACK = 1; | |
12 | ||
13 | // Function for adaptive DELTA calculation based on current depth | |
14 | inline int getDelta(int depth) { | |
15 | return (depth * depth); // Faster increase for high depths | |
16 | } | |
17 | ||
18 | // Precomputed move directions for each index | |
19 | // Direction 0: up, 1: down, 2: left, 3: right | |
20 | const int MOVES[16][4] = { | |
21 | {-1, 4, -1, 1}, // 0 (a1) | |
22 | {-1, 5, 0, 2}, // 1 (a2) | |
23 | {-1, 6, 1, 3}, // 2 (a3) | |
24 | {-1, 7, 2, -1}, // 3 (a4) | |
25 | ||
26 | {0, 8, -1, 5}, // 4 (b1) | |
27 | {1, 9, 4, 6}, // 5 (b2) | |
28 | {2, 10, 5, 7}, // 6 (b3) | |
29 | {3, 11, 6, -1}, // 7 (b4) | |
30 | ||
31 | {4, 12, -1, 9}, // 8 (c1) | |
32 | {5, 13, 8, 10}, // 9 (c2) | |
33 | {6, 14, 9, 11}, // 10 (c3) | |
34 | {7, 15, 10, -1}, // 11 (c4) | |
35 | ||
36 | {8, -1, -1, 13}, // 12 (d1) | |
37 | {9, -1, 12, 14}, // 13 (d2) | |
38 | {10, -1, 13, 15}, // 14 (d3) | |
39 | {11, -1, 14, -1} // 15 (d4) | |
40 | }; | |
41 | ||
42 | // Precomputed masks for shift limits | |
43 | const uint16_t NOT_A_FILE = 0xEEEE; // Exclude column A (0, 4, 8, 12) | |
44 | const uint16_t NOT_H_FILE = 0x7777; // Exclude column H (3, 7, 11, 15) | |
45 | const uint16_t NOT_1_RANK = 0xFFF0; // Exclude row 1 (0-3) | |
46 | const uint16_t NOT_8_RANK = 0x0FFF; // Exclude row 8 (12-15) | |
47 | ||
48 | // Structure for moves | |
49 | struct Move { | |
50 | bool drop; | |
51 | int src, dst; // Square indices (0-15) instead of coordinates | |
52 | ||
53 | // Constructor for easier move creation | |
54 | Move(bool is_drop = false, int source = -1, int dest = -1) | |
55 | : drop(is_drop), src(source), dst(dest) {} | |
56 | }; | |
57 | ||
58 | // Optimized game representation with 16-bit bitboards | |
59 | struct Game { | |
60 | // Bitboards - each represents one type of square (16 bits, one for each square) | |
61 | uint16_t empty_board; // 1 at positions where the square is empty | |
62 | uint16_t piece_boards[2]; // piece_boards[WHITE] = white pieces, piece_boards[BLACK] = black pieces | |
63 | uint16_t wall_board; // 1 at positions with walls | |
64 | ||
65 | // Other data | |
66 | uint8_t hand[2]; // hand[WHITE] = white pieces in hand, hand[BLACK] = black pieces | |
67 | uint8_t count[2]; // count[WHITE] = white piece count, count[BLACK] = black piece count | |
68 | uint8_t side; // WHITE = white to move, BLACK = black to move | |
69 | ||
70 | // Hash value for quick comparison in transposition table | |
71 | uint64_t hash; | |
72 | ||
73 | // Convert index (0-15) to coordinates (a1, a2, ..., d4) - for display only | |
74 | inline pair<char, int> indexToCoord(int idx) const { | |
75 | return {static_cast<char>('a' + (idx % N)), (idx / N) + 1}; | |
76 | } | |
77 | ||
78 | // Set bit in given bitboard at given index | |
79 | inline void setBit(uint16_t &board, int idx) { | |
80 | board |= (1u << idx); | |
81 | } | |
82 | ||
83 | // Clear bit in given bitboard at given index | |
84 | inline void clearBit(uint16_t &board, int idx) { | |
85 | board &= ~(1u << idx); | |
86 | } | |
87 | ||
88 | // Check if bit is set | |
89 | inline bool isBitSet(uint16_t board, int idx) const { | |
90 | return (board & (1u << idx)) != 0; | |
91 | } | |
92 | ||
93 | // Set only one bitboard at given index (clear others) | |
94 | inline void setOnlyOneBitboard(int idx, uint16_t &target_board) { | |
95 | uint16_t mask = 1u << idx; | |
96 | empty_board &= ~mask; | |
97 | piece_boards[WHITE] &= ~mask; | |
98 | piece_boards[BLACK] &= ~mask; | |
99 | wall_board &= ~mask; | |
100 | target_board |= mask; | |
101 | } | |
102 | ||
103 | // Get opponent side | |
104 | inline int opponent() const { | |
105 | return 1 - side; | |
106 | } | |
107 | ||
108 | // Initialize game | |
109 | void reset() { | |
110 | hand[WHITE] = hand[BLACK] = 0; | |
111 | count[WHITE] = count[BLACK] = 8; | |
112 | side = WHITE; // White starts | |
113 | ||
114 | // Initialize bitboards | |
115 | empty_board = 0; | |
116 | piece_boards[WHITE] = 0; | |
117 | piece_boards[BLACK] = 0; | |
118 | wall_board = 0; | |
119 | ||
120 | // Set up the board - alternating black and white squares | |
121 | for (int idx = 0; idx < 16; idx++) { | |
122 | int row = idx / N; | |
123 | int col = idx % N; | |
124 | if ((row + col) % 2 == 0) { | |
125 | setBit(piece_boards[BLACK], idx); | |
126 | } else { | |
127 | setBit(piece_boards[WHITE], idx); | |
128 | } | |
129 | } | |
130 | ||
131 | // Set hash value | |
132 | updateHash(); | |
133 | } | |
134 | ||
135 | // Update hash value | |
136 | inline void updateHash() { | |
137 | hash = 0; | |
138 | hash ^= empty_board; | |
139 | hash ^= ((uint64_t)piece_boards[WHITE] << 16); | |
140 | hash ^= ((uint64_t)piece_boards[BLACK] << 32); | |
141 | hash ^= ((uint64_t)wall_board << 48); | |
142 | hash ^= ((uint64_t)hand[WHITE] << 56); | |
143 | hash ^= ((uint64_t)hand[BLACK] << 58); | |
144 | hash ^= ((uint64_t)side << 60); | |
145 | } | |
146 | ||
147 | // Enhanced evaluation function | |
148 | int eval() const { | |
149 | int opp = opponent(); | |
150 | ||
151 | // Basic components | |
152 | int empty = hand[WHITE] + hand[BLACK]; | |
153 | int piece_diff = count[WHITE] - count[BLACK]; | |
154 | int hand_diff = hand[WHITE] - hand[BLACK]; | |
155 | ||
156 | // Functions for bitboard shifts | |
157 | auto shift_up = [](uint16_t b) { return (b & NOT_1_RANK) >> 4; }; | |
158 | auto shift_down = [](uint16_t b) { return (b & NOT_8_RANK) << 4; }; | |
159 | auto shift_left = [](uint16_t b) { return (b & NOT_A_FILE) >> 1; }; | |
160 | auto shift_right = [](uint16_t b) { return (b & NOT_H_FILE) << 1; }; | |
161 | ||
162 | // Calculate target squares for each player's pieces | |
163 | uint16_t targets[2]; | |
164 | ||
165 | for (int player = 0; player < 2; player++) { | |
166 | targets[player] = | |
167 | (shift_up(piece_boards[player]) | | |
168 | shift_down(piece_boards[player]) | | |
169 | shift_left(piece_boards[player]) | | |
170 | shift_right(piece_boards[player])) & ~wall_board; | |
171 | } | |
172 | ||
173 | // Count bits in target squares = number of possible moves | |
174 | int mobility[2]; | |
175 | mobility[WHITE] = __builtin_popcount(targets[WHITE]); | |
176 | mobility[BLACK] = __builtin_popcount(targets[BLACK]); | |
177 | int mobility_diff = mobility[WHITE] - mobility[BLACK]; | |
178 | ||
179 | // Final position score with weights for different components | |
180 | int score = 31 * mobility_diff + 25 * hand_diff + 19 * empty + 13 * piece_diff + 7; | |
181 | return score * (side == WHITE ? 1 : -1); | |
182 | } | |
183 | ||
184 | // Optimized move implementation with bitboards | |
185 | void do_move(const Move &m) { | |
186 | int me = side, opp = opponent(); | |
187 | ||
188 | if (m.drop) { | |
189 | // Placing a piece from hand | |
190 | uint16_t dst_mask = 1u << m.dst; | |
191 | empty_board &= ~dst_mask; | |
192 | ||
193 | piece_boards[me] |= dst_mask; | |
194 | hand[me]--; | |
195 | count[me]++; | |
196 | } else { | |
197 | // Moving a piece | |
198 | uint16_t src_mask = 1u << m.src; | |
199 | uint16_t dst_mask = 1u << m.dst; | |
200 | ||
201 | // Remove original piece | |
202 | piece_boards[me] &= ~src_mask; | |
203 | ||
204 | // Set empty square at original position | |
205 | empty_board |= src_mask; | |
206 | ||
207 | // Process target square | |
208 | if (empty_board & dst_mask) { | |
209 | // Target square is empty | |
210 | empty_board &= ~dst_mask; | |
211 | piece_boards[me] |= dst_mask; | |
212 | } else { | |
213 | // Set wall and adjust piece counts | |
214 | if (piece_boards[me] & dst_mask) { | |
215 | // Piece collided with own piece | |
216 | count[me] -= 2; | |
217 | } else { | |
218 | // Piece collided with opponent's piece | |
219 | count[me]--; | |
220 | count[opp]--; | |
221 | } | |
222 | ||
223 | // Clear all bitboards at target position | |
224 | empty_board &= ~dst_mask; | |
225 | piece_boards[WHITE] &= ~dst_mask; | |
226 | piece_boards[BLACK] &= ~dst_mask; | |
227 | ||
228 | // Set wall | |
229 | wall_board |= dst_mask; | |
230 | hand[me]++; | |
231 | } | |
232 | } | |
233 | ||
234 | side = opp; // Change side to move | |
235 | updateHash(); // Update hash value | |
236 | } | |
237 | ||
238 | // Compare two game states - optimized using hash | |
239 | bool equals(const Game &other) const { | |
240 | return hash == other.hash; | |
241 | } | |
242 | ||
243 | // For debugging - display board state | |
244 | void print() const { | |
245 | cout << "Side: " << (side == WHITE ? "WHITE" : "BLACK") << endl; | |
246 | cout << "Hand: W=" << (int)hand[WHITE] << ", B=" << (int)hand[BLACK] << endl; | |
247 | cout << "Count: W=" << (int)count[WHITE] << ", B=" << (int)count[BLACK] << endl; | |
248 | cout << "Board:" << endl; | |
249 | ||
250 | cout << " a b c d" << endl; | |
251 | cout << " +---+---+---+---+" << endl; | |
252 | // Prechod riadkami od 3 po 0 (štvrtý riadok ako prvý) | |
253 | for (int row = N-1; row >= 0; row--) { | |
254 | cout << row+1 << " |"; | |
255 | for (int col = 0; col < N; col++) { | |
256 | int idx = row * N + col; | |
257 | uint16_t mask = 1u << idx; | |
258 | if (piece_boards[WHITE] & mask) cout << " W |"; | |
259 | else if (piece_boards[BLACK] & mask) cout << " B |"; | |
260 | else if (empty_board & mask) cout << " |"; | |
261 | else if (wall_board & mask) cout << " X |"; | |
262 | else cout << "?|"; // Error - no bitboard is set | |
263 | } | |
264 | cout << " " << row+1 << endl; | |
265 | cout << " +---+---+---+---+" << endl; | |
266 | } | |
267 | cout << " a b c d" << endl; | |
268 | } | |
269 | }; | |
270 | ||
271 | // Enum for transposition table entry type | |
272 | enum TTFlag { | |
273 | TT_EXACT = 0, | |
274 | TT_UPPER = 1, | |
275 | TT_LOWER = 2 | |
276 | }; | |
277 | ||
278 | // Optimized transposition table with hash collision handling | |
279 | struct TTEntry { | |
280 | uint64_t hash; | |
281 | int16_t depth; | |
282 | int16_t score; | |
283 | uint8_t flag; // 0 = exact value, 1 = upper bound, 2 = lower bound | |
284 | Move best_move; | |
285 | }; | |
286 | ||
287 | // Increased transposition table size and using mask instead of modulo | |
288 | const int TT_BITS = 28; // 2^28 = 16777216 entries | |
289 | const int TT_SIZE = 1 << TT_BITS; | |
290 | const int TT_MASK = TT_SIZE - 1; | |
291 | vector<TTEntry> transposition_table(TT_SIZE); | |
292 | ||
293 | // Precomputed move history for ordering | |
294 | int history_table[2][17][16]; // [side][from][to], where from=16 represents moves from hand | |
295 | ||
296 | // Global counter of visited positions for statistics | |
297 | uint64_t nodes_visited = 0; | |
298 | ||
299 | // Serial history of states for draw detection | |
300 | Game state[128]; // Maximum moves in game | |
301 | ||
302 | // Forward declarations | |
303 | pair<int, Move> alphabeta(int ply, int depth, int alpha, int beta); | |
304 | void generate_all_moves(const Game& game, vector<Move>& moves); | |
305 | ||
306 | // Optimized bitboard shift operations | |
307 | inline uint16_t shift_up(uint16_t b) { return (b & NOT_1_RANK) >> 4; } | |
308 | inline uint16_t shift_down(uint16_t b) { return (b & NOT_8_RANK) << 4; } | |
309 | inline uint16_t shift_left(uint16_t b) { return (b & NOT_A_FILE) >> 1; } | |
310 | inline uint16_t shift_right(uint16_t b) { return (b & NOT_H_FILE) << 1; } | |
311 | ||
312 | // Function to generate all possible moves for a player | |
313 | void generate_all_moves(const Game& game, vector<Move>& moves) { | |
314 | int me = game.side; | |
315 | int opp = game.opponent(); | |
316 | ||
317 | // Get bitboards for current player and opponent | |
318 | uint16_t my_pieces = game.piece_boards[me]; | |
319 | uint16_t opp_pieces = game.piece_boards[opp]; | |
320 | ||
321 | // Bitmaps for possible moves in each direction | |
322 | uint16_t up_moves = shift_up(my_pieces) & ~(game.wall_board); | |
323 | uint16_t down_moves = shift_down(my_pieces) & ~(game.wall_board); | |
324 | uint16_t left_moves = shift_left(my_pieces) & ~(game.wall_board); | |
325 | uint16_t right_moves = shift_right(my_pieces) & ~(game.wall_board); | |
326 | ||
327 | // Process each direction to generate moves | |
328 | auto process_moves = [&](uint16_t moves_bitmap, int direction) { | |
329 | // Split moves into different types | |
330 | uint16_t captures_opp = moves_bitmap & opp_pieces; | |
331 | uint16_t captures_self = moves_bitmap & my_pieces; | |
332 | uint16_t to_empty = moves_bitmap & game.empty_board; | |
333 | ||
334 | // Process each type | |
335 | auto add_moves = [&](uint16_t bitmap, int offset) { | |
336 | uint16_t temp = bitmap; | |
337 | while (temp) { | |
338 | int dst_idx = __builtin_ctz(temp); | |
339 | temp &= ~(1u << dst_idx); | |
340 | ||
341 | int src_idx; | |
342 | switch (direction) { | |
343 | case 0: src_idx = dst_idx + 4; break; // Up | |
344 | case 1: src_idx = dst_idx - 4; break; // Down | |
345 | case 2: src_idx = dst_idx + 1; break; // Left | |
346 | case 3: src_idx = dst_idx - 1; break; // Right | |
347 | default: src_idx = -1; | |
348 | } | |
349 | ||
350 | if (src_idx >= 0 && src_idx < 16) { | |
351 | moves.push_back(Move(false, src_idx, dst_idx)); | |
352 | } | |
353 | } | |
354 | }; | |
355 | ||
356 | // Add all types of moves | |
357 | add_moves(captures_opp, 0); | |
358 | add_moves(captures_self, 0); | |
359 | add_moves(to_empty, 0); | |
360 | }; | |
361 | ||
362 | // Process moves in all directions | |
363 | process_moves(up_moves, 0); | |
364 | process_moves(down_moves, 1); | |
365 | process_moves(left_moves, 2); | |
366 | process_moves(right_moves, 3); | |
367 | ||
368 | // Placing a piece from hand | |
369 | if (game.hand[me] > 0) { | |
370 | uint16_t empty_positions = game.empty_board; | |
371 | while (empty_positions) { | |
372 | int empty_idx = __builtin_ctz(empty_positions); | |
373 | empty_positions &= ~(1u << empty_idx); | |
374 | moves.push_back(Move(true, -1, empty_idx)); | |
375 | } | |
376 | } | |
377 | } | |
378 | ||
379 | // Optimized alpha-beta search with move ordering | |
380 | pair<int, Move> alphabeta(int ply, int depth, int alpha, int beta) { | |
381 | nodes_visited++; | |
382 | int me = state[ply].side; | |
383 | int opp = state[ply].opponent(); | |
384 | ||
385 | // Base condition | |
386 | if (depth <= 0) return {state[ply].eval(), Move()}; | |
387 | ||
388 | // Use transposition table | |
389 | uint64_t hash = state[ply].hash; | |
390 | int tt_index = hash & TT_MASK; | |
391 | TTEntry &entry = transposition_table[tt_index]; | |
392 | ||
393 | // If we find a match and depth is sufficient, use value from table | |
394 | if (entry.hash == hash && entry.depth >= depth) { | |
395 | if (entry.flag == TT_EXACT) { | |
396 | return {entry.score, entry.best_move}; | |
397 | } else if (entry.flag == TT_LOWER && entry.score >= beta) { | |
398 | return {entry.score, entry.best_move}; | |
399 | } else if (entry.flag == TT_UPPER && entry.score <= alpha) { | |
400 | return {entry.score, entry.best_move}; | |
401 | } | |
402 | // Otherwise just use best_move for ordering | |
403 | } | |
404 | ||
405 | // Get bitboards for current player and opponent | |
406 | uint16_t my_pieces = state[ply].piece_boards[me]; | |
407 | uint16_t opp_pieces = state[ply].piece_boards[opp]; | |
408 | ||
409 | // Prepare list of all possible moves using bit operations | |
410 | vector<pair<Move, int>> moves; | |
411 | ||
412 | // Add move from transposition table, if it exists | |
413 | if (entry.hash == hash && entry.best_move.dst != -1) { | |
414 | if (entry.best_move.drop) { | |
415 | // Verify if drop move is valid | |
416 | if (state[ply].hand[me] > 0 && state[ply].isBitSet(state[ply].empty_board, entry.best_move.dst)) { | |
417 | moves.push_back({entry.best_move, 10000}); | |
418 | } | |
419 | } else { | |
420 | // Verify if move is valid | |
421 | if (state[ply].isBitSet(my_pieces, entry.best_move.src) && | |
422 | !state[ply].isBitSet(state[ply].wall_board, entry.best_move.dst)) { | |
423 | moves.push_back({entry.best_move, 10000}); | |
424 | } | |
425 | } | |
426 | } | |
427 | ||
428 | // Bitmaps for possible moves in each direction | |
429 | uint16_t up_moves = shift_up(my_pieces) & ~(state[ply].wall_board); | |
430 | uint16_t down_moves = shift_down(my_pieces) & ~(state[ply].wall_board); | |
431 | uint16_t left_moves = shift_left(my_pieces) & ~(state[ply].wall_board); | |
432 | uint16_t right_moves = shift_right(my_pieces) & ~(state[ply].wall_board); | |
433 | ||
434 | // Function to process moves in a given direction | |
435 | auto process_moves = [&](uint16_t moves_bitmap, int dir) { | |
436 | // Different types of target squares | |
437 | uint16_t captures_opp = moves_bitmap & opp_pieces; | |
438 | uint16_t captures_self = moves_bitmap & my_pieces; | |
439 | uint16_t to_empty = moves_bitmap & state[ply].empty_board; | |
440 | ||
441 | // Process captures of opponent's pieces (highest priority) | |
442 | uint16_t temp = captures_opp; | |
443 | while (temp) { | |
444 | int dst_idx = __builtin_ctz(temp); | |
445 | temp &= ~(1u << dst_idx); | |
446 | ||
447 | int src_idx; | |
448 | switch (dir) { | |
449 | case 0: src_idx = dst_idx + 4; break; // Up | |
450 | case 1: src_idx = dst_idx - 4; break; // Down | |
451 | case 2: src_idx = dst_idx + 1; break; // Left | |
452 | case 3: src_idx = dst_idx - 1; break; // Right | |
453 | default: src_idx = -1; | |
454 | } | |
455 | ||
456 | // Check if it's capturing opponent's last piece | |
457 | bool winning_move = (state[ply].count[opp] == 1 && state[ply].hand[opp] == 0); | |
458 | int move_score = winning_move ? 5000 + history_table[me][src_idx][dst_idx] : | |
459 | 2500 + history_table[me][src_idx][dst_idx]; | |
460 | ||
461 | moves.push_back({Move(false, src_idx, dst_idx), move_score}); | |
462 | } | |
463 | ||
464 | // Process captures of own pieces (lowest priority) | |
465 | temp = captures_self; | |
466 | while (temp) { | |
467 | int dst_idx = __builtin_ctz(temp); | |
468 | temp &= ~(1u << dst_idx); | |
469 | ||
470 | int src_idx; | |
471 | switch (dir) { | |
472 | case 0: src_idx = dst_idx + 4; break; | |
473 | case 1: src_idx = dst_idx - 4; break; | |
474 | case 2: src_idx = dst_idx + 1; break; | |
475 | case 3: src_idx = dst_idx - 1; break; | |
476 | default: src_idx = -1; | |
477 | } | |
478 | ||
479 | int move_score = history_table[me][src_idx][dst_idx]; | |
480 | moves.push_back({Move(false, src_idx, dst_idx), move_score}); | |
481 | } | |
482 | ||
483 | // Process moves to empty squares (medium priority) | |
484 | temp = to_empty; | |
485 | while (temp) { | |
486 | int dst_idx = __builtin_ctz(temp); | |
487 | temp &= ~(1u << dst_idx); | |
488 | ||
489 | int src_idx; | |
490 | switch (dir) { | |
491 | case 0: src_idx = dst_idx + 4; break; | |
492 | case 1: src_idx = dst_idx - 4; break; | |
493 | case 2: src_idx = dst_idx + 1; break; | |
494 | case 3: src_idx = dst_idx - 1; break; | |
495 | default: src_idx = -1; | |
496 | } | |
497 | ||
498 | int move_score = 1500 + history_table[me][src_idx][dst_idx]; | |
499 | moves.push_back({Move(false, src_idx, dst_idx), move_score}); | |
500 | } | |
501 | }; | |
502 | ||
503 | // Process moves in all directions | |
504 | process_moves(up_moves, 0); | |
505 | process_moves(down_moves, 1); | |
506 | process_moves(left_moves, 2); | |
507 | process_moves(right_moves, 3); | |
508 | ||
509 | // Moves by placing a piece from hand | |
510 | if (state[ply].hand[me] > 0) { | |
511 | uint16_t empty_positions = state[ply].empty_board; | |
512 | while (empty_positions) { | |
513 | int empty_idx = __builtin_ctz(empty_positions); | |
514 | empty_positions &= ~(1u << empty_idx); | |
515 | ||
516 | int move_score = 1000 + history_table[me][16][empty_idx]; | |
517 | moves.push_back({Move(true, -1, empty_idx), move_score}); | |
518 | } | |
519 | } | |
520 | ||
521 | // Sort moves by priority (descending) | |
522 | sort(moves.begin(), moves.end(), [](const auto &a, const auto &b) { | |
523 | return a.second > b.second; | |
524 | }); | |
525 | ||
526 | int best_score = -MAX_VALUE; | |
527 | Move best_move; | |
528 | bool found = false; | |
529 | uint8_t flag = TT_UPPER; | |
530 | ||
531 | // Iterate through sorted moves | |
532 | for (const auto &move_pair : moves) { | |
533 | const Move &m = move_pair.first; | |
534 | ||
535 | found = true; | |
536 | state[ply+1] = state[ply]; | |
537 | state[ply+1].do_move(m); | |
538 | ||
539 | // Principal Variation Search - NegaScout version | |
540 | int child_score; | |
541 | if (best_score == -MAX_VALUE) { | |
542 | auto result = alphabeta(ply+1, depth-1, -beta, -alpha); | |
543 | child_score = -result.first; | |
544 | } else { | |
545 | // Null-window search with reduced depth for later moves (Late Move Reduction) | |
546 | int reduction = 0; | |
547 | if (depth >= 3 && !m.drop && !state[ply].isBitSet(opp_pieces, m.dst)) { | |
548 | reduction = 1; | |
549 | } | |
550 | ||
551 | auto result = alphabeta(ply+1, depth-1-reduction, -alpha-1, -alpha); | |
552 | child_score = -result.first; | |
553 | ||
554 | // Re-search on null-window search failure | |
555 | if (child_score > alpha && child_score < beta) { | |
556 | auto result = alphabeta(ply+1, depth-1, -beta, -alpha); | |
557 | child_score = -result.first; | |
558 | } | |
559 | } | |
560 | ||
561 | if (child_score > best_score) { | |
562 | best_score = child_score; | |
563 | best_move = m; | |
564 | ||
565 | if (best_score > alpha) { | |
566 | alpha = best_score; | |
567 | flag = TT_EXACT; | |
568 | ||
569 | // Update move history for successful move | |
570 | if (!m.drop) { | |
571 | history_table[me][m.src][m.dst] += depth * depth; | |
572 | } else { | |
573 | history_table[me][16][m.dst] += depth * depth; | |
574 | } | |
575 | ||
576 | // Use adaptive DELTA value for more aggressive pruning at greater depths | |
577 | int currentDelta = getDelta(depth); | |
578 | if (alpha >= beta - currentDelta) { | |
579 | flag = TT_LOWER; | |
580 | break; | |
581 | } | |
582 | } | |
583 | } | |
584 | } | |
585 | ||
586 | if (!found || moves.empty()) { | |
587 | // No valid move or empty moves were generated | |
588 | // Return a value close to but not equal to MAX_VALUE | |
589 | return {-MAX_VALUE + 1000, Move()}; | |
590 | } | |
591 | ||
592 | // Check if we just captured the opponent's last piece | |
593 | // But don't end the search - let selfplay function handle game termination | |
594 | if (state[ply].count[opp] == 1) { | |
595 | for (const auto &move_pair : moves) { | |
596 | const Move &m = move_pair.first; | |
597 | if (!m.drop) { | |
598 | uint16_t dst_mask = 1u << m.dst; | |
599 | if (state[ply].piece_boards[opp] & dst_mask) { | |
600 | // We found a move that captures the last opponent's piece | |
601 | // Return a high but not maximum value | |
602 | return {MAX_VALUE - 1000, m}; | |
603 | } | |
604 | } | |
605 | } | |
606 | } | |
607 | ||
608 | // Save result to transposition table | |
609 | entry.hash = hash; | |
610 | entry.depth = depth; | |
611 | entry.score = best_score; | |
612 | entry.flag = flag; | |
613 | entry.best_move = best_move; | |
614 | ||
615 | return {best_score, best_move}; | |
616 | } | |
617 | ||
618 | void selfplay(int maxmoves = 64) { | |
619 | // Initialize transposition table | |
620 | for (int i = 0; i < TT_SIZE; i++) { | |
621 | transposition_table[i].hash = 0; | |
622 | transposition_table[i].depth = 0; | |
623 | transposition_table[i].score = 0; | |
624 | transposition_table[i].flag = TT_EXACT; | |
625 | transposition_table[i].best_move = Move(); | |
626 | } | |
627 | ||
628 | // Initialize move history | |
629 | memset(history_table, 0, sizeof(history_table)); | |
630 | ||
631 | // Initialize node counter | |
632 | nodes_visited = 0; | |
633 | ||
634 | srand(time(NULL)); | |
635 | state[0].reset(); | |
636 | ||
637 | bool game_over = false; | |
638 | string game_result = ""; | |
639 | int current_ply = 0; | |
640 | ||
641 | // Vypíš informáciu o začiatku hry | |
642 | cout << "Starting the game. Displaying moves:\n"; | |
643 | ||
644 | for (int ply = 0; ply < maxmoves; ply++) { | |
645 | current_ply = ply; | |
646 | int me = state[ply].side; | |
647 | int opp = state[ply].opponent(); | |
648 | ||
649 | auto start_time = chrono::high_resolution_clock::now(); | |
650 | auto [score, move] = alphabeta(ply, DEPTH + ply, -MAX_VALUE, MAX_VALUE); | |
651 | auto end_time = chrono::high_resolution_clock::now(); | |
652 | ||
653 | auto duration = chrono::duration_cast<chrono::milliseconds>(end_time - start_time).count(); | |
654 | ||
655 | // Check if a valid move was found | |
656 | bool valid_move = (move.src != -1 || move.drop); | |
657 | ||
658 | if (!valid_move) { | |
659 | // Check if there are really no valid moves | |
660 | vector<Move> possible_moves; | |
661 | generate_all_moves(state[ply], possible_moves); | |
662 | ||
663 | if (!possible_moves.empty()) { | |
664 | cout << "(ERROR: algorithm returned no move, but there are " << possible_moves.size() << " possible moves)"; | |
665 | // Use first available move | |
666 | move = possible_moves[0]; | |
667 | valid_move = true; | |
668 | } else { | |
669 | cout << (me == WHITE ? "W" : "B") << " " << (ply+1) << ": (no move) - no legal moves available"; | |
670 | cout << " eval=" << score << " time=" << duration << "ms"; | |
671 | cout << " nodes=" << nodes_visited << " nps=" << (nodes_visited * 1000 / (duration + 1)) << "\n"; | |
672 | ||
673 | // Set game over - player with no moves loses | |
674 | game_over = true; | |
675 | game_result = (me == WHITE ? "B" : "W") + string(" wins the game (opponent has no valid moves)."); | |
676 | break; | |
677 | } | |
678 | } | |
679 | ||
680 | state[ply+1] = state[ply]; | |
681 | state[ply+1].do_move(move); | |
682 | ||
683 | // Vypíš informácie o ťahu kompaktne | |
684 | cout << (me == WHITE ? "W" : "B") << " " << (ply+1) << ": "; | |
685 | if (move.drop) { | |
686 | auto [col, row] = state[ply].indexToCoord(move.dst); | |
687 | cout << "drop " << col << row; | |
688 | } | |
689 | else { | |
690 | auto [src_col, src_row] = state[ply].indexToCoord(move.src); | |
691 | auto [dst_col, dst_row] = state[ply].indexToCoord(move.dst); | |
692 | cout << src_col << src_row << "->" << dst_col << dst_row; | |
693 | } | |
694 | cout << " eval=" << score << " time=" << duration << "ms"; | |
695 | cout << " nodes=" << nodes_visited << " nps=" << (nodes_visited * 1000 / (duration + 1)) << endl; | |
696 | ||
697 | // Reset counter for next move | |
698 | nodes_visited = 0; | |
699 | ||
700 | // Check if game is over - did the move capture opponent's last piece? | |
701 | // Record opponent's state before and after move | |
702 | uint8_t opp_pieces_before = state[ply].count[opp]; | |
703 | uint8_t opp_pieces_after = state[ply+1].count[opp]; | |
704 | ||
705 | if (opp_pieces_before == 1 && opp_pieces_after == 0) { | |
706 | game_over = true; | |
707 | game_result = (me == WHITE ? "W" : "B") + string(" wins the game by capturing all opponent's pieces."); | |
708 | break; | |
709 | } | |
710 | ||
711 | // Check for draw | |
712 | for (int i = 0; i < ply; i++) { | |
713 | if (state[i].equals(state[ply+1])) { | |
714 | game_over = true; | |
715 | game_result = "Game ends in a draw by repetition with position after move " + to_string(i+1) + "."; | |
716 | break; | |
717 | } | |
718 | } | |
719 | ||
720 | if (game_over) break; | |
721 | } | |
722 | ||
723 | // Print final result | |
724 | if (game_over) { | |
725 | cout << "\n" << game_result << "\n"; | |
726 | } else { | |
727 | cout << "\nGame ended after maximum number of moves (" << maxmoves << ").\n"; | |
728 | } | |
729 | ||
730 | // Print final position | |
731 | cout << "\nFinal position:\n"; | |
732 | ||
733 | // If game ended early, use the correct variable for display | |
734 | int final_ply = (game_over && current_ply < maxmoves) ? current_ply + 1 : (maxmoves > 0 ? maxmoves : 0); | |
735 | state[final_ply].print(); | |
736 | } | |
737 | ||
738 | int main() { | |
739 | ios_base::sync_with_stdio(false); | |
740 | cin.tie(nullptr); | |
741 | ||
742 | cout << "Cloister - Optimized Version" << endl; | |
743 | cout << "Search depth: " << DEPTH << endl; | |
744 | cout << "TT table size: " << TT_SIZE << " entries" << endl << endl; | |
745 | ||
746 | selfplay(); | |
747 | return 0; | |
748 | } |