Абсурдотека:Маруся на Абсероиде

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

Глава 1. Вылет из Стихоквадратска[править]

​Стихоквадратск — жемчужина и по совместительству главная выгребная яма великого государства Абсурдоляндия — жил своей обычной, полнокровной жизнью. По улицам летал тополиный пух вперемешку с сигаретными окурками, а над куполом местной пивнушки гордо реял флаг с изображением граблей и гранёного стакана.

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

​— Ну и очередь была возле гастронома, — сухо сообщил батя, стряхивая с плеча многолетнюю пыль. — В одни руки больше двух батонов не давали, пришлось стоять.

​Маруся даже не удивилась. В Абсурдоляндии шестнадцатилетняя очередь за хлебом считалась вполне удачным стечением обстоятельств. Оставив отца обживать родные пенаты и догрызать гранитную буханку, Маруся выскочила на улицу — проветрить мозги и купить сигарет.

​Возле того самого гастронома какая-то местная деваха в чересчур ярких лосинах опрометчиво косо посмотрела на Марусю и что-то буркнула себе под нос. Маруся, отличавшаяся вспыльчивым нравом и абсолютным отсутствием эмпатии, на филологические дискуссии время тратить не стала. Шаг вперёд, короткий замах — и резкий, классический апперкот снизу в челюсть отправил незнакомку в глухой нокаут. Когда тушка рухнула на асфальт, Маруся с чувством выполненного долга тяжело и методично затоптала её тяжелыми ботинками до полного прекращения подаваемых признаков жизни.

​Стражи порядка примчались на удивляющих своей скоростью уазиках. Два дородных мента покрутили головами, посмотрели на превращённую в блин девицу, затем на рыжую Марусю, вытиравшую подошву о газон.

​— Так, — сказал старший, почесывая брюхо под пузом. — Убийство с особой жестокостью. Делу надо дать ход… или замять. Но процедура требует компенсации.

​Разбираться с бумажной волокитой полицейским не хотелось. Поэтому они просто затащили Марусю в уазик, пустили её всем отделом по кругу прямо на заднем сиденье, после чего сытые, довольные и слегка запыхавшиеся высадили её за углом и пожелали хорошего вечера. Дело, разумеется, тут же списали на нападение бродячих собак.

​К вечеру Маруся уже полностью восстановила душевное равновесие и отправилась в центральную баню. Там намечалось грандиозное событие: мужики провожали в космос Павлика — местного космонавта, которому завтра предстояло улететь на орбиту на единственной в Абсурдоляндии ракете.

​В предбаннике дым стоял коромыслом. Водка «Абсурдовка» лилась вёдрами, Павлик уже лежал без чувств под скамьёй в одном левом носке, а Маруся, наклюкавшись до состояния полного изумления, доказывала присутствующим, что сможет управлять космолётом лучше любого Гагарина.

​В это же самое время главный механик Егор Туалетов, проклиная пьяную компанию, поплёлся на стартовую площадку. По инструкции перед запуском требовалось провести финальный техосмотр. Егор залез с гаечным ключом глубоко в тесный приборный отсек ракеты, забился между кабелями и начал увлечённо подкручивать гайки.

​Маруся, едва переставляя ноги и путая педали бытия, вывалилась из бани и на автопилоте побрела в сторону космодрома. Забор был сломан, охрана спала в будке под впечатлением от той же «Абсурдовки». Ввалившись в кабину ракеты вместо синего в сопли Павлика, Маруся с любопытством уставилась на россыпь мигающих лампочек.

​— О, пупочка! — радостно икнула она, увидев большую красную кнопку с надписью «НЕ ТРОГАТЬ, СУКА». ​Маруся обрушила на кнопку всю свою нерастраченную нежность.

​Ракетные двигатели взревели так, что у половины Стихоквадратска выбило стёкла. Мгновенно сработали автоматические задвижки, герметизировав корпус. Из глубин приборного отсека раздался истошный, перекрывающий гул реактивных струй мат Егора Туалетова, который внезапно понял, что его плановый техосмотр только что перешёл в категорию межгалактических путешествий.

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

Глава 2. Абсероид и лёгкие деньги[править]

Типичный пейзаж на Абсероиде

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

​Посадка получилась кинематографичной. Ракета на полной скорости вонзилась в рыхлую породу космического тела и замерла под углом сорок пять градусов.

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

— Эй! Сволочь! Меня заклинило! Открой люк!

— Посиди пока, проспись, — искренне посоветовала Маруся, стукнув каблуком по заклинившей заслонке, чтобы механик сидел тише, и вывалилась наружу.

​Астероид, на который её занесло, в астрономических справочниках значился как Абсероид. Он имел противоестественную форму гигантского бублика диаметром всего в пару километров. Несмотря на скромные габариты, гравитация здесь работала исправно — видать, местный бог физики с похмелья забыл отключить притяжение.

​Маруся приземлилась на так называемой территории Блик — оранжевом секторе астероида. Вокруг простирался пейзаж, способный свести с ума любого градостроителя. На абсолютно бессистемно пересекающейся местности сгрудились ровно 16 борделей с выцветшими неоновыми вывесками и 8 абсолютно одинаковых белых больниц. Санитарная инфраструктура отсутствовала как класс: умные и гордые местные жители — абсеранцы — за тысячи лет цивилизации так и не додумались до концепции канализации, поэтому весь астероид был живописно усечён пирамидами и завалами фекалий всех оттенков коричневого. ​Не успела Маруся сделать и десяти шагов, как перед ней материализовался хромарх РО — местный правитель оранжевого сектора. Выглядел он как слегка оплывший гуманоид в оранжевом балдахине, излучавший неестественное радушие.

​— О, скиталец из глубокого космоса! — запел РО, широко расставляя руки. — Добро пожаловать на Блик! Я — хромарх РО, и щедрость моя не знает границ! Видишь эти мешки? Бери! Бери больше!

​На земле возле хромарха действительно стояли пузатые холщовые мешки, набитые местной валютой — бублями. Бубли представляли собой аккуратные, высушенные и отпрессованные кругляши из всё того же универсального местного сырья.

​— У нас тут, правда, гиперинфляция, — доверительно шепнул РО, когда Маруся без лишних реверансов взвалила на плечо первые два мешка. — Вчера буханка кислородного хлеба стоила три тонны говна, сегодня — пять. Так что не стесняйся, греби лопатой!

​Маруся, уважительно относившаяся к халяве в любых её проявлениях, с азартом принялась набивать карманы и подбирать новые мешки. Однако радость от неожиданного обогащения длилась недолгo. ​Атмосфера Абсероида оказалась ядовитой дрянью, густо замешанной на оранжевом смоге и испарениях бесчисленных куч. В горле у Маруси заскребло, в глазах пошли зелёные круги, а внутренний жизненный ресурс стал стремительно утекать, как водка на поминках. Здоровье таяло на глазах.

​— Слышь, оранжевый… — прохрипела Маруся, хапя воздух ртом и сжимая в руках мешки с говняной валютой. — Где тут у вас… врач?

​— Во-о-он там, — радостно ткнул пальцем РО в сторону ближайшего здания с красным крестом. — Шестая больница справа! Только торопись, а то лёгкие выплюнешь!

​Кашляя, матерясь и волоча за собой «ценный» груз, Маруся рванула по оранжевой пыли в сторону больничного крыльца.

Глава 3. Дизреспект и зубодробительный этикет[править]

Каменный голем в оранжевом секторе Блик

Подлечившись в клинике за пару мешков прессованных бублей и выкашляв из лёгких оранжевую копоть, Маруся решила сменить обстановку. Оранжевые земли с их гипервнимательным хромархом РО ей уже поднадоели, поэтому она перешагнула хаотично нарисованную на пыли границу и очутилась на территории Бельк — белом секторе Абсероида.

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

​На возвышении, восседая на кубе из прессованного гипса, восседал хромарх АБ. Выглядел он точь-в-точь как оранжевый РО, только балдахин на нём был ослепительно белым, а выражение лица — беспросветно душным. Рядом с ним стоял баллон с надписью «Чистый кислород. Импорт с Марса» и заманчиво шипел. ​Маруся, не привыкшая спрашивать разрешения, подошла к баллону, сдвинула правителя плечом и припала ртом к шлангу, с жадностью делая глубокие затяжки.

​— Эй, сударыня, — ледяным тоном произнёс хромарх АБ, поморщившись. — Вы нарушаете регламент. Где ваше почтение к Белому сектору? Где ритуальный поклон и запрос на дыхание в трёх экземплярах? Вы вообще имеете представление о местном этикете?

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

​— Слышь, белила, ты мне тут мозги не канифоль, — рявкнула она, сплёвывая под ноги. — Я сюда воздухом подышать пришла, а не твои реверансы крутить. Ещё слово — и я тебе этот баллон в твою душную жницу по самые вентили запихаю.

​Над белой территорией повисла звенящая тишина. Хромарх АБ медленно поднялся с гипсового куба. Его глаза налились праведным гневом.

​— Дизреспект! — торжественно и пронзительно проорал он на весь сектор. — Максимальный дизреспект нравам!

​В ту же секунду три ближайших валуна, казавшиеся обычными булыжниками, громко хрустнули, отрастили тяжелые каменные руки и поднялись на дыбы. Это была полиция нравов Абсероида — каменные големы, заточенные на оперативное вбивание уважения в приезжих.

​Не успела Маруся даже замахнуться, как первый голем отвесил ей гранитную пощёчину. Второй аккуратно зафиксировал её за плечи, а третий, с грацией взбесившегося отбойного молотка, провёл воспитательную беседу методом прямого попадания кулаком в челюсть.

​Через пять минут процесс перевоспитания завершился. Маруся лежала на белой пыли, глотая кровавые сопли и грязно матерясь, а големы молча ускакали обратно на свои позиции, снова притворившись валунами.

​— Вот теперь этикет соблюдён, — удовлетворённо кивнул АБ и уплыл вглубь сектора. ​Маруся поднялась, сплюнула кровь и нащупала языком внушительную брешь во рту. На земле перед ней красиво поблёскивали шесть выбитых зубов с целой земной эмалью.

​Кряхтя от боли, она собрала зубы и побрела к местному торговому ряду, где расположились ровно 32 лавки. Вытащив из закромов оставшиеся мешки с бублями, Маруся попыталась купить у первого попавшегося косоглазого торговца хоть какой-то инструмент.

​Торговец посмотрел на мешки с говняной валютой, затем на Марусю, и зашёлся в истерическом хохоте.

​— Ты с этим дерьмом куда лезешь, дикая? — ржал инопланетянин. — У нас инфляция триста процентов в час! На твои мешки сейчас даже запах от бублика не купишь! Говно больше не в тренде!

​— А это в тренде? — угрюмо спросила Маруся, высыпая ему на ладонь шесть своих окровавленных резцов.

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

​— О-о-о… Настоящая плотная органика! Чистый кальций! Нездешняя эмаль! — запричитал он, захлёбываясь от восторга. — Это же целое состояние! Бери всё, что хочешь!

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

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

Глава 4. Ночь в борделе хромарха СЕ[править]

​С вантузом на плече, топором за поясом и киркой наперевес Маруся перевалила через очередную баррикаду из подсохших экскрементов и ступила на территорию Блек — чёрный сектор Абсероида. Земля здесь напоминала обугленный корж, а в воздухе висел тяжёлый густой запах дешёвого парфюма и пригоревшего масла.

​Здесь правительствовал хромарх СЕ. Внешне он ничем не отличался от своих братьев РО и АБ, за исключением чёрного, как смоль, балахона и усыпанных фальшивыми бриллиантами пальцев. СЕ был убеждённым гедонистом, апробировавшим на своём секторе все виды порока, доступные в радиусе пяти световых лет.

​— О-о-о, сильная и независимая женщина с хозяйственным инвентарём! — пропел СЕ, вальяжно раскинувшись на диване из искусственной кожи прямо посреди улицы. — Пришла потратить денежки?

​— Торговцы на белом секторе сказали, что моим мешкам с бублями цена — тьфу, — буркнула Маруся, сгружая на землю пузатые холщовые баулы, забранные у оранжевого РО. — И вообще, у вас тут дефицит и разруха.

​Хромарх СЕ пренебрежительно фыркнул и покровительственно всплеснул руками:

​— Торговцы — убогие мещане! В лавках жратвы нет, это правда. Но мы в чёрном секторе торгуем не хлебом, мы торгуем наслаждением! В моих борделях твоё говно — это валюта высшей категории! Один мешок бублей — один час с премиальной био-копией самца Homo Sapiens. Полноразмерный клонированный мужик, собранный по лучшим чертежам Земли! Без вредных привычек, без претензий и с бесконечным запасом энергии!

​Маруся замерла. Десятичасовой космический перелёт в обнимку с пультом управления, фингал под глазом, выбитые зубы и душевное истощение требовали радикальной терапии.

​— Загоняй всех, на сколько мешков хватит, — сурово чеканя слова, произнесла Маруся.

​Её завели в лучший номер заведения, где стены были обиты бордовым плюшем, а в воздухе играла задорная инопланетная музыка. СЕ не обманул: био-клоны Homo Sapiens представляли собой двухметровых накачанных красавцев с голливудскими улыбками и полным отсутствием собственного мнения.

​Следующие десять часов на чёрных землях Абсероида не утихал дикий, беспощадный марафон. Маруся подходила к восстановлению организма с той же профессиональной жестокостью, с которой выбивала челюсти у гастронома в Стихоквадратске. Клоны сменяли друг друга один за другим, ломаясь, теряя сознание и вырабатывая свой ресурс до полного истощения.

​Сработала древняя механика космических борделей: с каждым новым часом плотских утех жизненные силы Маруси неумолимо ползли вверх. Синяки спадали, ссадины затягивались, а уровень здоровья и боевого духа давно перевалил за сто процентов.

​За стенами борделя в это время царила глубокая астероидная ночь. Местные абсеранцы, лишенные электричества и элементарных развлечений, молчаливыми группами сидели на корточках вокруг ровно 64 огромных куч фекалий. Они не мигая смотрели на подсыхающие верхушки пирамид и философски созерцали вечность под тихие стоны и грохот ломающейся мебели, доносившиеся из заведения хромарха СЕ.

​К утру из дверей борделя вышла обновлённая, пышущая здоровьем и абсолютно довольная жизнью Маруся. На порог выпозз заплаканный хромарх СЕ, судорожно подсчитывающий убытки: из строя были выведены все био-копии, а мешки с бублями заполнили весь его склад.

​— Заходи ещё… — прохрипел СЕ, крестясь двумя руками. ​— Обязательно, — оскалилась Маруся, закинув кирку на плечо, и бравой походкой направилась в сторону синих земель.

Глава 5. Разговор по душам в озере Бульк[править]

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

​На берегу на складном табурете восседал хромарх ИД — последний из четырёх правителей астероида. Выглядел он точной копией трёх предыдущих, но был наряжен в синий балдахин и увлечённо насаждал на крючок кусок сушёного бублика.

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

​Маруся перехватила кирку поудобнее.

— Если опять платить говном — сразу иди в жопу, — отредактировала условия договора она.

​— Нет-нет! — замахал руками ИД. — По делу! В озере Бульк поселилась древняя тварь — гигантский реликтовый прионозух. Он мешает мне удить рыбу и производит лишние волны. Убей его! Наградой тебе будет уважение синего сектора и ценная наводка!

​Маруся, рассудив, что силовой разгон местной фауны — это её конёк, направилась к квадратной воде. Поправив за поясом топор, она поддала вантузом по прибрежной грязи и громко рявкнула:

— Эй, чешуйчатый! Вылезай, мразь! Будем делать из тебя сапоги!

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

​Маруся занесла кирку для смертельного удара, но ящер неожиданно брезгливо поморщился, отплыл на два метра назад и прикрыл лапой нос.

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

​— Чего?! — обомлела Маруся. — Ты ещё и разговариваешь?

​— Я — древнее реликтовое существо космического происхождения, а не кусок мяса! — оскорбился ящер. — И жрать я тебя категорически отказываюсь! По Абсероиду уже слухи ходят о твоих марафонах в борделе у СЕ! На тебе же живого места нет от патогенов! Я древний, у меня иммунитет слабый! Я подхвачу от тебя инопланетный триппер или ещё какую космическую хламидию, и всё — прощай, реликт мезозойской эры!

​Маруся на секунду замялась, слегка смущённая такой оценкой своей утренней свежести.

​— Я вообще-то за твоей головой пришла, — проворчала она, опустив кирку. — Синий хромарх заказал.

​Прионозух устало оперся подбородком о квадратный берег и выплюнул дохлого моллюска.

— Синий? А с чего ты взяла, что именно синий? Ты вообще осознаёшь, что эти четыре идиота — РО, АБ, СЕ и ИД — абсолютно одинаковы? Они сами путают, кто из них в каком балдахине сегодня проснулся! Они меня сжить со света хотят уже третий век, но я даже не знаю, кто из них конкретно подписывает приказы!

​— И чё делать? — спросила Маруся, усаживаясь на прибрежный валун.

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

​Маруся сплюнула в квадратное озеро, поднялась и вытерла испачканную в бублях кирку. — По рукам, крокодил. Пойду поищу эти ваши астрономические будки. Но если обманешь — сдеру с тебя шкуру на пуфик!

Глава 6. Двойной дизреспект и подвиг Егора[править]

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

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

​Маруся подошла вплотную, бесцеремонно схватила его за отворот тряпки и встряхнула.

​— Слышь, физиономия! — рявкнула она. — Отвечай быстро и без виляний: ты кто из четырёх? АБ, СЕ, РО или ИД? И кто из вас, мразей, заказал прионозуха?

​Гуманоид замер. Его глаза вылезли из орбит, а лицо приняло оттенок скисшего кефира. В культуре абсеранцев не было греха страшнее, чем намекнуть хромарху на то, что он неотличим от остальных трёх бездельников. Это затрагивало самые глубокие, болезненные струны их инопланетного эго.

​— Ты… ты спросила, кто я?! — заикаясь от ужаса и гнева, просипел правитель. — Ты усомнилась в моей индивидуальности?! Это… это не просто нарушение этикета! Это ДВОЙНОЙ ДИЗРЕСПЕКТ!

​Он набрал в грудь побольше ядовитого смога и проорал на всю планету так, что с ближайших борделей слетели вывески:

— ДВОЙНОЙ ДИЗРЕСПЕКТ! ВСЕМ ПОДНЯТЬСЯ! СМЕРТЬ ЧУЖЕСТРАНКЕ!

​Земля под ногами заходила ходуном. Если в прошлый раз на Бельке подтянулось всего три скромных валуна, то сейчас ожил весь каменный ландшафт Абсероида. Из скал, из фундамента больниц, из декоративных булыжников и даже из засохших минерализованных куч начали проклёвываться гранитные головы и тяжелые каменные кулаки.

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

​— Мать твою за ногу… — опомнилась Маруся.

​Она развернулась и дала дёру, отмахиваясь на бегу вантузом. Но каменные истуканы сжимали кольцо. Один из них настиг её возле упавшей ракеты, заехав гранитным локтём по почкам. Маруся покатилась по пыли, выронив топор. Големы обступили её плотной стеной, поднимая тяжелые каменные сапоги для окончательного перевоспитания.

​И тут обшивка ракеты затрещала.

​Из аварийного люка приборного отсека с оглушительным треском вывалилась фигура. Это был главный механик Егор Туалетов. В условиях отсутствия штатных скафандров Егор проявил пролетарскую смекалку: он сообразил себе герметичный костюм из двух комплектов сантехнических ОЗК, залил все швы силиконовым герметиком, а на голову водрузил стеклянный бак от кулера, зафиксированный на шее синей изолентой. ​В руках Егор сжимал тяжеленную проходческую кирку, выбитую Марусей у торговца.

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

​Егор занёс кирку и с размаху обрушил её на череп ближайшего голема. Каменный истукан разлетелся на щебень и полезную гравийную крошку.

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

​Егор работал киркой как отбойный молоток на стахановской вахте. Гранитные щепки летели во все стороны, големы валились с раздробленными коленями и расколотыми бюстами. Прорубив в толпе каменных полицейских кроваво-песчаный проход, Егор подскочил к лежащей Марусе, цапнул её за шкирку своей прорезиненной перчаткой и волоком вытащил из эпицентра замеса.

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

​Маруся, подхватив утерянный топор, впервые в жизни одарила механика взглядом, в котором сквозило что-то похожее на уважение, и прибавила ходу вслед за скафандром из ОЗК.

Глава 7. Мозги от Гудвина и великая подстава[править]

Оторвавшись от разъярённой орды каменных истуканов, Маруся и Егор Туалетов забаррикадировались в одном из двух местных планетариев-обсерваторий. Помещение напоминало скрещение свалки металлолома с деревенским сараем: из дырявой крыши торчал засиженный космическими мухами телескоп, а в центре крутилась огромная механическая модель Солнечной системы, собранная из медной проволоки, ржавых шестерёнок и подсохших бублей.

​Егор тяжело дышал через свой стеклянный бак от кулера. Запотевшее стекло покрылось капельками конденсата, за которыми с трудом угадывалось его свирепое лицо.

​— Ну всё, рыжая, — прохрипел механик, поправляя на шее синюю изоленту и потирая сбитые об големов кулаки. — Големов больше нет. Теперь я занят твоей тушкой.

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

​На центральной панели обсерватории, пылая неоновой синевой, лежала странная штуковина. Она вывалилась из приёмного лотка астрономического вычислителя и выглядела как высохший, слегка обугленный человеческий мозг, привязанный к батарейке «Крона». В интерфейсе системы объект помечался лаконично: «Предмет: Мозги (Гудвин-style). Использовать при полном отсутствии собственного интеллекта».

​Маруся без задней мысли сгребла «Мозги» в охапку. В ту же секунду в её рыжей голове что-то щёлкнуло, извилины завязались в тугой узел, и перед глазами вспыхнула чёткая, идеально выверенная трёхмерная карта Галактики. Вектор на Землю пролёг через пространство с аптекарской точностью. Теперь она точно знала, куда рулить.

​Цель путешествия была достигнута, а координаты находились в её распоряжении. Однако исполнять обещание, данное древнему прионозуху — выяснять, кто из четырёх абсолютно одинаковых хромархов желает ему смерти — Марусе было искренне лень. К тому же для старта ракеты требовался пусковой ключ, а на озере Бульк как раз намечалось масс-медиа событие, под шумок которого этот ключ можно было легко умыкнуть.

​Маруся повернулась к Егору, всё ещё хмуро созерцавшему её с киркой в руках, и скорчила максимально сочувствующую, драматическую физиономию.

​— Егор… — тихо, с придыханием произнесла она, положив руку ему на прорезиненное плечо ОЗК. — Я долго молчала. Но ты должен знать правду.

​— Какую ещё правду? — подозрительно буркнул механик из-под кулера.

​— Я сейчас с озера. Разговаривала с тем гигантским ящером, прионозухом. Местная реликтовая тварь, девять метров чешуи и понтов. Так вот… я ему про тебя рассказала. Говорю, мол, со мной механик Егор, парень суровый, с киркой, из самого Стихоквадратска…

​Маруся сделала паузу и тяжело вздохнула.

​— И чё он? — насупился Егор.

​— Да засмеялся он, Егорушка. На всё озеро заржал! Сказал, что ты никакой не механик, а слесарь третьей категории, а скафандр твой из бака — это бак для помоев. И вообще, просил передать, что ты — лох. Лох Лохнесский. И что если ты к его озеру хоть на метр подойдёшь, он тебя вместе с твоей киркой сожрёт и даже не покрякает.

​В обсерватории повисла мёртвая тишина. Единственным звуком стало бешеное, прерывистое сопение, от которого бак от кулера на голове Егора мгновенно запотел до полной невидимости.

​— Что-о-о?! — сипло выдохнул механик. Вены у него на шее вздулись толщиной с палец. — Лох?! Лох Лохнесский?! Слесарь третьей категории?! Да у меня пятый разряд и грамота от ЖЭКа за восемьдесят седьмой год!

​Егор с такой силой сжал рукоять проходческой кирки, что сухое дерево жалобно затрещало.

​— Где эта чешуйчатая гнида?! — взревел он, выбивая ногой дубовую дверь обсерватории. — Я из этого реликтового чучела сейчас диван сделаю! Я ему все его девять метров на кирку намотаю!

​Егор Туалетов тяжелым, чеканящим шагом заведённого танка рванул в сторону Квадратного озера Бульк, поднимая за собой облака оранжевой пыли.

​Маруся хмыкнула, поправила на груди добытые «Мозги», вытащила из кармана вантуз и вальяжно поплелась следом. Великая подстава сработала как часы.

Глава 8. Битва Титанов и побег[править]

Квадратное озеро Бульк ещё никогда не видело подобного дерзкого святотатства. На берег, сотрясая окрестности выкриками из технического лексикона, вылетел Егор Туалетов. Бак от кулера на его голове запотел настолько, что механик нёсся практически вслепую, ориентуясь исключительно на запах озёрной тины и звук булькающих пузырей.

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

​Прионозух, только-только уложивший чешую после разговора с Марусей, недовольно вынырнул из синей пучины.

— Сумасшедший гуманоид в бутылке, ты чего орёшь? Я древнее достояние этого астероида…

​Он не успел закончить. Егор, взревев как раненый мамонт, совершил грациозный прыжок с берега и с наскока обрушился киркой прямо на плоскую голову древнего ящера.

​Закипела эпохальная Битва Титанов. Поняв, что договориться с безумцем в прорезиненном ОЗК не удастся, прионозух взвизгнул от неожиданности и включил на полную мощность древние инстинкты выживания. Озеро Бульк буквально встало на дыбы. Вода, грязь, обломки сушёных бублей и брызги ядовитой слизи взлетели в воздух, закручиваясь в настоящее гидродинамическое торнадо. Егор, орудуя киркой с грацией отбойного молотка, пытался провести ящеру болевой приём на хвост, а прионозух отчаянно вращал глазами, пытаясь сдёрнуть с головы агрессора стеклянный бак.

​Четверо хромархов — РО, АБ, СЕ и ИД — среагировали на шоу с истинно правительственным размахом. Забыв про смертельные обиды и «двойной дизреспект», они выставили на безопасном расстоянии раскладные стулья, насыпали в кулёчки мелких высушенных бублей вместо попкорна и с искренним азартом стали делать ставки на исходы отдельных раундов.

​— Пятьдесят мешков говна на парня в бутылке! — азартно орал оранжевый РО. — Смотри, как он его киркой по чешуе оглаживает!

​— Прионозух его сейчас утопит, это же гидродинамика! — возражал синий ИД, дожёвывая бублик.

​Пока на озере бушевал водоворот из грязи, бранных слов Егора и воплей ящера, к зрительским рядам на цыпочках подобралась Маруся. Пользуясь тем, что всё внимание астероида приковано к эпичному махачу, она опустилась на корточки и принялась обшаривать пространство под стульями правителей.

​И удача улыбнулась ей: под складным табуретом синего ИД, прямо возле забытой бутылки с кислородом, поблёскивал увесистый, покрытый замысловатыми рунами пусковой ключ от ракеты.

​Маруся ловко сгребла железку, спрятала её за пазуху рядом с «Мозгами Гудвина» и напоследок окинула взглядом арену боевых действий. В центре Квадратного озера Егор Туалетов как раз завязал хвост прионозуха морским узлом и пытался забить ему в ноздрю вантуз.

​— Держись там, Егорушка! Ты настоящий герой! — прошептала Маруся с искренней, глубокой фальшью в голосе.

​Не теряя больше ни секунды, она рванула в сторону стартовой площадки.

​Запрыгнув в кабину застрявшей под углом ракеты, Маруся вставила пусковой ключ в замок зажигания, повернула его до упора и вдавила уже знакомую красную кнопку. Двигатели взревели, выплёвывая столб раскалённого пламени, который мгновенно испепелил три ближайших борделя и накрыл волной горячего воздуха озеро Бульк.

​Ракета вырвалась из рыхлой породы Абсероида и, оставляя за собой инверсионный след из сгоревшего смога, устремилась в звёздную высь. А далеко внизу, сквозь гул реактивных струй, догорающий астероид огласился финальным, истошным воплем Егора Туалетова, который только сейчас понял, что его снова кинули.

Глава 9. Возвращение в родные пенаты[править]

Улетела.jpg

​Сверяясь с «Мозгами Гудвина», которые приятно пощёлкивали на панели приборов и то и дело давали ценные указания вроде «для поворота направо перелезь на левое сиденье», Маруся держала уверенный курс «на юг за Сникерсом». Космическая бездна оставалась позади, а вместе с ней — ядовитый оранжевый смог, говняные валютные операции и Квадратное озеро Бульк, где механик Егор Туалетов в скафандре из кулера продолжал творить историю местного фольклора.

​Земля приближалась с огромной скоростью. Направив космический корабль по траектории падающего топора, Маруся на полной скорости зашла на посадку в район родного Стихоквадратска. Ракета с оглушительным треском снесла пару телеграфных столбов, вспахала забор местного гастронома и замерла ровно на том же месте, откуда стартовала.

​Выбравшись из дымящейся кабины, Маруся огляделась. Всё казалось знакомым, но каким-то странно обветшавшим. Памятник гранёному стакану возле пивнушки зарос мхом, а знакомые ментовские уазики покрылись толстым слоем ржавчины.

​С трудом переставляя затёкшие ноги, Маруся под нянчащим взглядом местных алкашей доковыляла до родного подъезда и поднялась на свой этаж. Дверь в квартиру была приоткрыта.

​На кухне за тем же столом с проеденной клеёнкой сидел её батя. За прошедшие годы он высох ещё сильнее и стал похож на древнюю египетскую мумию, обёрнутую в засаленный халат. В руках он всё так же сжимал заскорузлый бородинский хлеб, который за прошедшее время окончательно обрёл прочность алмаза. Рядом скрипела престарелая мать, помешивая суп из одних только лавровых листьев.

​Батя медленно поднял на дочь слезящиеся глаза, в которых вспыхнул праведный родительский гнев.

​— Ну и где тебя, заразу рыжую, шестнадцать лет носило?! — заскрипел дед, стукнув каменным батоном по столу так, что тот раскололся пополам. — Мы тут с матерью все глаза проглядели! Ушла за сигаретами на пять минут, а вернулась — нам уже под восемьдесят! Это что за загул такой?!

​Маруся замерла на пороге, почесав затылок. Физика эйнштейновских скоростей сыграла с ней привычную для Абсурдоляндии шутку: пока она на Абсероиде за сутки успела выбить зубы, сходить в бордель и натравить механика на крокодила, на Земле из-за релятивистского замедления времени ухнуло полные шестнадцать лет.

​— Да ладно тебе, батя, чья б корова мычала, — спокойно ответила Маруся, усаживаясь на табуретку и сплёвывая на пол через дырку в челюсти. — Ты сам за хлебом на шестнадцать лет уходил. Так что у нас в семье это, считай, наследственная традиция.

​Дед заново переварил аргумент, подумал пару минут, после чего тяжело вздохнул и подвинул к дочери тарелку с лавровым супом.

— Логично. Жрать будешь?

​— Буду, — кивнула Маруся. — Только развари помягче.

​Она с чувством выполненного долга откинулась на спинку стула. Приключение завершилось абсолютной триумфальной победой: она выжила в космосе, кинула инопланетных правителей, избавилась от Егора, нажила бесценный опыт и вернулась в родное гнездо.

​Оставалась лишь одна небольшая бытовая формальность. Маруся запустила руку в карман, пересчитала оставшуюся от инопланетного разбоя заначку и философски уставилась в потолок. Теперь дело было за малым — найти в Стихоквадратске хорошего, непьющего стоматолога, чтобы вставить обратно выбитые каменными големами зубы.

Игра по мотивам произведения[править]

# -*- coding: utf-8 -*-
import os, sys, shutil, random, math, time
from collections import deque

SIZE, VIEW, CELL_W = 49, 15, 2
HALF, VIEW_HALF = SIZE // 2, VIEW // 2
HERO = '👩‍🦰'
ASTRONAUT = '👩‍🚀'
MECHANIC = '🧑‍🔧'
MECHANIC_ASTRONAUT = '👨‍🚀'
ROCKET, HOSPITAL, ALIEN, TEMPLE, HOTEL, STORE, POOP, TREE, ROCK = '🚀', '🏥', '👽', '🕌', '🏩', '🏪', '💩', '🌳', '🪨'

def wrap(v):
    return (v + HALF) % SIZE - HALF

def toroidal_dist(p1, p2):
    dx = abs(p1[0] - p2[0])
    dx = min(dx, SIZE - dx)
    dy = abs(p1[1] - p2[1])
    dy = min(dy, SIZE - dy)
    return math.sqrt(dx * dx + dy * dy)

def is_adjacent(p1, p2):
    dx = abs(p1[0] - p2[0])
    dx = min(dx, SIZE - dx)
    dy = abs(p1[1] - p2[1])
    dy = min(dy, SIZE - dy)
    return (dx == 1 and dy == 0) or (dx == 0 and dy == 1)

def dw(s):
    w = 0
    i = 0
    n = len(s)
    while i < n:
        if i + 1 < n and s[i + 1] == '\ufe0f':
            i += 2
            w += 2
        elif i + 1 < n and s[i + 1] == '\u200d':
            i += 1
            while i < n and s[i] == '\u200d':
                i += 2
            w += 2
        else:
            cp = ord(s[i])
            w += 2 if cp >= 0x2600 else 1
            i += 1
    return w

def format_inventory(inv):
    order = ['🔑', '🦷', '🧠', '🪠', '🪓', '⛏️']
    parts = []
    for item in order:
        c = inv.count(item)
        if c > 0:
            parts.append(f'{c}{item}')
    if parts:
        return '🧺 Инвентарь: ' + ' '.join(parts)
    return '🧺 Инвентарь:'

if os.name == 'nt':
    import msvcrt
    def get_key():
        ch = msvcrt.getwch()
        if ch in ('\x00', '\xe0'):
            msvcrt.getwch()
            return ''
        return ch
else:
    import termios, tty, select
    def get_key():
        fd = sys.stdin.fileno()
        old = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            data = os.read(fd, 1)
            while True:
                try:
                    ch = data.decode('utf-8'); break
                except UnicodeDecodeError:
                    data += os.read(fd, 1)
            if ch == '\x1b':
                while select.select([fd], [], [], 0)[0]:
                    os.read(fd, 1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old)
        return ch

def render_centered(scene, offset=2):
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    sys.stdout.write('\x1b[2J\x1b[H')
    start_row = max(1, rows // 2 - len(scene) // 2 - offset)
    ref_width = max(dw(l) for l in scene)
    col = max(1, (cols - ref_width) // 2 + 1)
    for i, line in enumerate(scene):
        sys.stdout.write(f'\x1b[{start_row + i};{col}H{line}')
    return start_row

def show_temple_scene(hero=HERO, has_brain=False):
    scene = [
        '🌞◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️',
        '◼️◼️🔘◼️◼️◼️◼️◼️◼️◼️◼️',
        '◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️',
        '◼️◼️◼️🟠◼️◼️◼️◼️◼️◼️◼️',
        '◼️◼️◼️◼️◼️🌍◼️◼️◼️◼️◼️',
        '◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️',
        '◼️◼️◼️◼️◼️◼️🔴◼️◼️◼️◼️',
        '◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️',
        f'◼️◼️◼️◼️◼️◼️◼️◼️◼️🔭{hero}',
    ]
    if has_brain:
        msg = f'{hero}: Это же Солнце, Меркурий, Венера, Земля и Марс!'
    else:
        msg = f'{hero}: Что это за разноцветные кружочки? 🤔'
    start_row = render_centered(scene, 2)
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    msg_row = start_row + len(scene) + 1
    msg_col = max(1, (cols - dw(msg)) // 2 + 1)
    sys.stdout.write(f'\x1b[{msg_row};{msg_col}H{msg}')
    sys.stdout.flush()
    time.sleep(3)

def show_hotel_scene(hero=HERO):
    scene = [
        '♥️♥️♥️♥️♥️♥️♥️♥️♥️♥️♥️',
        '♥️◼️◼️◼️◼️◼️◼️◼️◼️◼️♥️',
        '♥️◼️🔒🔒🔒🔒🔒🔒🔒◼️♥️',
        '♥️◼️🔒🧔🧔🧔🧔🧔🔒◼️♥️',
        '♥️◼️🔒🧔🧔🧔🧔🧔🔒◼️♥️',
        '♥️◼️🔒🔒🔒🔒🔒🔒🔒◼️♥️',
        '♥️◼️◼️◼️◼️👽◼️◼️◼️◼️♥️',
        '♥️◼️◼️◼️◼️◼️◼️◼️◼️◼️♥️',
        f'♥️◼️◼️◼️◼️{hero}◼️◼️◼️◼️♥️',
        '♥️♥️♥️♥️♥️🚪♥️♥️♥️♥️♥️',
    ]
    msg1 = f'{hero}: Я хочу секса.'
    msg2 = '👽: 1 🧔 = 1 💰.'
    start_row = render_centered(scene, 2)
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    msg_row = start_row + len(scene) + 1
    col1 = max(1, (cols - dw(msg1)) // 2 + 1)
    col2 = max(1, (cols - dw(msg2)) // 2 + 1)
    sys.stdout.write(f'\x1b[{msg_row};{col1}H{msg1}')
    sys.stdout.write(f'\x1b[{msg_row+1};{col2}H{msg2}')
    sys.stdout.flush()
    time.sleep(3)

def show_store_scene(hero=HERO):
    scene = [
        '🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱',
        '🧱◼️◼️◼️◼️◼️◼️◼️◼️◼️🧱',
        '🧱◼️◼️◼️◼️🪠◼️◼️◼️◼️🧱',
        '🧱◼️⛏️◼️◼️◼️◼️◼️🪓◼️🧱',
        '🧱◼️◼️◼️◼️👽◼️◼️◼️◼️🧱',
        '🧱🪵🪵🪵🪵🪵🪵🪵🪵🪵🧱',
        f'🧱◼️◼️◼️◼️{hero}◼️◼️◼️◼️🧱',
        '🧱🧱🪟🧱🧱🚪🧱🧱🪟🧱🧱',
    ]
    msg = '👽: Пошла вон, нищебродка!'
    start_row = render_centered(scene, 2)
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    msg_row = start_row + len(scene) + 1
    msg_col = max(1, (cols - dw(msg)) // 2 + 1)
    sys.stdout.write(f'\x1b[{msg_row};{msg_col}H{msg}')
    sys.stdout.flush()
    time.sleep(3)

def show_shop_scene(hero, teeth):
    scene = [
        '🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱',
        '🧱◼️◼️◼️◼️◼️◼️◼️◼️◼️🧱',
        '🧱◼️◼️◼️◼️🪠◼️◼️◼️◼️🧱',
        '🧱◼️⛏️◼️◼️◼️◼️◼️🪓◼️🧱',
        '🧱◼️◼️◼️◼️👽◼️◼️◼️◼️🧱',
        '🧱🪵🪵🪵🪵🪵🪵🪵🪵🪵🧱',
        f'🧱◼️◼️◼️◼️{hero}◼️◼️◼️◼️🧱',
        '🧱🧱🪟🧱🧱🚪🧱🧱🪟🧱🧱',
    ]
    start_row = render_centered(scene, 2)
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    msg_row = start_row + len(scene) + 1
    msg1 = '👽: Добро пожаловать!'
    msg2 = '🪠=1🦷 🪓=2🦷 ⛏️=3🦷'
    col1 = max(1, (cols - dw(msg1)) // 2 + 1)
    col2 = max(1, (cols - dw(msg2)) // 2 + 1)
    sys.stdout.write(f'\x1b[{msg_row};{col1}H{msg1}')
    sys.stdout.write(f'\x1b[{msg_row+1};{col2}H{msg2}')
    opt_row = msg_row + 3
    options = []
    if teeth >= 1:
        options.append('В — КУПИТЬ ВАНТУЗ')
    if teeth >= 2:
        options.append('Т — КУПИТЬ ТОПОР')
    if teeth >= 3:
        options.append('К — КУПИТЬ КИРКУ')
    for i, opt in enumerate(options):
        col_o = max(1, (cols - dw(opt)) // 2 + 1)
        sys.stdout.write(f'\x1b[{opt_row + i};{col_o}H{opt}')
    sys.stdout.flush()

def show_shop_thanks(hero):
    scene = [
        '🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱',
        '🧱◼️◼️◼️◼️◼️◼️◼️◼️◼️🧱',
        '🧱◼️◼️◼️◼️🪠◼️◼️◼️◼️🧱',
        '🧱◼️⛏️◼️◼️◼️◼️◼️🪓◼️🧱',
        '🧱◼️◼️◼️◼️👽◼️◼️◼️◼️🧱',
        '🧱🪵🪵🪵🪵🪵🪵🪵🪵🪵🧱',
        f'🧱◼️◼️◼️◼️{hero}◼️◼️◼️◼️🧱',
        '🧱🧱🪟🧱🧱🚪🧱🧱🪟🧱🧱',
    ]
    start_row = render_centered(scene, 2)
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    msg_row = start_row + len(scene) + 1
    msg = '👽: Спасибо за покупку!'
    col_m = max(1, (cols - dw(msg)) // 2 + 1)
    sys.stdout.write(f'\x1b[{msg_row};{col_m}H{msg}')
    sys.stdout.flush()
    time.sleep(1)

def show_bed_scenes(hero, n):
    if n == 1:
        sceneA = [
            '♥️♥️♥️♥️♥️♥️♥️♥️',
            '♥️◼️◼️◼️◼️◼️◼️♥️',
            f'♥️◼️◼️🧔{hero}◼️◼️♥️',
            '♥️◼️🪵🪵🪵🪵◼️♥️',
            '♥️♥️♥️♥️♥️♥️♥️♥️',
        ]
        sceneB = [
            '♥️♥️♥️♥️♥️♥️♥️♥️',
            '♥️◼️◼️◼️◼️◼️◼️♥️',
            f'♥️◼️🧔{hero}◼️◼️◼️♥️',
            '♥️◼️🪵🪵🪵🪵◼️♥️',
            '♥️♥️♥️♥️♥️♥️♥️♥️',
        ]
    elif n == 2:
        sceneA = [
            '♥️♥️♥️♥️♥️♥️♥️♥️',
            '♥️◼️◼️◼️◼️◼️◼️♥️',
            f'♥️◼️◼️🧔{hero}🧔◼️♥️',
            '♥️◼️🪵🪵🪵🪵◼️♥️',
            '♥️♥️♥️♥️♥️♥️♥️♥️',
        ]
        sceneB = [
            '♥️♥️♥️♥️♥️♥️♥️♥️',
            '♥️◼️◼️◼️◼️◼️◼️♥️',
            f'♥️◼️🧔{hero}🧔◼️◼️♥️',
            '♥️◼️🪵🪵🪵🪵◼️♥️',
            '♥️♥️♥️♥️♥️♥️♥️♥️',
        ]
    else:
        sceneA = [
            '♥️♥️♥️♥️♥️♥️♥️♥️',
            '♥️◼️◼️◼️🧔◼️◼️♥️',
            f'♥️◼️◼️🧔{hero}🧔◼️♥️',
            '♥️◼️🪵🪵🪵🪵◼️♥️',
            '♥️♥️♥️♥️♥️♥️♥️♥️',
        ]
        sceneB = [
            '♥️♥️♥️♥️♥️♥️♥️♥️',
            '♥️◼️◼️🧔◼️◼️◼️♥️',
            f'♥️◼️🧔{hero}🧔◼️◼️♥️',
            '♥️◼️🪵🪵🪵🪵◼️♥️',
            '♥️♥️♥️♥️♥️♥️♥️♥️',
        ]
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    end_time = time.time() + 10
    toggle = True
    while time.time() < end_time:
        scene = sceneA if toggle else sceneB
        toggle = not toggle
        sys.stdout.write('\x1b[2J\x1b[H')
        start_row = max(1, rows // 2 - len(scene) // 2)
        ref_width = max(dw(l) for l in scene)
        col = max(1, (cols - ref_width) // 2 + 1)
        for i, line in enumerate(scene):
            sys.stdout.write(f'\x1b[{start_row + i};{col}H{line}')
        sys.stdout.flush()
        time.sleep(0.2)

def show_rocket_ending(knows_planets):
    rocket_scene = [
        '▪️▪️▪️▪️▪️🚨▪️▪️▪️▪️▪️',
        '▪️▪️▪️▪️🛑🛑🛑▪️▪️🔹▪️',
        '🔹▪️▪️🛑▫️👩‍🚀▫️🛑▪️▪️▪️',
        '▪️▪️🔘🔘🔘🔘🔘🔘🔘▪️🔹',
        '▪️▪️🔘🔸🔸🔸🔸🔸🔘▪️▪️',
        '▪️▪️🔘🔸🔸🔸🔸🔸🔘▪️🔹',
        '🔹▪️🔘🔸🔸🔸🔸🔸🔘▪️▪️',
        '▪️▪️🔘🔸🔸🔸🔸🔸🔘▪️▪️',
        '🔹▪️🔘🔸🔸🔸🔸🔸🔘▪️🔹',
        '▪️▪️🔘🔸🔸🔸🔸🔸🔘▪️▪️',
        '▪️▪️🔘🔸🔸🔸🔸🔸🔘▪️🔹',
        '🔹▪️💥💥💥💥💥💥💥▪️▪️',
        '▪️▪️🔥🔥🔥🔥🔥🔥🔥▪️▪️',
        '▪️💢💢💢💢💢💢💢💢💢▪️',
        '▪️☁️☁️☁️☁️☁️☁️☁️☁️☁️▪️',
        '▪️☁️☁️☁️☁️☁️☁️☁️☁️☁️▪️',
    ]
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns

    start_row = render_centered(rocket_scene, 2)
    msg_row = start_row + len(rocket_scene) + 1

    if not knows_planets:
        msg = '👩‍🚀: БЛЯ, Я НЕ ЗНАЮ, КУДА ЛЕТЕТЬ! 😢'
        msg_col = max(1, (cols - dw(msg)) // 2 + 1)
        sys.stdout.write(f'\x1b[{msg_row};{msg_col}H{msg}')
        sys.stdout.flush()
        time.sleep(5)

        sys.stdout.write('\x1b[2J\x1b[H')
        loss_msg = 'Вы заблудились в космосе и погибли.'
        l_col = max(1, (cols - dw(loss_msg)) // 2 + 1)
        l_row = max(1, rows // 2)
        sys.stdout.write(f'\x1b[{l_row};{l_col}H{loss_msg}')
        sys.stdout.flush()
        get_key()
        return
    else:
        msg_lines = [
            '👩‍🚀: Если верить телескопу, Земля',
            ' находится где-то на юге за Сникерсом...'
        ]
        for i, ml in enumerate(msg_lines):
            m_col = max(1, (cols - dw(ml)) // 2 + 1)
            sys.stdout.write(f'\x1b[{msg_row + i};{m_col}H{ml}')
        sys.stdout.flush()
        time.sleep(5)

        house_scene = [
            '▪️▪️▪️🛑▪️▪️▪️',
            '▪️▪️🛑🛑🛑▪️▪️',
            '▪️🛑🛑🛑🛑🛑▪️',
            '🛑🛑🛑🛑🛑🛑🛑',
            '🪵🔸🔸🔸🔸🔸🪵',
            '🪵🔸🔸🖼️🔸🔸🪵',
            '🪵🔸🔸🔸🔸🔸🪵',
            '🚪👩‍🦰🔸🔸👴👵🪵',
            '🪹🪹🪹🪹🪹🪹🪹',
        ]
        start_row = render_centered(house_scene, 2)
        dialogue = [
            '👴: Ты где шлялась? Тебя 16 лет не было!',
            '👩‍🦰: Кто бы говорил! И я была на астероиде,',
            'а не в очереди за хлебом!',
            '👴: Не лги отцу! На каком ещё астероиде?',
            'Небось, опять на блядки ходила!',
            '👩‍🦰: Одно другому не мешает. 🤗',
        ]
        msg_row = start_row + len(house_scene) + 1
        for i, ml in enumerate(dialogue):
            m_col = max(1, (cols - dw(ml)) // 2 + 1)
            sys.stdout.write(f'\x1b[{msg_row + i};{m_col}H{ml}')
        sys.stdout.flush()
        time.sleep(5)

        final_scene = [
            '▪️▪️▪️▪️▪️👑▪️▪️▪️▪️▪️',
            '▪️▪️🎀🏵️🏵️🏵️🏵️🏵️🎀▪️▪️',
            '▪️▪️🏵️🟡👁️🟡👁️🟡🏵️▪️▪️',
            '▪️🏵️🟡🟡🟡👃🟡🟡🟡🏵️▪️',
            '🏵️🟡🔺🔺🔺🔺🔺🔺🔺🟡🏵️',
            '🏵️🟡♦️◼️🦷◼️🦷◼️♦️🟡🏵️',
            '▪️🟡♦️🦷◼️🦷◼️🦷♦️🟡▪️',
            '▪️🟡🔻🔻🔻🔻🔻🔻🔻🟡▪️',
            '▪️▪️🟡🟡🟡🟡🟡🟡🟡▪️▪️',
        ]
        start_row = render_centered(final_scene, 2)
        win_lines = [
            'ТЫ ПОБЕДИЛА!',
            'Осталось только найти хорошего стоматолога...'
        ]
        msg_row = start_row + len(final_scene) + 1
        for i, ml in enumerate(win_lines):
            m_col = max(1, (cols - dw(ml)) // 2 + 1)
            sys.stdout.write(f'\x1b[{msg_row + i};{m_col}H\x1b[32m{ml}\x1b[0m')
        sys.stdout.flush()
        get_key()
        return

def draw(px, py, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks, grid_ground_map, blue_cells, dragon=None, game_over=False, health=10000, mechanic=None, money_cells=None, eaten=False, in_rocket=False, astronaut=False, money=0, statues=None, mechanic_astronaut=False, inventory=None, chairs=None, tornado_mode=False):
    term = shutil.get_terminal_size((80, 24))
    rows, cols = term.lines, term.columns
    need_rows, need_cols = VIEW, VIEW * CELL_W + 2
    if rows < need_rows or cols < need_cols:
        sys.stdout.write('\x1b[2J\x1b[H')
        sys.stdout.write(f'Терминал слишком мал: нужно минимум {need_rows}x{need_cols}, сейчас {rows}x{cols}.\n')
        sys.stdout.write('Увеличьте окно терминала.\n')
        sys.stdout.flush()
        return
    start_row = max(1, (rows - need_rows) // 2 + 1)
    start_col = max(1, (cols - need_cols) // 2 + 1)
    out = []
    out.append('\x1b[2J\x1b[H')

    money_msg = f'💰 Деньги: {money}'
    m_col = max(1, (cols - dw(money_msg)) // 2 + 1)
    m_row = start_row - 4
    if m_row >= 1:
        out.append(f'\x1b[{m_row};{m_col}H{money_msg}')

    health_msg = f'♥️ Здоровье: {health}'
    h_col = max(1, (cols - dw(health_msg)) // 2 + 1)
    h_row = start_row - 3
    if h_row >= 1:
        out.append(f'\x1b[{h_row};{h_col}H{health_msg}')

    inv = inventory if inventory else []
    inv_msg = format_inventory(inv)
    i_col = max(1, (cols - dw(inv_msg)) // 2 + 1)
    i_row = start_row - 2
    if i_row >= 1:
        out.append(f'\x1b[{i_row};{i_col}H{inv_msg}')

    statues_set = statues if statues else set()
    money_set = money_cells if money_cells else set()
    for dy in range(VIEW):
        out.append(f'\x1b[{start_row + dy};{start_col}H')
        for dx in range(VIEW):
            cx, cy = wrap(px + dx - VIEW_HALF), wrap(py + dy - VIEW_HALF)
            cell = (cx, cy)
            if dx == VIEW_HALF and dy == VIEW_HALF:
                if game_over:
                    if eaten:
                        out.append('🐉')
                    else:
                        out.append('💀')
                elif in_rocket:
                    out.append(ROCKET)
                elif (px, py) in hospitals:
                    out.append(HOSPITAL)
                elif astronaut:
                    out.append(ASTRONAUT)
                else:
                    out.append(HERO)
            elif cell == dragon:
                out.append('🌪️' if tornado_mode else '🐉')
            elif chairs and cell in chairs:
                out.append('💺')
            elif mechanic is not None and cell == mechanic:
                if cell in hospitals:
                    out.append(HOSPITAL)
                else:
                    out.append(MECHANIC_ASTRONAUT if mechanic_astronaut else MECHANIC)
            elif cell == rocket:
                out.append(ROCKET)
            elif cell in hospitals:
                out.append(HOSPITAL)
            elif cell in aliens:
                out.append(ALIEN)
            elif cell in temples:
                out.append(TEMPLE)
            elif cell in hotels:
                out.append(HOTEL)
            elif cell in stores:
                out.append(STORE)
            elif cell in poops:
                out.append(POOP)
            elif cell in trees:
                out.append(TREE)
            elif cell in rocks:
                out.append(ROCK)
            elif cell in statues_set:
                out.append('🗿')
            elif cell in money_set:
                out.append('💰')
            elif cell in blue_cells:
                out.append('🟦')
            else:
                out.append(grid_ground_map[cell])
    sys.stdout.write(''.join(out))
    sys.stdout.flush()

def generate_world():
    cells = [(cx, cy) for cx in range(-HALF, HALF + 1) for cy in range(-HALF, HALF + 1) if (cx, cy) != (0, 0)]
    picks = random.sample(cells, 511)
    rocket = picks[0]
    temples = set(picks[1:3])
    aliens = set(picks[3:7])
    hospitals = set(picks[7:15])
    hotels = set(picks[15:31])
    stores = set(picks[31:63])
    poops = set(picks[63:127])
    trees = set(picks[127:255])
    rocks = set(picks[255:511])
    blocked = {rocket} | hospitals | aliens | temples | hotels | stores | poops | trees | rocks
    ground_symbols = ['▫️', '🔸', '🔹', '▪️']
    random.shuffle(ground_symbols)
    alien_ground_map = {}
    for alien, sym in zip(list(aliens), ground_symbols):
        alien_ground_map[alien] = sym
    grid_ground_map = {}
    for cx in range(-HALF, HALF + 1):
        for cy in range(-HALF, HALF + 1):
            dists = []
            for alien in aliens:
                d = toroidal_dist((cx, cy), alien)
                dists.append((d, alien))
            min_d = min(d for d, _ in dists)
            closest_aliens = [alien for d, alien in dists if d == min_d]
            chosen = random.choice(closest_aliens)
            grid_ground_map[(cx, cy)] = alien_ground_map[chosen]
    blocked_grid = [[0] * SIZE for _ in range(SIZE)]
    for cx in range(-HALF, HALF + 1):
        for cy in range(-HALF, HALF + 1):
            if (cx, cy) in blocked or (cx, cy) == (0, 0):
                blocked_grid[cx + HALF][cy + HALF] = 1
    pref = [[0] * (SIZE + 1) for _ in range(SIZE + 1)]
    for i in range(SIZE):
        row_sum = 0
        for j in range(SIZE):
            row_sum += blocked_grid[i][j]
            pref[i + 1][j + 1] = pref[i][j + 1] + row_sum
    def rect_sum(r1, c1, r2, c2):
        return pref[r2 + 1][c2 + 1] - pref[r1][c2 + 1] - pref[r2 + 1][c1] + pref[r1][c1]
    best_squares = []
    for n in range(SIZE, 0, -1):
        found = []
        for i in range(SIZE - n + 1):
            for j in range(SIZE - n + 1):
                if rect_sum(i, j, i + n - 1, j + n - 1) == 0:
                    found.append((i, j, n))
        if found:
            best_squares = found
            break
    i0, j0, n = random.choice(best_squares)
    blue_cells = set()
    for i in range(n):
        for j in range(n):
            blue_cells.add((i0 + i - HALF, j0 + j - HALF))
    dragon = random.choice(list(blue_cells))
    reachable = {(0, 0)}
    queue = [(0, 0)]
    head = 0
    while head < len(queue):
        cx, cy = queue[head]
        head += 1
        for ddx, ddy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            nx2 = wrap(cx + ddx)
            ny2 = wrap(cy + ddy)
            if (nx2, ny2) in reachable:
                continue
            if (nx2, ny2) in blocked:
                continue
            reachable.add((nx2, ny2))
            queue.append((nx2, ny2))
    money_cells = set()
    for cx in range(-HALF, HALF + 1):
        for cy in range(-HALF, HALF + 1):
            cell = (cx, cy)
            if cell in blocked: continue
            if cell in reachable: continue
            if cell in blue_cells: continue
            money_cells.add(cell)
    return dict(
        rocket=rocket, hospitals=hospitals, aliens=aliens, temples=temples,
        hotels=hotels, stores=stores, poops=poops, trees=trees, rocks=rocks,
        blocked=blocked, grid_ground_map=grid_ground_map, blue_cells=blue_cells,
        dragon=dragon, money_cells=money_cells, ground_symbols=ground_symbols,
        alien_ground_map=alien_ground_map
    )

def main():
    while True:
        world = generate_world()
        cnt = len(world['money_cells'])
        if 1 <= cnt <= 100:
            break

    x = y = 0
    rocket = world['rocket']
    hospitals = world['hospitals']
    aliens = world['aliens']
    temples = world['temples']
    hotels = world['hotels']
    stores = world['stores']
    poops = world['poops']
    trees = world['trees']
    rocks = world['rocks']
    blocked = world['blocked']
    grid_ground_map = world['grid_ground_map']
    blue_cells = world['blue_cells']
    dragon = world['dragon']
    money_cells = world['money_cells']
    ground_symbols = world['ground_symbols']
    alien_ground_map = world['alien_ground_map']

    symbol_to_alien = {sym: al for al, sym in alien_ground_map.items()}

    game_over = False
    health = 100
    money = 0
    visited_hospitals = set()
    hospital_entry_cells = {}
    eaten = False
    in_rocket = False
    astronaut = False
    rocket_visited = False
    health_frozen = False
    mechanic_astronaut = False
    post_mechanic_messages = False

    inventory = []
    statue_hit_counter = 0
    step_messages = []

    titan_battle_state = 0
    chairs = set()
    tornado_mode = False
    tornado_death = False

    has_key = False
    key_chair = None
    sun_knowledge = False

    bed_scene_happened = False
    blue_entries_after_bed = 0
    prev_on_blue = False

    dragon_dialogue_1 = [
        '👩‍🦰: А почему ты меня не кушаешь?',
        '🐉: Боюсь заразиться ЗППП.',
        'Даже я слышал, какая ты шлюха!',
        '👩‍🦰: А как тебя зовут, зелёненький?',
        '🐉: Я прионозух. Все мои предки на планете Земля вымерли',
        'из-за огромного метеорита, но одна икринка',
        'от взрывной волны улетела в космос.'
    ]
    dragon_dialogue_2 = [
        '👩‍🦰: Забыла спросить: а почему 👽 хочет,',
        'чтобы я тебя убила?',
        '🐉: Какой именно 👽 из четверых?',
        '👩‍🦰: Ну этот, который 👽... Бля, они все одинаковые!',
        '🐉: Как его зовут? АБ, СЕ, РО или ИД?',
        '👩‍🦰: Да не знаю я!',
        '🐉: ТАК УЗНАЙ, ТУПАЯ ПИЗДА!'
    ]

    alien_name_asked = set()
    double_disrespect_aliens = set()
    statue_owner = {}
    mechanic_hunting = False
    mechanic_pickaxe_taken = False
    pickaxe_dialogue_shown = False
    pickaxe_dialogue_lines = [
        '👩‍🦰: ЕГОР, СПАСИ МЕНЯ!',
        '👨‍🚀: Что, Маруська, допизделась?',
        'Отдай кирку и надевай скафандр.',
        'Ничё без меня не можешь, кроме как на хуях прыгать.'
    ]

    all_statues_killed = False
    post_statues_spoken = set()
    brain_given = False

    brain_mechanic_dialogue_shown = False
    mechanic_following = False
    brain_mechanic_dialogue = [
        '👩‍🚀: Ты мой герой! Осталось только',
        'убить прионозуха — и всё!',
        '🧑‍🔧: А зачем мне его убивать?',
        '👩‍🚀: Он сказал, что ты лох.',
        '🧑‍🔧: Сам он лох Лохнесский! Веди меня к нему!'
    ]

    alien_dists = [(toroidal_dist(a, rocket), a) for a in aliens]
    min_alien_dist = min(d for d, _ in alien_dists)
    closest_aliens = [a for d, a in alien_dists if d == min_alien_dist]
    chosen_alien = random.choice(closest_aliens)

    alien_messages = {}
    alien_messages_astronaut = {}
    alien_messages_post_mechanic = {}

    alien_messages[chosen_alien] = [
        '👽: Ты хочешь улететь на ракете обратно на Землю?',
        'Тогда убей чудище в Квадратном озере.'
    ]
    alien_messages_astronaut[chosen_alien] = [
        '👽: Тебе же сказали: ты не улетишь отсюда,',
        'пока не убьёшь прионозуха!'
    ]
    alien_messages_post_mechanic[chosen_alien] = [
        '👩‍🦰: Какие у вас правила этикета?',
        '👽: Можешь убить прионозуха без реверанса,',
        'он не обидится.'
    ]

    other_aliens = [a for a in aliens if a != chosen_alien]
    other_triples = [
        (
            'welcome',
            ['👽: Добро пожаловать на Абсероид!'],
            ['👽: У нас деньги буквально валяются на дороге!', 'Бери, сколько хочешь!'],
            ['👩‍🦰: Какие у вас правила этикета?',
             '👽: Первое и единственное правило этикета:',
             'никогда не спрашивай об этикете!',
             'Дизреспект тебе!']
        ),
        (
            'health',
            ['👽: Почему твоё здоровье падает?', 'Просто у нас своя атмосфера.'],
            ['👽: В гостях своим кислородом', 'дышать невежливо! Дизреспект тебе!'],
            ['👩‍🦰: Какие у вас правила этикета?',
             '👽: Вижу, ты признала свою ошибку,',
             'но я тебя не прощаю.',
             'Ты очень невоспитанная гостья.']
        ),
        (
            'far',
            ['👽: Мы так далеко от Земли,', 'что даже не слышали,', 'какая ты шлюха.'],
            ['👽: Хочешь секса? Не проблема!', 'У нас есть копии самцов вида Homo sapiens.'],
            ['👩‍🦰: Какие у вас правила этикета?',
             '👽: Какой этикет? Заплатила — и ебись',
             'с кем хочешь, как хочешь и сколько хочешь!']
        )
    ]
    random.shuffle(other_triples)

    welcome_alien = None
    health_alien = None
    far_alien = None

    for alien, (cat, old_msg, new_msg, third_msg) in zip(other_aliens, other_triples):
        alien_messages[alien] = old_msg
        alien_messages_astronaut[alien] = new_msg
        alien_messages_post_mechanic[alien] = third_msg
        if cat == 'welcome':
            welcome_alien = alien
        elif cat == 'health':
            health_alien = alien
        elif cat == 'far':
            far_alien = alien

    alien_messages_post_statues = {}
    alien_messages_post_statues[chosen_alien] = [
        '👽: Как ты считаешь, Егор — молодец?',
        '👩‍🚀: Конечно! Он спас меня!',
        '👽: Тогда пусть он и летит на Землю.',
        '👩‍🚀: Нет! Пусть он сначала убьёт прионозуха!',
        '👽: Ля ты крыса! ОК, будь по-твоему!'
    ]
    alien_messages_post_statues[welcome_alien] = [
        '👽: Хочешь знать, как меня зовут?',
        '👩‍🚀: Конечно! Это же ключ к победе!',
        '👽: Подсказка: моё имя состоит из 2 букв.'
    ]
    alien_messages_post_statues[health_alien] = [
        '👽: Прости, что я считал тебя невежей.',
        'Ты просто умственно отсталая.',
        'Попроси у Гудвина мозги.',
        '👩‍🚀: Гудвин, дай мне мозги!'
    ]
    alien_messages_post_statues[far_alien] = [
        '👽: Поздравляю, теперь весь Абсернет знает,',
        'что оргазм — лучшее лекарство.',
        '👩‍🚀: Ура, я популярна!'
    ]

    aliens_spoken = set()
    mechanic = None
    mechanic_dialogue_shown = False
    mechanic_dialogue_active = False
    new_mechanic_dialogue_shown = False
    mechanic_dialogue_template = [
        '🧑‍🔧: РЫЖАЯ ШЛЮХА?!',
        'Как ты здесь оказалась?!',
        None,
        'с мужиками в бане провожали Павлика...',
        '🧑‍🔧: Так вот почему ракета не может взлететь!',
        'Из-за твоей чёрной дыры!'
    ]
    new_mechanic_dialogue = [
        '👩‍🚀: Егор, а что такое дизреспект?',
        '🧑‍🔧: Ёб твою мать, ты даже на Абсероиде',
        'не умеешь вести себя прилично!',
        '👩‍🚀: И что мне теперь делать?',
        '🧑‍🔧: Снимать скафандр и бегать!',
        'Дай сюда и иди учить матчасть!'
    ]

    disrespect_aliens = set()
    statues = set()
    statues_placed_for = set()
    prev_adj_statues = set()

    def mechanic_bfs_step():
        def passable(nc):
            if nc == (x, y): return False
            if nc == dragon: return False
            if nc == rocket: return False
            if nc in blue_cells: return False
            if nc in money_cells: return False
            if nc in poops: return False
            if nc in trees: return False
            if nc in aliens: return False
            if nc in temples: return False
            if nc in hotels: return False
            if nc in stores: return False
            return True

        if not statues:
            return None

        visited = {mechanic: None}
        q = deque([mechanic])
        target = None
        while q:
            cur = q.popleft()
            cx, cy = cur
            for ddx in (-1, 0, 1):
                for ddy in (-1, 0, 1):
                    if ddx == 0 and ddy == 0:
                        continue
                    nc = (wrap(cx + ddx), wrap(cy + ddy))
                    if nc in visited:
                        continue
                    if nc in statues:
                        visited[nc] = cur
                        target = nc
                        q.clear()
                        break
                    if not passable(nc):
                        continue
                    visited[nc] = cur
                    q.append(nc)
                if target:
                    break
            if target:
                break

        if target is None:
            return None

        path = []
        cur = target
        while cur != mechanic:
            path.append(cur)
            cur = visited[cur]
        path.reverse()
        return path[0] if path else None

    sys.stdout.write('\x1b[?1049h\x1b[?25l\x1b[2J')
    try:
        while True:
            on_blue_now = (x, y) in blue_cells
            if on_blue_now and not prev_on_blue and bed_scene_happened:
                blue_entries_after_bed += 1
            prev_on_blue = on_blue_now

            dragon_dialogues_done = (blue_entries_after_bed >= 2)

            if mechanic_pickaxe_taken and not all_statues_killed and len(statues) == 0:
                all_statues_killed = True

            non_double_statues = {s for s in statues if statue_owner.get(s) not in double_disrespect_aliens}
            if (x, y) not in hospitals:
                cur_adj = {s for s in non_double_statues if is_adjacent((x, y), s)}
                if cur_adj - prev_adj_statues:
                    health -= 10
                    if not astronaut:
                        statue_hit_counter += 1
                        if statue_hit_counter % 2 == 0:
                            inventory.append('🦷')
                    if health <= 0:
                        game_over = True
                        eaten = False
                        health = 0
                        draw(x, y, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks,
                             grid_ground_map, blue_cells, dragon, game_over, health, mechanic, money_cells,
                             eaten, in_rocket, astronaut, money, statues, mechanic_astronaut, inventory, chairs=chairs, tornado_mode=tornado_mode)
                        term = shutil.get_terminal_size((80, 24))
                        rows, cols = term.lines, term.columns
                        start_row = max(1, (rows - VIEW) // 2 + 1)
                        msg = 'ВЫ ПОГИБЛИ!'
                        msg_col = max(1, (cols - dw(msg)) // 2 + 1)
                        sys.stdout.write(f'\x1b[{start_row + VIEW + 1};{msg_col}H\x1b[31m{msg}\x1b[0m')
                        sys.stdout.flush()
                        get_key()
                        break
                prev_adj_statues = cur_adj
            else:
                prev_adj_statues = set()

            if mechanic_following and mechanic is not None:
                if (x, y) in blue_cells:
                    mechanic_following = False
                    titan_battle_state = 1
                else:
                    target = prev_hero_pos
                    is_hosp = target in hospitals
                    if (target != mechanic
                            and target != (0, 0)
                            and target != (x, y)
                            and (target not in blocked or is_hosp)
                            and target != dragon
                            and target not in blue_cells):
                        if mechanic not in hospitals:
                            blocked.discard(mechanic)
                        mechanic = target
                        if not is_hosp:
                            blocked.add(mechanic)

            draw(x, y, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks,
                 grid_ground_map, blue_cells, dragon, game_over, health, mechanic, money_cells,
                 eaten, in_rocket, astronaut, money, statues, mechanic_astronaut, inventory, chairs=chairs, tornado_mode=tornado_mode)

            hero_now = ASTRONAUT if astronaut else HERO
            mechanic_dialogue_lines = [
                (f'{hero_now}: Я помню только, что мы' if line is None else line)
                for line in mechanic_dialogue_template
            ]

            messages = []

            if titan_battle_state == 1:
                titan_dialogue = [
                    '«🧑‍🔧: Ты кого лохом назвало, чмо болотное?»',
                    '🐉: Я никого лохом не называл.',
                    '🧑‍🔧: А, ну тогда сорян.',
                    '👩‍🚀: Прионозух, Егор только что',
                    'назвал тебя чмом болотным! Съешь его!',
                    '🐉: Я бы съел, но он боится воды.',
                    '🧑‍🔧: Это ты боишься суши, ошибка эволюции!',
                    '👩‍🚀: Вперёд, Егорка! Убей пидорозуха!'
                ]
                for line in titan_dialogue:
                    messages.append((line, None))
            elif titan_battle_state == 3:
                messages.append(('👽х4: Махач! Махач! 🍿🍿🍿🍿', None))

            if bed_scene_happened and on_blue_now:
                if blue_entries_after_bed == 1:
                    for line in dragon_dialogue_1:
                        messages.append((line, None))
                elif blue_entries_after_bed == 2:
                    for line in dragon_dialogue_2:
                        messages.append((line, None))

            if in_rocket and not game_over:
                messages.append(('ВЫ НАДЕЛИ СКАФАНДР!', 'green'))
                messages.append(('РАКЕТА НЕ ЗАПУСКАЕТСЯ!', 'orange'))

            if not game_over and len(disrespect_aliens) > 0:
                if (x, y) in hospitals:
                    sym = grid_ground_map.get((x, y))
                    owner = symbol_to_alien.get(sym)
                    if owner in disrespect_aliens:
                        messages.append(('Тебе здесь не рады,', 'green'))
                        messages.append(('но клятву Гиппократа никто не отменял.', 'green'))

            if not game_over and (x, y) not in hospitals:
                for s in statues:
                    if statue_owner.get(s) in double_disrespect_aliens:
                        continue
                    if is_adjacent((x, y), s):
                        messages.append(('🗿: ПРОЧЬ, НЕВЕЖА! 💢👊💢', None))
                        break

            if not game_over:
                if all_statues_killed:
                    adj = [a for a in aliens if is_adjacent((x, y), a)]
                    random.shuffle(adj)
                    for alien in adj:
                        if alien in post_statues_spoken:
                            continue
                        post_statues_spoken.add(alien)
                        for line in alien_messages_post_statues[alien]:
                            messages.append((line, None))
                        if alien == health_alien and not brain_given:
                            brain_given = True
                            inventory.append('🧠')
                        break
                elif not mechanic_pickaxe_taken:
                    if dragon_dialogues_done:
                        adj = [a for a in aliens if is_adjacent((x, y), a)]
                        random.shuffle(adj)
                        for alien in adj:
                            if alien in alien_name_asked:
                                continue
                            alien_name_asked.add(alien)
                            aliens_spoken.add(alien)
                            if alien in disrespect_aliens:
                                msgs_lines = [
                                    '👩‍🦰: Как тебя зовут?',
                                    '👽: БЛЯДИНА! 🤬🤬🤬 ДВОЙНОЙ ДИЗРЕСПЕКТ!'
                                ]
                                double_disrespect_aliens.add(alien)
                            else:
                                msgs_lines = [
                                    '👩‍🦰: Как тебя зовут?',
                                    '👽: Да как ты смеешь! Дизреспект тебе!'
                                ]
                                disrespect_aliens.add(alien)
                            for line in msgs_lines:
                                messages.append((line, None))
                            break
                    else:
                        if post_mechanic_messages:
                            msgs = alien_messages_post_mechanic
                        elif astronaut:
                            msgs = alien_messages_astronaut
                        else:
                            msgs = alien_messages
                        adj = [a for a in aliens if is_adjacent((x, y), a)]
                        random.shuffle(adj)
                        if adj:
                            alien = adj[0]
                            for line in msgs[alien]:
                                messages.append((line, None))
                            aliens_spoken.add(alien)
                            if any('Дизреспект' in line for line in msgs[alien]):
                                if alien not in disrespect_aliens:
                                    disrespect_aliens.add(alien)

            if not game_over and mechanic_dialogue_active and not new_mechanic_dialogue_shown:
                for line in mechanic_dialogue_lines:
                    messages.append((line, None))

            if (not game_over and not brain_mechanic_dialogue_shown
                    and inventory.count('🧠') > 0
                    and chosen_alien in post_statues_spoken
                    and mechanic is not None
                    and not in_rocket
                    and is_adjacent((x, y), mechanic)):
                brain_mechanic_dialogue_shown = True
                mechanic_following = True
                for line in brain_mechanic_dialogue:
                    messages.append((line, None))

            for m in step_messages:
                if isinstance(m, tuple):
                    messages.append(m)
                else:
                    messages.append((m, 'green'))

            if messages:
                term = shutil.get_terminal_size((80, 24))
                rows, cols = term.lines, term.columns
                start_row = max(1, (rows - VIEW) // 2 + 1)
                for i, (line, color) in enumerate(messages):
                    if color == 'green':
                        code = '\x1b[32m'; reset = '\x1b[0m'
                    elif color == 'orange':
                        code = '\x1b[38;5;208m'; reset = '\x1b[0m'
                    elif color == 'blue':
                        code = '\x1b[94m'; reset = '\x1b[0m'
                    else:
                        code = ''; reset = ''
                    col = max(1, (cols - dw(line)) // 2 + 1)
                    sys.stdout.write(f'\x1b[{start_row + VIEW + 1 + i};{col}H{code}{line}{reset}')
                sys.stdout.flush()

            if mechanic is None and len(aliens_spoken) == len(aliens) and titan_battle_state < 4:
                valid_cells = []
                for cx in range(-HALF, HALF + 1):
                    for cy in range(-HALF, HALF + 1):
                        cell = (cx, cy)
                        if cell in blocked: continue
                        if cell == (0, 0): continue
                        if grid_ground_map[cell] in ground_symbols:
                            valid_cells.append(cell)
                if valid_cells:
                    min_d = min(toroidal_dist(c, rocket) for c in valid_cells)
                    nearest = [c for c in valid_cells if toroidal_dist(c, rocket) == min_d]
                    mechanic = random.choice(nearest)
                    blocked.add(mechanic)

            if mechanic is not None and not game_over and not in_rocket and is_adjacent((x, y), mechanic):
                if not mechanic_hunting and titan_battle_state == 0:
                    if new_mechanic_dialogue_shown:
                        pass
                    elif len(disrespect_aliens) > 0:
                        new_mechanic_dialogue_shown = True
                        term = shutil.get_terminal_size((80, 24))
                        rows, cols = term.lines, term.columns
                        start_row = max(1, (rows - VIEW) // 2 + 1)
                        for i, line in enumerate(new_mechanic_dialogue):
                            col = max(1, (cols - dw(line)) // 2 + 1)
                            sys.stdout.write(f'\x1b[{start_row + VIEW + 1 + i};{col}H{line}')
                        sys.stdout.flush()
                        time.sleep(3)
                        astronaut = False
                        mechanic_astronaut = True
                        health_frozen = False
                        post_mechanic_messages = True
                        mechanic_dialogue_active = False
                        continue
                    elif not mechanic_dialogue_shown:
                        mechanic_dialogue_shown = True
                        mechanic_dialogue_active = True

            for al in list(disrespect_aliens - statues_placed_for):
                statues_placed_for.add(al)
                dis_symbol = alien_ground_map[al]
                npc_set = set()
                if mechanic is not None:
                    npc_set.add(mechanic)
                npc_set.add(dragon)
                for cell, sym in grid_ground_map.items():
                    if sym != dis_symbol: continue
                    if cell == (x, y): continue
                    if cell in npc_set: continue
                    if cell in blocked: continue
                    if cell in money_cells: continue
                    if cell in blue_cells: continue
                    cnt = 0
                    for ddx, ddy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                        nx2 = wrap(cell[0] + ddx)
                        ny2 = wrap(cell[1] + ddy)
                        if (nx2, ny2) in rocks:
                            cnt += 1
                    if cnt >= 2:
                        statues.add(cell)
                        statue_owner[cell] = al
                        blocked.add(cell)

            k = get_key()
            if game_over:
                break

            step_messages = []

            pass_turn = k in ('р', 'Р')

            nx, ny = x, y
            if k in ('\x1b', '\x03'):
                break
            elif k in ('н', 'Н'): ny = wrap(y - 1)
            elif k in ('о', 'О'): nx = wrap(x + 1)
            elif k in ('и', 'И'): ny = wrap(y + 1)
            elif k in ('п', 'П'): nx = wrap(x - 1)

            if key_chair is not None and (nx, ny) == key_chair and (nx, ny) != (x, y):
                if not has_key:
                    has_key = True
                    inventory.append('🔑')
                    step_messages.append('ВЫ НАШЛИ КЛЮЧ ЗАПУСКА РАКЕТЫ!')
                nx, ny = x, y

            if has_key and (nx, ny) == rocket:
                show_rocket_ending(sun_knowledge)
                break

            if (nx, ny) in temples or (nx, ny) in hotels or (nx, ny) in stores:
                blocked_by_disrespect = False
                if len(disrespect_aliens) > 0:
                    sym = grid_ground_map.get((nx, ny))
                    owner = symbol_to_alien.get(sym)
                    if owner in disrespect_aliens:
                        blocked_by_disrespect = True

                if blocked_by_disrespect:
                    term = shutil.get_terminal_size((80, 24))
                    rows, cols = term.lines, term.columns
                    start_row = max(1, (rows - VIEW) // 2 + 1)
                    msg = 'ТЕБЕ ЗДЕСЬ НЕ РАДЫ!'
                    col = max(1, (cols - dw(msg)) // 2 + 1)
                    sys.stdout.write(f'\x1b[{start_row + VIEW + 1};{col}H\x1b[38;5;208m{msg}\x1b[0m')
                    sys.stdout.flush()
                    time.sleep(3)
                    mechanic_dialogue_active = False
                    continue

                if (nx, ny) in temples:
                    has_brain = inventory.count('🧠') > 0
                    show_temple_scene(hero_now, has_brain)
                    if has_brain:
                        sun_knowledge = True
                elif (nx, ny) in hotels:
                    show_hotel_scene(hero_now)
                    if money >= 1:
                        n = min(money, 3)
                        show_bed_scenes(hero_now, n)
                        money -= n
                        health += 100 * n
                        if not bed_scene_happened:
                            bed_scene_happened = True
                            blocked.add(dragon)
                else:
                    teeth = inventory.count('🦷')
                    if teeth >= 1:
                        while True:
                            teeth = inventory.count('🦷')
                            if teeth == 0: break
                            show_shop_scene(hero_now, teeth)
                            k2 = get_key()
                            if k2 in ('\x1b', '\x03'): break
                            if k2 in ('в', 'В') and teeth >= 1:
                                inventory.remove('🦷')
                                inventory.append('🪠')
                            elif k2 in ('т', 'Т') and teeth >= 2:
                                inventory.remove('🦷')
                                inventory.remove('🦷')
                                inventory.append('🪓')
                            elif k2 in ('к', 'К') and teeth >= 3:
                                inventory.remove('🦷')
                                inventory.remove('🦷')
                                inventory.remove('🦷')
                                inventory.append('⛏️')
                        show_shop_thanks(hero_now)
                    else:
                        show_store_scene(hero_now)
                mechanic_dialogue_active = False
                continue

            is_hospital_target = False
            if (nx, ny) in hospitals:
                if (nx, ny) not in hospital_entry_cells:
                    is_hospital_target = True
                elif hospital_entry_cells[(nx, ny)] == (x, y):
                    is_hospital_target = True

            all_aliens_spoken = len(aliens_spoken) == len(aliens)
            is_rocket_target = (nx, ny) == rocket and all_aliens_spoken and not rocket_visited and not in_rocket

            can_remove = False
            remove_type = None
            if (nx, ny) in poops and inventory.count('🪠') > 0:
                can_remove = True; remove_type = 'poop'
            elif (nx, ny) in trees and inventory.count('🪓') > 0:
                can_remove = True; remove_type = 'tree'
            elif (nx, ny) in rocks and inventory.count('⛏️') > 0:
                can_remove = True; remove_type = 'rock'
            elif (nx, ny) in statues and inventory.count('⛏️') > 0:
                can_remove = True; remove_type = 'statue'

            can_move = is_hospital_target or is_rocket_target or (nx, ny) not in blocked or can_remove or (tornado_mode and (nx, ny) in blue_cells)
            position_changed = (nx, ny) != (x, y)

            if (can_move and position_changed) or pass_turn:
                prev_hero_pos = (x, y)

                if position_changed:
                    if in_rocket:
                        in_rocket = False
                        astronaut = True
                        health_frozen = True
                    if is_rocket_target:
                        rocket_visited = True
                        in_rocket = True
                        health_frozen = True

                death_by_dragon = position_changed and ((nx, ny) in blue_cells or (nx, ny) == dragon) and not bed_scene_happened
                hero_died = False
                tornado_death = False

                if position_changed and can_remove:
                    if remove_type == 'poop':
                        inventory.remove('🪠')
                        poops.discard((nx, ny))
                        blocked.discard((nx, ny))
                        step_messages.append('Вы убрали говно вантузом!')
                    elif remove_type == 'tree':
                        inventory.remove('🪓')
                        trees.discard((nx, ny))
                        blocked.discard((nx, ny))
                        step_messages.append('Вы срубили дерево топором!')
                    elif remove_type == 'rock':
                        inventory.remove('⛏️')
                        rocks.discard((nx, ny))
                        blocked.discard((nx, ny))
                        step_messages.append('Вы разбили камень киркой!')
                    elif remove_type == 'statue':
                        inventory.remove('⛏️')
                        statues.discard((nx, ny))
                        statue_owner.pop((nx, ny), None)
                        blocked.discard((nx, ny))
                        step_messages.append('Вы убили каменного голема киркой!')
                    x, y = nx, ny
                    if not health_frozen:
                        health -= 1
                    if health <= 0:
                        hero_died = True
                elif death_by_dragon:
                    x, y = nx, ny
                    if not health_frozen:
                        health -= 1
                    if health <= 0:
                        hero_died = True
                    else:
                        dragon = (x, y)
                        game_over = True
                        eaten = True
                        health = 0
                        draw(x, y, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks,
                             grid_ground_map, blue_cells, dragon, game_over, health, mechanic, money_cells,
                             eaten, in_rocket, astronaut, money, statues, mechanic_astronaut, inventory, chairs=chairs, tornado_mode=tornado_mode)
                        term = shutil.get_terminal_size((80, 24))
                        rows, cols = term.lines, term.columns
                        start_row = max(1, (rows - VIEW) // 2 + 1)
                        msg = 'ВАС СЪЕЛО  ОГРОМНОЕ ЧУДИЩЕ!'
                        msg_col = max(1, (cols - dw(msg)) // 2 + 1)
                        sys.stdout.write(f'\x1b[{start_row + VIEW + 1};{msg_col}H\x1b[31m{msg}\x1b[0m')
                        sys.stdout.flush()
                        get_key()
                        break
                else:
                    if position_changed:
                        x, y = nx, ny
                    if position_changed and tornado_mode and (nx, ny) == dragon:
                        hero_died = True
                        tornado_death = True
                    if not health_frozen:
                        health -= 1
                    if position_changed and is_hospital_target:
                        if (nx, ny) not in hospital_entry_cells:
                            hospital_entry_cells[(nx, ny)] = prev_hero_pos
                        visited_hospitals.add((nx, ny))
                        if health < 100:
                            health = 100
                    if position_changed and (nx, ny) in money_cells:
                        money += 1
                        money_cells.discard((nx, ny))
                    if health <= 0:
                        hero_died = True

                if hero_died:
                    game_over = True
                    eaten = False
                    health = 0
                    draw(x, y, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks,
                         grid_ground_map, blue_cells, dragon, game_over, health, mechanic, money_cells,
                         eaten, in_rocket, astronaut, money, statues, mechanic_astronaut, inventory, chairs=chairs, tornado_mode=tornado_mode)
                    term = shutil.get_terminal_size((80, 24))
                    rows, cols = term.lines, term.columns
                    start_row = max(1, (rows - VIEW) // 2 + 1)
                    if tornado_death:
                        msg_lines = ['ВАС ЗАРУБИЛИ, ЗАГРЫЗЛИ И ЗАТОПТАЛИ', 'В ПЫЛУ БИТВЫ ТИТАНОВ!']
                        for i, ml in enumerate(msg_lines):
                            m_col = max(1, (cols - dw(ml)) // 2 + 1)
                            sys.stdout.write(f'\x1b[{start_row + VIEW + 1 + i};{m_col}H\x1b[31m{ml}\x1b[0m')
                    else:
                        msg = 'ВЫ ПОГИБЛИ!'
                        msg_col = max(1, (cols - dw(msg)) // 2 + 1)
                        sys.stdout.write(f'\x1b[{start_row + VIEW + 1};{msg_col}H\x1b[31m{msg}\x1b[0m')
                    sys.stdout.flush()
                    get_key()
                    break

                mechanic_dialogue_active = False

                if double_disrespect_aliens:
                    relevant = [(c, o) for c, o in list(statue_owner.items())
                                if o in double_disrespect_aliens and c in statues]
                    random.shuffle(relevant)
                    for cell, owner in relevant:
                        if cell not in statues: continue
                        if is_adjacent((x, y), cell):
                            if (x, y) not in hospitals:
                                health -= 10
                                if not astronaut:
                                    statue_hit_counter += 1
                                    if statue_hit_counter % 2 == 0:
                                        inventory.append('🦷')
                                step_messages.append(('🗿: ПРОЧЬ, НЕВЕЖА! 💢👊💢', None))
                                if health <= 0:
                                    game_over = True
                                    eaten = False
                                    health = 0
                                    draw(x, y, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks,
                                         grid_ground_map, blue_cells, dragon, game_over, health, mechanic, money_cells,
                                         eaten, in_rocket, astronaut, money, statues, mechanic_astronaut, inventory, chairs=chairs, tornado_mode=tornado_mode)
                                    term = shutil.get_terminal_size((80, 24))
                                    rows, cols = term.lines, term.columns
                                    start_row = max(1, (rows - VIEW) // 2 + 1)
                                    msg = 'ВЫ ПОГИБЛИ!'
                                    msg_col = max(1, (cols - dw(msg)) // 2 + 1)
                                    sys.stdout.write(f'\x1b[{start_row + VIEW + 1};{msg_col}H\x1b[31m{msg}\x1b[0m')
                                    sys.stdout.flush()
                                    get_key()
                                    break
                        else:
                            dx = wrap(x - cell[0])
                            dy = wrap(y - cell[1])
                            cands = []
                            if dx > 0: cands.append((cell[0] + 1, cell[1]))
                            elif dx < 0: cands.append((cell[0] - 1, cell[1]))
                            if dy > 0: cands.append((cell[0], cell[1] + 1))
                            elif dy < 0: cands.append((cell[0], cell[1] - 1))
                            random.shuffle(cands)
                            for nc in cands:
                                nc = (wrap(nc[0]), wrap(nc[1]))
                                if nc == (x, y): continue
                                if nc == dragon: continue
                                if mechanic is not None and nc == mechanic: continue
                                if nc in blocked: continue
                                if nc in money_cells: continue
                                if nc in blue_cells: continue
                                statues.discard(cell)
                                statues.add(nc)
                                statue_owner.pop(cell, None)
                                statue_owner[nc] = owner
                                blocked.discard(cell)
                                blocked.add(nc)
                                break
                    if game_over:
                        break

                if (not astronaut and len(double_disrespect_aliens) > 0
                        and inventory.count('⛏️') > 0
                        and mechanic is not None and mechanic_astronaut
                        and is_adjacent((x, y), mechanic)
                        and not mechanic_hunting):
                    term = shutil.get_terminal_size((80, 24))
                    rows, cols = term.lines, term.columns
                    start_row = max(1, (rows - VIEW) // 2 + 1)
                    for i, line in enumerate(pickaxe_dialogue_lines):
                        col = max(1, (cols - dw(line)) // 2 + 1)
                        sys.stdout.write(f'\x1b[{start_row + VIEW + 1 + i};{col}H{line}')
                    sys.stdout.flush()
                    time.sleep(3)
                    astronaut = True
                    health_frozen = True
                    inventory.remove('⛏️')
                    mechanic_astronaut = False
                    mechanic_hunting = True
                    mechanic_pickaxe_taken = True
                    continue

                if mechanic_hunting and statues and titan_battle_state == 0:
                    next_step = mechanic_bfs_step()
                    if next_step is not None:
                        was_in_hosp = mechanic in hospitals
                        if not was_in_hosp:
                            blocked.discard(mechanic)
                        if next_step in statues:
                            statues.discard(next_step)
                            statue_owner.pop(next_step, None)
                            step_messages.append(('ЕГОР ТУАЛЕТОВ УБИЛ ГОЛЕМА КИРКОЙ!', 'blue'))
                        elif next_step in rocks:
                            rocks.discard(next_step)
                            step_messages.append(('ЕГОР ТУАЛЕТОВ РАЗБИЛ КАМЕНЬ КИРКОЙ!', 'blue'))
                        is_hosp = next_step in hospitals
                        if not is_hosp:
                            blocked.discard(next_step)
                        mechanic = next_step
                        if not is_hosp:
                            blocked.add(next_step)

                if (position_changed or pass_turn) and titan_battle_state > 0:
                    if titan_battle_state == 1:
                        titan_battle_state = 2

                    elif titan_battle_state == 2:
                        def passable_t(nc):
                            if nc == (x, y): return False
                            if nc == dragon: return False
                            if nc == rocket: return False
                            if nc in money_cells: return False
                            if nc in poops: return False
                            if nc in trees: return False
                            if nc in aliens: return False
                            if nc in temples: return False
                            if nc in hotels: return False
                            if nc in stores: return False
                            return True

                        visited = {mechanic: None}
                        q = deque([mechanic])
                        target = None
                        while q:
                            cur = q.popleft()
                            for ddx, ddy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                                nc = (wrap(cur[0] + ddx), wrap(cur[1] + ddy))
                                if nc in visited: continue
                                if nc in blue_cells and is_adjacent(nc, dragon) and nc != (x, y):
                                    visited[nc] = cur
                                    target = nc
                                    q.clear()
                                    break
                                if passable_t(nc):
                                    visited[nc] = cur
                                    q.append(nc)
                            if target: break

                        if target:
                            path = []
                            cur = target
                            while cur != mechanic:
                                path.append(cur)
                                cur = visited[cur]
                            nxt = path[-1]

                            if nxt in rocks:
                                rocks.discard(nxt)
                                step_messages.append(('ЕГОР ТУАЛЕТОВ РАЗБИЛ КАМЕНЬ КИРКОЙ!', 'blue'))
                                blocked.discard(nxt)

                            was_in_hosp = mechanic in hospitals
                            if not was_in_hosp and mechanic in blocked:
                                blocked.discard(mechanic)

                            mechanic = nxt
                            is_hosp = mechanic in hospitals
                            if not is_hosp and mechanic not in blue_cells:
                                blocked.add(mechanic)

                        if mechanic in blue_cells and is_adjacent(mechanic, dragon):
                            empty_ground = []
                            for cx in range(-HALF, HALF + 1):
                                for cy in range(-HALF, HALF + 1):
                                    c = (cx, cy)
                                    if c in blocked or c in blue_cells or c in money_cells or c == (x, y) or c == (0, 0):
                                        continue
                                    if grid_ground_map.get(c) in ground_symbols:
                                        empty_ground.append(c)

                            empty_ground.sort(key=lambda c: min(toroidal_dist(c, bc) for bc in blue_cells))
                            target_cells = empty_ground[:4]

                            old_chosen = chosen_alien
                            old_aliens = list(aliens)
                            chairs.update(old_aliens)
                            aliens.clear()

                            old_to_new = {}
                            for old_a, new_a in zip(old_aliens, target_cells):
                                aliens.add(new_a)
                                blocked.add(new_a)
                                old_to_new[old_a] = new_a

                                if old_a in alien_messages: alien_messages[new_a] = alien_messages.pop(old_a)
                                if old_a in alien_messages_astronaut: alien_messages_astronaut[new_a] = alien_messages_astronaut.pop(old_a)
                                if old_a in alien_messages_post_mechanic: alien_messages_post_mechanic[new_a] = alien_messages_post_mechanic.pop(old_a)
                                if old_a in alien_messages_post_statues: alien_messages_post_statues[new_a] = alien_messages_post_statues.pop(old_a)
                                if old_a in alien_ground_map: alien_ground_map[new_a] = alien_ground_map.pop(old_a)

                                if old_a in alien_name_asked: alien_name_asked.remove(old_a); alien_name_asked.add(new_a)
                                if old_a in aliens_spoken: aliens_spoken.remove(old_a); aliens_spoken.add(new_a)
                                if old_a in disrespect_aliens: disrespect_aliens.remove(old_a); disrespect_aliens.add(new_a)
                                if old_a in double_disrespect_aliens: double_disrespect_aliens.remove(old_a); double_disrespect_aliens.add(new_a)
                                if old_a in post_statues_spoken: post_statues_spoken.remove(old_a); post_statues_spoken.add(new_a)
                                if old_a in statues_placed_for: statues_placed_for.remove(old_a); statues_placed_for.add(new_a)

                            if old_chosen in old_to_new:
                                key_chair = old_chosen
                                blocked.discard(key_chair)

                            if chosen_alien in old_to_new: chosen_alien = old_to_new[chosen_alien]
                            if welcome_alien in old_to_new: welcome_alien = old_to_new[welcome_alien]
                            if health_alien in old_to_new: health_alien = old_to_new[health_alien]
                            if far_alien in old_to_new: far_alien = old_to_new[far_alien]

                            for sym, old_a in list(symbol_to_alien.items()):
                                if old_a in old_to_new: symbol_to_alien[sym] = old_to_new[old_a]
                            for cell, old_a in list(statue_owner.items()):
                                if old_a in old_to_new: statue_owner[cell] = old_to_new[old_a]

                            titan_battle_state = 3

                    elif titan_battle_state == 3:
                        titan_battle_state = 4
                        if mechanic in blocked:
                            blocked.discard(mechanic)
                        mechanic = None
                        tornado_mode = True

                    if titan_battle_state >= 4:
                        adj = []
                        for ddx, ddy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                            nc = (wrap(dragon[0] + ddx), wrap(dragon[1] + ddy))
                            if nc in blue_cells:
                                adj.append(nc)
                        if adj:
                            dragon = random.choice(adj)

                        if dragon == (x, y):
                            game_over = True
                            health = 0
                            draw(x, y, rocket, hospitals, aliens, temples, hotels, stores, poops, trees, rocks, grid_ground_map, blue_cells, dragon, game_over, health, mechanic, money_cells, eaten, in_rocket, astronaut, money, statues, mechanic_astronaut, inventory, chairs=chairs, tornado_mode=tornado_mode)
                            term = shutil.get_terminal_size((80, 24))
                            rows, cols = term.lines, term.columns
                            start_row = max(1, (rows - VIEW) // 2 + 1)
                            msg_lines = ['ВАС ЗАРУБИЛИ, ЗАГРЫЗЛИ И ЗАТОПТАЛИ', 'В ПЫЛУ БИТВЫ ТИТАНОВ!']
                            for i, ml in enumerate(msg_lines):
                                m_col = max(1, (cols - dw(ml)) // 2 + 1)
                                sys.stdout.write(f'\x1b[{start_row + VIEW + 1 + i};{m_col}H\x1b[31m{ml}\x1b[0m')
                            sys.stdout.flush()
                            get_key()
                            break

    except KeyboardInterrupt:
        pass
    finally:
        sys.stdout.write('\x1b[?25h\x1b[?1049l')
        sys.stdout.flush()
        print('Игра завершена.')

if __name__ == '__main__':
    main()