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

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

«Проф. Волкгонагалл: Лесная Трансфигурация» — это не сиквел и не спин-офф «Велогрибника», это просто авторский высер в буквальном смысле. Простой пример: все знают игру «Angry Birds», но мало кто знает про инвертированный высер «Bad Piggies».

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

Комбо, костры, заводы и дожди[править]

  • Комбо — отсутствует. Видимо, дикие звери не ведут подсчёт собственных действий, а просто подчиняются инстинкту, забыв о том, что комбо — основа любой динамичной игры. Хотя какая может быть динамика у столь неторопливой охоты на людей?
  • Костры — отсутствуют. Грете Тунберг, видите ли, не понравилось виртуальное поджигание леса. Мы бы просто послали её на три буквы, но наши конкуренты прогнулись под изменчивый мир и убрали возможность уничтожения никому не нужных деревьев.
  • Заводы — отсутствуют. Да что ж это за лес такой, который человек до сих пор не засрал своей промышленностью — средневековый из фэнтези? Жаль, что вышеупомянутая Грета там не живёт — после прохождения волчьего ЖКТ она стала бы чуточку лучше и полезней для общества.
  • Дожди (барабанная дробь) — ОТСУТСТВУЮТ! Да, в сеттинге «ВолкгоВнагалла» начисто отсутствует связь с реальностью: дождей нет, а грибы почему-то выросли. И кусты. И деревья. Густой лес вместо пустыни — просто фантастика!

Цель игры и условие победы[править]

Их тоже нет, так как волку не нужно добираться до особняка. Более того, у кровожадного хищника даже нет максофона, а это значит, что он террорист, экстремист и иноагент! Впрочем, те немногие задроты, которые играют в это говнище, преследуют следующие цели:

  • зохавать телеграмимум (нет «Макса» — нет максимума) грибников;
  • зохавать телегимум егерей;
  • зохавать ТГимум людишек суммарно;
  • составить секретный символ из говна, дающий чит на бессмертие (возможно, это свастика, но это не точно).

А что есть?[править]

КУСТИКИ И ДОМИКИ! Кустики как бы намекают, что игру лучше проходить в медленном и скучном режиме стелс. Действительно, если вы волк-ниндзя и вашим учителем была крыса Сплинтер, вы можете действовать в крысу и сожрать с десяток грибников, не привлекая внимание егерей, но какой в этом интерес?

Грибники нынче не те. Если раньше один грибник мог расправиться с дюжиной волков, то здесь дюжина грибников не может дать отпор единственному волку, предпочитая трусливо спрятаться в домике. Известно же, что волков бояться — в лес не ходить!

Казалось бы, егеря посмелее грибников, но легко быть героем, когда у тебя дальнобойное ружьё против клыков ближнего боя! В общем, мусора — они и в лесу мусора. Странно, что они не с гранатомётами на волка ходят.

Тактика говнокемпинга[править]

Если в нормальных играх предусмотрен дождь против хитрожопых ждунов, то здесь прямо-таки напрашивается построение говнобункера против егерей. Насрал говнокирпичами вокруг себя — и сиди в засаде, что может быть проще?

Игродел-говнодел на пресс-конференции что-то там бубнил про голод и про неэффективность говнокемпинга против двух и более егерей, бла-бла-бла. Лучше б он не отмазывался, а добавил в игру Пахома, который с удовольствием съест коричневые фортификационные сооружения.

Говнокод (осторожно, говновирусы!)[править]

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
    death_msg = ""
    
    eaten_pickers = 0
    eaten_hunters = 0
    total_yells = 0
    needs_to_poop = False # Флаг: волку нужно навалить кучу
    
    grid = {}
    npcs = [] 
    turn_events = []
    error_msg = ""
    
    def generate_cell(x, y):
        if (x, y) not in grid:
            if x == 0 and y == 0:
                grid[(x, y)] = '..'
                return
            r = random.random()
            if r < 0.2: grid[(x, y)] = '🌲'
            elif r < 0.3: grid[(x, y)] = '🌱'
            elif r < 0.35: grid[(x, y)] = '🍄'
            elif r < 0.36: grid[(x, y)] = '🏠'
            else: grid[(x, y)] = '..'

    def generate_viewport():
        R = 8 
        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 is_visible(dx, dy):
        return abs(dx) <= 7 and abs(dy) <= 7

    def has_los(x1, y1, x2, y2):
        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
        
        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

                # Куча ('💩') теперь тоже блокирует обзор
                if grid.get((cx, cy)) in ['🌲', '🏠', '🌱', '💩'] or any(n['x'] == cx and n['y'] == cy for n in npcs):
                    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):
        queue = [(npc['x'], npc['y'], [])]
        visited = set([(npc['x'], npc['y'])])
        
        def is_valid_step(x, y):
            if abs(x - player_x) > 7 or abs(y - player_y) > 7: return False
            # NPC не могут наступить на кучу ('💩')
            if grid.get((x, y)) in ['🌲', '🏠', '💩']: 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 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 == 'wolf': return x == player_x and y == player_y
            return False

        while queue:
            cx, cy, path = queue.pop(0)
            if is_target(cx, cy) and path: return path[0]
            if len(path) > 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 (nx, ny) not in visited:
                    if is_valid_step(nx, ny):
                        visited.add((nx, ny))
                        queue.append((nx, ny, path + [(dx, dy)]))
        return (0, 0)

    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"
        ])
        
        panic_count = sum(1 for n in npcs if n['type'] == 'picker' and n.get('aware'))
        panic_line = ""
        if panic_count > 0:
            panic_line = "\033[91m" + " ".join(["ВОЛК!"] * panic_count) + "\033[0m"
            
        feedback = err if err else (" | ".join(turn_events) if turn_events else "")
        wrapped_feedback = textwrap.wrap(feedback, 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 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 '🤠')
                        else:
                            row_cells.append('👮')
                    else:
                        row_cells.append(grid.get((x, y), '..'))
            row_str = " ".join(row_cells)
            print(" " * h_pad_grid + row_str)
            
        if dead:
            msg1 = f"💀 ИГРА ОКОНЧЕНА! {death_msg}"
            wrapped_death = textwrap.wrap(msg1, width=min(term_width, 45))
            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_viewport()
    
    while True:
        animations = []
        render(error_msg)
        
        if dead:
            get_input()
            break
            
        inp = get_input()
        
        if inp is None: break
        if inp == "invalid": continue
        
        dx, dy = inp
        pooped_this_turn = False
        
        # 1. ОБРАБОТКА ХОДА ИГРОКА (ВОЛКА)
        if 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
                    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:
                # Обычный шаг на 1 клетку
                dest_cell = grid.get((target_x, target_y), '..')
                if 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 = ""
        turn_events = []
        
        if pooped_this_turn:
            turn_events.append("\033[93mВы навалили огромную кучу!\033[0m")

        if dx != 0 or dy != 0:
            player_x, player_y = target_x, target_y
            generate_viewport()
            
            if grid.get((player_x, player_y)) in ['🍄', '🌱']:
                grid[(player_x, player_y)] = '..'
                
            eaten = [n for n in npcs if n['x'] == player_x and n['y'] == player_y]
            for n in eaten:
                eat(n['type'])
                if n['type'] == 'picker':
                    turn_events.append("\033[92mВы съели грибника! Приятного аппетита!\033[0m")
                elif n['type'] == 'hunter':
                    turn_events.append("\033[92mВы съели егеря! Приятного аппетита!\033[0m")
                npcs.remove(n)
                needs_to_poop = True # Волку приспичило

        # 2. ДО-ХОДОВАЯ ПРОВЕРКА ВИДИМОСТИ И СТРЕЛЬБА
        for npc in npcs:
            if npc['just_spawned']: continue
            npc['has_shot'] = False
            npc['has_yelled'] = False
            
            if npc['type'] == 'hunter' and has_los(npc['x'], npc['y'], player_x, player_y):
                npc['has_shot'] = True
                npc['skip_move'] = True
                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}")
                
            elif npc['type'] == 'picker' and not npc['aware']:
                if has_los(npc['x'], npc['y'], player_x, player_y):
                    npc['aware'] = True
                    npc['has_yelled'] = True
                    total_yells += 1
                    turn_events.append("📢 Грибник заметил вас и поднял тревогу!")

        # 3. НАМЕРЕНИЯ И ПУТЕЙСТВИЕ NPC
        intended_moves = {}
        for idx, npc in enumerate(npcs):
            if npc['just_spawned']:
                npc['just_spawned'] = False
                npc['age'] += 1
                continue
            if npc.get('skip_move'): continue
            
            ndx, ndy = 0, 0
            if npc['type'] == 'picker':
                if not npc['aware']:
                    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':
                ndx, ndy = get_direction_to_nearest(npc, 'wolf')
                
            if not npc.get('entering_house'):
                intended_moves[idx] = (npc['x'] + ndx, npc['y'] + ndy)
                
        # 4. РАЗРЕШЕНИЕ КОНФЛИКТОВ
        for i, pos_i in list(intended_moves.items()):
            for j, pos_j in list(intended_moves.items()):
                if i < j and pos_i == (npcs[j]['x'], npcs[j]['y']) and pos_j == (npcs[i]['x'], npcs[i]['y']):
                    intended_moves[i] = (npcs[i]['x'], npcs[i]['y'])
                    intended_moves[j] = (npcs[j]['x'], npcs[j]['y'])
                    
        changed = True
        while changed:
            changed = False
            counts = {}
            for idx, pos in intended_moves.items(): counts.setdefault(pos, []).append(idx)
            for pos, claimants in counts.items():
                if len(claimants) > 1:
                    changed = True
                    hunters = [c for c in claimants if npcs[c]['type'] == 'hunter']
                    pickers = [c for c in claimants if npcs[c]['type'] == 'picker']
                    winner = random.choice(hunters) if hunters else random.choice(pickers)
                    for c in claimants:
                        if c != winner:
                            intended_moves[c] = (npcs[c]['x'], npcs[c]['y'])

        # 5. ПРИМЕНЕНИЕ ХОДОВ NPC
        for idx in intended_moves:
            npc = npcs[idx]
            npc['x'], npc['y'] = intended_moves[idx]
            
            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'])] = '..'
                
        for npc in npcs:
            if npc.get('entering_house'): 
                npc['safe'] = True
                turn_events.append("\033[93mГрибник в домике!\033[0m")

        # 6. ПОСЛЕ-ХОДОВАЯ ПРОВЕРКА ВИДИМОСТИ
        fleeing_pickers = 0
        for npc in npcs:
            if npc.get('safe') or npc['just_spawned']: continue
            
            if npc['type'] == 'hunter' and not npc.get('has_shot'):
                if has_los(npc['x'], npc['y'], player_x, player_y):
                    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['x'] - player_x) / max(1, dist))
                    animations.extend([{'x': ax1, 'y': ay1, 'char': '💥'}, {'x': ax2, 'y': ay2, 'char': '🩸'}])
                    turn_events.append(f"💥 Егерь выстрелил в вас! Урон: {dmg}")
                    
            elif npc['type'] == 'picker' and not npc['aware']:
                if has_los(npc['x'], npc['y'], player_x, player_y):
                    npc['aware'] = True
                    if not npc.get('has_yelled'):
                        total_yells += 1
                        npc['has_yelled'] = True
                        turn_events.append("📢 Грибник заметил вас и поднял тревогу!")
            
            if npc['type'] == 'picker' and npc['aware'] and not npc.get('has_yelled'):
                total_yells += 1
                fleeing_pickers += 1

        if fleeing_pickers == 1:
            turn_events.append("📢 Испуганный грибник бежит к домику или не знает, куда бежать!")
        elif fleeing_pickers > 1:
            turn_events.append("📢 Испуганные грибники бегут к домику или не знают, куда бежать!")

        npcs = [n for n in npcs if not n.get('safe')]
        for n in npcs:
            n['age'] += 1
            n['skip_move'] = False

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

        # 7. СПАВН NPC НА ГРАНИЦЕ
        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)) == '..' 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, 'aware': False, 'just_spawned': True, 'age': 0})
                elif r < 0.005 + (0.0005 * total_yells):
                    npcs.append({'type': 'hunter', 'x': rx, 'y': ry, 'just_spawned': True, 'age': 0})

        # 8. СТАТИСТИКА ВОЛКА
        if satiety > 0:
            satiety -= 1
        else:
            health -= 1
            turn_events.append("🩸 Вы голодаете! Здоровье падает.")
            
        if health <= 0:
            dead = True
            death_msg = "Волк пал от пуль егерей или голода."

if __name__ == "__main__":
    main()