import pygame import sys import random import time pygame.init() # --- ПАРАМЕТРЫ ПОЛЯ --- CELL_SIZE = 32 GRID_WIDTH = 30 GRID_HEIGHT = 30 WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT # --- ЦВЕТА --- COLOR_BG = (30, 30, 30) COLOR_GRID = (50, 50, 50) COLOR_FOG = (20, 20, 20) COLOR_SELECTED = (255, 0, 0) COLOR_MOVE = (0, 255, 0) COLOR_WHITE_PIECE = (255, 255, 255) # Заливка белых фигур COLOR_BLACK_PIECE = (0, 0, 0) # Заливка чёрных фигур # Новые цвета для бонусов COLOR_REGEN = (0, 255, 0) # Зелёный COLOR_HP_UPGRADE = (0, 128, 255) # Синий COLOR_DAMAGE = (255, 0, 0) # Красный COLOR_KING_HP_UPGRADE = (255, 215, 0) # Золотой COLOR_ADD_PIECE = (255, 255, 255) # Белый (с плюсом) COLOR_ADD_PIECE_PINK = (255, 192, 203) # Розовый # Цвет текущего хода COLOR_TURN_TEXT = (255, 255, 255) # Белый # Новый цвет для пометки клеток, которые скоро уйдут в туман войны COLOR_PRE_FOG = (169, 169, 169) # Светло-серый # Цвет таймера COLOR_TIMER_NORMAL = (255, 255, 255) # Белый COLOR_TIMER_WARNING = (255, 0, 0) # Красный screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption("Шахматы с открытым миром 1.0") # Шрифты font = pygame.font.SysFont(None, CELL_SIZE // 2) victory_font = pygame.font.SysFont(None, 60) turn_font = pygame.font.SysFont(None, 30) counter_font = pygame.font.SysFont(None, 30) timer_font = pygame.font.SysFont(None, 25) # --- Глобальное множество открытых клеток (туман войны) --- global_revealed = set() # --- Словарь символов --- piece_symbols = { 'rook': 'Л', 'knight': 'К', 'bishop': 'С', 'queen': 'Ф', 'king': 'К', # Король (можно заменить на 'K' или другой символ) 'pawn': 'П' } # === МЕХАНИКА БОНУСОВ === # Храним в глобальном словаре bonus_cells: {(x,y): {'type': ...}} bonus_cells = {} # Вероятности появления различных типов бонусов base_bonus_probabilities = { 'regen': 0.42, # 42% 'hp_upgrade': 0.32, # 32% 'damage': 0.2, # 20% 'king_hp_upgrade': 0.05, # 5% 'add_piece': 0.01 # 1% } bonus_probabilities = base_bonus_probabilities.copy() # Функция для выбора бонуса на основе вероятностей def choose_bonus_type(): rand = random.random() cumulative = 0 for bonus_type, prob in bonus_probabilities.items(): cumulative += prob if rand < cumulative: return bonus_type return 'regen' # По умолчанию # === МЕХАНИКА БОЯ === def resolve_combat(attacker, defender): """ Атакующая фигура (A) идёт на клетку защитника (B). HP(A)=a, HP(B)=b. - Если a < b: B теряет a (b := b-a), A погибает. - Если a > b: A теряет b (a := a-b), B погибает. - Если a == b: A побеждает, убивает B, а у A остаётся 1 HP. Возвращаем кортеж: (attacker_alive, defender_alive) при этом изменяем HP в самих объектах. """ a = attacker.hp b = defender.hp if a < b: # Защитник теряет a HP defender.hp = b - a # Атакующая погибает attacker.hp = 0 return (False, True) elif a > b: # Атакующая теряет b HP attacker.hp = a - b # Защитник погибает defender.hp = 0 return (True, False) else: # a == b # Атакующая побеждает, остаётся с 1 HP attacker.hp = 1 defender.hp = 0 return (True, False) # === КЛАСС ФИГУРЫ === class Piece: def __init__(self, name, color, x, y): self.name = name self.color = color # 'white' или 'black' self.x = x self.y = y self.selected = False # Инициализация HP if self.name == 'king': self.max_hp = 5 self.hp = 5 elif self.name in ['rook', 'knight', 'bishop', 'queen']: self.max_hp = 3 self.hp = 3 elif self.name == 'pawn': self.max_hp = 1 self.hp = 1 def get_possible_moves(self, pieces): moves = [] if self.name == 'pawn': directions = [ (-1,-1), (0,-1), (1,-1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1) ] for dx, dy in directions: nx = self.x + dx ny = self.y + dy if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: blocking = next((p for p in pieces if p.x == nx and p.y == ny), None) # Можем ходить, если клетка пуста или там враг if not blocking or blocking.color != self.color: moves.append((nx, ny)) elif self.name in ['rook', 'bishop', 'queen']: # Ограничиваем движение до 3 клеток if self.name == 'rook': directions = [(-1,0),(1,0),(0,-1),(0,1)] elif self.name == 'bishop': directions = [(-1,-1),(1,-1),(1,1),(-1,1)] elif self.name == 'queen': directions = [ (-1,0),(1,0),(0,-1),(0,1), (-1,-1),(1,-1),(1,1),(-1,1) ] for dx, dy in directions: for step in range(1, 4): # До 3 клеток nx = self.x + dx * step ny = self.y + dy * step if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: blocking = next((p for p in pieces if p.x == nx and p.y == ny), None) if blocking: if blocking.color != self.color: moves.append((nx, ny)) break moves.append((nx, ny)) else: break elif self.name == 'knight': deltas = [(-2,-1),(-1,-2),(1,-2),(2,-1), (2,1),(1,2),(-1,2),(-2,1)] for dx, dy in deltas: nx, ny = self.x+dx, self.y+dy if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: blocking = next((p for p in pieces if p.x == nx and p.y == ny), None) if not blocking or blocking.color != self.color: moves.append((nx, ny)) elif self.name == 'king': directions = [ (-1,0),(1,0),(0,-1),(0,1), (-1,-1),(1,-1),(1,1),(-1,1) ] for dx, dy in directions: nx, ny = self.x+dx, self.y+dy if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: blocking = next((p for p in pieces if p.x == nx and p.y == ny), None) if not blocking or blocking.color != self.color: moves.append((nx, ny)) return moves # --- Инициализация фигур --- def initialize_pieces(): """Чёрные: y=2(back), y=3(pawns). Белые: y=27(back), y=26(pawns).""" pieces = [] cx = GRID_WIDTH//2 # ЧЁРНЫЕ black_back = ['rook','knight','bishop','king','queen','bishop','knight','rook'] for i,pname in enumerate(black_back): x = cx - 3 + i y = 2 pieces.append(Piece(pname,'black',x,y)) for i in range(8): x = cx-3 + i y = 3 pieces.append(Piece('pawn','black',x,y)) # БЕЛЫЕ white_back = ['rook','knight','bishop','king','queen','bishop','knight','rook'] for i,pname in enumerate(white_back): x = cx - 3 + i y = 27 pieces.append(Piece(pname,'white',x,y)) for i in range(8): x = cx-3 + i y = 26 pieces.append(Piece('pawn','white',x,y)) return pieces # Инициализация фигур pieces = initialize_pieces() # Remove initial bonus cell generation # initialize_bonus_cells(pieces, num_each=10) selected_piece = None possible_moves = [] game_over = False winner = None # --- Туман войны --- cell_counters = {} # {(x,y): turns_since_last_revealed} BONUS_GENERATION_CHANCE = 0.1 # 10% шанс появления бонуса при повторном открытии # --- Система ходов --- current_turn = 'white' # Начинаем с белых turn_count = 0 # --- Механика повышения вероятности красных полей --- def update_bonus_probabilities(): global bonus_probabilities if turn_count <= 100: bonus_probabilities = base_bonus_probabilities.copy() else: # Calculate how much to increase 'damage' probability extra_turns = turn_count - 100 # Let's say over the next 200 turns, it increases to 50% max_extra = 200 increase_per_turn = (0.50 - base_bonus_probabilities['damage']) / max_extra new_damage_prob = min(base_bonus_probabilities['damage'] + increase_per_turn * extra_turns, 0.50) # Adjust other probabilities accordingly remaining_prob = 1 - new_damage_prob total_other_probs = sum(base_bonus_probabilities[bt] for bt in base_bonus_probabilities if bt != 'damage') bonus_probabilities = {} for bt, prob in base_bonus_probabilities.items(): if bt == 'damage': bonus_probabilities[bt] = new_damage_prob else: bonus_probabilities[bt] = prob / total_other_probs * remaining_prob # --- Механика времени на ход --- TURN_TIME_LIMIT = 120 # seconds timer_start_time = time.time() timer_expired = False player_timeouts = {'white': 0, 'black': 0} def reset_timer(): global timer_start_time, timer_expired timer_start_time = time.time() timer_expired = False def get_time_left(): elapsed = time.time() - timer_start_time return max(0, TURN_TIME_LIMIT - int(elapsed)) def check_timer(): global timer_expired, game_over, winner time_left = get_time_left() if time_left <= 0 and not timer_expired: timer_expired = True player_timeouts[current_turn] += 1 if player_timeouts[current_turn] >= 2: # Opponent wins winner = 'black' if current_turn == 'white' else 'white' game_over = True else: # Switch turn switch_turn() def switch_turn(): global current_turn, turn_count, timer_expired current_turn = 'black' if current_turn == 'white' else 'white' turn_count += 1 reset_timer() timer_expired = False update_bonus_probabilities() def update_fog(): """ Обновляет туман войны: - Открытые клетки остаются открытыми. - Закрытые клетки, которые не находятся рядом с любыми фигурами на расстоянии 2 клеток, становятся закрытыми через 5 ходов. - При повторном открытии клетки шанс на появление нового бонуса. """ global global_revealed, bonus_cells # Сохраняем предыдущее состояние открытых клеток previous_revealed = global_revealed.copy() # Определяем все открытые клетки на основе позиций фигур new_revealed = set() for p in pieces: for dx in range(-2, 3): for dy in range(-2, 3): nx = p.x + dx ny = p.y + dy if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: new_revealed.add((nx, ny)) # Определяем только новые открытые клетки newly_revealed = new_revealed - previous_revealed # Обрабатываем все клетки for y in range(GRID_HEIGHT): for x in range(GRID_WIDTH): pos = (x, y) if pos in new_revealed: # Если клетка видна, сбрасываем счетчик cell_counters[pos] = 0 global_revealed.add(pos) else: # Если клетка не видна, увеличиваем счетчик if pos in cell_counters: cell_counters[pos] += 1 else: cell_counters[pos] = 1 # Если счетчик достигает 5, закрываем клетку if cell_counters[pos] >= 5: if pos in global_revealed: global_revealed.remove(pos) # Удаляем бонус, если клетка закрывается и бонус не является 'damage' if pos in bonus_cells and bonus_cells[pos]['type'] != 'damage': del bonus_cells[pos] # Генерируем бонусы только на новых открытых клетках for pos in newly_revealed: if pos not in bonus_cells and random.random() < BONUS_GENERATION_CHANCE: bonus_type = choose_bonus_type() # Проверяем, чтобы бонус не был под фигурой if not any(p.x == pos[0] and p.y == pos[1] for p in pieces): bonus_cells[pos] = {'type': bonus_type} def draw_grid(): for x in range(0, WINDOW_WIDTH, CELL_SIZE): pygame.draw.line(screen, COLOR_GRID, (x, 0), (x, WINDOW_HEIGHT)) for y in range(0, WINDOW_HEIGHT, CELL_SIZE): pygame.draw.line(screen, COLOR_GRID, (0, y), (WINDOW_WIDTH, y)) def draw_fog(): for y in range(GRID_HEIGHT): for x in range(GRID_WIDTH): pos = (x, y) if pos not in global_revealed: rect = pygame.Rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.rect(screen, COLOR_FOG, rect) else: # Проверяем, будет ли клетка закрыта в следующем ходу if cell_counters.get(pos, 0) == 4: # Помечаем клетку светло-серым rect = pygame.Rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.rect(screen, COLOR_PRE_FOG, rect) def draw_bonus_cells(): """ Отрисовка бонусов (если клетка открыта или бонус типа 'damage'). Цвета: - 'regen': (0,255,0) зелёный - 'hp_upgrade': (0,128,255) синий - 'damage': (255,0,0) красный - 'king_hp_upgrade': (255,215,0) золотой - 'add_piece': (255,255,255) белый с плюсом или розовый """ for (bx, by), info in bonus_cells.items(): # Для 'damage' бонусов отображаем всегда, иначе только если клетка видна if info['type'] != 'damage' and (bx, by) not in global_revealed: continue cx = bx*CELL_SIZE + CELL_SIZE//2 cy = by*CELL_SIZE + CELL_SIZE//2 bonus_type = info['type'] if bonus_type == 'regen': color = COLOR_REGEN pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE//4) elif bonus_type == 'hp_upgrade': color = COLOR_HP_UPGRADE pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE//4) elif bonus_type == 'damage': color = COLOR_DAMAGE pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE//4) elif bonus_type == 'king_hp_upgrade': color = COLOR_KING_HP_UPGRADE pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE//4) elif bonus_type == 'add_piece': # Проверяем, возможно ли добавить фигуру рядом possible = False for dx, dy in [(-1,0),(1,0),(0,-1),(0,1), (-1,-1),(1,-1),(1,1),(-1,1)]: nx = bx + dx ny = by + dy if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: if not any(p.x == nx and p.y == ny for p in pieces): possible = True break color = COLOR_ADD_PIECE if possible else COLOR_ADD_PIECE_PINK pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE//4) # Рисуем плюс pygame.draw.line(screen, (0,0,0), (cx - CELL_SIZE//8, cy), (cx + CELL_SIZE//8, cy), 2) pygame.draw.line(screen, (0,0,0), (cx, cy - CELL_SIZE//8), (cx, cy + CELL_SIZE//8), 2) def draw_pieces(pieces): """Рисуем фигуры, их HP и подсвечиваем ходы.""" for p in pieces: rect = pygame.Rect(p.x*CELL_SIZE, p.y*CELL_SIZE, CELL_SIZE, CELL_SIZE) if p.color == 'white': pygame.draw.rect(screen, COLOR_WHITE_PIECE, rect) symbol = piece_symbols.get(p.name,'?') text = font.render(symbol, True, (255,0,0)) text_rect = text.get_rect(center=rect.center) screen.blit(text, text_rect) # HP чёрным hp_str = f"{p.hp}/{p.max_hp}" hp_text = font.render(hp_str, True, (0,0,0)) screen.blit(hp_text, (rect.x+2, rect.y+2)) else: pygame.draw.rect(screen, COLOR_BLACK_PIECE, rect) symbol = piece_symbols.get(p.name,'?') text = font.render(symbol, True, (255,0,0)) text_rect = text.get_rect(center=rect.center) screen.blit(text, text_rect) # HP белым hp_str = f"{p.hp}/{p.max_hp}" hp_text = font.render(hp_str, True, (255,255,255)) screen.blit(hp_text, (rect.x+2, rect.y+2)) if p.selected: pygame.draw.rect(screen, COLOR_SELECTED, rect, 2) for (mx, my) in possible_moves: r = pygame.Rect(mx*CELL_SIZE, my*CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.rect(screen, COLOR_MOVE, r, 2) def display_victory(winner): text = victory_font.render(f"{winner.capitalize()} победил!", True, (255, 255, 255)) text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) screen.blit(text, text_rect) def display_turn(current_turn): text = turn_font.render(f"Текущий ход: {current_turn.capitalize()}", True, COLOR_TURN_TEXT) screen.blit(text, (10, WINDOW_HEIGHT - 30)) def display_turn_counter(): global turn_count if turn_count < 100: color = (255, 255, 255) else: color = (255, 0, 0) text = counter_font.render(f"Ход: {turn_count}", True, color) text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT - 15)) screen.blit(text, text_rect) def display_timer(): time_left = get_time_left() minutes = time_left // 60 seconds = time_left % 60 timer_text = f"{minutes:02}:{seconds:02}" if timer_expired and player_timeouts[current_turn] >=1: color = COLOR_TIMER_WARNING else: color = COLOR_TIMER_NORMAL text = timer_font.render(timer_text, True, color) text_rect = text.get_rect(bottomright=(WINDOW_WIDTH - 10, WINDOW_HEIGHT - 10)) screen.blit(text, text_rect) def apply_bonus(piece, bonus_type, pieces): """Применяем эффект бонуса к фигуре piece в зависимости от типа.""" if bonus_type == 'regen': # Регенерация (1..2) для НЕ короля if piece.name != 'king': amt = random.randint(1, 2) piece.hp = min(piece.hp + amt, piece.max_hp) elif bonus_type == 'hp_upgrade': # Повышение max_hp только НЕ королю без восстановления HP if piece.name != 'king': piece.max_hp += 1 # piece.hp остается неизменным elif bonus_type == 'king_hp_upgrade': # Только для короля, бонус активируется другим игроком if piece.name != 'king': # Найти короля той же команды king = next((p for p in pieces if p.name == 'king' and p.color == piece.color), None) if king: if king.hp == king.max_hp: # Если король на полном здоровье, # то увеличиваем max_hp на 1 king.max_hp += 1 king.hp = king.max_hp else: # Если король не на полном, то # просто восстанавливаем HP до max king.hp = min(king.hp + 1, king.max_hp) elif bonus_type == 'damage': # Нанесение урона (1..5) damage = random.randint(1, 5) piece.hp = max(piece.hp - damage, 0) if piece.hp == 0: # Удаляем фигуру, если HP равен 0 pieces.remove(piece) if piece.name == 'king': global winner, game_over winner = 'black' if piece.color == 'white' else 'white' game_over = True elif bonus_type == 'add_piece': # Добавление новой фигуры рядом, если возможно и не король if piece.name != 'king': # Определяем возможные соседние клетки directions = [(-1,0),(1,0),(0,-1),(0,1), (-1,-1),(1,-1),(1,1),(-1,1)] random.shuffle(directions) for dx, dy in directions: nx = piece.x + dx ny = piece.y + dy if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: # Проверяем, что клетка пуста if not any(p.x == nx and p.y == ny for p in pieces) and (nx, ny) not in bonus_cells: # Создаём новую фигуру того же типа new_piece = Piece(piece.name, piece.color, nx, ny) pieces.append(new_piece) break # Если не удалось разместить, бонус не активируется # Инициализируем туман войны в начале игры update_fog() running = True clock = pygame.time.Clock() while running: clock.tick(30) check_timer() for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN and not game_over: mx, my = pygame.mouse.get_pos() gx = mx // CELL_SIZE gy = my // CELL_SIZE clicked_piece = next((p for p in pieces if p.x == gx and p.y == gy), None) if selected_piece: if (gx, gy) in possible_moves: # Проверяем бонус if (gx, gy) in bonus_cells: bonus_type = bonus_cells[(gx, gy)]['type'] apply_bonus(selected_piece, bonus_type, pieces) del bonus_cells[(gx, gy)] # Проверяем, есть ли там вражеская фигура defender = next((p for p in pieces if p.x == gx and p.y == gy), None) if defender: # Проводим бой attacker = selected_piece attacker_alive, defender_alive = resolve_combat(attacker, defender) if not defender_alive: # Защитник погиб if defender.name == 'king': winner = attacker.color game_over = True pieces.remove(defender) if not attacker_alive: # Атакующая погибла pieces.remove(attacker) # Снимаем выделение selected_piece = None possible_moves.clear() # После изменения позиций фигур обновляем туман войны update_fog() # Смена хода switch_turn() continue else: # Атакующая жива, перемещаем её # Проверка прохождения через клетки (для длинных ходов) path = [] if attacker.name in ['rook', 'bishop', 'queen']: dx = gx - attacker.x dy = gy - attacker.y if dx != 0: dx = dx // abs(dx) if dy != 0: dy = dy // abs(dy) for step in range(1, max(abs(gx - attacker.x), abs(gy - attacker.y))): path_x = attacker.x + dx * step path_y = attacker.y + dy * step path.append((path_x, path_y)) # Применяем урон при прохождении через клетки и удаляем 'damage' клетки survived = True for pos in path: if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage': damage = random.randint(1,5) # Исправлено на 1..5 attacker.hp = max(attacker.hp - damage, 0) # Удаляем 'damage' клетку как использованную del bonus_cells[pos] if attacker.hp == 0: pieces.remove(attacker) selected_piece = None possible_moves.clear() survived = False # Смена хода, так как игрок погиб switch_turn() break if survived: attacker.x = gx attacker.y = gy attacker.selected = False selected_piece = None possible_moves.clear() # Смена хода switch_turn() else: # Пустая клетка — просто ходим # Проверка прохождения через клетки (для длинных ходов) path = [] if selected_piece.name in ['rook', 'bishop', 'queen']: dx = gx - selected_piece.x dy = gy - selected_piece.y if dx != 0: dx = dx // abs(dx) if dy != 0: dy = dy // abs(dy) for step in range(1, max(abs(gx - selected_piece.x), abs(gy - selected_piece.y))): path_x = selected_piece.x + dx * step path_y = selected_piece.y + dy * step path.append((path_x, path_y)) # Применяем урон при прохождении через клетки и удаляем 'damage' клетки survived = True for pos in path: if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage': damage = random.randint(1,5) # Исправлено на 1..5 selected_piece.hp = max(selected_piece.hp - damage, 0) # Удаляем 'damage' клетку как использованную del bonus_cells[pos] if selected_piece.hp == 0: pieces.remove(selected_piece) selected_piece = None possible_moves.clear() survived = False # Смена хода, так как игрок погиб switch_turn() break if survived: selected_piece.x = gx selected_piece.y = gy selected_piece.selected = False selected_piece = None possible_moves.clear() # Смена хода switch_turn() # После успешного хода обновляем туман войны update_fog() else: # Клик вне возможных ходов selected_piece.selected = False selected_piece = None possible_moves.clear() else: # Выбираем фигуру только если клетка видима и соответствует текущему ходу if clicked_piece and (gx, gy) in global_revealed and clicked_piece.color == current_turn: selected_piece = clicked_piece selected_piece.selected = True possible_moves = selected_piece.get_possible_moves(pieces) # Отрисовка screen.fill(COLOR_BG) draw_grid() draw_fog() draw_bonus_cells() draw_pieces(pieces) if game_over and winner: display_victory(winner) else: display_turn(current_turn) display_turn_counter() display_timer() pygame.display.flip() pygame.quit() sys.exit()