Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import time
- from datetime import datetime
- from collections import deque
- import copy
- def print_board(board):
- print(str(board))
- class ChessBoard:
- """Vlastní implementace šachovnice pro pohádkové figury"""
- def __init__(self, fen=None):
- self.board = [[None for _ in range(8)] for _ in range(8)]
- self.turn = True # True = bílý, False = černý
- self.castling_rights = {'K': True, 'Q': True, 'k': True, 'q': True}
- self.en_passant = None
- self.halfmove_clock = 0
- self.fullmove_number = 1
- if fen:
- self.set_fen(fen)
- else:
- self.setup_initial_position()
- def __str__(self):
- rows = []
- for i, row in enumerate(self.board[::-1], 1):
- safe_row = []
- for piece in row:
- if piece is None:
- safe_row.append('.')
- elif isinstance(piece, tuple):
- color, symbol = piece
- safe_row.append(symbol.upper() if color == 'w' else symbol.lower())
- else:
- safe_row.append(str(piece))
- rows.append(f"{8 - i + 1} " + " ".join(safe_row))
- rows.append(" a b c d e f g h")
- return "\n".join(rows)
- def setup_initial_position(self):
- """Nastaví základní pozici"""
- pieces = ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
- for i, piece in enumerate(pieces):
- self.board[0][i] = ('w', piece)
- for i in range(8):
- self.board[1][i] = ('w', 'P')
- pieces = ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
- for i, piece in enumerate(pieces):
- self.board[7][i] = ('b', piece)
- for i in range(8):
- self.board[6][i] = ('b', 'P')
- def set_fen(self, fen_string):
- """Nastaví pozici z FEN stringu"""
- parts = fen_string.split()
- # Pozice figur
- rows = parts[0].split('/')
- for rank, row in enumerate(rows):
- file_idx = 0
- for char in row:
- if char.isdigit():
- for _ in range(int(char)):
- self.board[7-rank][file_idx] = None
- file_idx += 1
- else:
- color = 'w' if char.isupper() else 'b'
- self.board[7-rank][file_idx] = (color, char.upper())
- file_idx += 1
- # Na tahu
- self.turn = parts[1] == 'w'
- # Rošáda
- castling = parts[2] if len(parts) > 2 else '-'
- self.castling_rights = {
- 'K': 'K' in castling,
- 'Q': 'Q' in castling,
- 'k': 'k' in castling,
- 'q': 'q' in castling
- }
- # En passant
- self.en_passant = parts[3] if len(parts) > 3 and parts[3] != '-' else None
- # Počítadla tahů
- self.halfmove_clock = int(parts[4]) if len(parts) > 4 else 0
- self.fullmove_number = int(parts[5]) if len(parts) > 5 else 1
- def get_fen(self):
- """Vrátí FEN string pozice"""
- fen_parts = []
- for rank in range(7, -1, -1):
- row_fen = ""
- empty_count = 0
- for file in range(8):
- piece = self.board[rank][file]
- if piece is None:
- empty_count += 1
- else:
- if empty_count > 0:
- row_fen += str(empty_count)
- empty_count = 0
- color, piece_type = piece
- row_fen += piece_type if color == 'w' else piece_type.lower()
- if empty_count > 0:
- row_fen += str(empty_count)
- fen_parts.append(row_fen)
- position = '/'.join(fen_parts)
- # Na tahu
- turn = 'w' if self.turn else 'b'
- # Rošáda
- castling = ''
- for right in ['K', 'Q', 'k', 'q']:
- if self.castling_rights[right]:
- castling += right
- if not castling:
- castling = '-'
- # En passant
- en_passant = self.en_passant if self.en_passant else '-'
- return f"{position} {turn} {castling} {en_passant} {self.halfmove_clock} {self.fullmove_number}"
- def copy(self):
- """Vytvoří kopii šachovnice"""
- new_board = ChessBoard()
- new_board.board = [row[:] for row in self.board]
- new_board.turn = self.turn
- new_board.castling_rights = self.castling_rights.copy()
- new_board.en_passant = self.en_passant
- new_board.halfmove_clock = self.halfmove_clock
- new_board.fullmove_number = self.fullmove_number
- return new_board
- def make_move(self, from_pos, to_pos):
- """Provede tah"""
- from_rank, from_file = from_pos
- to_rank, to_file = to_pos
- piece = self.board[from_rank][from_file]
- self.board[to_rank][to_file] = piece
- self.board[from_rank][from_file] = None
- # Změnit hráče na tahu
- self.turn = not self.turn
- # Inkrementovat počítadla
- if self.turn:
- self.fullmove_number += 1
- self.halfmove_clock += 1
- def find_king(self, color):
- """Najde krále dané barvy"""
- for rank in range(8):
- for file in range(8):
- piece = self.board[rank][file]
- if piece and piece[0] == color and piece[1] == 'K':
- return (rank, file)
- return None
- def is_check(self, color):
- """Kontroluje zda je král v šachu"""
- king_pos = self.find_king(color)
- if not king_pos:
- return False
- enemy_color = 'b' if color == 'w' else 'w'
- for rank in range(8):
- for file in range(8):
- piece = self.board[rank][file]
- if piece and piece[0] == enemy_color:
- if self.attacks_square((rank, file), king_pos):
- return True
- return False
- def attacks_square(self, from_pos, to_pos):
- """Kontroluje zda figura útočí na dané pole"""
- rank, file = from_pos
- piece = self.board[rank][file]
- if not piece:
- return False
- color, piece_type = piece
- if piece_type == 'P':
- return self.pawn_attacks(from_pos, to_pos, color)
- elif piece_type == 'R':
- return self.rook_attacks(from_pos, to_pos)
- elif piece_type == 'N':
- return self.knight_attacks(from_pos, to_pos)
- elif piece_type == 'B':
- return self.bishop_attacks(from_pos, to_pos)
- elif piece_type == 'Q':
- return self.queen_attacks(from_pos, to_pos)
- elif piece_type == 'K':
- return self.king_attacks(from_pos, to_pos)
- elif piece_type == 'A': # Amazonka (Q+N)
- return self.queen_attacks(from_pos, to_pos) or self.knight_attacks(from_pos, to_pos)
- elif piece_type == 'C': # Cyril (R+N)
- return self.rook_attacks(from_pos, to_pos) or self.knight_attacks(from_pos, to_pos)
- elif piece_type == 'E': # Eve (B+N)
- return self.bishop_attacks(from_pos, to_pos) or self.knight_attacks(from_pos, to_pos)
- return False
- def pawn_attacks(self, from_pos, to_pos, color):
- """Kontroluje útok pěšce"""
- from_rank, from_file = from_pos
- to_rank, to_file = to_pos
- direction = 1 if color == 'w' else -1
- return (abs(from_file - to_file) == 1 and
- to_rank - from_rank == direction)
- def knight_attacks(self, from_pos, to_pos):
- """Kontroluje útok jezdce"""
- from_rank, from_file = from_pos
- to_rank, to_file = to_pos
- dr = abs(from_rank - to_rank)
- df = abs(from_file - to_file)
- return (dr == 2 and df == 1) or (dr == 1 and df == 2)
- def rook_attacks(self, from_pos, to_pos):
- """Kontroluje útok věže"""
- from_rank, from_file = from_pos
- to_rank, to_file = to_pos
- if from_rank != to_rank and from_file != to_file:
- return False
- if from_rank == to_rank: # Horizontální
- start = min(from_file, to_file) + 1
- end = max(from_file, to_file)
- for f in range(start, end):
- if self.board[from_rank][f] is not None:
- return False
- else: # Vertikální
- start = min(from_rank, to_rank) + 1
- end = max(from_rank, to_rank)
- for r in range(start, end):
- if self.board[r][from_file] is not None:
- return False
- return True
- def bishop_attacks(self, from_pos, to_pos):
- """Kontroluje útok střelce"""
- from_rank, from_file = from_pos
- to_rank, to_file = to_pos
- dr = to_rank - from_rank
- df = to_file - from_file
- if abs(dr) != abs(df) or dr == 0:
- return False
- step_r = 1 if dr > 0 else -1
- step_f = 1 if df > 0 else -1
- steps = abs(dr)
- for i in range(1, steps):
- check_rank = from_rank + i * step_r
- check_file = from_file + i * step_f
- if self.board[check_rank][check_file] is not None:
- return False
- return True
- def queen_attacks(self, from_pos, to_pos):
- """Kontroluje útok dámy"""
- return self.rook_attacks(from_pos, to_pos) or self.bishop_attacks(from_pos, to_pos)
- def king_attacks(self, from_pos, to_pos):
- """Kontroluje útok krále"""
- from_rank, from_file = from_pos
- to_rank, to_file = to_pos
- return (abs(from_rank - to_rank) <= 1 and
- abs(from_file - to_file) <= 1 and
- from_pos != to_pos)
- class FairyChessAnalyzer:
- def __init__(self, start_fen):
- self.start_fen = start_fen
- self.L = []
- self.fen_to_index = {}
- self.iteration_count = 0
- # Konstanty pro hodnocení
- self.WHITE_MATE = -1000
- self.BLACK_MATE = 1000
- self.DRAW = 0
- def print_time(self, message=""):
- """Vypíše čas ve formátu %hh%mm%ss"""
- now = datetime.now()
- time_str = now.strftime("%H:%M:%S")
- print(f"[{time_str}] Průchod {self.iteration_count}: {message}")
- def generate_moves(self, board, from_pos):
- """Generuje všechny možné tahy z dané pozice"""
- rank, file = from_pos
- piece = board.board[rank][file]
- if not piece:
- return []
- color, piece_type = piece
- moves = []
- # Kontrola zda je figura na tahu
- if (color == 'w') != board.turn:
- return []
- if piece_type == 'P':
- moves = self.generate_pawn_moves(board, from_pos)
- elif piece_type == 'R':
- moves = self.generate_rook_moves(board, from_pos)
- elif piece_type == 'N':
- moves = self.generate_knight_moves(board, from_pos)
- elif piece_type == 'B':
- moves = self.generate_bishop_moves(board, from_pos)
- elif piece_type == 'Q':
- moves = self.generate_queen_moves(board, from_pos)
- elif piece_type == 'K':
- moves = self.generate_king_moves(board, from_pos)
- elif piece_type == 'A': # Amazonka (Q+N)
- moves = self.generate_queen_moves(board, from_pos) + self.generate_knight_moves(board, from_pos)
- elif piece_type == 'C': # Cyril (R+N)
- moves = self.generate_rook_moves(board, from_pos) + self.generate_knight_moves(board, from_pos)
- elif piece_type == 'E': # Eve (B+N)
- moves = self.generate_bishop_moves(board, from_pos) + self.generate_knight_moves(board, from_pos)
- # Filtrovat legální tahy
- legal_moves = []
- for move in moves:
- if self.is_move_legal(board, from_pos, move):
- legal_moves.append(move)
- return legal_moves
- def generate_pawn_moves(self, board, from_pos):
- """Generuje tahy pěšce"""
- rank, file = from_pos
- piece = board.board[rank][file]
- color = piece[0]
- moves = []
- direction = 1 if color == 'w' else -1
- start_rank = 1 if color == 'w' else 6
- # Tah dopředu
- new_rank = rank + direction
- if 0 <= new_rank <= 7 and board.board[new_rank][file] is None:
- moves.append((new_rank, file))
- # Dvojitý tah z výchozí pozice
- if rank == start_rank and board.board[new_rank + direction][file] is None:
- moves.append((new_rank + direction, file))
- # Braní
- for df in [-1, 1]:
- new_file = file + df
- if 0 <= new_file <= 7 and 0 <= new_rank <= 7:
- target = board.board[new_rank][new_file]
- if target and target[0] != color:
- moves.append((new_rank, new_file))
- return moves
- def generate_knight_moves(self, board, from_pos):
- """Generuje tahy jezdce"""
- rank, file = from_pos
- piece = board.board[rank][file]
- color = piece[0]
- moves = []
- knight_moves = [(2,1), (2,-1), (-2,1), (-2,-1), (1,2), (1,-2), (-1,2), (-1,-2)]
- for dr, df in knight_moves:
- new_rank = rank + dr
- new_file = file + df
- if 0 <= new_rank <= 7 and 0 <= new_file <= 7:
- target = board.board[new_rank][new_file]
- if not target or target[0] != color:
- moves.append((new_rank, new_file))
- return moves
- def generate_sliding_moves(self, board, from_pos, directions):
- """Generuje tahy pro posuvné figury"""
- rank, file = from_pos
- piece = board.board[rank][file]
- color = piece[0]
- moves = []
- for dr, df in directions:
- for distance in range(1, 8):
- new_rank = rank + dr * distance
- new_file = file + df * distance
- if not (0 <= new_rank <= 7 and 0 <= new_file <= 7):
- break
- target = board.board[new_rank][new_file]
- if not target:
- moves.append((new_rank, new_file))
- elif target[0] != color:
- moves.append((new_rank, new_file))
- break
- else:
- break
- return moves
- def generate_rook_moves(self, board, from_pos):
- """Generuje tahy věže"""
- directions = [(0,1), (0,-1), (1,0), (-1,0)]
- return self.generate_sliding_moves(board, from_pos, directions)
- def generate_bishop_moves(self, board, from_pos):
- """Generuje tahy střelce"""
- directions = [(1,1), (1,-1), (-1,1), (-1,-1)]
- return self.generate_sliding_moves(board, from_pos, directions)
- def generate_queen_moves(self, board, from_pos):
- """Generuje tahy dámy"""
- directions = [(0,1), (0,-1), (1,0), (-1,0), (1,1), (1,-1), (-1,1), (-1,-1)]
- return self.generate_sliding_moves(board, from_pos, directions)
- def generate_king_moves(self, board, from_pos):
- """Generuje tahy krále"""
- rank, file = from_pos
- piece = board.board[rank][file]
- color = piece[0]
- moves = []
- for dr in [-1, 0, 1]:
- for df in [-1, 0, 1]:
- if dr == 0 and df == 0:
- continue
- new_rank = rank + dr
- new_file = file + df
- if 0 <= new_rank <= 7 and 0 <= new_file <= 7:
- target = board.board[new_rank][new_file]
- if not target or target[0] != color:
- moves.append((new_rank, new_file))
- return moves
- def is_move_legal(self, board, from_pos, to_pos):
- """Kontroluje zda je tah legální"""
- temp_board = board.copy()
- temp_board.make_move(from_pos, to_pos)
- # Kontrola zda vlastní král není v šachu
- color = 'w' if board.turn else 'b'
- return not temp_board.is_check(color)
- def get_all_legal_moves(self, board):
- """Získá všechny legální tahy"""
- moves = []
- for rank in range(8):
- for file in range(8):
- piece = board.board[rank][file]
- if piece:
- color = piece[0]
- if (color == 'w') == board.turn:
- piece_moves = self.generate_moves(board, (rank, file))
- for to_pos in piece_moves:
- moves.append((rank, file, to_pos[0], to_pos[1]))
- return moves
- def evaluate_position(self, board):
- """Evaluuje pozici - vrací hodnotu z pohledu bílého"""
- # Získat všechny legální tahy
- legal_moves = self.get_all_legal_moves(board)
- # Kontrola koncových pozic
- if len(legal_moves) == 0:
- current_color = 'w' if board.turn else 'b'
- if board.is_check(current_color):
- # Mat - pokud je na tahu bílý a je mat, černý vyhrál
- return self.WHITE_MATE if board.turn else self.BLACK_MATE
- else:
- # Pat
- return self.DRAW
- # Pozice není koncová
- return None
- def create_position_dict(self, fen, depth):
- """Vytvoří slovník pro pozici"""
- board = ChessBoard(fen)
- evaluation = self.evaluate_position(board)
- return {
- 'depth': depth,
- 'N': [], # Následníci
- 'P': [], # Předchůdci
- 'FEN': board.get_fen(),
- 'board': board,
- 'evaluation': evaluation,
- 'minimax_value': evaluation
- }
- def build_position_tree(self, max_depth=5):
- """Postaví strom pozic pomocí BFS"""
- self.iteration_count += 1
- self.print_time("Začátek generování stromu pozic")
- # Inicializace
- start_dict = self.create_position_dict(self.start_fen, 0)
- self.L.append(start_dict)
- self.fen_to_index[start_dict['FEN']] = 0
- queue = deque([0])
- for depth in range(max_depth):
- self.iteration_count += 1
- if not queue:
- break
- level_size = len(queue)
- self.print_time(f"Hloubka {depth}, pozic: {level_size}")
- for _ in range(level_size):
- if not queue:
- break
- current_index = queue.popleft()
- current_pos = self.L[current_index]
- # Přeskočit koncové pozice
- if current_pos['evaluation'] is not None:
- continue
- try:
- # Získat všechny legální tahy
- legal_moves = self.get_all_legal_moves(current_pos['board'])
- for move in legal_moves:
- from_rank, from_file, to_rank, to_file = move
- # Vytvořit novou pozici
- temp_board = current_pos['board'].copy()
- temp_board.make_move((from_rank, from_file), (to_rank, to_file))
- new_fen = temp_board.get_fen()
- if new_fen not in self.fen_to_index:
- # Nová pozice
- new_dict = self.create_position_dict(new_fen, depth + 1)
- new_index = len(self.L)
- self.L.append(new_dict)
- self.fen_to_index[new_fen] = new_index
- queue.append(new_index)
- else:
- new_index = self.fen_to_index[new_fen]
- # Přidat vazby
- if new_index not in current_pos['N']:
- current_pos['N'].append(new_index)
- if current_index not in self.L[new_index]['P']:
- self.L[new_index]['P'].append(current_index)
- except Exception as e:
- self.print_time(f"Chyba při generování tahů: {e}")
- continue
- self.print_time(f"Strom postaven, celkem pozic: {len(self.L)}")
- def minimax_evaluation(self):
- """Provede minimax evaluaci pozic"""
- self.iteration_count += 1
- self.print_time("Začátek minimax evaluace")
- changed = True
- iterations = 0
- while changed and iterations < 100:
- changed = False
- iterations += 1
- if iterations % 10 == 0:
- self.print_time(f"Iterace minimax {iterations}")
- # Procházet od nejhlubších pozic
- for i in reversed(range(len(self.L))):
- pos = self.L[i]
- # Přeskočit už vyhodnocené pozice
- if pos['minimax_value'] is not None:
- continue
- # Pokud nemá následníky, už je vyhodnocena
- if not pos['N']:
- continue
- # Zkontrolovat zda jsou všichni následníci vyhodnoceni
- successor_values = []
- all_evaluated = True
- for succ_idx in pos['N']:
- if succ_idx < len(self.L):
- succ_value = self.L[succ_idx]['minimax_value']
- if succ_value is not None:
- successor_values.append(succ_value)
- else:
- all_evaluated = False
- break
- if all_evaluated and successor_values:
- # Aplikovat minimax
- if pos['board'].turn: # Bílý na tahu - minimalizuje
- pos['minimax_value'] = min(successor_values)
- else: # Černý na tahu - maximalizuje
- pos['minimax_value'] = max(successor_values)
- # Přidat penalizaci za vzdálenost (pokud je to matová pozice)
- if pos['minimax_value'] == self.WHITE_MATE:
- pos['minimax_value'] = self.WHITE_MATE + pos['depth']
- elif pos['minimax_value'] == self.BLACK_MATE:
- pos['minimax_value'] = self.BLACK_MATE - pos['depth']
- changed = True
- self.print_time(f"Minimax evaluace dokončena po {iterations} iteracích")
- def find_best_move_sequence(self, start_index=0):
- """Najde nejlepší sekvenci tahů"""
- sequence = []
- current_idx = start_index
- visited = set()
- while current_idx not in visited and current_idx < len(self.L):
- visited.add(current_idx)
- current_pos = self.L[current_idx]
- sequence.append(current_idx)
- # Pokud nemá následníky, konec
- if not current_pos['N']:
- break
- # Najít nejlepší tah
- best_idx = None
- best_value = None
- for succ_idx in current_pos['N']:
- if succ_idx < len(self.L):
- succ_value = self.L[succ_idx]['minimax_value']
- if succ_value is not None:
- if best_value is None:
- best_value = succ_value
- best_idx = succ_idx
- else:
- # Vybrat tah podle minimax
- if current_pos['board'].turn: # Bílý minimalizuje
- if succ_value < best_value:
- best_value = succ_value
- best_idx = succ_idx
- else: # Černý maximalizuje
- if succ_value > best_value:
- best_value = succ_value
- best_idx = succ_idx
- if best_idx is None:
- break
- current_idx = best_idx
- return sequence
- def analyze(self, max_depth=6):
- """Hlavní analýza"""
- start_time = time.time()
- self.iteration_count += 1
- self.print_time("Začátek analýzy")
- try:
- # Postavit strom
- self.build_position_tree(max_depth)
- # Provést minimax evaluaci
- self.minimax_evaluation()
- # Najít nejlepší sekvenci
- best_sequence = self.find_best_move_sequence()
- end_time = time.time()
- self.iteration_count += 1
- self.print_time(f"Analýza dokončena za {end_time - start_time:.2f} sekund")
- # Výsledky
- print(f"\nCelkem pozic: {len(self.L)}")
- print(f"Nejlepší sekvence má {len(best_sequence)} tahů")
- if self.L:
- start_value = self.L[0]['minimax_value']
- print(f"Hodnota počáteční pozice: {start_value}")
- if start_value == self.WHITE_MATE:
- print("Bílý je v matu!")
- elif start_value == self.BLACK_MATE:
- print("Černý je v matu!")
- elif start_value == self.DRAW:
- print("Pozice je remíza")
- elif start_value is not None:
- if start_value > 0:
- print(f"Černý má výhodu ({start_value})")
- else:
- print(f"Bílý má výhodu ({start_value})")
- print(f"\nNejlepší sekvence (prvních 10 tahů):")
- for i, pos_idx in enumerate(best_sequence[:10]):
- if pos_idx < len(self.L):
- pos = self.L[pos_idx]
- turn = "Bílý" if pos['board'].turn else "Černý"
- value = pos['minimax_value']
- print(f"Tah {i} ({turn}): hodnota={value}")
- print_board(pos['board'])
- print()
- return best_sequence
- except Exception as e:
- self.iteration_count += 1
- self.print_time(f"Chyba při analýze: {e}")
- import traceback
- traceback.print_exc()
- return []
- def print_board2(fen):
- """Pomocná funkce pro tisk pozice z FEN"""
- rows = fen.split()[0].split("/")
- print(" +-----------------+")
- for i, row in enumerate(rows):
- line = ""
- for char in row:
- if char.isdigit():
- line += "." * int(char)
- else:
- line += char
- print(f"{8 - i} | {' '.join(line)} |")
- print(" +-----------------+")
- print(" a b c d e f g h")
- # Testování
- if __name__ == "__main__":
- # Testovací pozice
- simple_mate = "8/8/8/8/A7/4c1k1/8/6K1 w - - 0 1"
- simple_mate = "8/8/8/8/A7/4c1k1/8/6K1 w - - 0 1"
- print("TESTOVÁNÍ KOREKTNÍHO ŠACHOVÉHO ANALYZÁTORU")
- print("=" * 60)
- print("Výchozí pozice:")
- print_board2(simple_mate)
- print(f"\nAnalýza pozice: {simple_mate}")
- print("Pohádkové figury: A=Amazonka(Q+N), C=Cyril(R+N), E=Eve(B+N)")
- print("="*60)
- analyzer = FairyChessAnalyzer(simple_mate)
- best_sequence = analyzer.analyze(max_depth=8)
- print(f"\nFINÁLNÍ VÝSLEDEK:")
- print(f"Pozic: {len(analyzer.L)}")
- print(f"Nejlepší sekvence: {len(best_sequence)} tahů")
- # Detailní analýza prvních tahů
- if len(best_sequence) > 1:
- print("\nDetailní analýza prvních tahů:")
- for i in range(min(3, len(best_sequence) - 1)):
- current_pos = analyzer.L[best_sequence[i]]
- next_pos = analyzer.L[best_sequence[i + 1]]
- turn = "Bílý" if current_pos['board'].turn else "Černý"
- value = next_pos['minimax_value']
- print(f"\nTah {i + 1} ({turn}): hodnota po tahu = {value}")
- print("Po tahu:")
- print_board(next_pos['board'])
- # Zkontrolovat všechny možné následující tahy
- legal_moves = analyzer.get_all_legal_moves(next_pos['board'])
- print(f"Možných tahů pro {('Černý' if next_pos['board'].turn else 'Bílý')}: {len(legal_moves)}")
- if len(legal_moves) == 0:
- if next_pos['board'].is_check('w' if next_pos['board'].turn else 'b'):
- print("*** POZICE JE MAT! ***")
- else:
- print("*** POZICE JE PAT! ***")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement