From c655d5b81532d98ce9a14b2b2e1a9cd566f8a504 Mon Sep 17 00:00:00 2001 From: dima Date: Fri, 31 Jan 2025 22:03:51 +0300 Subject: [PATCH] Ver 1.1.1 --- README.md | 68 +++--- chess_open_world.py | 503 ++++++++++++++++++++++++++------------------ 2 files changed, 337 insertions(+), 234 deletions(-) diff --git a/README.md b/README.md index fbc10d5..54a1482 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Шахматы с Открытым Миром 1.1 +# Шахматы с Открытым Миром 1.1.1 -Добро пожаловать в **Шахматы с Открытым Миром 1.1** – инновационную версию классической игры, обогащённую новыми механиками, бонусами и динамическим туманом войны. Погрузитесь в захватывающий мир стратегии, где каждое решение может изменить ход партии! +Добро пожаловать в **Шахматы с Открытым Миром 1.1.1** – инновационную версию классической игры, обогащённую новыми механиками, бонусами, магазином и динамическим туманом войны. Погрузитесь в захватывающий мир стратегии, где каждое решение может изменить ход партии! ## Содержание @@ -9,6 +9,7 @@ - [Туман Войны](#туман-войны) - [Бонусные Поля](#бонусные-поля) - [Бои и HP Фигур](#бои-и-hp-фигур) + - [Торговцы и Магазин](#торговцы-и-магазин) 3. [Механика Времени](#механика-времени) 4. [Процедурная Генерация Бонусов](#процедурная-генерация-бонусов) 5. [Баланс Фигур](#баланс-фигур) @@ -44,7 +45,7 @@ - **Типы Бонусов**: - **Регенерация (regen)**: Восстанавливает 1-2 HP фигуре (кроме короля). - **Повышение HP (hp_upgrade)**: Увеличивает максимальный HP фигуры на 1 (кроме короля). - - **Урон (damage)**: Наносит 1-5 урона фигуре. + - **Урон (damage)**: Вычитает 3 HP из фигуры противника без её уничтожения. - **Повышение HP Короля (king_hp_upgrade)**: Увеличивает или восстанавливает HP короля. - **Добавление Фигуры (add_piece)**: Добавляет новую фигуру рядом с текущей, если возможно. @@ -70,12 +71,24 @@ - **Взаимодействие Фигур**: - **Атака**: Когда фигура перемещается на клетку с вражеской фигурой, начинается бой. - **Исход Боя**: - - Если HP атакующего меньше HP защитника: Защитник теряет HP атакующего, атакующий погибает. - - Если HP атакующего больше HP защитника: Атакующий теряет HP защитника, защитник погибает. - - Если HP равны: Атакующий остаётся с 1 HP, защитник погибает. + - Если HP атакующего меньше HP защитника: Защитник теряет HP атакующего, атакующий теряет 3 HP (не умирает). + - Если HP атакующего больше HP защитника: Атакующий теряет HP защитника, защитник теряет 3 HP (не умирает). + - Если HP равны: Атакующий остаётся с 1 HP, защитник теряет 3 HP (не умирает). - **Убийство Короля**: Захват короля противника завершает игру. +### Торговцы и Магазин + +На поле расположены **торговцы**, представляющие специальные клетки, с которыми можно взаимодействовать: + +- **Позиции Торговцев**: Центральная линия по краям поля. +- **Взаимодействие с Торговцем**: При перемещении на клетку торговца открывается **магазин**. +- **Магазин** предлагает следующие улучшения: + - **Расширить ход**: Увеличивает длину хода для специальных фигур до 5 клеток и для пешек до 2 клеток. Стоимость: 3 монеты. + - **Увеличить HP**: Увеличивает `max_hp` всех фигур на 3, кроме короля. Стоимость: 3 монеты. + - **Уменьшить HP врага**: Вычитает 3 HP у всех фигур противника без их уничтожения. Если HP противника меньше 3, устанавливает HP на 1. Стоимость: 3 монеты. + - **Проклятие**: Уменьшает вероятность появления бонусов у противника `regen` и `hp_upgrade` на 10% каждая. Стоимость: 3 монеты. + --- ## Механика Времени @@ -86,7 +99,9 @@ - **Отсчёт Времени**: - Таймер отображается в правом нижнем углу. - При истечении времени, таймер становится красным. - - **Повторный Пропуск**: Если игрок снова пропустит свой ход, он автоматически проигрывает. +- **Переполнение Времени**: + - **Первый пропуск**: Таймер становится красным. + - **Второй пропуск**: Игрок автоматически проигрывает. - **Переключение Хода**: После каждого хода время сбрасывается для следующего игрока. --- @@ -97,10 +112,10 @@ - **Когда Генерируются Бонусы**: - При выходе клетки из тумана войны, с вероятностью **10%** может появиться бонусное поле. - + - **Тип Бонуса**: - Тип бонуса выбирается на основе текущих вероятностей. - - После **100 ходов**, вероятность появления бонусов типа "урон" (красные поля) постепенно увеличивается до **50%**, делая игру более опасной. + - После **100 ходов**, вероятность появления бонусов типа "урон" (**damage**) постепенно увеличивается до **50%**, делая игру более опасной. --- @@ -118,27 +133,24 @@ ## Обновления и Изменения -### Версия 1.1 +### Версия 1.1.1 -1. **Баланс Красных Полей**: - - **Урон**: Теперь красные поля наносят от **1 до 5 урона**, исключая возможность отсутствия урона. - - **Исправление Бага**: При прохождении через красное поле и гибели фигуры, ход корректно переходит к противнику. +1. **Добавление Магазина и Монет**: + - **Магазин**: Теперь доступен при взаимодействии с торговцами на поле. Магазин предлагает различные улучшения за монеты. + - **Монеты**: Появляются на поле с шансом **0.1%**, становясь более ценными и редкими. + +2. **Исправление Механики Товара "Уменьшить HP врага"**: + - Теперь при покупке этого бонуса у всех фигур противника вычитается **3 HP**, но фигуры не умирают. + - Если HP противника меньше 3, устанавливается **1 HP**. -2. **Процедурная Генерация Бонусов**: - - Бонусные поля теперь генерируются **процедурно** при выходе клеток из тумана войны. - - **Вероятность Появления**: Каждая новая открытая клетка имеет **10%** шанс стать бонусной. - - **Тип Бонуса**: Выбор типа бонуса осуществляется согласно текущим вероятностям. +3. **Улучшение Баланса и Механик**: + - **Шанс генерации монет** снижен до **0.1%**, уменьшая их количество и повышая ценность. + - **Удалено отображение вероятностей** генерации бонусов и монет из интерфейса игры, упрощая визуальное восприятие. -3. **Баланс Фигур с Свободным Передвижением**: - - **Ферзь и Ладья** теперь могут перемещаться до **3 клеток** вместо 5, обеспечивая более сбалансированную игру. +4. **Прочие Улучшения**: + - Оптимизация интерфейса магазина для улучшения взаимодействия с пользователем. + - Устранение багов, связанных с переходом ходов после применения бонусов. -4. **Механика Повышения Вероятности Красных Полей**: - - После **100 ходов**, вероятность появления бонусных полей типа "урон" постепенно увеличивается до **50%**. - - **Отображение Ходов**: Внизу экрана отображается счётчик ходов. После 100 ходов счётчик становится красным, сигнализируя о начале увеличения опасности. +Спасибо за внимание и приятной игры в **Шахматы с Открытым Миром 1.1.1**! -5. **Механика Времени на Ход**: - - Каждый игрок имеет **2 минуты** на свой ход. - - **Отображение Таймера**: Таймер находится в правом нижнем углу экрана. - - **Переполнение Времени**: - - При первом переполнении таймер становится красным. - - При втором переполнении, игрок автоматически проигрывает. +--- diff --git a/chess_open_world.py b/chess_open_world.py index 0064e18..8ad6879 100644 --- a/chess_open_world.py +++ b/chess_open_world.py @@ -9,7 +9,8 @@ pygame.init() CELL_SIZE = 32 GRID_WIDTH = 30 GRID_HEIGHT = 30 -WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH +PANEL_WIDTH = 200 # Ширина правой панели +WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH + PANEL_WIDTH WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT # --- ЦВЕТА --- @@ -40,8 +41,14 @@ COLOR_PRE_FOG = (169, 169, 169) # Светло-серый COLOR_TIMER_NORMAL = (255, 255, 255) # Белый COLOR_TIMER_WARNING = (255, 0, 0) # Красный +# Цвет торговца +COLOR_MERCHANT = (139, 69, 19) # Коричневый + +# Цвет монеты +COLOR_COIN = (255, 165, 0) # Оранжевый + screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) -pygame.display.set_caption("Шахматы с открытым миром 1.0") +pygame.display.set_caption("Шахматы с Открытым Миром 1.1.1") # Шрифты font = pygame.font.SysFont(None, CELL_SIZE // 2) @@ -49,6 +56,7 @@ 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) +shop_font = pygame.font.SysFont(None, 24) # --- Глобальное множество открытых клеток (туман войны) --- global_revealed = set() @@ -59,15 +67,13 @@ piece_symbols = { 'knight': 'К', 'bishop': 'С', 'queen': 'Ф', - 'king': 'К', # Король (можно заменить на 'K' или другой символ) + 'king': 'К', # Король 'pawn': 'П' } # === МЕХАНИКА БОНУСОВ === -# Храним в глобальном словаре bonus_cells: {(x,y): {'type': ...}} bonus_cells = {} -# Вероятности появления различных типов бонусов base_bonus_probabilities = { 'regen': 0.42, # 42% 'hp_upgrade': 0.32, # 32% @@ -78,7 +84,6 @@ base_bonus_probabilities = { bonus_probabilities = base_bonus_probabilities.copy() -# Функция для выбора бонуса на основе вероятностей def choose_bonus_type(): rand = random.random() cumulative = 0 @@ -90,35 +95,20 @@ def choose_bonus_type(): # === МЕХАНИКА БОЯ === 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) @@ -156,12 +146,12 @@ class Piece: 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 клеток + # Ограничиваем движение до 3 клеток или до 5 для улучшенного хода + max_steps = self.move_range if hasattr(self, 'move_range') else 3 if self.name == 'rook': directions = [(-1,0),(1,0),(0,-1),(0,1)] elif self.name == 'bishop': @@ -172,7 +162,7 @@ class Piece: (-1,-1),(1,-1),(1,1),(-1,1) ] for dx, dy in directions: - for step in range(1, 4): # До 3 клеток + for step in range(1, max_steps + 1): nx = self.x + dx * step ny = self.y + dy * step if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: @@ -242,9 +232,6 @@ def initialize_pieces(): # Инициализация фигур pieces = initialize_pieces() -# Remove initial bonus cell generation -# initialize_bonus_cells(pieces, num_each=10) - selected_piece = None possible_moves = [] game_over = False @@ -252,7 +239,8 @@ winner = None # --- Туман войны --- cell_counters = {} # {(x,y): turns_since_last_revealed} -BONUS_GENERATION_CHANCE = 0.1 # 10% шанс появления бонуса при повторном открытии +BONUS_GENERATION_CHANCE = 0.10 # 10% шанс появления бонуса при повторном открытии +COIN_GENERATION_CHANCE = 0.001 # 0.1% шанс появления монеты # --- Система ходов --- current_turn = 'white' # Начинаем с белых @@ -264,13 +252,10 @@ def update_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 = {} @@ -302,11 +287,9 @@ def check_timer(): 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(): @@ -327,10 +310,8 @@ def update_fog(): """ global global_revealed, bonus_cells - # Сохраняем предыдущее состояние открытых клеток previous_revealed = global_revealed.copy() - # Определяем все открытые клетки на основе позиций фигур new_revealed = set() for p in pieces: for dx in range(-2, 3): @@ -340,25 +321,24 @@ def update_fog(): if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT: new_revealed.add((nx, ny)) - # Определяем только новые открытые клетки + # Исключаем клетки торговцев из тумана войны + for merchant_pos in merchant_positions: + new_revealed.add(merchant_pos) + 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) @@ -366,19 +346,49 @@ def update_fog(): 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: + if pos not in bonus_cells and pos not in coin_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} + # Генерация монет + if pos not in coin_cells and random.random() < COIN_GENERATION_CHANCE: + if not any(p.x == pos[0] and p.y == pos[1] for p in pieces): + coin_cells.add(pos) +# --- Добавление торговцев --- +# Определяем позиции торговцев: центральная линия по краям +central_line_y = GRID_HEIGHT // 2 +merchant_positions = [ + (0, central_line_y), + (GRID_WIDTH - 1, central_line_y) +] + +# Добавляем торговцы в глобальное множество открытых клеток +global_revealed.update(merchant_positions) + +# --- Монеты на поле --- +coin_cells = set() + +# --- Счет монет для игроков --- +player_coins = {'white': 0, 'black': 0} + +# --- Механика магазина --- +shop_open = False +shop_items = { + 'extend_move': {'name': 'Расширить ход', 'cost': 3}, + 'increase_hp': {'name': 'Увеличить HP', 'cost': 3}, + 'damage_enemy': {'name': 'Уменьшить HP врага', 'cost': 3}, + 'curse': {'name': 'Проклятие', 'cost': 3} +} + +# --- Функции отрисовки --- 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)) + for x in range(0, CELL_SIZE * GRID_WIDTH, CELL_SIZE): + pygame.draw.line(screen, COLOR_GRID, (x, 0), (x, CELL_SIZE * GRID_HEIGHT)) + for y in range(0, CELL_SIZE * GRID_HEIGHT, CELL_SIZE): + pygame.draw.line(screen, COLOR_GRID, (0, y), (CELL_SIZE * GRID_WIDTH, y)) def draw_fog(): for y in range(GRID_HEIGHT): @@ -388,22 +398,13 @@ def draw_fog(): rect = pygame.Rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.rect(screen, COLOR_FOG, rect) else: - # Проверяем, будет ли клетка закрыта в следующем ходу + if pos in merchant_positions: + continue # Торговцы всегда видимы 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: @@ -424,7 +425,6 @@ def draw_bonus_cells(): 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 @@ -439,8 +439,40 @@ def draw_bonus_cells(): 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_coin_cells(): + for pos in coin_cells: + if pos not in global_revealed: + continue # Монеты видны только в открытых клетках + x, y = pos + cx = x * CELL_SIZE + CELL_SIZE // 2 + cy = y * CELL_SIZE + CELL_SIZE // 2 + size = CELL_SIZE // 3 + # Рисуем оранжевый треугольник + point1 = (cx, cy - size) + point2 = (cx - size, cy + size) + point3 = (cx + size, cy + size) + pygame.draw.polygon(screen, COLOR_COIN, [point1, point2, point3]) + # Рисуем символ доллара + dollar_text = font.render('$', True, (0, 0, 0)) + text_rect = dollar_text.get_rect(center=(cx, cy)) + screen.blit(dollar_text, text_rect) + +def draw_merchants(): + for pos in merchant_positions: + x, y = pos + rect = pygame.Rect(x*CELL_SIZE, y*CELL_SIZE, CELL_SIZE, CELL_SIZE) + pygame.draw.rect(screen, COLOR_MERCHANT, rect) + # Рисуем стрелку вверх + arrow_color = (255, 255, 255) + arrow_size = CELL_SIZE // 2 + points = [ + (x * CELL_SIZE + CELL_SIZE // 2, y * CELL_SIZE + CELL_SIZE // 4), + (x * CELL_SIZE + CELL_SIZE // 4, y * CELL_SIZE + 3 * CELL_SIZE // 4), + (x * CELL_SIZE + 3 * CELL_SIZE // 4, y * CELL_SIZE + 3 * CELL_SIZE // 4) + ] + pygame.draw.polygon(screen, arrow_color, points) + def draw_pieces(pieces): - """Рисуем фигуры, их HP и подсвечиваем ходы.""" for p in pieces: rect = pygame.Rect(p.x*CELL_SIZE, p.y*CELL_SIZE, CELL_SIZE, CELL_SIZE) @@ -472,36 +504,69 @@ def draw_pieces(pieces): 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 draw_statistics_panel(): + panel_rect = pygame.Rect(CELL_SIZE * GRID_WIDTH, 0, PANEL_WIDTH, WINDOW_HEIGHT) + pygame.draw.rect(screen, (50, 50, 50), panel_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)) + # Заголовок + title_text = shop_font.render("Статистика", True, (255, 255, 255)) + screen.blit(title_text, (CELL_SIZE * GRID_WIDTH + 10, 10)) -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) + # Текущий ход + turn_text = turn_font.render(f"Ход: {current_turn.capitalize()}", True, COLOR_TURN_TEXT) + screen.blit(turn_text, (CELL_SIZE * GRID_WIDTH + 10, 50)) -def display_timer(): + # Количество ходов + counter_text = counter_font.render(f"Ходов: {turn_count}", True, (255, 255, 255)) + screen.blit(counter_text, (CELL_SIZE * GRID_WIDTH + 10, 80)) + + # Таймер time_left = get_time_left() minutes = time_left // 60 seconds = time_left % 60 - timer_text = f"{minutes:02}:{seconds:02}" + timer_text_str = 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)) + timer_text = timer_font.render(timer_text_str, True, color) + screen.blit(timer_text, (CELL_SIZE * GRID_WIDTH + 10, 110)) + + # Количество монет + coins_text_white = counter_font.render(f"Белые монеты: {player_coins['white']}", True, (255, 255, 0)) + coins_text_black = counter_font.render(f"Чёрные монеты: {player_coins['black']}", True, (255, 255, 0)) + screen.blit(coins_text_white, (CELL_SIZE * GRID_WIDTH + 10, 140)) + screen.blit(coins_text_black, (CELL_SIZE * GRID_WIDTH + 10, 170)) + + # Если открыт магазин, отображаем его + if shop_open: + draw_shop() + +def draw_shop(): + shop_x = CELL_SIZE * GRID_WIDTH + 10 + shop_y = 200 + shop_title = shop_font.render("Магазин", True, (255, 255, 0)) + screen.blit(shop_title, (shop_x, shop_y)) + + for idx, (key, item) in enumerate(shop_items.items()): + item_y = shop_y + 40 + idx * 60 + # Кнопка + button_rect = pygame.Rect(shop_x, item_y, PANEL_WIDTH - 20, 50) + # Проверка, хватает ли монет + if player_coins[current_turn] >= item['cost']: + button_color = (0, 255, 0) # Зелёный + else: + button_color = (128, 128, 128) # Серый + pygame.draw.rect(screen, button_color, button_rect) + # Название предмета + item_text = shop_font.render(f"{item['name']} - {item['cost']} мон.", True, (0,0,0)) + screen.blit(item_text, (shop_x + 5, item_y + 15)) + # Хранение позиции кнопки для обработки кликов + shop_items[key]['button_rect'] = button_rect + +def draw_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 apply_bonus(piece, bonus_type, pieces): @@ -525,48 +590,70 @@ def apply_bonus(piece, bonus_type, pieces): 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 + # Увеличиваем max_hp на 1 king.max_hp += 1 king.hp = king.max_hp else: - # Если король не на полном, то - # просто восстанавливаем HP до max + # Восстанавливаем 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 + # Урон при прохождении через клетку 'damage' уже обрабатывается отдельно + pass 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: - # Создаём новую фигуру того же типа + if not any(p.x == nx and p.y == ny for p in pieces) and (nx, ny) not in bonus_cells and (nx, ny) not in merchant_positions: new_piece = Piece(piece.name, piece.color, nx, ny) pieces.append(new_piece) break - # Если не удалось разместить, бонус не активируется -# Инициализируем туман войны в начале игры +def handle_shop_purchase(key): + if player_coins[current_turn] < shop_items[key]['cost']: + return # Недостаточно монет + player_coins[current_turn] -= shop_items[key]['cost'] + if key == 'extend_move': + for p in pieces: + if p.color == current_turn: + if p.name in ['rook', 'bishop', 'queen']: + setattr(p, 'move_range', getattr(p, 'move_range', 3) + 2) # До 5 + elif p.name == 'pawn': + setattr(p, 'move_range', getattr(p, 'move_range', 1) + 1) # До 2 + elif key == 'increase_hp': + for p in pieces: + if p.color == current_turn and p.name != 'king': + p.max_hp += 3 + p.hp = min(p.hp + 3, p.max_hp) + elif key == 'damage_enemy': + enemy_color = 'black' if current_turn == 'white' else 'white' + for p in pieces: + if p.color == enemy_color: + if p.hp > 3: + p.hp -= 3 + elif p.hp > 1: + p.hp = 1 + else: + p.hp = 1 # Уже 1, остаётся 1 + elif key == 'curse': + # Уменьшаем вероятность появления 'regen' и 'hp_upgrade' на 10% + bonus_probabilities['regen'] = max(bonus_probabilities['regen'] - 0.10, 0) + bonus_probabilities['hp_upgrade'] = max(bonus_probabilities['hp_upgrade'] - 0.10, 0) + # Нормализуем вероятности + total_prob = sum(bonus_probabilities.values()) + for bt in bonus_probabilities: + bonus_probabilities[bt] /= total_prob if total_prob > 0 else 1 + +# --- Инициализируем туман войны в начале игры --- update_fog() +# --- Основной цикл игры --- running = True clock = pygame.time.Clock() @@ -579,149 +666,153 @@ while running: elif event.type == pygame.MOUSEBUTTONDOWN and not game_over: mx, my = pygame.mouse.get_pos() - gx = mx // CELL_SIZE - gy = my // CELL_SIZE + if mx < CELL_SIZE * GRID_WIDTH and my < CELL_SIZE * GRID_HEIGHT: + 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) + 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)] + 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)] + + # Проверяем монету + if (gx, gy) in coin_cells: + player_coins[current_turn] += 1 + coin_cells.remove((gx, gy)) + + # Проверяем, является ли клетка торговцем + if (gx, gy) in merchant_positions: + shop_open = True + + # Проверяем, есть ли там вражеская фигура + 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)) + survived = True + for pos in path: + if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage': + damage = random.randint(1,5) + attacker.hp = max(attacker.hp - damage, 0) + 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() - # Проверяем, есть ли там вражеская фигура - 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 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 - attacker.x), abs(gy - attacker.y))): - path_x = attacker.x + dx * step - path_y = attacker.y + dy * step + 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 - attacker.hp = max(attacker.hp - damage, 0) - # Удаляем 'damage' клетку как использованную + damage = random.randint(1,5) + selected_piece.hp = max(selected_piece.hp - damage, 0) del bonus_cells[pos] - if attacker.hp == 0: - pieces.remove(attacker) + if selected_piece.hp == 0: + pieces.remove(selected_piece) selected_piece = None possible_moves.clear() survived = False - # Смена хода, так как игрок погиб switch_turn() break if survived: - attacker.x = gx - attacker.y = gy - attacker.selected = False + selected_piece.x = gx + selected_piece.y = gy + selected_piece.selected = False selected_piece = None possible_moves.clear() - # Смена хода switch_turn() + update_fog() 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() + selected_piece.selected = False + selected_piece = None + possible_moves.clear() - # После успешного хода обновляем туман войны - update_fog() else: - # Клик вне возможных ходов - selected_piece.selected = False - selected_piece = None - possible_moves.clear() + # Выбираем фигуру только если клетка видима и соответствует текущему ходу + 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) 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) + # Клик в правой панели + if shop_open: + for key, item in shop_items.items(): + if 'button_rect' in item and item['button_rect'].collidepoint(mx, my): + if player_coins[current_turn] >= item['cost']: + handle_shop_purchase(key) + + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + shop_open = False # Отрисовка screen.fill(COLOR_BG) draw_grid() draw_fog() + draw_merchants() draw_bonus_cells() + draw_coin_cells() draw_pieces(pieces) + draw_statistics_panel() if game_over and winner: - display_victory(winner) - else: - display_turn(current_turn) - display_turn_counter() - display_timer() + draw_victory(winner) pygame.display.flip() pygame.quit()