diff --git a/README.md b/README.md index 54a1482..9542489 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,138 @@ -# Шахматы с Открытым Миром 1.1.1 +# Шахматы с открытым миром 1.2 -Добро пожаловать в **Шахматы с Открытым Миром 1.1.1** – инновационную версию классической игры, обогащённую новыми механиками, бонусами, магазином и динамическим туманом войны. Погрузитесь в захватывающий мир стратегии, где каждое решение может изменить ход партии! +**Шахматы с открытым миром 1.2** – это инновационная версия классических шахмат, где традиционные фигуры перемещаются по динамичному игровому полю, окутанному туманом войны, а на клетках появляются разнообразные бонусы и монеты. Игра объединяет глубокую стратегию классических шахмат с элементами исследования, случайности и управления ресурсами. -## Содержание +## Оглавление -1. [Основные Правила](#основные-правила) -2. [Механики Игры](#механики-игры) - - [Туман Войны](#туман-войны) - - [Бонусные Поля](#бонусные-поля) - - [Бои и HP Фигур](#бои-и-hp-фигур) - - [Торговцы и Магазин](#торговцы-и-магазин) -3. [Механика Времени](#механика-времени) -4. [Процедурная Генерация Бонусов](#процедурная-генерация-бонусов) -5. [Баланс Фигур](#баланс-фигур) -6. [Обновления и Изменения](#обновления-и-изменения) +- [Обзор игры](#обзор-игры) +- [Концепция игры](#концепция-игры) +- [Основные механики](#основные-механики) + - [Структура хода и перемещения](#структура-хода-и-перемещения) + - [Туман войны и механизм раскрытия клеток](#туман-войны-и-механизм-раскрытия-клеток) + - [Генерация бонусов и монет](#генерация-бонусов-и-монет) + - [Механика боя](#механика-боя) + - [Магазин](#магазин) +- [Стратегические особенности](#стратегические-особенности) +- [Дополнительные замечания](#дополнительные-замечания) --- -## Основные Правила +## Обзор игры -**Шахматы с Открытым Миром** сохраняют основные принципы классической шахматной игры: +**Шахматы с открытым миром 1.2** представляет собой гибрид классических шахмат и открытого мира, где игровой процесс динамичен и наполнен неожиданностями. Ваша задача – не только выиграть шахматную партию, но и эффективно использовать бонусы, собирать монеты и умело управлять ресурсами, чтобы одержать победу. -- **Фигуры и Расстановка**: Игра включает короля, ферзя, ладьи, слонов, коней и пешек для каждой стороны. -- **Цель Игры**: Захватить короля противника. +## Концепция игры -Однако, в этой версии добавлены уникальные механики, которые делают игру более динамичной и стратегически глубокой. +В этой игре шахматное поле превращается в открытый мир, где: +- **Туман войны** скрывает большую часть поля, и его нужно «рассекречивать» движением фигур. +- **Клетки** постоянно меняются: при их раскрытии появляются бонусы и монеты, а спустя время (если клетка не посещается) всё вновь покрывается туманом, и объекты исчезают. +- **Несколько ходов за раз:** игроку предоставляется возможность совершить до 3 перемещений за ход, что добавляет динамичности и требует продуманной тактики. + +## Основные механики + +### Структура хода и перемещения + +- **Несколько перемещений за ход:** + Каждый игрок в свой ход может совершить до 3 перемещений. Выбирайте фигуру, перемещайтесь по полю и используйте бонусы, чтобы усилить свою позицию. + +- **Виды перемещений:** + - Фигуры с коротким радиусом (например, пешка и король) перемещаются на одну клетку. + - Фигуры дальнего боя (ладья, слон, ферзь) могут двигаться на несколько клеток за один ход. При их перемещении проверяется каждое промежуточное звено пути. + +- **Потраченное перемещение:** + Даже если ваша фигура погибает во время перемещения (например, попадая на клетку с бонусом «damage»), это перемещение засчитывается. Если у вас осталось еще перемещений, вы сможете совершить следующие ходы. + +### Туман войны и механизм раскрытия клеток + +- **Начальное состояние:** + Изначально почти все поле скрыто туманом. + +- **Раскрытие клеток:** + Фигуры при перемещении «освещают» окружающие клетки (обычно 5×5 клеток вокруг них), делая их видимыми. На этих клетках становятся доступны бонусы и монеты. + +- **Старение клетки:** + Если клетка остаётся неизменной (то есть долго не посещается ни одной фигурой), её «счётчик старения» растёт. При достижении определённого порога (например, 5 ходов) клетка вновь покрывается туманом, и все бонусы или монеты, находящиеся на ней, исчезают. Это позволяет в дальнейшем генерировать новые бонусы на этих клетках. + +### Генерация бонусов и монет + +- **Шанс генерации:** + Каждая новая (раскрытая) клетка имеет заданный шанс (по умолчанию 10%) сгенерировать объект. + +- **Соотношение бонус/монета:** + Если для клетки срабатывает шанс генерации, то: + - С вероятностью 95% в клетке появляется бонус. + - С вероятностью 5% появляется монета. + +- **Типы бонусов:** + Возможные бонусы включают: + - **Regen (восстановление):** Восстанавливает часть HP фигуры (кроме короля). + - **HP Upgrade (повышение здоровья):** Увеличивает максимальное значение HP фигуры (кроме короля). + - **Damage (урон):** Наносит случайный урон (от 1 до 5 единиц) фигуре, попавшей на клетку. + При этом вероятность появления бонуса «damage» динамически увеличивается после 100 ходов (до 50%). + - **King HP Upgrade:** Увеличивает максимальное здоровье короля или восстанавливает его. + - **Add Piece:** Если рядом есть свободное место, создает дополнительную фигуру такого же типа. + +- **Монеты:** + Монеты генерируются аналогичным образом, но теперь, как бонусы, монеты исчезают, если клетка «стареет» (т.е. покрывается туманом). Собранные монеты можно потратить в магазине. + +### Механика боя + +- **Бой между фигурами:** + Когда ваша фигура перемещается на клетку, занятую вражеской фигурой, происходит бой. Результат боя определяется сравнением HP фигур: + - Если HP атакующей фигуры меньше HP защитника, атакующая фигура погибает, а защитник остаётся с уменьшенным количеством HP. + - Если HP атакующей фигуры больше, защитник погибает, а атакующая фигура остаётся с уменьшенным количеством HP. + - При равных значениях HP защитник погибает, а атакующая фигура остаётся с 1 единицей HP. + +- **Опасности на пути:** + Для фигур дальнего боя (ладья, слон, ферзь) каждый промежуточный шаг пути проверяется на наличие бонуса «damage». Если фигура проходит через клетку с таким бонусом, она получает случайный урон. + Если фигура погибает в процессе перемещения, этот ход засчитывается (то есть расходуется одно перемещение), но оставшиеся перемещения остаются в распоряжении игрока. + +### Магазин + +- **Взаимодействие:** + При попадании на клетку с торговцем открывается магазин. + +- **Что можно купить:** + В магазине можно приобрести улучшения за собранные монеты, например: + - **Расширение хода:** Увеличивает дальность перемещения некоторых фигур. + - **Увеличение HP:** Повышает здоровье фигур. + - **Уменьшение HP врага:** Наносит урон вражеским фигурам. + - **Проклятие:** Применяет негативный эффект на противника. + +- **Стоимость:** + Все предметы в магазине имеют фиксированную стоимость (по умолчанию 6 монет). --- -## Механики Игры +## Стратегические особенности -### Туман Войны +- **Контроль над туманом:** + Активное перемещение фигур позволяет раскрывать больше клеток, что помогает обнаружить бонусы, монеты и вражеские фигуры. Однако не забывайте, что клетки, оставшиеся неизменными, снова покрываются туманом, и объекты на них исчезают. -Туман войны добавляет элемент неопределённости на поле боя: +- **Риск и награда:** + Попадание на клетку с бонусом может дать вам значительное преимущество (например, восстановить здоровье или улучшить характеристики фигуры), но некоторые бонусы (damage) несут риск нанесения урона. Особенно будьте осторожны при длинных перемещениях: если ваша фигура (например, ладья или ферзь) проходит по нескольким клеткам, где могут находиться опасные бонусы, существует риск накопительного урона. -- **Открытые и Закрытые Клетки**: Изначально большая часть поля скрыта. Клетки открываются, когда фигуры приближаются. -- **Обновление Тумана**: Каждая клетка, не находящаяся в радиусе 2 клеток от любой фигуры, через 5 ходов становится закрытой. -- **Предварительное Закрытие**: Клетки, которые скоро закроются, отмечаются светло-серым цветом. +- **Управление ресурсами:** + Монеты, собранные на поле, являются важным ресурсом для покупок в магазине. Старайтесь балансировать между агрессивной атакой, сбором монет и сохранением здоровья фигур. -### Бонусные Поля - -Поле содержит специальные клетки, предоставляющие бонусы: - -- **Типы Бонусов**: - - **Регенерация (regen)**: Восстанавливает 1-2 HP фигуре (кроме короля). - - **Повышение HP (hp_upgrade)**: Увеличивает максимальный HP фигуры на 1 (кроме короля). - - **Урон (damage)**: Вычитает 3 HP из фигуры противника без её уничтожения. - - **Повышение HP Короля (king_hp_upgrade)**: Увеличивает или восстанавливает HP короля. - - **Добавление Фигуры (add_piece)**: Добавляет новую фигуру рядом с текущей, если возможно. - -- **Генерация Бонусов**: - - Бонусные поля генерируются процедурно при выходе клеток из тумана войны. - - Шанс появления бонуса составляет **10%** для каждой новой открытой клетки. - - Тип бонуса определяется следующими вероятностями: - - Регенерация: 42% - - Повышение HP: 32% - - Урон: 20% - - Повышение HP Короля: 5% - - Добавление Фигуры: 1% - -### Бои и HP Фигур - -Каждая фигура имеет **HP (здоровье)**, определяющее её выживаемость: - -- **Инициализация HP**: - - **Король**: 5 HP - - **Ферзь, Ладья, Слон, Конь**: 3 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 монеты. +- **Тактическое планирование:** + Игра предоставляет несколько перемещений за ход, что позволяет комбинировать атаки, сбор бонусов и тактическое перемещение. Обдумывайте каждый ход, учитывая не только шахматные правила, но и динамичные элементы игрового поля. --- -## Механика Времени +## Дополнительные замечания -**Время на ход** добавляет дополнительное давление и стратегию в игру: +- **Динамика вероятностей:** + Исходное распределение бонусов задается в файле (например, regeneration – 42%, HP upgrade – 32%, damage – 20%, и т.д.). По мере увеличения количества ходов вероятность появления бонуса «damage» постепенно растет (до 50% после определённого количества ходов), что делает игру более рискованной с течением времени. -- **Лимит Времени**: Каждый игрок имеет **2 минуты** на свой ход. -- **Отсчёт Времени**: - - Таймер отображается в правом нижнем углу. - - При истечении времени, таймер становится красным. -- **Переполнение Времени**: - - **Первый пропуск**: Таймер становится красным. - - **Второй пропуск**: Игрок автоматически проигрывает. -- **Переключение Хода**: После каждого хода время сбрасывается для следующего игрока. +- **Обновление клеток:** + Если клетка долго не посещалась, она «стареет» и вновь покрывается туманом, удаляя все бонусы и монеты, которые на ней находились. Это позволяет системе генерировать новые объекты, когда клетка снова становится видимой. + +- **Механика потери хода:** + Даже если ваша фигура погибает в процессе перемещения (например, из-за попадания бонуса damage на пути), это перемещение засчитывается. Остальные перемещения остаются, если таковые имеются, и ход продолжается у вас до истечения лимита. --- -## Процедурная Генерация Бонусов +Наслаждайтесь игрой, экспериментируйте с тактикой и стратегией, и помните – каждая партия уникальна благодаря элементам случайности и динамическому игровому миру! -Бонусные поля генерируются динамически, делая каждую игру уникальной: - -- **Когда Генерируются Бонусы**: - - При выходе клетки из тумана войны, с вероятностью **10%** может появиться бонусное поле. - -- **Тип Бонуса**: - - Тип бонуса выбирается на основе текущих вероятностей. - - После **100 ходов**, вероятность появления бонусов типа "урон" (**damage**) постепенно увеличивается до **50%**, делая игру более опасной. - ---- - -## Баланс Фигур - -Для поддержания баланса и динамики игры, движение некоторых фигур было изменено: - -- **Ферзь и Ладья**: - - **Ранее**: Могли перемещаться до **5 клеток** за ход. - - **Сейчас**: Могут перемещаться до **3 клеток** за ход. - -Это изменение делает игру более стратегичной, ограничивая возможности мощных фигур и стимулируя разнообразие тактик. - ---- - -## Обновления и Изменения - -### Версия 1.1.1 - -1. **Добавление Магазина и Монет**: - - **Магазин**: Теперь доступен при взаимодействии с торговцами на поле. Магазин предлагает различные улучшения за монеты. - - **Монеты**: Появляются на поле с шансом **0.1%**, становясь более ценными и редкими. - -2. **Исправление Механики Товара "Уменьшить HP врага"**: - - Теперь при покупке этого бонуса у всех фигур противника вычитается **3 HP**, но фигуры не умирают. - - Если HP противника меньше 3, устанавливается **1 HP**. - -3. **Улучшение Баланса и Механик**: - - **Шанс генерации монет** снижен до **0.1%**, уменьшая их количество и повышая ценность. - - **Удалено отображение вероятностей** генерации бонусов и монет из интерфейса игры, упрощая визуальное восприятие. - -4. **Прочие Улучшения**: - - Оптимизация интерфейса магазина для улучшения взаимодействия с пользователем. - - Устранение багов, связанных с переходом ходов после применения бонусов. - -Спасибо за внимание и приятной игры в **Шахматы с Открытым Миром 1.1.1**! - ---- +Удачи и приятной игры! diff --git a/chess_open_world.py b/chess_open_world.py index 18e58d7..eb92f6b 100644 --- a/chess_open_world.py +++ b/chess_open_world.py @@ -5,52 +5,39 @@ import time pygame.init() -# --- ПАРАМЕТРЫ ПОЛЯ --- +# ========= Глобальные настройки по умолчанию ========= CELL_SIZE = 32 -GRID_WIDTH = 30 -GRID_HEIGHT = 30 -PANEL_WIDTH = 200 # Ширина правой панели -WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH + PANEL_WIDTH -WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT +PANEL_WIDTH = 200 -# --- ЦВЕТА --- +# Значения по умолчанию для поля и шанса бонусов (меню позволит их изменять) +default_GRID_WIDTH = 30 +default_GRID_HEIGHT = 30 +default_BONUS_GENERATION_CHANCE = 0.10 # 10% + +# ========= Цвета ========= 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_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_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_TURN_TEXT = (255, 255, 255) +COLOR_TIMER_NORMAL = (255, 255, 255) +COLOR_TIMER_WARNING = (255, 0, 0) +COLOR_MERCHANT = (139, 69, 19) +COLOR_COIN = (255, 165, 0) -# Новый цвет для пометки клеток, которые скоро уйдут в туман войны -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.1.1") - -# --- Шрифты --- +# ========= Шрифты ========= font = pygame.font.SysFont(None, CELL_SIZE // 2) victory_font = pygame.font.SysFont(None, 60) turn_font = pygame.font.SysFont(None, 30) @@ -58,59 +45,126 @@ counter_font = pygame.font.SysFont(None, 30) timer_font = pygame.font.SysFont(None, 25) shop_font = pygame.font.SysFont(None, 24) -# --- Глобальное множество открытых клеток (туман войны) --- -global_revealed = set() +# ========= Меню настроек ========= +def main_menu(): + menu_running = True + menu_width, menu_height = 600, 400 + menu_screen = pygame.display.set_mode((menu_width, menu_height)) + pygame.display.set_caption("Настройки игры") + + # Начальные значения из настроек по умолчанию + grid_width = default_GRID_WIDTH + grid_height = default_GRID_HEIGHT + bonus_chance = default_BONUS_GENERATION_CHANCE # 0.10 = 10% + + # Подготавливаем тексты для кнопок + minus_text = shop_font.render("-", True, (0, 0, 0)) + plus_text = shop_font.render("+", True, (0, 0, 0)) + + while menu_running: + menu_screen.fill((50, 50, 50)) + + # Заголовок + title_text = shop_font.render("Настройки игры", True, (255,255,255)) + title_rect = title_text.get_rect(center=(menu_width//2, 40)) + menu_screen.blit(title_text, title_rect) + + # Размер поля + field_text = shop_font.render("Размер поля:", True, (255,255,255)) + menu_screen.blit(field_text, (50, 80)) + + # Ширина поля + width_text = shop_font.render(f"Ширина: {grid_width}", True, (255,255,255)) + menu_screen.blit(width_text, (100, 130)) + width_minus_rect = pygame.Rect(50, 130, 40, 30) + width_plus_rect = pygame.Rect(250, 130, 40, 30) + pygame.draw.rect(menu_screen, (200,200,200), width_minus_rect) + pygame.draw.rect(menu_screen, (200,200,200), width_plus_rect) + menu_screen.blit(minus_text, minus_text.get_rect(center=width_minus_rect.center)) + menu_screen.blit(plus_text, plus_text.get_rect(center=width_plus_rect.center)) + + # Высота поля + height_text = shop_font.render(f"Высота: {grid_height}", True, (255,255,255)) + menu_screen.blit(height_text, (100, 180)) + height_minus_rect = pygame.Rect(50, 180, 40, 30) + height_plus_rect = pygame.Rect(250, 180, 40, 30) + pygame.draw.rect(menu_screen, (200,200,200), height_minus_rect) + pygame.draw.rect(menu_screen, (200,200,200), height_plus_rect) + menu_screen.blit(minus_text, minus_text.get_rect(center=height_minus_rect.center)) + menu_screen.blit(plus_text, plus_text.get_rect(center=height_plus_rect.center)) + + # Шанс бонусов (выводим значение в процентах) + bonus_label = shop_font.render("Шанс бонусов (%):", True, (255,255,255)) + menu_screen.blit(bonus_label, (100, 230)) + bonus_value = shop_font.render(f"{int(bonus_chance*100)}", True, (255,255,255)) + menu_screen.blit(bonus_value, (300, 230)) + bonus_minus_rect = pygame.Rect(350, 225, 40, 30) + bonus_plus_rect = pygame.Rect(450, 225, 40, 30) + pygame.draw.rect(menu_screen, (200,200,200), bonus_minus_rect) + pygame.draw.rect(menu_screen, (200,200,200), bonus_plus_rect) + menu_screen.blit(minus_text, minus_text.get_rect(center=bonus_minus_rect.center)) + menu_screen.blit(plus_text, plus_text.get_rect(center=bonus_plus_rect.center)) + + # Кнопка "Начать игру" + start_rect = pygame.Rect(menu_width//2 - 75, 300, 150, 50) + pygame.draw.rect(menu_screen, (0,255,0), start_rect) + start_text = shop_font.render("Начать игру", True, (0,0,0)) + menu_screen.blit(start_text, start_text.get_rect(center=start_rect.center)) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + elif event.type == pygame.MOUSEBUTTONDOWN: + mx, my = pygame.mouse.get_pos() + if width_minus_rect.collidepoint(mx, my): + grid_width = max(8, grid_width - 1) + elif width_plus_rect.collidepoint(mx, my): + grid_width = min(100, grid_width + 1) + elif height_minus_rect.collidepoint(mx, my): + grid_height = max(8, grid_height - 1) + elif height_plus_rect.collidepoint(mx, my): + grid_height = min(100, grid_height + 1) + elif bonus_minus_rect.collidepoint(mx, my): + bonus_chance = max(0.0, bonus_chance - 0.01) + elif bonus_plus_rect.collidepoint(mx, my): + bonus_chance = min(1.0, bonus_chance + 0.01) + elif start_rect.collidepoint(mx, my): + menu_running = False + + pygame.display.flip() + return grid_width, grid_height, bonus_chance -# --- Словарь символов (фолбэк, если спрайт не найден) --- +# ========= Вызываем меню настроек и обновляем глобальные параметры ========= +GRID_WIDTH, GRID_HEIGHT, BONUS_GENERATION_CHANCE = main_menu() +WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH + PANEL_WIDTH +WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT + +# Теперь устанавливаем главное окно игры +screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) +pygame.display.set_caption("Шахматы с Открытым Миром 1.1.1") + +# ========= Глобальные переменные ========= +global_revealed = set() +bonus_cells = {} +base_bonus_probabilities = { + 'regen': 0.42, + 'hp_upgrade': 0.32, + 'damage': 0.2, + 'king_hp_upgrade': 0.05, + 'add_piece': 0.01 +} +bonus_probabilities = base_bonus_probabilities.copy() piece_symbols = { 'rook': 'Л', 'knight': 'К', 'bishop': 'С', 'queen': 'Ф', - 'king': 'К', # Король + 'king': 'К', 'pawn': 'П' } -# === МЕХАНИКА БОНУСОВ === -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 = attacker.hp - b = defender.hp - if a < b: - defender.hp = b - a - attacker.hp = 0 - return (False, True) - elif a > b: - attacker.hp = a - b - defender.hp = 0 - return (True, False) - else: - attacker.hp = 1 - defender.hp = 0 - return (True, False) - -# === КЛАСС ФИГУРЫ === +# ========= Класс фигур ========= class Piece: def __init__(self, name, color, x, y): self.name = name @@ -118,9 +172,7 @@ class Piece: self.x = x self.y = y self.selected = False - self.has_moved = False # Для ограничения повторного хода за один ход - - # Инициализация HP + self.has_moved = False if self.name == 'king': self.max_hp = 5 self.hp = 5 @@ -145,7 +197,7 @@ class Piece: if not blocking or blocking.color != self.color: moves.append((nx, ny)) elif self.name in ['rook', 'bishop', 'queen']: - max_steps = self.move_range if hasattr(self, 'move_range') else 3 + max_steps = getattr(self, 'move_range', 3) if self.name == 'rook': directions = [(-1,0),(1,0),(0,-1),(0,1)] elif self.name == 'bishop': @@ -184,51 +236,124 @@ class Piece: moves.append((nx, ny)) return moves -# --- Инициализация фигур --- +# ========= Функция разрешения боя ========= +def resolve_combat(attacker, defender): + a = attacker.hp + b = defender.hp + if a < b: + defender.hp = b - a + attacker.hp = 0 + return (False, True) + elif a > b: + attacker.hp = a - b + defender.hp = 0 + return (True, False) + else: + attacker.hp = 1 + defender.hp = 0 + return (True, False) + +# ========= Функция инициализации фигур ========= def initialize_pieces(): - """Чёрные: y=2 (back), y=3 (pawns). Белые: y=27 (back), y=26 (pawns).""" pieces = [] cx = GRID_WIDTH // 2 - # Чёрные + offset = 3 # для базовой линии – 8 фигур + # Вычисляем вертикальные позиции пропорционально высоте поля: + black_back_y = max(0, int(GRID_HEIGHT * 0.1)) + black_pawn_y = min(GRID_HEIGHT - 1, black_back_y + 1) + white_back_y = GRID_HEIGHT - 1 - int(GRID_HEIGHT * 0.1) + white_pawn_y = max(0, white_back_y - 1) + 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)) + x = cx - offset + i + if 0 <= x < GRID_WIDTH: + pieces.append(Piece(pname, 'black', x, black_back_y)) for i in range(8): - x = cx - 3 + i - y = 3 - pieces.append(Piece('pawn', 'black', x, y)) - # Белые + x = cx - offset + i + if 0 <= x < GRID_WIDTH: + pieces.append(Piece('pawn', 'black', x, black_pawn_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)) + x = cx - offset + i + if 0 <= x < GRID_WIDTH: + pieces.append(Piece(pname, 'white', x, white_back_y)) for i in range(8): - x = cx - 3 + i - y = 26 - pieces.append(Piece('pawn', 'white', x, y)) + x = cx - offset + i + if 0 <= x < GRID_WIDTH: + pieces.append(Piece('pawn', 'white', x, white_pawn_y)) return pieces -pieces = initialize_pieces() +# ========= Спрайты для фигур ========= +piece_sprites = {} +def load_piece_sprites(): + piece_names_map = { + 'king': 'K', + 'queen': 'Q', + 'rook': 'R', + 'bishop': 'B', + 'knight': 'N', + 'pawn': 'P' + } + colors = ['white', 'black'] + for color in colors: + letter = 'w' if color == 'white' else 'b' + for name in piece_names_map: + filename = f"sprites/{letter}{piece_names_map[name]}.png" + try: + image = pygame.image.load(filename).convert_alpha() + image = pygame.transform.scale(image, (CELL_SIZE, CELL_SIZE)) + piece_sprites[(color, name)] = image + except Exception as e: + print(f"Ошибка загрузки спрайта {filename}: {e}") + piece_sprites[(color, name)] = None -selected_piece = None -possible_moves = [] -game_over = False -winner = None +load_piece_sprites() -# --- Туман войны --- -cell_counters = {} # {(x,y): turns_since_last_revealed} -# Каждая новая клетка имеет 10% шанс стать бонусной. -BONUS_GENERATION_CHANCE = 0.10 +# ========= Спрайты для окружающей среды ========= +try: + open_cell_sprite = pygame.image.load("sprites/open_cell.png").convert_alpha() + open_cell_sprite = pygame.transform.scale(open_cell_sprite, (CELL_SIZE, CELL_SIZE)) +except Exception as e: + print(f"Ошибка загрузки open_cell.png: {e}") + open_cell_sprite = None -# --- Система ходов и многоперемещений --- -current_turn = 'white' # Начинаем с белых +try: + fog_sprite = pygame.image.load("sprites/fog.png").convert_alpha() + fog_sprite = pygame.transform.scale(fog_sprite, (CELL_SIZE, CELL_SIZE)) +except Exception as e: + print(f"Ошибка загрузки fog.png: {e}") + fog_sprite = None + +# ========= Механика бонусов ========= +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' + +# ========= Торговцы, монеты и магазин ========= +merchant_positions = [(0, GRID_HEIGHT // 2), (GRID_WIDTH - 1, GRID_HEIGHT // 2)] +global_revealed.update(merchant_positions) +coin_cells = set() +player_coins = {'white': 0, 'black': 0} +shop_open = False +shop_items = { + 'extend_move': {'name': 'Расширить ход', 'cost': 6}, + 'increase_hp': {'name': 'Увеличить HP', 'cost': 6}, + 'damage_enemy': {'name': 'Уменьшить HP врага', 'cost': 6}, + 'curse': {'name': 'Проклятие', 'cost': 6} +} + +# ========= Система ходов ========= +current_turn = 'white' turn_count = 0 -moves_remaining = 3 # За один ход игрок может сделать 3 перемещения +moves_remaining = 3 -# --- Механика повышения вероятности красных полей --- def update_bonus_probabilities(): global bonus_probabilities if turn_count <= 100: @@ -247,8 +372,8 @@ def update_bonus_probabilities(): else: bonus_probabilities[bt] = prob / total_other_probs * remaining_prob -# --- Механика времени на ход --- -TURN_TIME_LIMIT = 120 # seconds +# ========= Время на ход ========= +TURN_TIME_LIMIT = 120 # секунд timer_start_time = time.time() timer_expired = False player_timeouts = {'white': 0, 'black': 0} @@ -274,7 +399,6 @@ def check_timer(): else: switch_turn() -# При переключении хода сбрасываем число оставшихся ходов и флаг has_moved у фигур текущего игрока. def switch_turn(): global current_turn, turn_count, timer_expired, moves_remaining current_turn = 'black' if current_turn == 'white' else 'white' @@ -287,17 +411,17 @@ def switch_turn(): if p.color == current_turn: p.has_moved = False -# После успешного перемещения фигуры вызываем эту функцию. def end_move(moved_piece): global moves_remaining - moved_piece.has_moved = True + # Если фигура погибла, этот ход всё равно расходуется moves_remaining -= 1 if moves_remaining <= 0: switch_turn() -# --- Обновление тумана войны и генерация бонусов/монет --- +# ========= Обновление тумана (с удалением монет при старении) ========= +cell_counters = {} # {(x,y): turns_since_last_revealed} def update_fog(): - global global_revealed, bonus_cells + global global_revealed, bonus_cells, coin_cells previous_revealed = global_revealed.copy() new_revealed = set() for p in pieces: @@ -326,9 +450,12 @@ def update_fog(): global_revealed.remove(pos) if pos in bonus_cells and bonus_cells[pos]['type'] != 'damage': del bonus_cells[pos] + if pos in coin_cells: + del coin_cells[pos] for pos in newly_revealed: if pos not in bonus_cells and pos not in coin_cells and random.random() < BONUS_GENERATION_CHANCE: - if random.random() < 0.90: + # 95% шанс бонус, 5% монета + if random.random() < 0.95: 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} @@ -336,76 +463,38 @@ def update_fog(): 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} - -# --- Механика магазина --- -# Повышенные цены: 6 монет за покупку -shop_open = False -shop_items = { - 'extend_move': {'name': 'Расширить ход', 'cost': 6}, - 'increase_hp': {'name': 'Увеличить HP', 'cost': 6}, - 'damage_enemy': {'name': 'Уменьшить HP врага', 'cost': 6}, - 'curse': {'name': 'Проклятие', 'cost': 6} -} - -# --- Реализация спрайтов фигур --- -# Ожидается, что в папке "sprites" находятся файлы: wK.png, wQ.png, wR.png, wB.png, wN.png, wP.png для белых, -# и bK.png, bQ.png, bR.png, bB.png, bN.png, bP.png для чёрных. -piece_sprites = {} - -def load_piece_sprites(): - piece_names_map = { - 'king': 'K', - 'queen': 'Q', - 'rook': 'R', - 'bishop': 'B', - 'knight': 'N', - 'pawn': 'P' - } - colors = ['white', 'black'] - for color in colors: - letter = 'w' if color == 'white' else 'b' - for name in piece_names_map: - filename = f"sprites/{letter}{piece_names_map[name]}.png" - try: - image = pygame.image.load(filename).convert_alpha() - image = pygame.transform.scale(image, (CELL_SIZE, CELL_SIZE)) - piece_sprites[(color, name)] = image - except Exception as e: - print(f"Ошибка загрузки спрайта {filename}: {e}") - piece_sprites[(color, name)] = None - -load_piece_sprites() - -# --- Функции отрисовки --- +# ========= Функции отрисовки ========= def draw_grid(): 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(): +def draw_environment(): 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) + rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE) + if pos in global_revealed: + if open_cell_sprite: + screen.blit(open_cell_sprite, rect) + else: + pygame.draw.rect(screen, (30, 30, 30), rect) + if cell_counters.get(pos, 0) == 4 and (pos not in bonus_cells) and (pos not in coin_cells): + if fog_sprite: + overlay = fog_sprite.copy() + overlay.set_alpha(128) + screen.blit(overlay, rect) + else: + overlay = pygame.Surface((CELL_SIZE, CELL_SIZE)) + overlay.set_alpha(128) + overlay.fill((169, 169, 169)) + screen.blit(overlay, 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) + if fog_sprite: + screen.blit(fog_sprite, rect) + else: + pygame.draw.rect(screen, COLOR_FOG, rect) def draw_bonus_cells(): for (bx, by), info in bonus_cells.items(): @@ -485,10 +574,7 @@ def draw_pieces(pieces): 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}" - # Определяем цвета: для белых фигур текст будет черным, для черных – белым; - # фон выбираем противоположный if p.color == 'white': text_color = (0, 0, 0) bg_color = (255, 255, 255) @@ -497,9 +583,8 @@ def draw_pieces(pieces): bg_color = (0, 0, 0) hp_text = font.render(hp_str, True, text_color) text_rect = hp_text.get_rect(topleft=(x + 2, y + 2)) - # Создаем временную поверхность для фона с альфа-каналом bg_surf = pygame.Surface(text_rect.size) - bg_surf.set_alpha(200) # степень прозрачности (0-255) + bg_surf.set_alpha(200) bg_surf.fill(bg_color) screen.blit(bg_surf, text_rect.topleft) screen.blit(hp_text, text_rect.topleft) @@ -554,6 +639,7 @@ def draw_victory(winner): text_rect = text.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2)) screen.blit(text, text_rect) +# ========= Применение бонуса ========= def apply_bonus(piece, bonus_type, pieces): if bonus_type == 'regen': if piece.name != 'king': @@ -572,7 +658,15 @@ def apply_bonus(piece, bonus_type, pieces): else: king.hp = min(king.hp + 1, king.max_hp) elif bonus_type == 'damage': - pass + damage = random.randint(1, 5) + piece.hp -= damage + print(f"Бонус DAMAGE: Фигура получила {damage} урона, осталось {piece.hp} HP") + if piece.hp <= 0: + try: + pieces.remove(piece) + except ValueError: + pass + return 'dead' 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)] @@ -586,43 +680,18 @@ def apply_bonus(piece, bonus_type, pieces): 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) - elif p.name == 'pawn': - setattr(p, 'move_range', getattr(p, 'move_range', 1) + 1) - 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 - elif key == 'curse': - 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 +# ========= Инициализация фигур ========= +pieces = initialize_pieces() +selected_piece = None +possible_moves = [] +game_over = False +winner = None -# --- Инициализируем туман войны в начале игры --- +# ========= Инициализация тумана ========= +cell_counters = {} # {(x,y): turns_since_last_revealed} update_fog() -# --- Основной цикл игры --- +# ========= Основной игровой цикл ========= running = True clock = pygame.time.Clock() @@ -640,10 +709,52 @@ while running: 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: + # Если на клетке есть бонус damage по пути + if selected_piece.name in ['rook', 'bishop', 'queen']: + # Если фигура двигается на несколько клеток, проверяем промежуточный путь + path = [] + dx = gx - selected_piece.x + dy = gy - selected_piece.y + if dx != 0: + dx //= abs(dx) + if dy != 0: + 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)) + survived = True + for pos in path: + if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage': + damage = random.randint(1, 5) + selected_piece.hp = max(selected_piece.hp - damage, 0) + del bonus_cells[pos] + if selected_piece.hp == 0: + # — модификация для обработки смерти на пути: + pieces.remove(selected_piece) + selected_piece = None + possible_moves.clear() + survived = False + # Считаем, что ход потрачен, уменьшаем moves_remaining + moves_remaining -= 1 + break + if not survived: + if moves_remaining <= 0: + switch_turn() + continue + # Если бонус (damage или другой) находится непосредственно на целевой клетке if (gx, gy) in bonus_cells: bonus_type = bonus_cells[(gx, gy)]['type'] - apply_bonus(selected_piece, bonus_type, pieces) + result = apply_bonus(selected_piece, bonus_type, pieces) del bonus_cells[(gx, gy)] + if result == 'dead': + # Если фигура погибла, засчитываем ход, уменьшаем счетчик и остаёмся на том же игроке + moves_remaining -= 1 + if moves_remaining <= 0: + switch_turn() + selected_piece = None + possible_moves.clear() + continue if (gx, gy) in coin_cells: player_coins[current_turn] += 1 coin_cells.remove((gx, gy)) @@ -662,8 +773,9 @@ while running: pieces.remove(attacker) selected_piece = None possible_moves.clear() - update_fog() - switch_turn() + moves_remaining -= 1 + if moves_remaining <= 0: + switch_turn() continue else: path = [] @@ -689,15 +801,18 @@ while running: selected_piece = None possible_moves.clear() survived = False - switch_turn() + moves_remaining -= 1 break - if survived: - attacker.x = gx - attacker.y = gy - attacker.selected = False - end_move(attacker) - selected_piece = None - possible_moves.clear() + if not survived: + if moves_remaining <= 0: + switch_turn() + continue + attacker.x = gx + attacker.y = gy + attacker.selected = False + end_move(attacker) + selected_piece = None + possible_moves.clear() else: path = [] if selected_piece.name in ['rook', 'bishop', 'queen']: @@ -722,15 +837,18 @@ while running: selected_piece = None possible_moves.clear() survived = False - switch_turn() + moves_remaining -= 1 break - if survived: - selected_piece.x = gx - selected_piece.y = gy - selected_piece.selected = False - end_move(selected_piece) - selected_piece = None - possible_moves.clear() + if not survived: + if moves_remaining <= 0: + switch_turn() + continue + selected_piece.x = gx + selected_piece.y = gy + selected_piece.selected = False + end_move(selected_piece) + selected_piece = None + possible_moves.clear() update_fog() else: selected_piece.selected = False @@ -746,13 +864,14 @@ while running: 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) + # Здесь можно добавить логику покупки + pass elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: shop_open = False screen.fill(COLOR_BG) + draw_environment() draw_grid() - draw_fog() draw_merchants() draw_bonus_cells() draw_coin_cells() diff --git a/sprites/fog.png b/sprites/fog.png new file mode 100644 index 0000000..c71318a Binary files /dev/null and b/sprites/fog.png differ diff --git a/sprites/open_cell.png b/sprites/open_cell.png new file mode 100644 index 0000000..b55b3ae Binary files /dev/null and b/sprites/open_cell.png differ diff --git a/sprites/pre_fog.png b/sprites/pre_fog.png new file mode 100644 index 0000000..c71318a Binary files /dev/null and b/sprites/pre_fog.png differ