View difference between Paste ID: KBgvC1pb and dErYDSjf
SHOW: | | - or go back to the newest paste.
1
import chess
2
from typing import Iterator, Optional, Dict, Tuple
3
from chess import Move, BB_ALL, Bitboard, PieceType, Color
4
import time
5
from collections import deque
6
import threading
7
8
# Definice nových figur
9
AMAZON = 7
10
CYRIL = 8
11
EVE = 9
12
13
# Rozšíření seznamu PIECE_SYMBOLS
14
chess.PIECE_SYMBOLS.append('a')
15
chess.PIECE_SYMBOLS.append('c')
16
chess.PIECE_SYMBOLS.append('e')
17
18
class CustomBoard(chess.Board):
19
    def __init__(self, fen=None):
20
        self.amazons_white = chess.BB_EMPTY
21
        self.amazons_black = chess.BB_EMPTY
22
        self.cyrils_white = chess.BB_EMPTY
23
        self.cyrils_black = chess.BB_EMPTY
24
        self.eves_white = chess.BB_EMPTY
25
        self.eves_black = chess.BB_EMPTY
26
        super().__init__(None)
27
        if fen:
28
            self.set_custom_fen(fen)
29
        print("Šachovnice inicializována")
30
        self.debug_amazons()
31
        self.debug_cyrils()
32
        self.debug_eves()
33
34
    def clear_square(self, square):
35
        super()._remove_piece_at(square)
36
        self.amazons_white &= ~chess.BB_SQUARES[square]
37
        self.amazons_black &= ~chess.BB_SQUARES[square]
38
        self.cyrils_white &= ~chess.BB_SQUARES[square]
39
        self.cyrils_black &= ~chess.BB_SQUARES[square]
40
        self.eves_white &= ~chess.BB_SQUARES[square]
41
        self.eves_black &= ~chess.BB_SQUARES[square]
42
43
    def set_custom_fen(self, fen):
44
        parts = fen.split()
45
        board_part = parts[0]
46
47
        self.clear()
48
        self.amazons_white = chess.BB_EMPTY
49
        self.amazons_black = chess.BB_EMPTY
50
        self.cyrils_white = chess.BB_EMPTY
51
        self.cyrils_black = chess.BB_EMPTY
52
        self.eves_white = chess.BB_EMPTY
53
        self.eves_black = chess.BB_EMPTY
54
55
        square = 56
56
        for c in board_part:
57
            if c == '/':
58
                square -= 16
59
            elif c.isdigit():
60
                square += int(c)
61
            else:
62
                color = chess.WHITE if c.isupper() else chess.BLACK
63
                if c.upper() == 'A':
64
                    if color == chess.WHITE:
65
                        self.amazons_white |= chess.BB_SQUARES[square]
66
                    else:
67
                        self.amazons_black |= chess.BB_SQUARES[square]
68
                    piece_type = AMAZON
69
                elif c.upper() == 'C':
70
                    if color == chess.WHITE:
71
                        self.cyrils_white |= chess.BB_SQUARES[square]
72
                    else:
73
                        self.cyrils_black |= chess.BB_SQUARES[square]
74
                    piece_type = CYRIL
75
                elif c.upper() == 'E':
76
                    if color == chess.WHITE:
77
                        self.eves_white |= chess.BB_SQUARES[square]
78
                    else:
79
                        self.eves_black |= chess.BB_SQUARES[square]
80
                    piece_type = EVE
81
                else:
82
                    piece_type = chess.PIECE_SYMBOLS.index(c.lower())
83
                self._set_piece_at(square, piece_type, color)
84
                square += 1
85
86
        self.turn = chess.WHITE if parts[1] == 'w' else chess.BLACK
87
        self.castling_rights = chess.BB_EMPTY
88
        if '-' not in parts[2]:
89
            if 'K' in parts[2]: self.castling_rights |= chess.BB_H1
90
            if 'Q' in parts[2]: self.castling_rights |= chess.BB_A1
91
            if 'k' in parts[2]: self.castling_rights |= chess.BB_H8
92
            if 'q' in parts[2]: self.castling_rights |= chess.BB_A8
93
        self.ep_square = chess.parse_square(parts[3]) if parts[3] != '-' else None
94
95
    def _set_piece_at(self, square: chess.Square, piece_type: PieceType, color: Color) -> None:
96
        self.clear_square(square)
97
        super()._set_piece_at(square, piece_type, color)
98
        if piece_type == AMAZON:
99
            if color == chess.WHITE:
100
                self.amazons_white |= chess.BB_SQUARES[square]
101
            else:
102
                self.amazons_black |= chess.BB_SQUARES[square]
103
        elif piece_type == CYRIL:
104
            if color == chess.WHITE:
105
                self.cyrils_white |= chess.BB_SQUARES[square]
106
            else:
107
                self.cyrils_black |= chess.BB_SQUARES[square]
108
        elif piece_type == EVE:
109
            if color == chess.WHITE:
110
                self.eves_white |= chess.BB_SQUARES[square]
111
            else:
112
                self.eves_black |= chess.BB_SQUARES[square]
113
114
    def piece_at(self, square: chess.Square) -> Optional[chess.Piece]:
115
        if self.amazons_white & chess.BB_SQUARES[square]:
116
            return chess.Piece(AMAZON, chess.WHITE)
117
        elif self.amazons_black & chess.BB_SQUARES[square]:
118
            return chess.Piece(AMAZON, chess.BLACK)
119
        elif self.cyrils_white & chess.BB_SQUARES[square]:
120
            return chess.Piece(CYRIL, chess.WHITE)
121
        elif self.cyrils_black & chess.BB_SQUARES[square]:
122
            return chess.Piece(CYRIL, chess.BLACK)
123
        elif self.eves_white & chess.BB_SQUARES[square]:
124
            return chess.Piece(EVE, chess.WHITE)
125
        elif self.eves_black & chess.BB_SQUARES[square]:
126
            return chess.Piece(EVE, chess.BLACK)
127
        return super().piece_at(square)
128
129
    def generate_pseudo_legal_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboard = BB_ALL) -> Iterator[Move]:
130
        our_pieces = self.occupied_co[self.turn]
131
        if self.turn == chess.WHITE:
132
            our_amazons = self.amazons_white
133
            our_cyrils = self.cyrils_white
134
            our_eves = self.eves_white
135
        else:
136
            our_amazons = self.amazons_black
137
            our_cyrils = self.cyrils_black
138
            our_eves = self.eves_black
139
140
        for from_square in chess.scan_forward(our_amazons & from_mask):
141
            attacks = self.amazon_attacks(from_square)
142
            valid_moves = attacks & ~our_pieces & to_mask
143
            for to_square in chess.scan_forward(valid_moves):
144
                yield Move(from_square, to_square)
145
146
        for from_square in chess.scan_forward(our_cyrils & from_mask):
147
            attacks = self.cyril_attacks(from_square)
148
            valid_moves = attacks & ~our_pieces & to_mask
149
            for to_square in chess.scan_forward(valid_moves):
150
                yield Move(from_square, to_square)
151
152
        for from_square in chess.scan_forward(our_eves & from_mask):
153
            attacks = self.eve_attacks(from_square)
154
            valid_moves = attacks & ~our_pieces & to_mask
155
            for to_square in chess.scan_forward(valid_moves):
156
                yield Move(from_square, to_square)
157
158
        for move in super().generate_pseudo_legal_moves(from_mask, to_mask):
159
            if self.piece_type_at(move.from_square) not in [AMAZON, CYRIL, EVE]:
160
                yield move
161
162
    def queen_attacks(self, square):
163
        return self.bishop_attacks(square) | self.rook_attacks(square)
164
165
    def bishop_attacks(self, square):
166
        return chess.BB_DIAG_ATTACKS[square][self.occupied & chess.BB_DIAG_MASKS[square]]
167
168
    def rook_attacks(self, square):
169
        return (chess.BB_RANK_ATTACKS[square][self.occupied & chess.BB_RANK_MASKS[square]] |
170
                chess.BB_FILE_ATTACKS[square][self.occupied & chess.BB_FILE_MASKS[square]])
171
172
    def amazon_attacks(self, square):
173
        return self.queen_attacks(square) | chess.BB_KNIGHT_ATTACKS[square]
174
175
    def cyril_attacks(self, square):
176
        return self.rook_attacks(square) | chess.BB_KNIGHT_ATTACKS[square]
177
178
    def eve_attacks(self, square):
179
        return self.bishop_attacks(square) | chess.BB_KNIGHT_ATTACKS[square]
180
181
    def is_pseudo_legal(self, move):
182
        from_square = move.from_square
183
        to_square = move.to_square
184
        piece = self.piece_at(from_square)
185
186
        if not piece or piece.color != self.turn:
187
            return False
188
189
        if self.occupied_co[self.turn] & chess.BB_SQUARES[to_square]:
190
            return False
191
192
        if self.is_castling(move):
193
            return True
194
195
        if piece.piece_type == AMAZON:
196
            return bool(self.amazon_attacks(from_square) & chess.BB_SQUARES[to_square])
197
        elif piece.piece_type == CYRIL:
198
            return bool(self.cyril_attacks(from_square) & chess.BB_SQUARES[to_square])
199
        elif piece.piece_type == EVE:
200
            return bool(self.eve_attacks(from_square) & chess.BB_SQUARES[to_square])
201
        else:
202
            return super().is_pseudo_legal(move)
203
204
    def is_legal(self, move):
205
        if not self.is_pseudo_legal(move):
206
            return False
207
208
        from_square = move.from_square
209
        to_square = move.to_square
210
        piece = self.piece_at(from_square)
211
        captured_piece = self.piece_at(to_square)
212
213
        self.clear_square(from_square)
214
        self.clear_square(to_square)
215
        self._set_piece_at(to_square, piece.piece_type, piece.color)
216
217
        king_square = to_square if piece.piece_type == chess.KING else self.king(self.turn)
218
        is_check, attacker_square = self._is_attacked_by(not self.turn, king_square)
219
220
        self.clear_square(to_square)
221
        self._set_piece_at(from_square, piece.piece_type, piece.color)
222
        if captured_piece:
223
            self._set_piece_at(to_square, captured_piece.piece_type, captured_piece.color)
224
225
        return not is_check
226
227
    def _is_attacked_by(self, color, square):
228
        attackers = self.attackers(color, square)
229
        if attackers:
230
            for attacker_square in chess.scan_forward(attackers):
231
                return True, attacker_square
232
        return False, None
233
234
    def attackers(self, color, square):
235
        attackers = chess.BB_EMPTY
236
237
        knights = self.knights & self.occupied_co[color]
238
        attackers |= knights & chess.BB_KNIGHT_ATTACKS[square]
239
240
        king = self.kings & self.occupied_co[color]
241
        attackers |= king & chess.BB_KING_ATTACKS[square]
242
243
        pawns = self.pawns & self.occupied_co[color]
244
        if color == chess.WHITE:
245
            attackers |= pawns & chess.BB_PAWN_ATTACKS[chess.BLACK][square]
246
        else:
247
            attackers |= pawns & chess.BB_PAWN_ATTACKS[chess.WHITE][square]
248
249
        queens = self.queens & self.occupied_co[color]
250
        bishops = (self.bishops | queens) & self.occupied_co[color]
251
        rooks = (self.rooks | queens) & self.occupied_co[color]
252
253
        attackers |= chess.BB_DIAG_ATTACKS[square][self.occupied & chess.BB_DIAG_MASKS[square]] & bishops
254
        attackers |= (chess.BB_RANK_ATTACKS[square][self.occupied & chess.BB_RANK_MASKS[square]] |
255
                      chess.BB_FILE_ATTACKS[square][self.occupied & chess.BB_FILE_MASKS[square]]) & rooks
256
257
        amazons = self.amazons_white if color == chess.WHITE else self.amazons_black
258
        for amazon_square in chess.scan_forward(amazons):
259
            if self.amazon_attacks(amazon_square) & chess.BB_SQUARES[square]:
260
                attackers |= chess.BB_SQUARES[amazon_square]
261
262
        cyrils = self.cyrils_white if color == chess.WHITE else self.cyrils_black
263
        for cyril_square in chess.scan_forward(cyrils):
264
            if self.cyril_attacks(cyril_square) & chess.BB_SQUARES[square]:
265
                attackers |= chess.BB_SQUARES[cyril_square]
266
267
        eves = self.eves_white if color == chess.WHITE else self.eves_black
268
        for eve_square in chess.scan_forward(eves):
269
            if self.eve_attacks(eve_square) & chess.BB_SQUARES[square]:
270
                attackers |= chess.BB_SQUARES[eve_square]
271
272
        return attackers
273
274
    def push(self, move):
275
        if not self.is_legal(move):
276-
            pass
276+
            raise ValueError(f"Move {move} is not legal in position {self.fen()}")
277-
         #  raise ValueError(f"Move {move} is not legal in position {self.fen()}")
277+
278
        piece = self.piece_at(move.from_square)
279
        captured_piece = self.piece_at(move.to_square)
280
281
        self.clear_square(move.from_square)
282
        self.clear_square(move.to_square)
283
        self._set_piece_at(move.to_square, piece.piece_type, piece.color)
284
285
        self.turn = not self.turn
286
287
        self.move_stack.append((move, captured_piece))
288
289
    def pop(self):
290
        if not self.move_stack:
291
            return None
292
293
        move, captured_piece = self.move_stack.pop()
294
295
        piece = self.piece_at(move.to_square)
296
        
297
        self.clear_square(move.from_square)
298
        self.clear_square(move.to_square)
299
300
        self._set_piece_at(move.from_square, piece.piece_type, piece.color)
301
302
        if captured_piece:
303
            self._set_piece_at(move.to_square, captured_piece.piece_type, captured_piece.color)
304
305
        self.turn = not self.turn
306
307
        return move
308
309
    def is_check(self):
310
        king_square = self.king(self.turn)
311
        if king_square is None:
312
            return False
313
        return self._is_attacked_by(not self.turn, king_square)[0]
314
315
    def is_checkmate(self):
316
        if not self.is_check():
317
            return False
318
        return not any(self.generate_legal_moves())
319
320
    def is_stalemate(self):
321
        if self.is_check():
322
            return False
323
        return not any(self.generate_legal_moves())
324
    
325
    def is_insufficient_material(self):
326
            return (self.pawns | self.rooks | self.queens | self.amazons_white | self.amazons_black |
327
                    self.cyrils_white | self.cyrils_black | self.eves_white | self.eves_black) == 0 and (
328
                chess.popcount(self.occupied) <= 3
329
            )
330
331
    def is_game_over(self):
332
        return self.is_checkmate() or self.is_stalemate() or self.is_insufficient_material()
333
334
    def debug_amazons(self):
335
        print(f"Bitboard bílých amazonek: {format(self.amazons_white, '064b')}")
336
        print(f"Bitboard černých amazonek: {format(self.amazons_black, '064b')}")
337
        for square in chess.SQUARES:
338
            if self.amazons_white & chess.BB_SQUARES[square]:
339
                print(f"Bílá amazonka na {chess.SQUARE_NAMES[square]}")
340
            if self.amazons_black & chess.BB_SQUARES[square]:
341
                print(f"Černá amazonka na {chess.SQUARE_NAMES[square]}")
342
343
    def debug_cyrils(self):
344
        print(f"Bitboard bílých Cyrils: {format(self.cyrils_white, '064b')}")
345
        print(f"Bitboard černých Cyrils: {format(self.cyrils_black, '064b')}")
346
        for square in chess.SQUARES:
347
            if self.cyrils_white & chess.BB_SQUARES[square]:
348
                print(f"Bílý Cyril na {chess.SQUARE_NAMES[square]}")
349
            if self.cyrils_black & chess.BB_SQUARES[square]:
350
                print(f"Černý Cyril na {chess.SQUARE_NAMES[square]}")
351
352
    def debug_eves(self):
353
        print(f"Bitboard bílých Eves: {format(self.eves_white, '064b')}")
354
        print(f"Bitboard černých Eves: {format(self.eves_black, '064b')}")
355
        for square in chess.SQUARES:
356
            if self.eves_white & chess.BB_SQUARES[square]:
357
                print(f"Bílá Eve na {chess.SQUARE_NAMES[square]}")
358
            if self.eves_black & chess.BB_SQUARES[square]:
359
                print(f"Černá Eve na {chess.SQUARE_NAMES[square]}")
360
361
    def piece_symbol(self, piece):
362
        if piece is None:
363
            return '.'
364
        if piece.piece_type == AMAZON:
365
            return 'A' if piece.color == chess.WHITE else 'a'
366
        if piece.piece_type == CYRIL:
367
            return 'C' if piece.color == chess.WHITE else 'c'
368
        if piece.piece_type == EVE:
369
            return 'E' if piece.color == chess.WHITE else 'e'
370
        return piece.symbol()
371
372
    def piece_type_at(self, square):
373
        if (self.amazons_white | self.amazons_black) & chess.BB_SQUARES[square]:
374
            return AMAZON
375
        if (self.cyrils_white | self.cyrils_black) & chess.BB_SQUARES[square]:
376
            return CYRIL
377
        if (self.eves_white | self.eves_black) & chess.BB_SQUARES[square]:
378
            return EVE
379
        return super().piece_type_at(square)
380
381
    def color_at(self, square):
382
        if self.amazons_white & chess.BB_SQUARES[square]:
383
            return chess.WHITE
384
        if self.amazons_black & chess.BB_SQUARES[square]:
385
            return chess.BLACK
386
        if self.cyrils_white & chess.BB_SQUARES[square]:
387
            return chess.WHITE
388
        if self.cyrils_black & chess.BB_SQUARES[square]:
389
            return chess.BLACK
390
        if self.eves_white & chess.BB_SQUARES[square]:
391
            return chess.WHITE
392
        if self.eves_black & chess.BB_SQUARES[square]:
393
            return chess.BLACK
394
        return super().color_at(square)
395
396
    @property
397
    def legal_moves(self):
398
        return [move for move in self.generate_pseudo_legal_moves() if self.is_legal(move)]
399
400
    def __str__(self):
401
        builder = []
402
        for square in chess.SQUARES_180:
403
            piece = self.piece_at(square)
404
            symbol = self.piece_symbol(piece) if piece else '.'
405
            builder.append(symbol)
406
            if chess.square_file(square) == 7:
407
                if square != chess.H1:
408
                    builder.append('\n')
409
        return ''.join(builder)
410
411
    def simplify_fen_string(fen):
412
        parts = fen.split(' ')
413
        return ' '.join(parts[:4])  # Zachováváme pouze informace o pozici, barvě na tahu, rošádách a en passant
414
    
415
    def format_time(seconds):
416
        hours, remainder = divmod(seconds, 3600)
417
        minutes, seconds = divmod(remainder, 60)
418
        return f"{int(hours):02d}h {int(minutes):02d}m {int(seconds):02d}s"
419
    
420
    def print_elapsed_time(stop_event):
421
        start_time = time.time()
422
        while not stop_event.is_set():
423
            elapsed_time = time.time() - start_time
424
            formatted_time = time.strftime("%H:%M:%S", time.gmtime(elapsed_time))
425
            print(f"Uplynulý čas: {formatted_time}", end='\r')
426
            time.sleep(1)
427
    
428
    def calculate_optimal_moves(start_fen: str) -> Dict[str, int]:
429
        board = CustomBoard(start_fen)
430
        AR = {simplify_fen_string(start_fen): 0}
431
        queue = deque([(simplify_fen_string(start_fen), 0)])
432
        visited = set()
433
    
434
        start_time = time.time()
435
        current_level = 0
436
        pozice_na_urovni = 0
437
        level_start_time = start_time
438
    
439
        stop_event = threading.Event()
440
        timer_thread = threading.Thread(target=print_elapsed_time, args=(stop_event,))
441
        timer_thread.start()
442
    
443
        try:
444
            while queue:
445
                fen, hloubka = queue.popleft()
446
    
447
                if hloubka > current_level:
448
                    level_time = time.time() - level_start_time
449
                    print(f"\nHloubka {current_level}: {pozice_na_urovni} pozic, Čas: {format_time(level_time)}")
450
                    current_level = hloubka
451
                    pozice_na_urovni = 0
452
                    level_start_time = time.time()
453
    
454
                if fen in visited:
455
                    continue
456
    
457
                visited.add(fen)
458
                pozice_na_urovni += 1
459
                board.set_custom_fen(fen)
460
    
461
                if board.is_checkmate():
462
                    AR[fen] = -1000 + hloubka if board.turn == chess.WHITE else 1000 - hloubka
463
                    continue
464
                elif board.is_stalemate() or board.is_insufficient_material():
465
                    AR[fen] = 0
466
                    continue
467
    
468
                legal_moves = list(board.legal_moves)
469
    
470
                for move in legal_moves:
471
                    board.push(move)
472
                    new_fen = simplify_fen_string(board.fen())
473
                    if new_fen not in AR:
474
                        AR[new_fen] = 0
475
                        queue.append((new_fen, hloubka + 1))
476
                    board.pop()
477
    
478
            level_time = time.time() - level_start_time
479
            print(f"\nHloubka {current_level}: {pozice_na_urovni} pozic, Čas: {format_time(level_time)}")
480
    
481
            # Procházení a aktualizace hodnot
482
            changed = True
483
            while changed:
484
                changed = False
485
                for fen in AR:
486
                    board.set_custom_fen(fen)
487
                    if board.is_game_over():
488
                        continue
489
    
490
                    legal_moves = list(board.legal_moves)
491
                    if board.turn == chess.WHITE:
492
                        best_value = max(AR[simplify_fen_string(board.fen())] for _ in [board.push(move), board.pop()] for move in legal_moves)
493
                        if best_value > AR[fen]:
494
                            AR[fen] = best_value
495
                            changed = True
496
                    else:
497
                        best_value = min(AR[simplify_fen_string(board.fen())] for _ in [board.push(move), board.pop()] for move in legal_moves)
498
                        if best_value < AR[fen]:
499
                            AR[fen] = best_value
500
                            changed = True
501
    
502
            celkovy_cas = time.time() - start_time
503
            print(f"\nVýpočet dokončen za {format_time(celkovy_cas)}")
504
    
505
        finally:
506
            stop_event.set()
507
            timer_thread.join()
508
    
509
        return AR
510
511
if __name__ == "__main__":
512
    start_fen = "8/3k4/8/8/8/8/A7/6K1 w - - 0 1"
513
    AR = calculate_optimal_moves(start_fen)
514
515
    # Výpis výsledků
516
    for fen, hodnota in AR.items():
517
        print(f"FEN: {fen}")
518
        print(f"Hodnota: {hodnota}")
519
        print()