Велогрибник: Заводской Дрифт
Велогрибник (англ. Mushroom Drifter: The Acid Rain) — флагманская дисциплина современного киберспорта, сместившая с пьедестала Dota 2 и CS:GO. Построенная на передовом движке ANSI-графики в терминале, эта игра требует от спортсменов нечеловеческой реакции, глубокого понимания геометрии и умения грамотно «дунуть» в критической ситуации.
Лор и сеттинг: Экология, боль и грибы[править]
Действие игры разворачивается в лесу, который методично пожирают лесные заводы. Сюжет игры — это суровая аллюзия на реальный мир:
- Дикая природа жестока и беспощадна. Леса кишат волками, которые готовы разорвать вас за малейшую ошибку.
- Человечество ещё хуже. Заводы отравляют всё живое, провоцируя кислотные дожди, из-за которых волки мутируют в многоглазых радиоактивных чудовищ, а спасительные костры мгновенно гаснут.
Вы — безумный велогрибник-непоседа. Вы не можете просто гулять по лесу. Вы обязаны гнать на велосипеде, потому что только скорость даёт вам смысл жизни. Ваша цель — собирать грибы и относить их зажравшемуся Хозяину лесных заводов.
Хозяин обитает в роскошном особняке, украшенном красными фонарями. Ему абсолютно плевать на экологию и на вас. Он оценивает вашу никчёмную жизнь исключительно по количеству принесённых грибов. Принесёте мало — вас обматерят и изобьют охранники. Принесёте много — он сияя назовёт вас «лучшим другом» и, возможно, пустит на свои новые территории, где прямо на земле растут алмазы.
Игровые механики и правила выживания[править]
В основе игры лежит безжалостная физика инерции. Велогрибник не знает слова «тормоз». С каждым шагом в одном направлении его скорость неумолимо растёт. Врезаться на огромной скорости в сосну или кирпичную стену завода — значит досрочно закончить карьеру.
Искусство «Дунуть»[править]
У вашего велосипеда нет тормозов. Чтобы сбросить скорость до нуля, киберспортсмен должен нажать клавишу «Р» (или «H» на английской раскладке) — то есть буквально «дунуть». Если вы «дунули» рядом с костром, огонь может перекинуться на соседние деревья, массово сжигая волков. Если костра рядом нет — вы просто эпично выдыхаете воздух в пустоту и останавливаетесь, теряя драгоценный ход и покрываясь холодным потом.
Бонусная диагональ и психология волков (Комбо-система)[править]
В базовом состоянии законы физики суровы: вы можете двигаться только крест-накрест. Однако игра вознаграждает агрессию. Если за один ход вы совершаете два и более полезных действия (собираете два гриба, сбиваете двух волков или собираете гриб и давите волка), вы ломаете ткань пространства. Открывается бонусное право на движение по диагонали, но лишь на один ход!
Но это ещё не всё! Если вы совершаете полезные действия несколько ходов подряд, запускается счётчик Комбо. И тут вскрывается потрясающая деталь лора: волки — не просто бездумные пиксели, они ценят стиль. Когда вы выписываете безумные дрифты, сбивая сородичей и пылесося грибы на скорости, волки сидят в кустах и в шоке наблюдают за вашим мастерством.
Чем выше ваш счётчик комбо, тем сильнее волки вас уважают (и боятся). Математически доказано: чем агрессивнее и техничнее вы едете, набивая комбо, тем реже игра осмеливается спавнить новых волков на границе вашей видимости. Будь крутым — и природа отступит!
Уведомления в мессенджере Max (ловит даже в лесу)[править]
Найти особняк в бесконечном лесу было бы невозможно, если бы не современные технологии. Ровно за каждые пять собранных грибов ваш смартфон вибрирует. Это приходит пуш-уведомление в сверхзащищенном мессенджере Max.
Кто вам пишет — загадка. Возможно, это недовольная секретарша Хозяина заводов сливает вам геоданные. Сообщение в Max всегда лаконично и содержит точное расстояние до особняка, рассчитанное по прямой линии, словно гипотенуза прямоугольного треугольника. Собрали 5 грибов — «Дзынь! До цели 42.4 клетки». Собрали 10 — «Дзынь! Вы отдалились, уже 56.1».
Правило особняка (синдром хорошего мальчика)[править]
Самая абсурдная и жестокая механика игры. Допустим, за вами гонится стая радиоактивных волков. Вы видите спасительный особняк. Казалось бы — влетай в двери на полной скорости! Но нет. Если вы врежетесь в особняк на скорости, вы разобьётесь насмерть. Вы обязаны полностью остановиться (сбросить скорость до нуля), и только на следующем ходу аккуратно сделать шажок в двери. Почему? Этикет! Хозяин заводов терпеть не может грязные следы шин на своих персидских коврах. Приходится элегантно тормозить перед порогом, пока волки радостно откусывают вам икры.
Гнев небес и экология: дождь, заводы и разложение волков[править]
Погода в этой игре — это не просто фоновое явление, а строгий, нетерпеливый и крайне токсичный рефери. Природа органически не переваривает халтурщиков. Если вы гордо называетесь велогрибником, но в течение долгих 10 ходов катаетесь туда-сюда и не собираете ни одного гриба (или алмаза), небеса буквально теряют терпение. Они смотрят на вас с осуждением и наказывают ливнем. Этот дождь — абсолютный кайфолом. Для киберспортсмена он работает как тройное проклятие:
- Спидбрейкер: Вода заливает подшипники, и ваша с таким трудом набранная скорость мгновенно сбрасывается до унизительного нуля.
- Комбобрейкер: Дождь смывает весь пафос. Ваш счётчик стиля обнуляется, а волки перестают вас уважать и ещё больше спавнятся прямо по курсу.
- Фаербрейкер: Ливень безжалостно тушит все костры в радиусе 9x9 клеток вокруг вас, лишая главного оружия массового поражения.
Откуда в лесу костры и почему они взрываются?[править]
Лес густо усеян тлеющими кострами, которые оставили предыдущие, менее удачливые спидранеры, пытавшиеся согреться перед смертью от инерции. Но почему, если Велогрибник эпично «дунет» на такой костёр, пламя моментально сжирает целую рощу? Всё дело в экологии. Из-за близости индустриальной зоны местная древесина давно мутировала и на 90% состоит из сухого спирта, скипидара и промышленных растворителей. Местные сосны — это, по сути, гигантские зеленые спички. Достаточно одного направленного потока воздуха из ваших легких, чтобы спровоцировать цепную реакцию, которая за секунду превращает целую стаю волков в хрустящее барбекю.
Лесные заводы и кислотный апокалипсис[править]
Если вас угораздило прогневать небеса, находясь рядом с Лесным заводом, локальный дождь превращается в катастрофу локального масштаба. Что именно производят эти заводы? Официально — высокооктановую смазку для велосипедных цепей и концентрированную безысходность. На деле же они круглосуточно перерабатывают лес в густой индустриальный смог. Стоит дождевому облаку проплыть над трубами завода, как вода превращается в чистую кислоту (🧪).
И вот тут раскрывается величайшая несправедливость и комедия игрового лора: велогрибник абсолютно невосприимчив к кислотному дождю. Почему? Потому что его спортивный костюм соткан из стопроцентного асбеста, непробиваемого эго и амбиций, а кровь давно кристаллизовалась от адреналина. Ему эта кислота — как лёгкий душ.
А вот волки страдают по полной программе. Кислотный ливень не убивает их мгновенно, а запускает жуткий, но уморительно детализированный процесс пошагового растворения. С каждым ходом, проведенным под кислотным дождём, волк деградирует:
- Стадия 1 (👽): У волка мгновенно слезает вся шерсть. Он становится похож на мерзкого лысого пришельца-чихуахуа.
- Стадия 2 (👺): Кислота разъедает кожу, обнажая мышцы. Волк превращается в освежёванного красного демона, которому очень, очень больно.
- Стадия 3 (👾): Растворяется мясо. За вами по лесу с лязгом начинает гнаться безумный скелет с обрывками мяса и потрохов.
- Стадия 4 (☠️): Кости рассыпаются в труху. Волк окончательно умирает, так и не поняв, за что ему это всё.
Именно поэтому опытные игроки часто используют тактику пассивного геноцида: просто нарезают круги вокруг завода, намеренно не собирая грибы, вызывают кислотный дождь и с попкорном наблюдают за тем, как стая хищников поэтапно растворяется в лужах!
Киберспортивные дисциплины[править]
Хотя существуют фановые режимы вроде «Сбей максимум волков» или «Продержись как можно дольше ходов», официальные турниры признают только три дисциплины:
- Фарм Грибов (для аматоров до 1000 MMR): Игрок методично ездит по лесу, уворачиваясь от заводов, и собирает грибы. Он ждет сообщений в мессенджере Max каждые пять грибов и пытается методом триангуляции вычислить особняк. Главная цель — найти Хозяина и не размазаться по кирпичной стене от инерции.
- Фарм Алмазов (для профессионалов): После сдачи огромной партии грибов Хозяину, на карте начинают спавниться алмазы. Про-игроки используют лесные пожары для контроля толпы волков и бесконечного кайта, чтобы набивать безумные комбо и пылесосить драгоценности.
- 0-Mushroom Run (Для мазохистов и спидранеров): Умудриться пройти в особняк с абсолютно пустыми руками (ноль грибов). Поскольку без грибов мессенджер Max молчит и не присылает координаты, найти особняк в огромной сетке координат — это чистая лотерея, требующая идеального чтения генерации чанков леса и невероятного везения. Вас изобьют охранники, но зато какой флекс!
Восстание машин: AlphaZero против Кожаных Мешков[править]
Велогрибник — математически совершенная игра. Вероятность появления алмаза на карте после визита в особняк не случайна. Она подчиняется строгому закону: базовая вероятность (одна стотысячная) умножается на квадрат количества грибов, которые вы сдали Хозяину. Чтобы алмазы начали появляться с приемлемой частотой, нужно принести просто нереальный урожай.
Людям тяжело держать эти вероятности в голове, параллельно уворачиваясь от радиоактивных волков и считая ходы для комбо. Долгое время считалось, что предел человеческих возможностей — 73 гриба перед входом в особняк. Хозяин лесных заводов при этом искренне радуется, обнимает игрока и закатывает пир. Но в 2026 году компания DeepMind подключила к симуляции нейросеть AlphaZero. Восьми лет непрерывного обучения, сжигания тераватт энергии и плавки тензорных ядер хватило, чтобы ИИ совершил невозможное. AlphaZero собрал 317 грибов за один заход.
Когда бот элегантно остановился перед дверью и зашёл в особняк, игра буквально не знала, что ему ответить (ведь реплики Хозяина прописаны только до 100 грибов). Хозяин лесных заводов просто стоял и молча раскрывал рот от удивления, а генератор карты сошел с ума, засыпав весь лес сплошными алмазами. Ни одному живому человеку до этого далеко. Кожаные мешки слишком ценят свою жизнь и слишком боятся врезаться в сосну на девятой скорости, чтобы постичь этот дрифт-дзен.
Практическая польза: Как киберспорт спасает жизни на Юконе[править]
Скептики часто ворчат, что видеоигры не учат ничему полезному. Скажите это Генри — герою документальной повести Джека Лондона «Белый Клык», который выжил в заснеженной глуши исключительно благодаря тысячам часов, наигранным в «Велогрибника».
Если вы читали первоисточник, то помните трагическую судьбу его напарника, Билла. Билл был типичным бумером. Он искренне считал киберспорт «ерундой для бездельников», отказывался понимать концепцию пиксельного хитбокса и принципиально не фармил грибы. Когда стая голодных волков взяла их упряжку в кольцо, Билл совершил классическую ошибку нуба. Вместо того чтобы набивать комбо-счетчик и отпугивать хищников своим мастерством передвижения, он запаниковал, нарушил правило диагонального хода без собранных объектов и сбросил скорость до нуля прямо на соседней с волком клетке. Система не прощает таких ошибок: анимация мгновенной смерти, Game Over, Билла съели.
Генри же был тертым калачом с MMR выше 1500. Оставшись один на один со стаей, он не стал тратить патроны (ведь в игре нет огнестрела!). Он начал методично абьюзить механику костров. Понимая, что алгоритм поиска пути у волков строго запрещает им наступать на тайлы со статусом 🔥, Генри выстроил вокруг себя идеальный защитный периметр. Но стая не уходила, ожидая, когда он совершит ошибку или когда небеса пошлют локальный дождь. И тогда Генри вспомнил главное правило дрифт-выживания.
Он набрал полную грудь морозного воздуха, нажал воображаемую клавишу Р и эпично дунул на костёр. Как мы знаем из физики «Велогрибника», пламя моментально перекинулось на соседние обледенелые ели, вызвав цепную реакцию. Лес вспыхнул. Огромный AoE-урон накрыл передовой отряд волков, мгновенно превратив их в хрустящий шашлык, а на экране внутреннего интерфейса Генри зажегся счетчик: «🔥 Отлично! Комбо x12! 🔥».
Остатки стаи, увидев, как этот безумец разносит лес одним выдохом и набивает стрик, банально побоялись спавниться рядом и в ужасе отступили, признав его альфа-киберспортсменом. Вскоре Генри спасли проезжавшие мимо люди.
Мораль этой истории, которую Джек Лондон пытался донести до потомков, проста: если бы Билл в молодости не тратил время на глупости вроде колки дров и охоты, а гриндил алмазы на новых территориях Хозяина лесных заводов — он был бы жив!
Код игры (без СМС и регистрации)[править]
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):
# Вычисляет длину строки без учёта ANSI-кодов для правильного центрирования
return len(re.sub(r'\033\[[0-9;]*m', , s))
def get_input(menu_mode=False):
# Маппинг движения для русской (и английской) раскладки вокруг клавиши Р (H)
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)
}
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)
elif key == '\x03': return None
else:
k = key.lower()
if k in ['q', 'й']: return None
if k in ['р', 'h']: return 'blow'
if menu_mode and k.isdigit():
return int(k)
if k in move_map:
return move_map[k]
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)
elif ch == '\x03': return None
else:
k = ch.lower()
if k in ['q', 'й']: return None
if k in ['р', 'h']: return 'blow'
if menu_mode and k.isdigit():
return int(k)
if k in move_map:
return move_map[k]
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return (0, 0)
def main():
player_x, player_y = 0, 0
moves = 0
speed = 0
last_dx, last_dy = 0, 0
mushrooms = 0
dead_wolves = 0
total_combos = 0
combo_streak = 0
turns_without_mushrooms = 0
diagonal_unlocked = False
# Генерация координат особняка
mx = random.randint(0, 100)
my = 100 - mx
mx *= random.choice([-1, 1])
my *= random.choice([-1, 1])
mansion_x, mansion_y = mx, my
mansion_visited = False
given_mushrooms = 0
diamonds = 0
# Отслеживаем "уровни" подсказок (каждые 5 грибов)
last_hint_tier = 0
grid = {}
wolves = []
burning_trees_cells = set()
turn_events = []
combo_msg = ""
feedback_msg = ""
dead = False
death_msg = ""
is_igniting = False
def get_wolf_emoji(w):
stage = w.get('acid_stage', -1)
if stage == 1: return '👽'
elif stage == 2: return '👺'
elif stage == 3: return '👾'
return '🐺'
def is_visible(dx, dy):
return abs(dx) <= 7 and abs(dy) <= 7
def is_on_leading_edge(x, y, px, py, mdx, mdy, R=7):
if mdx == 0 and mdy == -1:
return y == py - R and abs(x - px) <= R
elif mdx == 0 and mdy == 1:
return y == py + R and abs(x - px) <= R
elif mdx == -1 and mdy == 0:
return x == px - R and abs(y - py) <= R
elif mdx == 1 and mdy == 0:
return x == px + R and abs(y - py) <= R
return False
def trigger_mansion_scene():
nonlocal mansion_visited, given_mushrooms, mushrooms, wolves, dead_wolves
clear_screen()
term_size = shutil.get_terminal_size((80, 24))
term_width = term_size.columns
if mushrooms == 0:
msg = "Хозяин лесных заводов багровеет от ярости! «Ни одного гриба?! Ты смеешь тратить моё время?!» Он обрушивает на вас трёхэтажный мат, а его охрана жестоко избивает вас, после чего вышвыривает вон."
elif mushrooms < 10:
msg = f"Хозяин лесных заводов брезгливо морщится. «Всего {mushrooms}? Жалкое зрелище, ты убожество. Убирайся, пока я не спустил собак.» Вас грубо выталкивают за дверь."
elif mushrooms < 20:
msg = f"Хозяин лесных заводов холодно смотрит на вас сверху вниз. «Что ж, {mushrooms} грибов. Положи на стол и уходи. И не топчи ковры.»"
elif mushrooms < 30:
msg = f"Хозяин лесных заводов кивает. «{mushrooms} грибов. Неплохо. Оставь их прислуге. Мы в расчёте.» Обычная, формальная встреча."
elif mushrooms < 40:
msg = f"Хозяин лесных заводов уважительно жмёт вам руку. «О, {mushrooms} отличных грибов! Вижу в тебе перспективного и надёжного партнёра по бизнесу. Заходи ещё!»"
else:
msg = f"Хозяин лесных заводов сияет от радости и обнимает вас! «Браво! {mushrooms} грибов! Мой дорогой гость, мой лучший друг! Закатим пир!» Вас принимают по высшему разряду."
wrapped = textwrap.wrap(msg, width=min(term_width - 4, 60))
print("\n" * max(0, term_size.lines // 2 - len(wrapped) // 2 - 2))
for line in wrapped:
print(" " * max(0, (term_width - len(line)) // 2) + line)
prompt = "[Нажмите любую клавишу для выхода из особняка]"
print("\n\n" + " " * max(0, (term_width - len(prompt)) // 2) + prompt)
get_input(menu_mode=True)
given_mushrooms = mushrooms
mansion_visited = True
mushrooms = 0
wolves.clear()
for pos in list(grid.keys()):
if grid[pos] == '🍄':
grid[pos] = '..'
def ignite_tree_group(start_x, start_y):
nonlocal dead_wolves, is_igniting
if is_igniting: return 0, 0
is_igniting = True
try:
burned_count = 0
ignited_trees = 0
visited = set([(start_x, start_y)])
stack = [(start_x, start_y)]
while stack:
cx, cy = stack.pop()
if grid.get((cx, cy)) == '🌲':
grid[(cx, cy)] = '🔥'
burning_trees_cells.add((cx, cy))
ignited_trees += 1
for w in list(wolves):
if abs(w['x'] - cx) <= 1 and abs(w['y'] - cy) <= 1:
wolves.remove(w)
dead_wolves += 1
burned_count += 1
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: continue
nx, ny = cx + dx, cy + dy
if (nx, ny) not in visited:
if (nx, ny) not in grid:
generate_cell(nx, ny)
if grid.get((nx, ny)) == '🌲':
visited.add((nx, ny))
stack.append((nx, ny))
return ignited_trees, burned_count
finally:
is_igniting = False
def generate_cell(x, y, move_dx=0, move_dy=0):
nonlocal is_igniting
dx_m = abs(x - mansion_x)
dy_m = abs(y - mansion_y)
if dx_m <= 2 and dy_m <= 2:
if (x, y) not in grid:
if dx_m == 0 and dy_m == 0:
grid[(x, y)] = '🏯'
elif dx_m == 1 and dy_m == 1:
grid[(x, y)] = '🏮'
else:
grid[(x, y)] = '..'
return
on_leading_edge = is_on_leading_edge(x, y, player_x, player_y, move_dx, move_dy, R=7)
wolf_prob = max(0.0, (moves * 0.0005) - (total_combos * 0.003)) if on_leading_edge else 0.0
if (x, y) not in grid:
if x == 0 and y == 0:
grid[(x, y)] = '..'
return
if mansion_visited:
diamond_prob = 0.00001 * (given_mushrooms ** 2)
if random.random() < diamond_prob:
grid[(x, y)] = '💎'
return
r = random.random()
if r < 0.20:
has_factory = any(grid.get((x+dx, y+dy)) == '🏭' for dx in range(-2, 3) for dy in range(-2, 3))
if not has_factory:
grid[(x, y)] = '🌲'
if not is_igniting and any((x+adx, y+ady) in burning_trees_cells for adx in [-1,0,1] for ady in [-1,0,1] if adx!=0 or ady!=0):
_, w_b = ignite_tree_group(x, y)
if w_b > 0:
turn_events.append(f"🔥 Распространяющийся пожар поглотил волков: {w_b}!")
else:
grid[(x, y)] = '..'
elif r < 0.25:
has_factory = any(grid.get((x+dx, y+dy)) == '🏭' for dx in range(-2, 3) for dy in range(-2, 3))
if not mansion_visited and not has_factory:
grid[(x, y)] = '🍄'
else:
grid[(x, y)] = '..'
elif r < 0.27:
has_factory = any(grid.get((x+dx, y+dy)) == '🏭' for dx in range(-2, 3) for dy in range(-2, 3))
if not has_factory:
is_near_wolf = any(abs(w['x'] - x) <= 1 and abs(w['y'] - y) <= 1 for w in wolves)
grid[(x, y)] = '..' if is_near_wolf else '🔥'
else:
grid[(x, y)] = '..'
elif r < 0.32:
has_nature = any(grid.get((x+dx, y+dy)) in ['🌲', '🍄', '🔥'] for dx in range(-2, 3) for dy in range(-2, 3))
grid[(x, y)] = '🏭' if not has_nature else '..'
else:
grid[(x, y)] = '..'
if grid[(x, y)] == '..' and wolf_prob > 0:
has_wolf = any(w['x'] == x and w['y'] == y for w in wolves)
if not has_wolf:
is_near_fire = False
for adx in [-1, 0, 1]:
for ady in [-1, 0, 1]:
if grid.get((x + adx, y + ady)) == '🔥':
is_near_fire = True
break
if is_near_fire:
break
if not is_near_fire and random.random() < wolf_prob:
wolves.append({'x': x, 'y': y, 'just_spawned': True})
def generate_viewport(move_dx=0, move_dy=0):
R = 7
for y in range(player_y - R, player_y + R + 1):
for x in range(player_x - R, player_x + R + 1):
if is_visible(x - player_x, y - player_y):
generate_cell(x, y, move_dx, move_dy)
def is_safe_for_wolf(nx, ny, current_wolf=None):
if grid.get((nx, ny)) in ['🌲', '🏭', '🔥', '🏯', '🏮', '💎']: return False
for adx in [-1, 0, 1]:
for ady in [-1, 0, 1]:
if grid.get((nx + adx, ny + ady)) == '🔥':
return False
for other in wolves:
if current_wolf and other is current_wolf: continue
if other['x'] == nx and other['y'] == ny: return False
if current_wolf and 'old_x' in current_wolf and 'old_x' in other:
if nx == other['old_x'] and ny == other['old_y']:
if other['x'] == current_wolf['old_x'] and other['y'] == current_wolf['old_y']:
return False
return True
def render_rain(is_acid_rain):
clear_screen()
term_size = shutil.get_terminal_size((80, 24))
term_width = term_size.columns
term_height = term_size.lines
if mansion_visited:
h1 = f"\033[90m🍄 Грибов: 0 (Сдано: {given_mushrooms})\033[0m | 💎 Алмазов: {diamonds}"
else:
h1 = f"🍄 Грибов: {mushrooms}"
h2 = f"💀 Волков: {dead_wolves} | 🔥 Комбо: {total_combos} | ⏱ Ходы: {moves} | 💠 8-way: {'ВКЛ' if diagonal_unlocked else 'ВЫКЛ'}"
h3 = f"🚀 Скор: 0 🛑 | 💨 Дуть: Р | 🧭 Ход: Е Н Г / П О / М И Т | Выход: Й/Q"
if is_acid_rain:
feedback = "🧪 КИСЛОТНЫЙ ДОЖДЬ (9x9)! Костры потухли, скорость утеряна, волки облучены радиацией."
else:
feedback = "🌧️ ЛОКАЛЬНЫЙ ДОЖДЬ (9x9)! Костры потухли, скорость утеряна, комбо прервано."
wrapped_feedback = textwrap.wrap(feedback, width=min(term_width, 45))
R = 7
grid_width_visual = (2 * R + 1) * 2
grid_height = 2 * R + 1
header_height = 3 + len(wrapped_feedback) + 1
total_height = header_height + grid_height
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="")
print(" " * max(0, (term_width - visible_len(h1)) // 2) + h1)
print(" " * max(0, (term_width - visible_len(h2)) // 2) + h2)
print(" " * max(0, (term_width - visible_len(h3)) // 2) + h3)
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_str = ""
for x in range(player_x - R, player_x + R + 1):
if not is_visible(x - player_x, y - player_y):
row_str += " "
elif x == player_x and y == player_y:
row_str += "🚴♀️"
elif abs(x - player_x) <= 4 and abs(y - player_y) <= 4:
row_str += "🧪" if is_acid_rain else "🌧️"
else:
is_wolf = next((w for w in wolves if w['x'] == x and w['y'] == y), None)
if is_wolf:
row_str += get_wolf_emoji(is_wolf)
else:
row_str += grid.get((x, y), '..')
print(" " * h_pad_grid + row_str)
def render():
clear_screen()
term_size = shutil.get_terminal_size((80, 24))
term_width = term_size.columns
term_height = term_size.lines
dir_emoji = ""
if last_dx == 0 and last_dy == -1: dir_emoji = "⬆️"
elif last_dx == 0 and last_dy == 1: dir_emoji = "⬇️"
elif last_dx == -1 and last_dy == 0: dir_emoji = "⬅️"
elif last_dx == 1 and last_dy == 0: dir_emoji = "➡️"
elif last_dx == -1 and last_dy == -1: dir_emoji = "↖️"
elif last_dx == 1 and last_dy == -1: dir_emoji = "↗️"
elif last_dx == -1 and last_dy == 1: dir_emoji = "↙️"
elif last_dx == 1 and last_dy == 1: dir_emoji = "↘️"
elif speed == 0: dir_emoji = "🛑"
speed_str = f"{speed} {dir_emoji}".strip()
if mansion_visited:
h1 = f"\033[90m🍄 Грибов: 0 (Сдано: {given_mushrooms})\033[0m | 💎 Алмазов: {diamonds}"
else:
h1 = f"🍄 Грибов: {mushrooms}"
h2 = f"💀 Волков: {dead_wolves} | 🔥 Комбо: {total_combos} | ⏱ Ходы: {moves} | 💠 8-way: {'ВКЛ' if diagonal_unlocked else 'ВЫКЛ'}"
h3 = f"🚀 Скор: {speed_str} | 💨 Дуть: Р | 🧭 Ход: Е Н Г / П О / М И Т | Выход: Й/Q"
if feedback_msg:
wrapped_feedback = textwrap.wrap(feedback_msg, width=min(term_width, 60))
else:
wrapped_feedback = []
R = 7
grid_width_visual = (2 * R + 1) * 2
grid_height = 2 * R + 1
header_height = 3 + max(1, len(wrapped_feedback)) + 1
total_height = header_height + grid_height + (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="")
print(" " * max(0, (term_width - visible_len(h1)) // 2) + h1)
print(" " * max(0, (term_width - visible_len(h2)) // 2) + h2)
print(" " * max(0, (term_width - visible_len(h3)) // 2) + h3)
if wrapped_feedback:
for line in wrapped_feedback:
print(" " * max(0, (term_width - visible_len(line)) // 2) + line)
else:
print()
print()
for y in range(player_y - R, player_y + R + 1):
row_str = ""
for x in range(player_x - R, player_x + R + 1):
if not is_visible(x - player_x, y - player_y):
row_str += " "
elif dead and x == player_x and y == player_y:
row_str += "💀"
elif not dead and x == player_x and y == player_y:
row_str += "🚴♀️"
else:
is_wolf = next((w for w in wolves if w['x'] == x and w['y'] == y), None)
if is_wolf:
row_str += get_wolf_emoji(is_wolf)
else:
row_str += grid.get((x, y), '..')
print(" " * h_pad_grid + row_str)
if dead:
msg1 = f"💀 ИГРА ОКОНЧЕНА! {death_msg}"
wrapped_death = textwrap.wrap(msg1, width=min(term_width, 45))
msg2 = "Нажмите любую клавишу для выхода..."
print("\n", end="")
for line in wrapped_death:
print(" " * max(0, (term_width - visible_len(line)) // 2) + line)
print(" " * max(0, (term_width - visible_len(msg2)) // 2) + msg2)
generate_viewport()
while True:
if turn_events or combo_msg:
feedback_msg = " | ".join(turn_events)
if combo_msg:
feedback_msg = f"{feedback_msg} {combo_msg}".strip(" | ")
else:
feedback_msg = ""
render()
turn_events = []
combo_msg = ""
if dead:
get_input()
break
inp = get_input()
if inp is None: break
if inp == (0, 0): continue
combo_action_this_turn = False
if inp == 'blow':
fires = []
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: continue
if grid.get((player_x + dx, player_y + dy)) == '🔥':
fires.append((dx, dy))
target_fire = None
fires_around = len(fires)
if fires_around == 1:
target_fire = fires[0]
elif fires_around > 1:
dir_names = {
(0,-1): "вверх", (0,1): "вниз", (-1,0): "влево", (1,0): "вправо",
(-1,-1): "вверх-влево", (1,-1): "вверх-вправо", (-1,1): "вниз-влево", (1,1): "вниз-вправо"
}
dir_keys_display = {
(0,-1): "Н", (0,1): "И", (-1,0): "П", (1,0): "О",
(-1,-1): "Е", (1,-1): "Г", (-1,1): "М", (1,1): "Т"
}
options_str = []
for f in fires:
options_str.append(f"{dir_keys_display.get(f)} ({dir_names.get(f)})")
feedback_msg = f"🔥 Укажите направление костра: " + ", ".join(options_str)
render()
while True:
choice = get_input(menu_mode=False)
if isinstance(choice, tuple):
if choice in fires:
target_fire = choice
break
elif choice is None:
return
trees_ignited_by_blow = 0
wolves_burned_by_blow = 0
if target_fire:
fdx, fdy = target_fire
tx, ty = player_x + 2*fdx, player_y + 2*fdy
generate_cell(tx, ty)
if grid.get((tx, ty)) == '🌲':
i_t, w_b = ignite_tree_group(tx, ty)
trees_ignited_by_blow += i_t
wolves_burned_by_blow += w_b
speed = 0
last_dx, last_dy = 0, 0
diagonal_unlocked = False
moves += 1
turns_without_mushrooms += 1
if fires_around == 0:
turn_events.append("💨 Вы подули, но костра рядом нет.")
elif trees_ignited_by_blow > 0:
combo_action_this_turn = True
msg = f"🌲 Вы успешно подожгли лес!"
if wolves_burned_by_blow > 0:
msg += f" Волки сгорели: {wolves_burned_by_blow}!"
turn_events.append(msg)
else:
turn_events.append("💨 Вы подули на костёр, но ни одно дерево не загорелось.")
generate_viewport(0, 0)
elif isinstance(inp, tuple):
dx, dy = inp
if abs(dx) == 1 and abs(dy) == 1:
if not diagonal_unlocked:
turn_events.append("❌ Диагональный ход недоступен! Нужно собрать/сбить 2+ объекта за 1 ход.")
continue
was_speed_zero = (speed == 0)
if speed > 0:
if dx == last_dx and dy == last_dy: current_speed = speed + 1
elif (dx - last_dx)**2 + (dy - last_dy)**2 == 1: current_speed = speed
else: current_speed = 1
else:
current_speed = 1
next_x, next_y = player_x + dx, player_y + dy
generate_cell(next_x, next_y, dx, dy)
if grid[(next_x, next_y)] in ['🌲', '🏭', '🏯', '🏮']:
if grid[(next_x, next_y)] == '🏯' and not mansion_visited and was_speed_zero and current_speed == 1 and next_x == mansion_x and next_y == mansion_y:
pass
else:
continue
mushrooms_this_turn = 0
diamonds_this_turn = 0
wolves_killed_this_turn = 0
entered_mansion = False
for step in range(1, current_speed + 1):
cx = player_x + dx * step
cy = player_y + dy * step
generate_cell(cx, cy, dx, dy)
if grid[(cx, cy)] == '🔥':
dead = True
death_msg = "Вы сдуру заехали прямо в костёр и сгорели заживо!"
player_x, player_y = cx, cy
break
if grid[(cx, cy)] == '🏯':
if not mansion_visited and was_speed_zero and current_speed == 1 and cx == mansion_x and cy == mansion_y:
entered_mansion = True
break
else:
dead = True
death_msg = "Вы врезались в стену роскошного особняка и разбились!"
player_x, player_y = cx, cy
break
if grid[(cx, cy)] == '🏮':
dead = True
death_msg = "Вы влетели в красный фонарь и разбились!"
player_x, player_y = cx, cy
break
if grid[(cx, cy)] == '🌲':
dead = True
death_msg = "Вы на огромной скорости влетели в дерево и разбились!"
player_x, player_y = cx, cy
break
if grid[(cx, cy)] == '🏭':
dead = True
death_msg = "Вы на огромной скорости врезались в заводскую стену и разбились!"
player_x, player_y = cx, cy
break
wolf_here = next((w for w in wolves if w['x'] == cx and w['y'] == cy), None)
if wolf_here:
if step == current_speed:
dead = True
death_msg = "Вы остановились прямо перед волком, и он вас загрыз!"
player_x, player_y = cx, cy
wolves.remove(wolf_here)
break
else:
wolves.remove(wolf_here)
dead_wolves += 1
wolves_killed_this_turn += 1
if grid[(cx, cy)] == '🍄':
mushrooms += 1
mushrooms_this_turn += 1
grid[(cx, cy)] = '..'
elif grid[(cx, cy)] == '💎':
diamonds += 1
diamonds_this_turn += 1
grid[(cx, cy)] = '..'
if entered_mansion:
trigger_mansion_scene()
speed = 0
last_dx, last_dy = 0, 0
turns_without_mushrooms = 0
diagonal_unlocked = False
moves += 1
generate_viewport(0, 0)
continue
if not dead:
if wolves_killed_this_turn > 0:
turn_events.append(f"🐺 Вы сбили волков: {wolves_killed_this_turn}!")
items_collected = mushrooms_this_turn + diamonds_this_turn + wolves_killed_this_turn
if items_collected >= 2:
diagonal_unlocked = True
turn_events.append("✨ Доступен ход по диагонали на следующий ход! (Е, Г, М, Т)")
else:
diagonal_unlocked = False
if items_collected > 0:
combo_action_this_turn = True
if mushrooms_this_turn > 0 or diamonds_this_turn > 0:
turns_without_mushrooms = 0
else:
turns_without_mushrooms += 1
# Обновляем координаты В КОНЦЕ хода
player_x += dx * current_speed
player_y += dy * current_speed
last_dx, last_dy = dx, dy
speed = current_speed
moves += 1
# Подсказка о расстоянии до особняка, проверяется после смещения по уровню (tier) грибов
current_hint_tier = mushrooms // 5
if not mansion_visited and current_hint_tier > last_hint_tier:
last_hint_tier = current_hint_tier
dist = round(math.hypot(mansion_x - player_x, mansion_y - player_y), 1)
turn_events.append(f"🗺️ ПОДСКАЗКА: Особняк хозяина лесных заводов находится на расстоянии {dist} клеток.")
generate_viewport(dx, dy)
if not dead:
if combo_action_this_turn:
combo_streak += 1
if combo_streak >= 2:
total_combos += 1
exclamations = ["Супер!", "Огонь!", "Отлично!", "Безумие!", "Невероятно!", "Мощно!"]
combo_msg = f"🔥 {random.choice(exclamations)} Комбо x{combo_streak}! 🔥"
else:
combo_streak = 0
died_from_acid = 0
for w in list(wolves):
if 'acid_stage' in w:
w['acid_stage'] += 1
if w['acid_stage'] >= 4:
wolves.remove(w)
dead_wolves += 1
died_from_acid += 1
if died_from_acid > 0:
turn_events.append(f"💀 Кислота разъела скелеты волков (👾 -> ☠️): {died_from_acid} шт!")
for w in wolves:
w['old_x'] = w['x']
w['old_y'] = w['y']
for w in wolves:
if w['just_spawned']:
w['just_spawned'] = False
continue
wx, wy = w['x'], w['y']
wdx = (player_x > wx) - (player_x < wx)
wdy = (player_y > wy) - (player_y < wy)
candidates = []
if wdx != 0 and wdy != 0:
candidates = [(wdx, wdy), (wdx, 0), (0, wdy)]
elif wdx != 0:
candidates = [(wdx, 0), (wdx, 1), (wdx, -1)]
elif wdy != 0:
candidates = [(0, wdy), (1, wdy), (-1, wdy)]
for cdx, cdy in candidates:
tx, ty = wx + cdx, wy + cdy
generate_cell(tx, ty)
if not is_safe_for_wolf(tx, ty, w): continue
w['x'], w['y'] = tx, ty
break
if w['x'] == player_x and w['y'] == player_y:
dead = True
death_msg = "Голодный волк догнал вас и загрыз!"
wolves.remove(w)
break
if not dead and grid.get((w['x'], w['y'])) in ['🍄', '💎']:
if grid.get((w['x'], w['y'])) == '🍄':
grid[(w['x'], w['y'])] = '..'
if not dead and turns_without_mushrooms >= 10:
combo_streak = 0
diagonal_unlocked = False
# Удаляем ложное сообщение, если дождь отменил диагональ в тот же ход
diag_msg = "✨ Доступен ход по диагонали на следующий ход! (Е, Г, М, Т)"
if diag_msg in turn_events:
turn_events.remove(diag_msg)
is_acid_rain = False
for dy in range(-4, 5):
for dx in range(-4, 5):
if grid.get((player_x + dx, player_y + dy)) == '🏭':
is_acid_rain = True
break
if is_acid_rain:
break
render_rain(is_acid_rain)
time.sleep(3.5)
for dy in range(-4, 5):
for dx in range(-4, 5):
pos = (player_x + dx, player_y + dy)
if grid.get(pos) == '🔥':
grid[pos] = '..'
if pos in burning_trees_cells:
burning_trees_cells.remove(pos)
if is_acid_rain:
for w in wolves:
if abs(w['x'] - player_x) <= 4 and abs(w['y'] - player_y) <= 4:
w['acid_stage'] = 0
speed = 0
last_dx, last_dy = 0, 0
turns_without_mushrooms = 0
if is_acid_rain:
turn_events.append("🧪 Прошёл КИСЛОТНЫЙ дождь! Костры в зоне 9x9 потухли, волки облучены.")
else:
turn_events.append("🌧️ Прошёл локальный дождь! Костры в зоне 9x9 потухли, комбо сброшено.")
if __name__ == "__main__":
main()
Игровая комната |
||
|---|---|---|
| Собранные игрушки | Second Hand · SimCity 2000 · QTE · Wikigacha · Весёлый работодатель · Казино «Флэш Рояль» · Маленький человечек в большой квартире: Яичница · Отшей блондинку · Пустыня · Путешествие куда-нибудь и не только · Сказка про белого бычка | |
| Разобранные игрушки | AI Adventures · Бездна · Интерактивное кино · Крестики-нолики · Самый сложный экзамен · Симулятор динозавра | |
| Игрушечный питон | Велогрибник: Заводской Дрифт | |
| Ещё чего: Портал «Игры Абсурдопедии» | Компьютерные игры | Ролевая игра Абсурдопедии | ||