Проф. Волкгонагалл: Лесная Трансфигурация 37

Материал из Абсурдопедии
Перейти к навигацииПерейти к поиску

​Поднимите руки те, кто считает себя геймером. Ага, вижу. Вы во что играете? В Доту? В Киберпанк? В Геншин Импакт, прости господи? Ребят, это всё детский сад. Это песочница для зумеров. Я на днях открыл для себя настоящий, суровый, экзистенциальный киберспорт. Игра называется «Проф. Волкгонагалл: Лесная Трансфигурация 37».

​Звучит как фанфик по Гарри Поттеру, который написала нейросеть с депрессией, да? Но нет! Это текстовая выживалка на Питоне. И это, мать его, самый точный симулятор жизни в современной России, который я когда-либо видел!

​Серьёзно, давайте разберём. Вы играете за волка. У вас сто хитпоинтов и шкала сытости. Обычное дело, да? Но механика питания — это просто шедевр реализма. Вы съедаете, например, грибника. Сытость восстанавливается, всё классно. Но вы не можете съесть следующего грибника, пока не... сходите в туалет.

​Понимаете? Это не сказка про Серого Волка, где он проглотил бабушку, Красную Шапочку, трех поросят и пошел на танцы. Это суровая биология! У тебя полный желудок хипстера в дождевике, куда тебе еще лесничего пихать? Ты стоишь перед аппетитным мужиком, у тебя слюни текут, но игра тебе пишет: «Сначала нужно навалить кучу от предыдущего съеденного!» Это же идеальная метафора нашей потребительской корзины! Ты не можешь взять новый кредит, пока не высрешь старый! Ты не можешь впихнуть в себя новые новости, пока не переваришь утренние! Физика, сука, бессердечная ты сука!

​И вот ты, как нормальный гражданин, ищешь, где бы присесть. А игра тебе говорит: «Туда насрать нельзя!». В дом нельзя. На палатку нельзя. На других людей нельзя. Ребят, да этот волк культурнее половины жителей моего родного спального района! (Смех) Он ищет чистую полянку, грибочек или кустик, чтобы сделать свои дела. Уважение таким хищникам.

​А вокруг — лес. Деревья, росточки, грибочки. Красота. Но тут в игру вступает урбанистика. Знаете эту фишку Москвы, когда ты ложишься спать с видом на парк, а просыпаешься, потому что тебе в окно стучит стрела башенного крана? В игре то же самое! Ты бежишь по лесу, и вдруг — бац! — генерируется завод. Просто из ниоткуда! Причем он не один, заводы начинают строиться и сразу пускать смог 5 на 5 клеток. Заходишь в смог — минус 10 хитпоинтов. Ты задыхаешься! Волк кашляет, плюется, у него астма начинается!

Это же симулятор жителя Челябинска или Красноярска! Ты просто шёл за грибами, а теперь у тебя ХОБЛ и ты ищешь ингалятор. Заводы даже не строятся рядом с рабочими городками — они строятся там, где ты гуляешь! Прямо как в жизни: промзону мы воткнем в лесопарк, а элитные ЖК — на месте бывшей свалки радиационных отходов. Идеальный баланс!

​Но давайте о еде. Самая частая добыча волка — грибники. О, грибники в этой игре — это отдельный вид искусства. Это мы с вами, ребята. Это типичный офисный планктон, который выехал на природу «перезагрузиться». Как они себя ведут? Они ходят от одного гриба к другому. Бессмысленно, хаотично, ведомые первобытным инстинктом собирательства.

​Но если грибник видит табличку, он начинает её читать. И знаете, сколько он её читает? Четыре хода! Четыре, мать его, хода!

Первый ход он читает: «Осторожно, волк-людоед!..»

Второй ход: «...В этом лесу водится опасный хищник...»

Третий: «...который съел не одну сотню грибников...»

Четвертый: «...Что тебе дороже: грибы или жизнь?»

​Вы понимаете, насколько это тупо и жизненно одновременно?! Человек стоит в лесу. Рядом воет волк. А он такой: «Подождите, не мешайте, тут лонгрид интересный, я должен дочитать, вдруг в конце промокод на скидку в Яндекс.Лавку!» Это же точная копия того, как мы читаем пользовательские соглашения! Мы видим красную табличку «ОПАСНОСТЬ, ВАШИ ДАННЫЕ УКРАДУТ, ВАС УБЬЮТ», и мы такие: «Угу... угу... так... галочка... Принять! Где мой кэшбек?»

​Если грибник все-таки замечает волка (а это бывает, если он оторвался от чтения), он начинает орать. Начинается паника. Красным текстом на экране капсом пишет: ВОЛК! ВОЛК! Грибник бежит к ближайшему домику. Забегает туда и игра пишет: «Грибник в домике!». Всё. Чик-чирик, я в домике! Волшебный щит. Волк не может выломать дверь, не может сдуть дом, как в сказке. Потому что это Россия, у нас железные двери с домофоном от «Цифрал», хрен ты туда пролезешь!

​Но у волка есть проблема посерьёзнее хипстеров. Это Закон. Лесничие. Егеря. Местные менты, короче. Егеря спавнятся, когда грибники начинают орать. У них ружья, они стреляют в тебя, как только видят, причем урон зависит от расстояния. Подошёл вплотную — получил дробью в морду на 180 хитпоинтов, сразу труп.

Если егеря ранить, он не дерётся до конца, как Рэмбо. Он бежит в больницу! Реально, находит здание с красным крестом, заходит, сидит там, лечится по ОМС и выходит с полным здоровьем. Идеальная система здравоохранения!

​Но самое гениальное начинается потом. Егерь ходит-ходит по лесу, устает. И куда он идет? Правильно, в бар! Посреди леса, между заводом, больницей и кучей дерьма, стоит цирк-шапито, который работает как бар. Егерь заходит туда абсолютно трезвым служителем закона. А выходит — ВНИМАНИЕ — пьяным!

И тут ИИ игры раскрывается во всей красе. Пьяный егерь — это машина хаоса. Это просто Джокер этого леса. У него сбит прицел, он ходит зигзагами, наступает в капканы (об этом позже) и, самое главное, ОН НАЧИНАЕТ ПАЛИТЬ ВО ВСЕХ ПОДРЯД!

​Трезвый егерь стреляет только в волка. Пьяный егерь выходит из бара, видит куст, видит грибника и такой: «О, браконьеры, стоять!» БАХ!

И игра пишет: «💥 Пьяный егерь выстрелил в грибника!»

И что делает грибник? Вы думаете, он умирает в агонии? Нет! Он же москвич! Он кричит: «Я БУДУ ЖАЛОВАТЬСЯ В РОСЛЕСХОЗ! 😭😭😭»

Ребята, тебя только что прострелил из дробовика пьяный мужик в камуфляже, у тебя дыра в животе размером с арбуз, а ты достаёшь айфон и строчишь жалобу в Добродел! «В лесу некомфортная среда, егеря токсичные, поставьте оценку 1 звезда».

​Но вы не поверите, жалоба РАБОТАЕТ! Если два грибника напишут жалобы, игра выдает эпичное: «Грибник написал жалобу в Рослесхоз! ВСЕ БАРЫ ЗАКРЫТЫ!»

Всё! Правительство реагирует молниеносно! Сухой закон в лесу! Никакого бухла! Бары закрыты! Но пьяные егеря-то уже вышли! И грибник бежит от них и кричит: «Бары закрыли, а алкаши остались! 😭😭😭». Это самая точная политическая сатира на антиалкогольные кампании, которую я видел! Закрыть наливайки — это мы можем. А что делать с теми, кто уже в хламину и с дробовиком? Это ваши проблемы, граждане, держитесь там!

​Но пьяный егерь стреляет не только в грибников. Иногда он стреляет в других егерей! Один мент стреляет в другого мента. Тот, в кого попали, если он трезвый, орёт: «Ты куда стреляешь, придурок?!» А если в кого попали тоже пьяный, он отвечает: «Оп, маслину поймал, гы-гы-гы!» Пацаны вообще ребята, у них корпоратив полным ходом, им вообще плевать на волка! Волк сидит в кустах, жует ногу грибника и просто смотрит бесплатное кино от Тарантино!

​Кстати, про ловушки. По лесу раскиданы капканы. Волк в них попадает, получает урон, застревает. Обычный егерь их обходит. А пьяный егерь? Пьяный егерь смело шагает прямо в капкан! Игра пишет: «Пьяный егерь попался в капкан!» Он стоит, орёт, хитпоинты уходят. А может вообще наступить на капкан и сдохнуть на месте. Карма в действии.

​Но помимо всей этой фауны, в игре есть третья сила. Настоящая, хтоническая мощь. Это Рабочие.

Они генерируются сразу группами. У них свои рабочие городки 7 на 7 клеток. Они ходят на заводы. Они строят экономику. Волк их боится. Реально, если волк попытается съесть рабочего, игра пишет: «Вы попытались съесть рабочего, но он убил вас монтировкой!». Всё, Ваншот. Геймовер. Волк — сверххищник, жрет ментов, жрет людей, но против Василича с третьего разряда у него нет шансов! У Василича ипотека, алименты, похмелье и монтировка из советской стали. Ему твой укус за ляжку — как комариный писк!

Но рабочие мирные. Они ходят, работают. Пока их не трогают.

Но помните наших пьяных егерей? Джокеров леса?

Иногда пьяный егерь, промазав по волку, попадает дробью в Рабочего.

​И вот тут... ребята, вот тут начинается то, ради чего Карл Маркс писал «Капитал».

Начинается Пролетарская Ярость. У рабочих включается параметр aggro_active. Игра пишет: «👷х10: Э, чё за хуйня? Ну всё, пизда вам, пидоры лесные!»

ВЕСЬ ГОРОДОК. Все 10 рабочих бросают свои дела. Они не пишут жалобы в Рослесхоз, как эти соевые грибники. Они берут в руки монтировки и идут пиздить власть!

​Это выглядит страшно! Они вычисляют ближайшего егеря, причем любого, пьяного, трезвого — им пофиг, форма есть — значит виноват. Они идут толпой. Они наступают на капканы... и знаете, что происходит? Они не застревают. Игра пишет: «Рабочий сломал китайский капкан крепким сапогом!» Иммортулы! Терминаторы в оранжевых жилетах!

Они догоняют егеря. И игра буднично так сообщает: «Рабочий убил егеря монтировкой!» Бам! Следующий! «Рабочий убил грибника монтировкой!» Просто под горячую руку попался, нефиг было тут с корзинкой стоять, когда пролетариат бунтует! А если на их пути оказывается волк? «Рабочий наступил на вас и убил монтировкой!»

Они зачищают сервер! Это не лес, это Пикалёво в 2009 году, если бы туда не приехал Путин!

​Это идеальная модель общества. Хипстеры жалуются, силовики бухают и стреляют по своим, а работягам всё пофиг, пока их не заденут, но если заденут — они снесут всё до основания! И на фоне всего этого волк просто хочет... покакать, чтобы съесть ещё одного хипстера.

​Но как же волку выжить во всем этом безумии? Как победить?

Механика передвижения позволяет прыгать. Но самое крутое — это Кроличьи норы. Иногда на карте генерируется нора. Волк прыгает в неё. И дальше начинается катсцена! На экране буквально рисуется, как волк летит вниз, потом ползет по туннелю, а по дороге — «АМ!» — жрет кроликов. Земля, земля, кролик, земля. Восстанавливает 100 хитпоинтов. И вылезает он... угадайте где? В центре рабочего городка! Прямо из пола в магазине «Пятерочка»!

Игра так и пишет: «Туннель ведёт в магазин в центре рабочего городка! Так вот зачем они нужны!» Это же гениальное объяснение, откуда в наших супермаркетах берутся странные люди! Вы когда-нибудь стояли в очереди на кассу в спальном районе? Стоишь ты, бабушка, и вдруг вылезает какой-то хмырь, небритый, дикий, с полным ртом какой-то шерсти. Откуда он взялся? А он по кроличьей норе из леса пришел! И туннель за ним тут же осыпается. Всё, обратной дороги нет, иди покупай пиво по акции.

​Но главная цель игры, её финал — это квест, который достоин Кафки.

Посреди леса стоит Здание. Библиотека.

Зайти в неё просто так нельзя — «Препятствие (или библиотека закрыта)».

Чтобы туда попасть, нужно стать Читателем. Как волк может стать читателем? Логично: он должен сожрать людей с книгами.

Вы жрёте пять грибников. ПЯТЬ. И из пятого выпадает... «Книга грибника (📗)». Видимо, справочник, как отличить поганку от шампиньона, который ему не сильно помог.

Потом вы жрёте трех егерей. Из третьего выпадает... «Книга егеря (📘)». Устав, наверное. Или сборник тостов.

То есть наоборот. И то не факт, что заветную Книгу грибника вы таки получите. Короче, сами разберётесь.

То есть вы совершаете восемь жестоких убийств. Вы маньяк, заливший кровью весь лес, за вами гоняется полиция и рабочие с монтировками. И вот, с двумя окровавленными книгами в зубах, вы подходите к Библиотеке.

​Там стоит Библиотекарь. Старый, седой мудрец. И он говорит вам стихами:

«Вот спасибо, умный волк!
Вижу, знаешь в книгах толк.
Ты теперь друг человека.
Заходи в библиотеку!»

​И тут начинается невероятное! Игра перекидывает вас на отдельный экран. Рисуется огромная библиотека, полки с книгами, и Библиотекарь говорит: «О, благородный спаситель литературы! Ты вернул наши бесценные тома! В знак бесконечной благодарности я торжественно заношу тебя в Красную книгу! Отныне ты под охраной закона, и ни один егерь не посмеет поднять на тебя ствол!»

​Ребята. Это. Гениально. Ты съел восьмерых налогоплательщиков. Ты терроризировал регион. Но ты сдал книги в библиотеку! Ты УВАЖАЕШЬ ПЕЧАТНОЕ СЛОВО! Ты принес справочник грибника!

И тебе дают не пожизненное. Тебе дают статус неприкосновенности! Красная книга! Бронь от мобилизации! Корочка депутата лесной думы!

​Ты выходишь из библиотеки. У тебя статус is_red_book_protected. Трезвые егеря, которые еще вчера палили по тебе картечью, теперь подходят, смотрят на тебя и говорят: «Бля-я, его ж нельзя убивать...» И просто уходят!

Это Россия! У тебя может быть морда в крови, изо рта торчит сапог товарища майора, но если у тебя есть правильная Бумажка с Печатью... Извините, Иван Иванович, не признали! Хорошего дня, счастливой охоты!

​Правда, в библиотеке есть интересная концовка. Если подождать, на экране появляется надпись: «М-м-м, вкусно, как Орбит со вкусом библиотекаря!» А потом волк оставляет посреди читального зала огромную кучу дерьма 💩. И возвращается в лес. Потому что бюрократия бюрократией, а физиологию никто не отменял! Получил справку — насри на систему!

​И вот ты стоишь в лесу. Трезвые менты тебя не трогают. Заводы дымят. Рабочие монтировками забивают последних грибников.

Но для победы этого мало. Помните пьяных егерей? Им-то пофиг на Красную книгу. Пьяный егерь стреляет не в статус, он стреляет в силуэт!

Чтобы выиграть игру окончательно, нужно чтобы совпали три вещи:

1. ​Ты получил Красную книгу.

​2. Грибники написали жалобы и закрыли все бары (чтобы новые егеря не могли напиться).

​3. Ты должен убить или дождаться, пока помрут сами, все оставшиеся пьяные егеря.

​То есть победа в этой игре — это не просто выжить. Это уничтожить саму систему алкоголизма в правоохранительных органах через жалобы либеральной общественности, параллельно прикрываясь депутатской корочкой из библиотеки!

Да это не игра на Питоне! Это предвыборная программа! Это готовый сценарий для сериала Юрия Быкова! ​И когда последний пьяный егерь проваливается в кроличью нору и задыхается там насмерть, игра выдает тебе: «🎉🎉🎉 Теперь ты в полной безопасности! Ты — хозяин леса! ПОБЕДА!»

​Я когда прошёл это, я плакал. Я реально сидел перед терминалом консоли и вытирал слёзы. Потому что я понял: чтобы стать хозяином в этой жизни, не нужно быть самым сильным. Не нужно быть самым быстрым. Нужно просто вовремя какать, уметь прятаться в Пятёрочке от мужиков с монтировками, вовремя сдать книжки в библиотеку и подождать, пока твои враги сопьются и сами себя перестреляют!

​Спасибо вам огромное, вы потрясающая публика! Играйте в правильные игры!

Ах да, чуть не забыл: код игры[править]

import os
import sys
import random
import shutil
import time
import textwrap
import math
import re

if os.name == 'nt':
    os.system('chcp 65001 >nul')

def clear_screen():
    if os.name == 'nt':
        os.system('cls')
    else:
        print("\033[H\033[J", end="")

def visible_len(s):
    return len(re.sub(r'\033\[[0-9;]*m', '', s))

def get_input():
    # Маппинг движения для 1 шага
    move_map = {
        'е': (-1, -1), 't': (-1, -1),
        'н': (0, -1),  'y': (0, -1),
        'г': (1, -1),  'u': (1, -1),
        'п': (-1, 0),  'g': (-1, 0),
        'о': (1, 0),   'j': (1, 0),
        'м': (-1, 1),  'v': (-1, 1),
        'и': (0, 1),   'b': (0, 1),
        'т': (1, 1),   'n': (1, 1),
        '7': (-1, -1), '8': (0, -1), '9': (1, -1),
        '4': (-1, 0),                '6': (1, 0),
        '1': (-1, 1),  '2': (0, 1),  '3': (1, 1),
        '5': (0, 0),   ' ': (0, 0),
        'р': (0, 0),   'p': (0, 0) # Клавиша Р (и P) для пропуска хода
    }
    # Маппинг для прыжка на 2 клетки
    jump_map = {
        'w': (0, -2), 'ц': (0, -2),
        'a': (-2, 0), 'ф': (-2, 0),
        's': (0, 2),  'ы': (0, 2),
        'd': (2, 0),  'в': (2, 0)
    }
    
    if os.name == 'nt':
        import msvcrt
        while True:
            key = msvcrt.getwch()
            if key in ('\xe0', '\x00'):
                arr = msvcrt.getwch()
                if arr == 'H': return (0, -1)
                if arr == 'P': return (0, 1)
                if arr == 'K': return (-1, 0)
                if arr == 'M': return (1, 0)
                return "invalid"
            elif key == '\x03': return None
            else:
                k = key.lower()
                if k in ['q', 'й']: return None
                if k in move_map: return move_map[k]
                if k in jump_map: return jump_map[k]
                return "invalid"
    else:
        import tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
            if ch == '\x1b':
                sys.stdin.read(1)
                arr = sys.stdin.read(1)
                if arr == 'A': return (0, -1)
                if arr == 'B': return (0, 1)
                if arr == 'D': return (-1, 0)
                if arr == 'C': return (1, 0)
                return "invalid"
            elif ch == '\x03': return None
            else:
                k = ch.lower()
                if k in ['q', 'й']: return None
                if k in move_map: return move_map[k]
                if k in jump_map: return jump_map[k]
                return "invalid"
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

def main():
    player_x, player_y = 0, 0
    health = 100
    satiety = 50
    dead = False
    won = False
    bars_closed = False
    death_msg = ""
    complaints_count = 0
    
    eaten_pickers = 0
    eaten_hunters = 0
    total_yells = 0
    needs_to_poop = False 
    
    has_hunter_book = False
    has_picker_book = False
    library_poem_heard = False
    library_entered = False
    is_red_book_protected = False
    
    turns_since_drunk_seen = 0
    hunter_spawn_cooldown = 0
    
    grid = {}
    factories = set()
    never_empty = set()
    npcs = [] 
    bar_hunters = [] 
    hospital_hunters = []
    turn_events = []
    error_msg = ""
    
    discovered_traps = set()
    wolf_trapped = False
    
    generated_towns = set()
    town_centers = []
    aggro_towns_notified = set()

    def generate_towns():
        x = random.randint(0, 19)
        centers = [
            (x, 20 - x),
            (x - 20, x),
            (-x, x - 20),
            (20 - x, -x)
        ]
        for n, (cx, cy) in enumerate(centers, 1):
            grid[(cx, cy)] = '🏪'
            never_empty.add((cx, cy))
            town_centers.append((cx, cy))
            
            available = []
            for dx in range(-3, 4):
                for dy in range(-3, 4):
                    if dx == 0 and dy == 0: continue
                    available.append((cx + dx, cy + dy))
                    
            random.shuffle(available)
            for _ in range(10):
                bx, by = available.pop()
                grid[(bx, by)] = '🏢'
                never_empty.add((bx, by))
                
            for _ in range(10):
                wx, wy = available.pop()
                grid[(wx, wy)] = '..'
                never_empty.add((wx, wy))
                npcs.append({
                    'type': 'worker', 'x': wx, 'y': wy, 'hp': 100, 
                    'town_id': n, 'aggro_active': False, 'aggro_pending': False,
                    'just_spawned': False, 'age': 0
                })
                
            for wx, wy in available:
                grid[(wx, wy)] = '..'
                never_empty.add((wx, wy))
            generated_towns.add(n)

    def check_discover_traps():
        discovered_any = False
        for dx_t in [-1, 0, 1]:
            for dy_t in [-1, 0, 1]:
                check_x, check_y = player_x + dx_t, player_y + dy_t
                if grid.get((check_x, check_y)) == '🪤' and (check_x, check_y) not in discovered_traps:
                    discovered_traps.add((check_x, check_y))
                    discovered_any = True
        if discovered_any:
            turn_events.append("\033[33mВы обнаружили капкан(ы)!\033[0m")
            
    def play_library_scene():
        clear_screen()
        print("\033[93m" + "="*52)
        print("                Б И Б Л И О Т Е К А")
        print("="*52 + "\033[0m\n")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————\n")
        print("                       🧓")
        print("                     / | \\\n")
        print("                       🐺")
        print("\033[93m" + "="*52 + "\033[0m\n")
        print("  🧓 Библиотекарь: «О, благородный спаситель литературы!")
        print("  Ты вернул наши бесценные тома!")
        print("  В знак бесконечной благодарности я торжественно")
        print("  заношу тебя в Красную книгу (📕)!")
        print("  Отныне ты под охраной закона, и ни один")
        print("  егерь не посмеет поднять на тебя ствол!»")
        time.sleep(10)
        
        clear_screen()
        print("\033[93m" + "="*52)
        print("                Б И Б Л И О Т Е К А")
        print("="*52 + "\033[0m\n")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————\n")
        print("                       🐺🩸")
        print("                 М-м-м, вкусно, как Орбит ")
        print("                   со вкусом библиотекаря!\n")
        print("\033[93m" + "="*52 + "\033[0m\n")
        time.sleep(3)
        
        clear_screen()
        print("\033[93m" + "="*52)
        print("                Б И Б Л И О Т Е К А")
        print("="*52 + "\033[0m\n")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————")
        print(" |📚📚📚📚📚|    |📚📚📚📚📚|    |📚📚📚📚📚|")
        print("  ——————————       ——————————       ——————————\n")
        print("                       🐺")
        print("                       💩\n\n")
        print("\033[93m" + "="*52 + "\033[0m\n")
        time.sleep(2)
        clear_screen()

    def play_rabbit_hole_scene():
        # Часть 1: волк падает вниз
        for i in range(3):
            clear_screen()
            print("\n" * (i + 5))
            print(" " * 20 + "🐺")
            print(" " * 19 + "|  |")
            print(" " * 19 + "|  |")
            print(" " * 19 + "|  |")
            time.sleep(1)
            
        # Часть 2: туннель и поедание кроликов
        for i in range(20):
            clear_screen()
            earth = "🟫" * i
            am = " АМ! " if i > 0 else " "
            rabbits = "🐰" * (20 - i)
            print("\n" * 10)
            print(" " * 5 + earth + "🐺" + am + rabbits)
            time.sleep(1)
            
        # Часть 3: финал туннеля
        clear_screen()
        print("\n" * 10)
        print(" " * 5 + "🟫" * 20 + "💩🐺 ⬆️")
        print("\nТуннель ведёт в магазин в центре рабочего городка!")
        print("Так вот зачем они нужны!")
        time.sleep(3)
        clear_screen()

    def generate_cell(x, y):
        if (x, y) not in grid:
            if x == 0 and y == 0:
                grid[(x, y)] = '🐾'
                return
            
            hole_prob = min(1.0, 0.00002 * (total_yells ** 2))
            r_hole = random.random()
            if r_hole < hole_prob:
                grid[(x, y)] = '🕳️'
                never_empty.add((x, y))
                return

            r = random.random()
            if r < 0.2: grid[(x, y)] = '🌲'
            elif r < 0.34: grid[(x, y)] = '🌱'
            elif r < 0.38: grid[(x, y)] = '🍄'
            elif r < 0.39: grid[(x, y)] = '🏠'
            elif r < 0.392: grid[(x, y)] = '🏛️'
            elif r < 0.4: grid[(x, y)] = '🪧'
            elif r < 0.45: grid[(x, y)] = '🎪'
            elif r < 0.455: grid[(x, y)] = '🏥'
            elif r < 0.495: grid[(x, y)] = '🚩'
            elif r < 0.5: grid[(x, y)] = '🪤'
            else: grid[(x, y)] = '..'
            
            if grid[(x, y)] != '..':
                never_empty.add((x, y))

    def generate_viewport():
        R = 12
        for y in range(player_y - R, player_y + R + 1):
            for x in range(player_x - R, player_x + R + 1):
                generate_cell(x, y)

    def update_factories_and_smog():
        R = 12 
        for cy in range(player_y - R + 2, player_y + R - 1):
            for cx in range(player_x - R + 2, player_x + R - 1):
                if grid.get((cx, cy)) == '🏭':
                    continue
                
                can_build = True
                for dy_t in range(-2, 3):
                    for dx_t in range(-2, 3):
                        # Исключаем углы квадрата 5x5
                        if abs(dx_t) == 2 and abs(dy_t) == 2:
                            continue
                            
                        nx, ny = cx + dx_t, cy + dy_t
                        if nx == 0 and ny == 0:
                            can_build = False
                            break
                        if (nx, ny) not in grid:
                            can_build = False
                            break
                        if (nx, ny) in never_empty and grid.get((nx, ny)) not in ['🌱', '🍄', '🚩', '🎪', '🪤', '🕳️']:
                            can_build = False
                            break
                        if grid.get((nx, ny)) not in ['..', '🐾', '☁️', '🏭', '🌱', '🍄', '🚩', '🎪', '🪤', '🕳️']:
                            can_build = False
                            break
                        
                        # Проверка, чтобы завод не пересекал рабочие городки 7x7
                        for cx_town, cy_town in town_centers:
                            if abs(nx - cx_town) <= 3 and abs(ny - cy_town) <= 3:
                                can_build = False
                                break
                    if not can_build:
                        break
                
                if can_build:
                    grid[(cx, cy)] = '🏭'
                    factories.add((cx, cy))
                    
        for fx, fy in factories:
            if abs(fx - player_x) > 20 or abs(fy - player_y) > 20:
                continue
            for dy_t in range(-2, 3):
                for dx_t in range(-2, 3):
                    # Исключаем углы квадрата 5x5
                    if abs(dx_t) == 2 and abs(dy_t) == 2:
                        continue
                        
                    nx, ny = fx + dx_t, fy + dy_t
                    if nx == 0 and ny == 0:
                        continue
                    if grid.get((nx, ny)) in ['..', '🐾', '🌱', '🍄', '🚩', '🎪', '🪤', '🕳️']:
                        if grid.get((nx, ny)) in ['🌱', '🍄', '🚩', '🎪', '🪤', '🕳️']:
                            never_empty.discard((nx, ny))
                        grid[(nx, ny)] = '☁️'

    def is_visible(dx, dy):
        return abs(dx) <= 7 and abs(dy) <= 7

    def has_los(x1, y1, x2, y2, limit_to_viewport=True):
        if limit_to_viewport:
            if abs(x1 - player_x) > 7 or abs(y1 - player_y) > 7: return False
            if abs(x2 - player_x) > 7 or abs(y2 - player_y) > 7: return False
            
        if abs(x1 - x2) > 7 or abs(y1 - y2) > 7: return False
        
        dx = x2 - x1
        dy = y2 - y1
        if dx == 0 and dy == 0: return True

        min_x, max_x = min(x1, x2), max(x1, x2)
        min_y, max_y = min(y1, y2), max(y1, y2)
        eps = 1e-7

        for cx in range(min_x, max_x + 1):
            for cy in range(min_y, max_y + 1):
                if (cx, cy) == (x1, y1) or (cx, cy) == (x2, y2):
                    continue

                cell_val = grid.get((cx, cy))
                if cell_val in ['🌲', '🏠', '🌱', '💩', '🏛️', '🎪', '🪧', '🏥', '🏭', '🏪', '🏢'] or any(n['x'] == cx and n['y'] == cy for n in npcs) or (cell_val == '☁️' and not (cx == player_x and cy == player_y)):
                    t_min = 0.0
                    t_max = 1.0

                    if dx == 0:
                        if x1 <= cx - 0.5 or x1 >= cx + 0.5:
                            continue
                    else:
                        tx1 = (cx - 0.5 - x1) / dx
                        tx2 = (cx + 0.5 - x1) / dx
                        t_min = max(t_min, min(tx1, tx2))
                        t_max = min(t_max, max(tx1, tx2))

                    if dy == 0:
                        if y1 <= cy - 0.5 or y1 >= cy + 0.5:
                            continue
                    else:
                        ty1 = (cy - 0.5 - y1) / dy
                        ty2 = (cy + 0.5 - y1) / dy
                        t_min = max(t_min, min(ty1, ty2))
                        t_max = min(t_max, max(ty1, ty2))

                    if t_min < t_max - eps:
                        return False

        return True

    def eat(entity_type):
        nonlocal health, satiety, eaten_pickers, eaten_hunters
        if entity_type == 'picker': eaten_pickers += 1
        elif entity_type == 'hunter': eaten_hunters += 1
        
        gain = 20
        if health < 100:
            need = 100 - health
            if gain <= need:
                health += gain
                gain = 0
            else:
                health = 100
                gain -= need
        satiety += gain

    def get_direction_to_nearest(npc, target_type):
        def is_valid_step(x, y):
            if abs(x - player_x) > 12 or abs(y - player_y) > 12: return False
            if grid.get((x, y)) in ['🌲', '🏠', '💩', '🏛️', '🎪', '🪧', '🏥', '🏭', '🏪', '🏢']: return False
            if grid.get((x, y)) in ['🪤', '🕳️'] and not (npc['type'] == 'hunter' and npc.get('is_drunk')): return False
            if any(n != npc and n['x'] == x and n['y'] == y for n in npcs): return False
            if x == player_x and y == player_y and target_type != 'wolf': return False
            
            cell = grid.get((x, y), '..')
            if cell == '🚩':
                if npc['type'] == 'picker' and not npc.get('aware'): return False
                if npc['type'] == 'hunter' and not npc.get('is_drunk'): return False
                
            if npc['type'] == 'picker' and not npc['aware']:
                if cell == '🌱': return False
            return True

        def is_target(x, y):
            if target_type == '🍄': return grid.get((x, y)) == '🍄'
            elif target_type == '🏠':
                return any(grid.get((x+dx, y+dy)) == '🏠' for dx, dy in [(0,1), (0,-1), (1,0), (-1,0)])
            elif target_type == '🏥':
                return any(grid.get((x+dx, y+dy)) == '🏥' for dx, dy in [(0,1), (0,-1), (1,0), (-1,0)])
            elif target_type == 'wolf': return x == player_x and y == player_y
            return False

        if is_target(npc['x'], npc['y']):
            return (0, 0)

        queue = []
        visited = {}
        
        for dx, dy in [(0, -1), (-1, 0), (1, 0), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)]:
            nx, ny = npc['x'] + dx, npc['y'] + dy
            if is_valid_step(nx, ny):
                queue.append((nx, ny, (dx, dy), 1))
                visited[(nx, ny)] = {(dx, dy): 1}
                
        min_target_depth = 999
        best_first_steps = set()
        
        while queue:
            cx, cy, first_step, depth = queue.pop(0)
            
            if depth > min_target_depth:
                continue
                
            if is_target(cx, cy):
                min_target_depth = depth
                best_first_steps.add(first_step)
                continue
                
            if depth >= 10:
                continue
                
            for dx, dy in [(0, -1), (-1, 0), (1, 0), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)]:
                nx, ny = cx + dx, cy + dy
                if is_valid_step(nx, ny):
                    if (nx, ny) not in visited:
                        visited[(nx, ny)] = {}
                    
                    if first_step not in visited[(nx, ny)] or visited[(nx, ny)][first_step] > depth + 1:
                        visited[(nx, ny)][first_step] = depth + 1
                        queue.append((nx, ny, first_step, depth + 1))
                        
        if best_first_steps:
            best_list = list(best_first_steps)
            if target_type == 'wolf':
                old_x, old_y = npc['x'], npc['y']
                npc['x'], npc['y'] = -999, -999
                currently_has_los = has_los(old_x, old_y, player_x, player_y)
                
                if currently_has_los:
                    los_steps = []
                    for step in best_list:
                        nx, ny = old_x + step[0], old_y + step[1]
                        if has_los(nx, ny, player_x, player_y):
                            los_steps.append(step)
                    npc['x'], npc['y'] = old_x, old_y
                    
                    if los_steps:
                        return random.choice(los_steps)
                    else:
                        return (0, 0)
                
                npc['x'], npc['y'] = old_x, old_y
                return random.choice(best_list)
                
            return random.choice(best_list)
            
        return (0, 0)

    def find_drunk_target(drunk_npc):
        candidates = []
        
        if has_los(drunk_npc['x'], drunk_npc['y'], player_x, player_y, limit_to_viewport=False):
            dist = math.hypot(player_x - drunk_npc['x'], player_y - drunk_npc['y'])
            candidates.append({'prio': 1, 'dist': dist, 'type': 'wolf', 'obj': None})
            
        for target in npcs:
            if target is drunk_npc or target.get('dead') or target.get('safe') or target.get('just_spawned') or target.get('skip_turn'):
                continue
                
            if has_los(drunk_npc['x'], drunk_npc['y'], target['x'], target['y'], limit_to_viewport=False):
                dist = math.hypot(target['x'] - drunk_npc['x'], target['y'] - drunk_npc['y'])
                prio = None
                
                if target['type'] == 'worker':
                    prio = 2
                elif target['type'] == 'picker':
                    prio = 3 if target.get('aware') else 4
                elif target['type'] == 'hunter':
                    prio = 5 if not target.get('is_drunk') else 6
                    
                if prio is not None:
                    candidates.append({'prio': prio, 'dist': dist, 'type': target['type'], 'obj': target})
                    
        if not candidates:
            return None
            
        min_prio = min(c['prio'] for c in candidates)
        best_prio_candidates = [c for c in candidates if c['prio'] == min_prio]
        
        min_dist = min(c['dist'] for c in best_prio_candidates)
        best_dist_candidates = [c for c in best_prio_candidates if abs(c['dist'] - min_dist) < 1e-6]
        
        return random.choice(best_dist_candidates)

    def render(err="", animations=None):
        clear_screen()
        term_size = shutil.get_terminal_size((80, 24))
        term_width = term_size.columns
        term_height = term_size.lines
        
        display_lines = [
            f"❤️ Здоровье: {health}/100  |  🍖 Сытость: {satiety} {'(ГОЛОД!)' if satiety == 0 else ''}"
        ]
        
        if needs_to_poop:
            display_lines.append("\033[93m💩 НУЖНО НАВАЛИТЬ КУЧУ (WASD)!\033[0m")
            
        display_lines.extend([
            f"🤠 Съедено грибников: {eaten_pickers}  |  👮 Съедено егерей: {eaten_hunters}",
            f"📢 Всего поднято тревог: {total_yells}",
            f"🐾 Ход 1 шаг: Е Н Г / П О / М И Т / Numpad / Стрелки",
            f"⚡ Прыжок 2х: WASD / ЦФЫВ  |  ⏳ Пропуск: Р / Пробел / 5"
        ])
        
        if not library_entered and (library_poem_heard or has_hunter_book or has_picker_book):
            book_status = []
            if has_hunter_book: book_status.append("📘 Егеря")
            if has_picker_book: book_status.append("📗 Грибника")
            if not book_status: book_status.append("(пока нет)")
            display_lines.insert(2, f"📚 Книги: {', '.join(book_status)}")
        
        panic_count = sum(1 for n in npcs if n['type'] == 'picker' and n.get('aware') and not n.get('scared_by_hunter_only'))
        panic_line = ""
        if panic_count > 0:
            panic_line = "\033[91m" + " ".join(["ВОЛК!"] * panic_count) + "\033[0m"
            
        wrapped_feedback = []
        if err:
            wrapped_feedback = textwrap.wrap(err, width=min(term_width, 70))
        else:
            for event in turn_events:
                for line in str(event).split('\n'):
                    wrapped_feedback.extend(textwrap.wrap(line, width=min(term_width, 70)))
            
        R = 7
        grid_width_visual = (2 * R + 1) * 3 - 1
        
        header_height = len(display_lines) + 1 + 1 + len(wrapped_feedback) + 1
        total_height = header_height + (2 * R + 1) + (3 if dead or won else 0)
        v_pad = max(0, (term_height - total_height) // 2)
        h_pad_grid = max(0, (term_width - grid_width_visual) // 2)
        
        print("\n" * v_pad, end="")
        
        for line in display_lines:
            print(" " * max(0, (term_width - visible_len(line)) // 2) + line)
        
        print() 
        
        if panic_count > 0:
            print(" " * max(0, (term_width - visible_len(panic_line)) // 2) + panic_line)
        else:
            print() 
            
        if wrapped_feedback:
            for line in wrapped_feedback:
                print(" " * max(0, (term_width - visible_len(line)) // 2) + line)
        print() 
            
        for y in range(player_y - R, player_y + R + 1):
            row_cells = []
            for x in range(player_x - R, player_x + R + 1):
                if not is_visible(x - player_x, y - player_y):
                    row_cells.append("  ")
                    continue
                
                anim_char = None
                if animations:
                    for anim in animations:
                        if anim['x'] == x and anim['y'] == y: anim_char = anim['char']
                
                if anim_char:
                    row_cells.append(anim_char)
                elif dead and x == player_x and y == player_y:
                    row_cells.append("💀")
                elif not dead and x == player_x and y == player_y:
                    row_cells.append("🐺")
                else:
                    npc_here = next((n for n in npcs if n['x'] == x and n['y'] == y), None)
                    if npc_here:
                        if npc_here['type'] == 'picker':
                            row_cells.append('😱' if npc_here.get('aware') else '🤠')
                        elif npc_here['type'] == 'worker':
                            if npc_here.get('killed_this_turn'):
                                row_cells.append('👲')
                            else:
                                row_cells.append('👷')
                        else:
                            if npc_here.get('trapped_timer', 0) > 0:
                                row_cells.append('🥵')
                            else:
                                row_cells.append('🤡' if npc_here.get('is_drunk') else '👮')
                    else:
                        cell = grid.get((x, y), '..')
                        if cell == '🪤' and (x, y) not in discovered_traps:
                            row_cells.append('..')
                        else:
                            row_cells.append(cell)
            row_str = " ".join(row_cells)
            print(" " * h_pad_grid + row_str)
            
        if won:
            msg1 = "🎉🎉🎉 Теперь ты в полной безопасности!"
            msg2 = "Ты — хозяин леса! ПОБЕДА! 🎉🎉🎉"
            print()
            print(" " * max(0, (term_width - visible_len(msg1)) // 2) + msg1)
            print(" " * max(0, (term_width - visible_len(msg2)) // 2) + msg2)
            msg3 = "Нажмите любую клавишу для выхода..."
            print(" " * max(0, (term_width - visible_len(msg3)) // 2) + msg3)
        elif dead:
            msg1 = f"💀 ИГРА ОКОНЧЕНА! {death_msg}"
            wrapped_death = textwrap.wrap(msg1, width=min(term_width, 60))
            print()
            for line in wrapped_death:
                print(" " * max(0, (term_width - visible_len(line)) // 2) + line)
            msg2 = "Нажмите любую клавишу для выхода..."
            print(" " * max(0, (term_width - visible_len(msg2)) // 2) + msg2)

    generate_towns()
    generate_viewport()
    update_factories_and_smog()
    check_discover_traps()
    
    while True:
        skip_npc_turn = False
        animations = []
        render(error_msg)
        
        if dead or won:
            get_input()
            break
            
        inp = get_input()
        
        if inp is None: break
        if inp == "invalid": continue
        
        for w in npcs:
            if w['type'] == 'worker':
                w['killed_this_turn'] = False
        
        dx, dy = inp
        pooped_this_turn = False
        poop_msg = ""
        
        if wolf_trapped:
            if dx != 0 or dy != 0:
                error_msg = "❌ Вы в капкане! Нажмите пропуск хода, чтобы высвободиться."
                continue
            else:
                wolf_trapped = False
                turn_events = []
                turn_events.append("\033[92mВы высвободились из капкана!\033[0m")
        else:
            turn_events = []

        if not wolf_trapped and (dx != 0 or dy != 0):
            target_x = player_x + dx
            target_y = player_y + dy
            
            is_jump = (abs(dx) == 2 or abs(dy) == 2)
            
            if is_jump and needs_to_poop:
                px = player_x + dx // 2
                py = player_y + dy // 2
                cell = grid.get((px, py), '..')
                
                if cell in ['..', '🍄', '🐾', '🪤', '☁️', '🕳️'] and not any(n['x'] == px and n['y'] == py for n in npcs):
                    grid[(px, py)] = '💩'
                    needs_to_poop = False
                    pooped_this_turn = True
                    poop_msg = "\033[93mВы навалили огромную кучу!\033[0m"
                    dx, dy = 0, 0
                else:
                    error_msg = "❌ Туда насрать нельзя!"
                    continue
                    
            elif is_jump and not needs_to_poop:
                jump_blocked = False
                mid_x = player_x + dx // 2
                mid_y = player_y + dy // 2
                
                if grid.get((mid_x, mid_y)) in ['🌲', '🏠', '🏛️', '🎪', '🚩', '🪧', '🏥', '🏭', '🏪', '🏢']:
                    jump_blocked = True
                if any(n['x'] == mid_x and n['y'] == mid_y for n in npcs):
                    jump_blocked = True
                
                if jump_blocked:
                    error_msg = "❌ Прыжок заблокирован препятствием!"
                    continue
                    
                dest_cell = grid.get((target_x, target_y), '..')
                if dest_cell in ['🌲', '🏠', '💩', '🏛️', '🎪', '🚩', '🪧', '🏥', '🏭', '🏪', '🏢']:
                    error_msg = "❌ Туда нельзя! Препятствие."
                    continue
                    
            elif not is_jump:
                dest_cell = grid.get((target_x, target_y), '..')
                
                if dest_cell == '🏛️':
                    if has_hunter_book and has_picker_book and not library_entered:
                        if abs(dx) + abs(dy) == 1:
                            pass 
                        else:
                            error_msg = "❌ В библиотеку можно войти только по прямой (не по диагонали)!"
                            continue
                    else:
                        error_msg = "❌ Туда нельзя! Препятствие (или библиотека закрыта)."
                        continue
                        
                elif dest_cell in ['🌲', '🏠', '💩', '🎪', '🚩', '🪧', '🏥', '🏭', '🏪', '🏢']:
                    error_msg = "❌ Туда нельзя! Препятствие."
                    continue
                    
                if needs_to_poop and any(n['x'] == target_x and n['y'] == target_y for n in npcs):
                    error_msg = "❌ Сначала нужно навалить кучу от предыдущего съеденного! (WASD)"
                    continue
                    
        error_msg = ""

        new_bar_hunters = []
        for bh in bar_hunters:
            ex, ey = bh['entry_x'], bh['entry_y']
            is_occupied = (player_x == ex and player_y == ey) or any(n['x'] == ex and n['y'] == ey for n in npcs)
            if not is_occupied:
                npcs.append({
                    'type': 'hunter', 'is_drunk': True,
                    'x': ex, 'y': ey, 'hp': bh['hp'],
                    'just_spawned': True, 'age': bh['age'],
                    'drunk_active_turns': 1
                })
                turn_events.append("🤡 Пьяный егерь вышел из бара на ту же клетку!")
            else:
                new_bar_hunters.append(bh)
        bar_hunters = new_bar_hunters

        new_hospital_hunters = []
        for hh in hospital_hunters:
            ex, ey = hh['entry_x'], hh['entry_y']
            is_occupied = (player_x == ex and player_y == ey) or any(n['x'] == ex and n['y'] == ey for n in npcs)
            if not is_occupied:
                npcs.append({
                    'type': 'hunter', 'is_drunk': False,
                    'x': ex, 'y': ey, 'hp': 100,
                    'just_spawned': True, 'age': hh['age']
                })
                turn_events.append("\033[96m👮 Вылеченный егерь вышел из больницы!\033[0m")
            else:
                new_hospital_hunters.append(hh)
        hospital_hunters = new_hospital_hunters

        if pooped_this_turn:
            turn_events.append(poop_msg)

        if not wolf_trapped and (dx != 0 or dy != 0):
            target_x = player_x + dx
            target_y = player_y + dy
            dest_cell = grid.get((target_x, target_y), '..')
            
            if dest_cell == '🏛️':
                play_library_scene()
                library_entered = True
                is_red_book_protected = True
                needs_to_poop = False 
                has_hunter_book = False
                has_picker_book = False
                
                gain = 20
                if health < 100:
                    need = 100 - health
                    if gain <= need:
                        health += gain
                        gain = 0
                    else:
                        health = 100
                        gain -= need
                satiety += gain
                
                turn_events.append("\033[92mВы вернулись из библиотеки под защитой Красной книги!\nОсталось только разобраться с алкашами!\033[0m")
                target_x, target_y = player_x, player_y 
                
            player_x, player_y = target_x, target_y
            generate_viewport()
            update_factories_and_smog()
            
            if grid.get((player_x, player_y)) == '🪤':
                grid[(player_x, player_y)] = '🐾'
                health -= 20
                wolf_trapped = True
                turn_events.append("\033[91mВы попались в капкан! Урон: 20\033[0m")
            elif grid.get((player_x, player_y)) == '🕳️':
                play_rabbit_hole_scene()
                grid[(player_x, player_y)] = '..'
                
                candidates = []
                for cx, cy in town_centers:
                    for c_dx, c_dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                        nx, ny = cx + c_dx, cy + c_dy
                        c_cell = grid.get((nx, ny), '..')
                        if c_cell in ['..', '🐾', '☁️', '🌱', '🍄'] and not any(n['x'] == nx and n['y'] == ny for n in npcs):
                            candidates.append((nx, ny))
                
                if not candidates:
                    for cx, cy in town_centers:
                        for c_dx in range(-2, 3):
                            for c_dy in range(-2, 3):
                                nx, ny = cx + c_dx, cy + c_dy
                                c_cell = grid.get((nx, ny), '..')
                                if c_cell in ['..', '🐾', '☁️', '🌱', '🍄'] and not any(n['x'] == nx and n['y'] == ny for n in npcs):
                                    candidates.append((nx, ny))
                
                if candidates:
                    hunters = [n for n in npcs if n['type'] == 'hunter' and not n.get('dead')]
                    best_cell = None
                    max_min_dist = -1
                    for cx, cy in candidates:
                        if hunters:
                            min_dist = min(math.hypot(h['x'] - cx, h['y'] - cy) for h in hunters)
                        else:
                            min_dist = 9999
                        if min_dist > max_min_dist:
                            max_min_dist = min_dist
                            best_cell = (cx, cy)
                    if best_cell:
                        player_x, player_y = best_cell
                    else:
                        player_x, player_y = random.choice(candidates)
                
                gain = 100
                if health < 100:
                    need = 100 - health
                    if gain <= need:
                        health += gain
                        gain = 0
                    else:
                        health = 100
                        gain -= need
                satiety += gain
                needs_to_poop = False
                
                turn_events.append("\033[93mВы вышли из туннеля через магазин, но туннель засыпан землёй!\033[0m")
                skip_npc_turn = True
                generate_viewport()
                update_factories_and_smog()
                
            elif grid.get((player_x, player_y)) in ['🍄', '🌱', '..', '🐾']:
                grid[(player_x, player_y)] = '🐾'
                
            check_discover_traps()
                
            eaten = [n for n in npcs if n['x'] == player_x and n['y'] == player_y]
            sign_bloodied = False
            worker_stepped = False
            
            for n in eaten:
                if n['type'] == 'worker':
                    health -= 100
                    dead = True
                    n['killed_this_turn'] = True
                    turn_events.append("\033[91mВы попытались съесть рабочего, но он убил вас монтировкой!\033[0m")
                    worker_stepped = True
                    break
            
            if not worker_stepped:
                for n in eaten:
                    eat(n['type'])
                    if n['type'] == 'picker':
                        turn_events.append("\033[92mВы съели грибника! Приятного аппетита!\033[0m")
                        if has_hunter_book and eaten_pickers >= 5 and not n.get('aware') and not has_picker_book and not library_entered:
                            has_picker_book = True
                            turn_events.append("\033[96mВы нашли книгу грибника (📗)!\033[0m")
                            
                    elif n['type'] == 'hunter':
                        if n.get('is_drunk'):
                            turn_events.append("\033[92mВы съели пьяного егеря! Приятного аппетита!\033[0m")
                        else:
                            turn_events.append("\033[92mВы съели егеря! Приятного аппетита!\033[0m")
                        
                        hunter_spawn_cooldown += 10
                        turn_events.append("\033[93mПодкрепление очкует! Спавн новых егерей приостановлен.\033[0m")
                        
                        if eaten_hunters >= 3 and not has_hunter_book and not library_entered:
                            has_hunter_book = True
                            turn_events.append("\033[96mВы нашли книгу егеря (📘)!\033[0m")
                            
                    npcs.remove(n)
                    needs_to_poop = True
                    
                    for ax, ay in [(0,1), (0,-1), (1,0), (-1,0)]:
                        bx, by = player_x + ax, player_y + ay
                        if grid.get((bx, by)) == '🪧':
                            grid[(bx, by)] = '🪧'
                            sign_bloodied = True
                            
            if sign_bloodied:
                turn_events.append("\033[91mВолк заляпал табличку кровью!\033[0m")
                for npc in npcs:
                    if npc.get('reading_sign_timer', 0) > 0:
                        rx, ry = npc['reading_sign_pos']
                        if grid.get((rx, ry)) == '🪧':
                            if (npc['x'] - rx, npc['y'] - ry) == (rx - player_x, ry - player_y):
                                npc['reading_sign_timer'] = 0
                                npc['reading_sign_pos'] = None
                                npc['interrupted_this_turn'] = True
                                if not has_los(npc['x'], npc['y'], player_x, player_y):
                                    turn_events.append("🤠: Что это было? А, показалось.")

            adj_library = False
            for ax, ay in [(0,1), (0,-1), (1,0), (-1,0)]:
                if grid.get((player_x + ax, player_y + ay)) == '🏛️':
                    adj_library = True
                    break
            
            if adj_library and not library_entered:
                if has_hunter_book and has_picker_book:
                    turn_events.append("\033[95m«Вот спасибо, умный волк!\033[0m\n\033[95mВижу, знаешь в книгах толк.\033[0m\n\033[95mТы теперь друг человека.\033[0m\n\033[95mЗаходи в библиотеку!»\033[0m")
                else:
                    if not library_poem_heard:
                        library_poem_heard = True
                        turn_events.append("\033[35m«Мудаки нас обманули:\033[0m\n\033[35mНам 2 книги не вернули.\033[0m\n\033[35mЧто ты воешь, глупый волк?\033[0m\n\033[35mТы хоть в книгах знаешь толк?»\033[0m")

        # --- Проверка смога для волка ---
        if grid.get((player_x, player_y)) == '☁️':
            health -= 10
            turn_events.append("Вы задыхаетесь от заводского смога! Урон: 10")

        for npc in npcs:
            if npc.get('just_spawned'):
                npc['just_spawned'] = False
                npc['age'] += 1
                npc['skip_turn'] = True
            else:
                npc['skip_turn'] = skip_npc_turn

        def process_npc_group(group_npcs):
            intended_moves = {}
            for npc in group_npcs:
                if npc.get('skip_turn'):
                    continue
                
                if npc.get('interrupted_this_turn'):
                    npc['interrupted_this_turn'] = False
                    intended_moves[id(npc)] = (npc['x'], npc['y'])
                    continue
                
                ndx, ndy = 0, 0
                if npc['type'] == 'picker':
                    if not npc['aware']:
                        if not npc.get('has_read_sign') and not npc.get('reading_sign_timer'):
                            adj_signs = []
                            for ax, ay in [(0,1), (0,-1), (1,0), (-1,0)]:
                                if grid.get((npc['x']+ax, npc['y']+ay)) == '🪧':
                                    adj_signs.append((npc['x']+ax, npc['y']+ay))
                            if adj_signs:
                                npc['reading_sign_timer'] = 4
                                npc['reading_sign_pos'] = random.choice(adj_signs)
                                npc['has_read_sign'] = True
                        
                        if npc.get('reading_sign_timer', 0) > 0:
                            intended_moves[id(npc)] = (npc['x'], npc['y'])
                            continue
                            
                        if npc['age'] % 2 == 0:
                            ndx, ndy = get_direction_to_nearest(npc, '🍄')
                    else:
                        if any(grid.get((npc['x']+ax, npc['y']+ay)) == '🏠' for ax, ay in [(0,1),(0,-1),(1,0),(-1,0)]):
                            npc['entering_house'] = True
                        else:
                            ndx, ndy = get_direction_to_nearest(npc, '🏠')
                elif npc['type'] == 'hunter':
                    if npc.get('trapped_timer', 0) > 0:
                        ndx, ndy = 0, 0
                    elif npc.get('waiting_at_bar'):
                        ndx, ndy = 0, 0
                    elif not npc.get('is_drunk') and npc.get('hp', 100) < 100:
                        if any(grid.get((npc['x']+ax, npc['y']+ay)) == '🏥' for ax, ay in [(0,1),(0,-1),(1,0),(-1,0)]):
                            npc['entering_hospital'] = True
                        else:
                            ndx, ndy = get_direction_to_nearest(npc, '🏥')
                    elif max(abs(npc['x'] - player_x), abs(npc['y'] - player_y)) <= 1:
                        ndx, ndy = 0, 0
                    else:
                        ndx, ndy = get_direction_to_nearest(npc, 'wolf')
                        if npc['x'] + ndx == player_x and npc['y'] + ndy == player_y:
                            ndx, ndy = 0, 0
                            
                if not npc.get('entering_house') and not npc.get('entering_hospital'):
                    intended_moves[id(npc)] = (npc['x'] + ndx, npc['y'] + ndy)

            for i, pos_i in list(intended_moves.items()):
                for j, pos_j in list(intended_moves.items()):
                    if i < j:
                        npc_i = next((n for n in group_npcs if id(n) == i), None)
                        npc_j = next((n for n in group_npcs if id(n) == j), None)
                        if npc_i and npc_j and pos_i == (npc_j['x'], npc_j['y']) and pos_j == (npc_i['x'], npc_i['y']):
                            intended_moves[i] = (npc_i['x'], npc_i['y'])
                            intended_moves[j] = (npc_j['x'], npc_j['y'])
                            
            changed = True
            while changed:
                changed = False
                counts = {}
                for i, pos in intended_moves.items(): counts.setdefault(pos, []).append(i)
                for pos, claimants in counts.items():
                    if len(claimants) > 1:
                        changed = True
                        winner = random.choice(claimants)
                        for c in claimants:
                            if c != winner:
                                npc_c = next(n for n in group_npcs if id(n) == c)
                                intended_moves[c] = (npc_c['x'], npc_c['y'])

            for i in intended_moves:
                npc = next(n for n in group_npcs if id(n) == i)
                npc['x'], npc['y'] = intended_moves[i]
                
                cell = grid.get((npc['x'], npc['y']))
                if cell == '🍄': grid[(npc['x'], npc['y'])] = '..'
                if (npc['type'] == 'hunter' or npc['aware']) and cell == '🌱':
                    grid[(npc['x'], npc['y'])] = '..'
                if cell == '🚩':
                    if (npc['type'] == 'picker' and npc.get('aware')) or (npc['type'] == 'hunter' and npc.get('is_drunk')):
                        grid[(npc['x'], npc['y'])] = '..'
                        
                if npc['type'] == 'hunter' and npc.get('is_drunk'):
                    if cell == '🪤':
                        grid[(npc['x'], npc['y'])] = '..'
                        npc['hp'] -= 20
                        if npc['hp'] <= 0:
                            npc['dead'] = True
                            turn_events.append("\033[95mПьяный егерь попался в капкан и сдох!\033[0m")
                        else:
                            npc['trapped_timer'] = 5
                            npc['just_trapped'] = True
                            turn_events.append("\033[95mПьяный егерь попался в капкан!\033[0m")
                    elif cell == '🕳️':
                        npc['hp'] = 0
                        npc['dead'] = True
                        turn_events.append("\033[95mПьяный егерь провалился в кроличью нору и задохнулся!\033[0m")

        scared_pickers = [n for n in npcs if n['type'] == 'picker' and n.get('aware')]
        process_npc_group(scared_pickers)
        
        unscared_pickers = [n for n in npcs if n['type'] == 'picker' and not n.get('aware')]
        process_npc_group(unscared_pickers)
        
        drunk_hunters_group = [n for n in npcs if n['type'] == 'hunter' and n.get('is_drunk')]
        process_npc_group(drunk_hunters_group)
        
        sober_hunters_group = [n for n in npcs if n['type'] == 'hunter' and not n.get('is_drunk')]
        sober_not_seeing = []
        sober_seeing = []
        for n in sober_hunters_group:
            if n.get('skip_turn'):
                sober_not_seeing.append(n)
                continue
                
            ox, oy = n['x'], n['y']
            n['x'], n['y'] = -999, -999
            if has_los(ox, oy, player_x, player_y):
                sober_seeing.append(n)
            else:
                sober_not_seeing.append(n)
            n['x'], n['y'] = ox, oy
            
        process_npc_group(sober_not_seeing)
        process_npc_group(sober_seeing)
                
        for npc in npcs:
            if npc.get('entering_house'): 
                npc['safe'] = True
                turn_events.append("\033[93mГрибник в домике!\033[0m")
                
                if npc.get('will_complain') and npc.get('scared_by_hunter_only'):
                    if not bars_closed:
                        complaints_count += 1
                        if complaints_count >= 2:
                            bars_closed = True
                            turn_events.append("\033[91mГрибник написал жалобу в Рослесхоз! ВСЕ БАРЫ ЗАКРЫТЫ!\033[0m")
                        else:
                            turn_events.append("\033[91mГрибник написал жалобу в Рослесхоз! (Нужна еще 1 для закрытия баров)\033[0m")
            elif npc.get('entering_hospital'):
                npc['entered_hospital'] = True
                hospital_hunters.append({'entry_x': npc['x'], 'entry_y': npc['y'], 'age': npc['age']})
                turn_events.append("\033[96m🏥 Раненый егерь зашёл в больницу!\033[0m")

        for npc in npcs:
            if npc.get('safe') or npc.get('dead') or npc.get('skip_turn'): continue
            if npc['type'] == 'picker':
                if npc.get('reading_sign_timer', 0) > 0:
                    continue
                if not npc.get('aware') or npc.get('scared_by_hunter_only'):
                    if has_los(npc['x'], npc['y'], player_x, player_y):
                        npc['aware'] = True
                        npc['scared_by_hunter_only'] = False
                        if not npc.get('has_yelled_wolf'):
                            npc['has_yelled_wolf'] = True
                            turn_events.append("📢 Грибник заметил вас и поднял тревогу!")

        sober_hunters = [n for n in npcs if n['type'] == 'hunter' and not n.get('is_drunk') and not n.get('dead') and not n.get('safe') and not n.get('entered_hospital')]
        for npc in sober_hunters:
            if npc.get('skip_turn') or npc.get('dead'): continue
            if has_los(npc['x'], npc['y'], player_x, player_y):
                npc['waiting_at_bar'] = False
                if is_red_book_protected:
                    turn_events.append("👮 Егерь: «Бля-я, его ж нельзя убивать...»")
                else:
                    dist = math.hypot(npc['x'] - player_x, npc['y'] - player_y)
                    dmg = round(180 / max(1, dist))
                    health -= dmg
                    ax1 = npc['x'] + round((player_x - npc['x']) / max(1, dist))
                    ay1 = npc['y'] + round((player_y - npc['y']) / max(1, dist))
                    ax2 = player_x + round((npc['x'] - player_x) / max(1, dist))
                    ay2 = player_y + round((npc['y'] - player_y) / max(1, dist))
                    animations.extend([{'x': ax1, 'y': ay1, 'char': '💥'}, {'x': ax2, 'y': ay2, 'char': '🩸'}])
                    turn_events.append(f"💥 Егерь выстрелил в вас! Урон: {dmg}")
            else:
                is_adj_bar = any(grid.get((npc['x'] + ax, npc['y'] + ay)) == '🎪' for ax, ay in [(0,1), (0,-1), (1,0), (-1,0)])
                if is_adj_bar and not bars_closed and npc.get('hp', 100) == 100:
                    if npc.get('waiting_at_bar'):
                        npc['entered_bar'] = True
                        bar_hunters.append({'entry_x': npc['x'], 'entry_y': npc['y'], 'hp': npc.get('hp', 100), 'age': npc['age']})
                        turn_events.append("👮 Егерь зашел в бар!")
                    else:
                        npc['waiting_at_bar'] = True
                else:
                    npc['waiting_at_bar'] = False

        drunk_hunters = [n for n in npcs if n['type'] == 'hunter' and n.get('is_drunk') and not n.get('dead') and not n.get('safe')]
        
        active_drunk_hunters = []
        trapped_drunk_hunters = []
        
        for n in drunk_hunters:
            if n.get('just_trapped'):
                n['just_trapped'] = False
            elif n.get('trapped_timer', 0) > 0:
                trapped_drunk_hunters.append(n)
                n['trapped_timer'] -= 1
            else:
                active_drunk_hunters.append(n)
                
        random.shuffle(active_drunk_hunters)
        random.shuffle(trapped_drunk_hunters)
        
        aggro_towns_this_turn = set()

        for npc in active_drunk_hunters + trapped_drunk_hunters:
            if npc.get('skip_turn') or npc.get('dead'): continue
            target_info = find_drunk_target(npc)
            if target_info:
                if npc.get('drunk_active_turns', 1) % 2 == 0:
                    turn_events.append("Пьяный егерь промахнулся!")
                    t_obj = target_info['obj']
                    dist = target_info['dist']
                    if target_info['type'] == 'wolf':
                        tx, ty = player_x, player_y
                    else:
                        tx, ty = t_obj['x'], t_obj['y']
                        
                    ax1 = npc['x'] + round((tx - npc['x']) / max(1, dist))
                    ay1 = npc['y'] + round((ty - npc['y']) / max(1, dist))
                    animations.append({'x': ax1, 'y': ay1, 'char': '💥'})
                    continue

                t_obj = target_info['obj']
                dist = target_info['dist']

                if target_info['type'] == 'wolf':
                    dmg = round(180 / max(1, dist))
                    health -= dmg
                    ax1 = npc['x'] + round((player_x - npc['x']) / max(1, dist))
                    ay1 = npc['y'] + round((player_y - npc['y']) / max(1, dist))
                    ax2 = player_x + round((npc['x'] - player_x) / max(1, dist))
                    ay2 = player_y + round((npc['y'] - player_y) / max(1, dist))
                    animations.extend([{'x': ax1, 'y': ay1, 'char': '💥'}, {'x': ax2, 'y': ay2, 'char': '🩸'}])
                    turn_events.append(f"💥 Пьяный егерь выстрелил в вас! Урон: {dmg}")
                else:
                    dmg = round(180 / max(1, dist))
                    t_obj['hp'] -= dmg
                    ax1 = npc['x'] + round((t_obj['x'] - npc['x']) / max(1, dist))
                    ay1 = npc['y'] + round((t_obj['y'] - npc['y']) / max(1, dist))
                    ax2 = t_obj['x'] + round((npc['x'] - t_obj['x']) / max(1, dist))
                    ay2 = t_obj['y'] + round((npc['y'] - t_obj['y']) / max(1, dist))
                    animations.extend([{'x': ax1, 'y': ay1, 'char': '💥'}, {'x': ax2, 'y': ay2, 'char': '🩸'}])
                    
                    if t_obj['type'] == 'worker':
                        if t_obj['hp'] <= 0:
                            t_obj['dead'] = True
                            turn_events.append("💥 Пьяный егерь убил рабочего!")
                        else:
                            turn_events.append(f"💥 Пьяный егерь выстрелил в рабочего! Урон: {dmg}")
                        aggro_towns_this_turn.add(t_obj['town_id'])
                        
                    elif t_obj['hp'] <= 0:
                        t_obj['dead'] = True
                        if t_obj['type'] == 'picker':
                            turn_events.append("💥 Пьяный егерь убил грибника!")
                        else:
                            turn_events.append("💥 Пьяный егерь убил другого егеря!")
                    else:
                        if t_obj['type'] == 'picker':
                            if t_obj.get('reading_sign_timer', 0) > 0:
                                t_obj['reading_sign_timer'] = 0
                                t_obj['reading_sign_pos'] = None
                                
                            turn_events.append(f"💥 Пьяный егерь выстрелил в грибника! Урон: {dmg}")
                            if not bars_closed:
                                turn_events.append("😱: Я буду жаловаться в Рослесхоз! 😭😭😭")
                            else:
                                turn_events.append("😱: Бары закрыли, а алкаши остались! 😭😭😭")
                                
                            if not t_obj.get('aware'):
                                t_obj['aware'] = True
                                t_obj['scared_by_hunter_only'] = True
                                t_obj['will_complain'] = True
                            elif t_obj.get('scared_by_hunter_only'):
                                t_obj['will_complain'] = True
                                
                            if t_obj['aware'] and t_obj.get('scared_by_hunter_only'):
                                if has_los(t_obj['x'], t_obj['y'], player_x, player_y):
                                    t_obj['scared_by_hunter_only'] = False
                                    if not t_obj.get('has_yelled_wolf'):
                                        t_obj['has_yelled_wolf'] = True
                                        turn_events.append("📢 Грибник заметил вас и поднял тревогу!")
                        else:
                            turn_events.append(f"💥 Пьяный егерь выстрелил в другого егеря! Урон: {dmg}")
                            if t_obj.get('is_drunk'):
                                turn_events.append("🤡: Оп, маслину поймал, гы-гы-гы!")
                            else:
                                turn_events.append("👮: Ты куда стреляешь, придурок?")

        for tid in aggro_towns_this_turn:
            if tid not in aggro_towns_notified:
                aggro_towns_notified.add(tid)
                alive_workers = sum(1 for w in npcs if w['type'] == 'worker' and w['town_id'] == tid and not w.get('dead'))
                turn_events.append(f"\033[93m👷х{alive_workers}: Э, чё за хуйня? Ну всё, пизда вам, пидоры лесные!\033[0m")
            for w in npcs:
                if w.get('skip_turn'): continue
                if w['type'] == 'worker' and w['town_id'] == tid:
                    w['aggro_pending'] = True

        workers_to_move = [w for w in npcs if w['type'] == 'worker' and w.get('aggro_active') and not w.get('dead') and not w.get('skip_turn')]
        if workers_to_move:
            intended_moves = {}
            for npc in workers_to_move:
                hunters = [n for n in npcs if n['type'] == 'hunter' and not n.get('dead')]
                if not hunters:
                    intended_moves[id(npc)] = (npc['x'], npc['y'])
                    continue
                
                nearest = min(hunters, key=lambda h: math.hypot(h['x'] - npc['x'], h['y'] - npc['y']))
                best_dist = math.hypot(nearest['x'] - npc['x'], nearest['y'] - npc['y'])
                best_moves = [(npc['x'], npc['y'])]
                
                for dx in [-1, 0, 1]:
                    for dy in [-1, 0, 1]:
                        if dx == 0 and dy == 0: continue
                        nx, ny = npc['x'] + dx, npc['y'] + dy
                        
                        cell = grid.get((nx, ny), '..')
                        if cell in ['🌲', '🏠', '💩', '🏛️', '🎪', '🪧', '🏥', '🏭', '🏪', '🏢']:
                            continue
                        
                        if any(n['type'] == 'worker' and n['x'] == nx and n['y'] == ny and id(n) != id(npc) for n in npcs):
                            continue
                            
                        dist = math.hypot(nearest['x'] - nx, nearest['y'] - ny)
                        if dist < best_dist:
                            best_dist = dist
                            best_moves = [(nx, ny)]
                        elif abs(dist - best_dist) < 1e-6:
                            best_moves.append((nx, ny))
                            
                intended_moves[id(npc)] = random.choice(best_moves)
                
            for i, pos_i in list(intended_moves.items()):
                for j, pos_j in list(intended_moves.items()):
                    if i < j:
                        npc_i = next((n for n in workers_to_move if id(n) == i), None)
                        npc_j = next((n for n in workers_to_move if id(n) == j), None)
                        if npc_i and npc_j and pos_i == (npc_j['x'], npc_j['y']) and pos_j == (npc_i['x'], npc_i['y']):
                            intended_moves[i] = (npc_i['x'], npc_i['y'])
                            intended_moves[j] = (npc_j['x'], npc_j['y'])
                            
            changed = True
            while changed:
                changed = False
                counts = {}
                for i, pos in intended_moves.items(): counts.setdefault(pos, []).append(i)
                for pos, claimants in counts.items():
                    if len(claimants) > 1:
                        changed = True
                        winner = random.choice(claimants)
                        for c in claimants:
                            if c != winner:
                                npc_c = next(n for n in workers_to_move if id(n) == c)
                                intended_moves[c] = (npc_c['x'], npc_c['y'])
                                
            for i, pos in intended_moves.items():
                npc = next(n for n in workers_to_move if id(n) == i)
                nx, ny = pos
                npc['x'], npc['y'] = nx, ny
                
                cell = grid.get((nx, ny))
                if cell in ['🍄', '🌱', '🚩']:
                    grid[(nx, ny)] = '..'
                elif cell == '🪤':
                    grid[(nx, ny)] = '..'
                    turn_events.append("Рабочий сломал китайский капкан крепким сапогом!")
                    
                if nx == player_x and ny == player_y:
                    if not dead:
                        health -= 100
                        dead = True
                        npc['killed_this_turn'] = True
                        turn_events.append("\033[91m👷 Рабочий наступил на вас и убил монтировкой!\033[0m")
                else:
                    victims = [n for n in npcs if n['x'] == nx and n['y'] == ny and id(n) != id(npc)]
                    for v in victims:
                        if v['type'] != 'worker':
                            v['hp'] = 0
                            v['dead'] = True
                            npc['killed_this_turn'] = True
                            if v['type'] == 'hunter':
                                turn_events.append("👷 Рабочий убил егеря монтировкой!")
                            elif v['type'] == 'picker':
                                turn_events.append("👷 Рабочий убил грибника монтировкой!")

        for w in npcs:
            if w['type'] == 'worker' and w.get('aggro_pending'):
                w['aggro_pending'] = False
                w['aggro_active'] = True

        for npc in npcs:
            if npc.get('safe') or npc.get('dead') or npc.get('skip_turn'): continue
            if npc['type'] == 'picker' and npc.get('reading_sign_timer', 0) > 0:
                timer = npc['reading_sign_timer']
                if timer == 4:
                    turn_events.append('*🤠 читает: "Осторожно, волк-людоед!.."')
                elif timer == 3:
                    turn_events.append('*🤠 читает: "...В этом лесу водится опасный хищник,.."')
                elif timer == 2:
                    turn_events.append('*🤠 читает: "...который съел не одну сотню грибников..."')
                elif timer == 1:
                    turn_events.append('*🤠 читает: "...Что тебе дороже: грибы или жизнь?"')
                
                npc['reading_sign_timer'] -= 1
                if npc['reading_sign_timer'] <= 0:
                    npc['reading_sign_pos'] = None

        fleeing_pickers = 0
        for npc in npcs:
            if npc.get('safe') or npc.get('dead'): continue
            if npc['type'] == 'picker' and npc['aware'] and not npc.get('scared_by_hunter_only'):
                total_yells += 1
                fleeing_pickers += 1

        if fleeing_pickers == 1:
            turn_events.append("📢 Испуганный грибник бежит к домику или не знает, куда бежать!")
        elif fleeing_pickers > 1:
            turn_events.append("📢 Испуганные грибники бегут к домику или не знает, куда бежать!")
            
        if fleeing_pickers > 0:
            if hunter_spawn_cooldown > 0:
                turn_events.append("\033[93mНачальник вздрючил очкующих егерей и заставил их выйти на охоту!\033[0m")
            hunter_spawn_cooldown = 0

        npcs = [n for n in npcs if not n.get('safe') and not n.get('dead') and not n.get('entered_bar') and not n.get('entered_hospital')]
        for n in npcs:
            n['age'] += 1
            if n.get('is_drunk'):
                n['drunk_active_turns'] = n.get('drunk_active_turns', 1) + 1

        if animations:
            render(error_msg, animations)
            time.sleep(0.5)

        rim_cells = set()
        for i in range(-7, 8):
            rim_cells.add((player_x + i, player_y - 7))
            rim_cells.add((player_x + i, player_y + 7))
            rim_cells.add((player_x - 7, player_y + i))
            rim_cells.add((player_x + 7, player_y + i))
            
        for rx, ry in rim_cells:
            if grid.get((rx, ry)) in ['..', '🐾', '☁️'] and not any(n['x'] == rx and n['y'] == ry for n in npcs):
                r = random.random()
                if r < 0.005:
                    npcs.append({'type': 'picker', 'x': rx, 'y': ry, 'hp': 100, 'aware': False, 'just_spawned': True, 'age': 0})
                elif r < 0.005 + (0.001 * total_yells):
                    if hunter_spawn_cooldown == 0:
                        npcs.append({'type': 'hunter', 'x': rx, 'y': ry, 'hp': 100, 'just_spawned': True, 'age': 0})

        if health <= 0 and not dead:
            dead = True
            
        died_of_starvation = False
        if not dead:
            if satiety > 0:
                satiety -= 1
            else:
                health -= 1
                turn_events.append("🩸 Вы голодаете! Здоровье падает.")
                if health <= 0:
                    dead = True
                    died_of_starvation = True
                    
        drunk_in_viewport = any(
            n['type'] == 'hunter' and n.get('is_drunk') and not n.get('dead') and not n.get('safe')
            and abs(n['x'] - player_x) <= 7 and abs(n['y'] - player_y) <= 7
            for n in npcs
        )
        if drunk_in_viewport:
            turns_since_drunk_seen = 0
        else:
            turns_since_drunk_seen += 1
            
        if hunter_spawn_cooldown > 0:
            hunter_spawn_cooldown -= 1
            if hunter_spawn_cooldown == 0:
                turn_events.append("\033[93mНачальник вздрючил очкующих егерей и заставил их выйти на охоту!\033[0m")
                    
        if dead and not death_msg:
            if died_of_starvation:
                death_msg = "Ты решил стать веганом?"
            elif eaten_pickers == 0 and eaten_hunters == 0:
                death_msg = "Ты не волк, а жалкий щенок!"
            elif eaten_pickers > 0 and eaten_hunters == 0:
                death_msg = "Что, волчок, только безоружных можешь кушать?"
            elif eaten_hunters > 0:
                death_msg = "Ты храбро сражался и не обосрался (в переносном смысле)"

        if not dead and library_entered and bars_closed:
            has_drunks = any(n['type'] == 'hunter' and n.get('is_drunk') and not n.get('dead') and not n.get('safe') for n in npcs)
            if not has_drunks and turns_since_drunk_seen > 10:
                won = True

if __name__ == "__main__":
    main()