Маруся-Нетакуся
Маруся-Нетакуся — это не просто игра, это целая история о девушке по имени Маруся. Маруся-Нетакуся. И у неё есть мечта. Нет, не выйти замуж за олигарха. Хотя… нет, не это.
Её мечта — найти отца. Звучит трогательно, да? Отцы и дети, вечная тема. Тургенев там, «Отцы и дети»… Но есть нюанс. Её отец не ушёл на войну. Он не ушёл в другую семью. Он не ушёл за сигаретами, как это обычно бывает. Нет, это было бы слишком банально для нашего города. Папаша Маруси 16 лет назад ушёл… за хлебом.
Так, стоп. 16 лет. За хлебом. Вы понимаете масштаб трагедии? Это не просто «вышел и пропал». Это осознанный выбор. Человек взвесил все «за» и «против»: с одной стороны — жена, маленькая дочь, семейный очаг. С другой — свежая, хрустящая буханка бородинского. И, как видите, выбор был очевиден! Хлеб победил. Счёт 1:0 в пользу хлебопекарной промышленности. И, честно говоря, зная характер нашей Маруси, я начинаю подозревать, что папаша не просто ушёл. Он сбежал. Он, наверное, бежал так быстро, что его тапки обогнали его на повороте. И вот он, спустя 16 лет, возможно, живёт где-нибудь в пекарне, ест батоны, и у него всё хорошо. Потому что у человека, который ушёл от Маруси, не может быть плохо. Это как сбежать из горящего дома. Даже если на улице дождь, ты всё равно счастлив, что не сгорел.
Итак, вступление окончено. Давайте разберём этот шедевр геймдизайна по косточкам, по винтикам, по пикселям. Это будет долгий разговор, потому что тут материала — как в предвыборной кампании. Итак, начинаем наш разбор. Поехали.
Архитектура и городское планирование, или квадратно-гнездовой способ застройки[править]
Первое, что бросается в глаза в этой игре — это город. Это не просто город. Это… трактат по геометрии. Постмодернистское высказывание. Город, который спроектировал человек, который слышал про градостроительство, но решил, что это слишком сложно. Зачем нам кривые улочки, как в Париже? Зачем нам запутанные переулки, как в Праге? Зачем нам история, как в Риме? Нет! Нам нужен идеальный квадрат. Квадрат, разделённый на квадраты, внутри которых квадраты. Это как если бы Казимир Малевич выиграл тендер на реновацию и застроил всё «Чёрными квадратами», но с деревьями.
И вот в этом городе живут люди. Нормальные, адекватные люди. И у них есть свои районы. Названия районов — это отдельная песня. Это не просто названия, это диагнозы. «Ктулховский район». Вы можете себе представить риелтора? «У нас отличная квартира в Ктулховском районе! Развитая инфраструктура, рядом школа, детский сад, и наш владыка Дагон! Очень удобная транспортная развязка, пять минут до Р’льеха!» Или «Дровосексуальный район». Я, честно, боюсь даже спрашивать, какие там вакансии в центре занятости. Наверное, «требуется специалист по интимной близости с древесиной». А «Пердолётный район»? Это же просто гимн авиации! Туда, наверное, не нужны билеты. Просто копишь газы, и — вжух! — ты уже там. «Навуходопоносорский» — для любителей древней истории. «Снюсоедский» — для гурманов. «Ясос-Бибский» — для тех, кто в поиске. А есть ещё «Убитоенотский». Просто… Убитый енот. Как символ. Как тотем. Я представляю герб этого района: на зелёном поле — распластанный енот, пронзённый стрелой. Девиз: «Мы не виноваты».
И вот в этом архитектурном безумии живёт наша Маруся. И она, как ни странно, идеально вписывается. Потому что её внутренний мир — это такой же хаос, только вместо проспектов там — неудовлетворённые желания, а вместо тупиков — завышенная самооценка. Город — это метафора её души. Правда, в её душе, в отличие от города, нет работающей канализации. Отсюда и все проблемы.
Но самое гениальное в городе — это дороги. Они есть. Они куда-то ведут. Точнее, они должны куда-то вести. Но по факту, это сеть, сплетённая пьяным пауком, которого били током. Чтобы перейти дорогу, нужно обладать квалификацией сапёра и удачей каскадёра. И вот тут мы подходим к одному из самых важных элементов геймплея — бабушке.
Бабушка-камикадзе, или «Спасибо, внучка, я всем расскажу, какая ты хорошая девочка»[править]
В этой игре есть мини-квест. Классический. Перевести бабушку через дорогу. Казалось бы, что может быть проще? Ты подходишь к старушке, предлагаешь помощь, она соглашается, и вы, не спеша, на зелёный свет, переходите дорогу. Идиллия. Пастораль. Но не в этом городе.
Здесь бабушка — это не просто бабушка. Это — терминатор. Это — замаскированный суперсолдат. У неё нет тормозов, нет чувства самосохранения, и, самое главное, у неё нет страха. Она стоит на обочине и ждёт. Ждёт свою жертву. Свою внучку.
И вот ты, весь такой благородный, подходишь к ней. «Давайте я переведу Вас через дорогу». И тут начинается. Эта бабушка, которая, казалось бы, должна молиться на каждый свой сустав, срывается с места, как ракета «Булава». И ты должен идти с ней НОГА В НОГУ. Ты — её личный ассистент по выживанию. Ты — её телохранитель. Ты — её поводырь в мир, где автобусы ездят по расписанию «всегда».
И вот ты идёшь. Шаг. Ещё шаг. Бабушка смотрит вперёд. Ты смотришь на дорогу. И тут ты понимаешь, что весь этот город, все эти дороги, все эти пробки — они не просто так. Они созданы, чтобы убить эту конкретную бабушку. Каждый автобус — это управляемая ракета, нацеленная на её божий одуванчик.
А если ты, не дай бог, решишь изменить направление? Если ты поймёшь, что на вас несётся многотонный «Икарус» с горящим взглядом водителя? Если ты инстинктивно дёрнешься в сторону? Всё. Конец. Ты не можешь бросить бабушку. Ты прикован к ней, как к галере. Ты — Сизиф. Бабушка — камень. Дорога — гора. Автобус — Зевс с обострением геморроя.
Логика у бабушки железная. Она считает, что раз ты предложил помощь, то ты несёшь за неё полную ответственность. И не важно, что ты увидел, как на вас несётся смерть. Ты не имеешь права на панику. Ты должен идти ровно, смотреть вперёд и молиться, чтобы водитель автобуса в этот момент не отвлёкся на смс от жены. Потому что, если он отвлечётся, то бабушка станет не просто бабушкой. Она станет… артефактом. Ленточкой. Памятью.
И вот, если тебе чудом удастся перевести её через эту полосу препятствий, она говорит эту гениальную фразу: «Спасибо, внучка. Я всем расскажу, какая ты хорошая девочка». Стоп! Всем?! Бабушка, а не много ли ты на себя берёшь? Ты что, блогер? У тебя своя газета? Ты главный редактор районного паблика «Подслушано у Пердолётного»? Кому ты расскажешь? Своим подругам на лавочке? «Знаете, а я сегодня познакомилась с замечательной девушкой! Она перевела меня через дорогу. А потом, когда мы дошли до дома, она пошла дальше и, кажется, собиралась собирать розы в саду у Ктулховского района». Всё, репутация испорчена.
И самое смешное, что Маруся реально на это ведётся. Она реально считает, что помощь бабушке — это её билет в рай. Что этот поступок перевесит все её остальные «подвиги». Что там, в небесной канцелярии, ангел-секретарь поставит галочку: «Перевела бабушку через дорогу. Зачтено. Минус один грех за попытку соблазнить механика». Ага, сейчас. Не в этом городе.
Эта игра беспощадно высмеивает лицемерие. Маруся делает «добрые» дела не потому, что она добрая. А потому, что ей это выгодно. Ей нужна репутация. Ей нужно, чтобы о ней говорили. Ей нужно, чтобы отец, этот великий хлебный странник, узнал, какая у него замечательная дочь. Но, как мы знаем, папаше на это глубоко фиолетово. Он там, с хлебом, и ему хорошо.
Бабушка, кстати, тоже хороша. Она не задаётся вопросом: «А почему эта девушка с маникюром и в мини-юбке помогает мне перейти дорогу?» Ей всё равно. Ей нужна помощь. И она её получает. А то, что её спасительница — городская сумасшедшая с комплексом спасителя, — это уже детали. Главное, что до дома довели. А что было по дороге — это уже не важно. Подумаешь, пару раз чуть не сбили автобусом. Зато какая история!
Флорист-эксгибиционист, или розы, ленточки и селфи на фоне истукана[править]
Помимо бабушки, у Маруси есть ещё одна страсть. Это сбор природных материалов. Она собирает розы. И не просто розы, а розы, которые растут… везде. На газонах, у дорог, в каких-то заброшенных кварталах. Она как Терминатор, только вместо «I need your clothes, your boots and your motorcycle» — «I need your roses, your ribbons and your likes on Instagram».
И вот она ходит по городу и собирает розы. Она их срывает. Она их коллекционирует. Для чего? Для букета. Чтобы сделать букет, который она подарит… кому? Правильно, кавказцу. Мужчине, которого она видит впервые в жизни. Логика железная: «Мне не дают, значит, нужно подарить цветы незнакомому мужику, который, возможно, в этом городе проездом. И тогда он, ослеплённый моей щедростью, поведёт меня в клуб, напоит текилой и, возможно, женится».
Гениальный план. Надёжный, как швейцарские часы. Только вот есть нюанс. Розы нужно не просто собрать. Их нужно переработать. У памятника. У памятника Василию Пупкину. И не просто у памятника, а с помощью ленточки, которую она подбирает… где? Правильно, на дороге. Там, где она, возможно, недавно оставила нашу бабушку.
Итак, у неё есть розы (нечётное количество). У неё есть ленточка (с места ДТП). И у неё есть… всё. Она подходит к памятнику Пупкину. И тут происходит таинство. Она не просто делает букет. Она… медитирует. Она произносит: «ЭВРИКА! Я сделаю букет из роз и ленточки! Хоп-хоп-хоп — готово! Так, улыбочку! Щёлк! А фото выложу в Нельзяграм и получу кучу лайков! 😍».
Стоп! СТОП! Она делает букет не для того, чтобы подарить его? А для того, чтобы сделать селфи?! Вся эта эпопея с розами, ленточками, памятником — это всё ради фотографии в социальную сеть?! Ради лайков?! Ну, конечно! Это же логично! Зачем дарить цветы живому человеку, если можно сфотографировать их на фоне мертвого истукана и получить одобрение от ботов и бывших одноклассниц, которые, кстати, уже давно замужем и родили по двое детей? Это же намного важнее!
И вот, смотрите, как это выглядит со стороны. Идёт по городу девушка. В одной руке — охапка краденых роз. В другой — ленточка с места трагедии. Она подходит к памятнику, начинает там что-то мастерить, приговаривая «хоп-хоп-хоп». Фотографирует. Выкладывает. Ждёт. Лайки не приходят. Потому что всем на неё плевать. Потому что в этом городе у всех свои проблемы: у кого-то — Ктулху в подвале, у кого-то — дровосексуальная зависимость.
И тут-то она и решает, что букет можно использовать и для второго шанса. Для кавказца. Логика: «Фото не зашло, но, может, зайдёт кавказцу». И она, с этим букетом, который уже морально устарел, идёт к нему. И он ей говорит что-то вроде: «Вах, какой дэвущка! Зачэм тебе этот жялкий веник? Это жьиних купиль, да? Викинь его, айда в клюб».
То есть даже кавказец, который, по идее, должен быть впечатлён букетом, видит в нём просто жалкий веник! Он не оценил! Он не понял! Он предложил ВЫКИНУТЬ букет! И Маруся… соглашается! Она выбрасывает свой букет, своё творение, свой билет в мир лайков, ради призрачной надежды на текилу! Это же просто крик души! Это просто… Маруся.
И вот она идёт в клуб. И там, в клубе, она, естественно, находит себе приключения на одно место. Но об этом — позже. Сначала нужно понять, что движет этой женщиной. А движет ей не поиск отца. И не желание помочь бабушке. И не любовь к прекрасному. Ей движет отчаянное желание быть хоть кому-то нужной. Хоть на секунду. Хоть на одно селфи. Хоть на один танец. Хоть на один глоток текилы. И это, на самом деле, очень грустно. Если бы она не была такой… Марусей.
Олень, соль и месть, или «Как я перестала волноваться и полюбила лес»[править]
В этой игре есть очень странная механика. И связана она с оленями. В лесу, за городом, живут олени. Они там едят мох, пьют воду из ручьёв и, видимо, ждут, когда какая-нибудь Маруся принесёт им соль. Потому что олени в этом мире — это, по сути, жители Ленинградской области. Они обожают соль.
И вот, у Маруси есть соль. Откуда? Из клуба. Из того самого клуба, где она пила текилу. Точнее, ей дали соль и лимон, как атрибут для распития. Но она, конечно же, не стала пить текилу, как нормальный человек. Она соль оставила. Лимон — потом засунет… ну, вы поняли. А соль — оставила. Для оленя.
И вот она идёт по лесу. Зачем? А кто её знает. Может, грибы собирает. Может, ищет отца, который, возможно, ушёл за хлебом не а булочную, а в лес. И тут она видит оленя. Красивого, грациозного, с большими глазами. И она, конечно же, решает, что олень — это её шанс. Шанс на что? На всё!
Она подходит к оленю и протягивает ему соль. Олень, будучи адекватным существом, соль слизывает. И тут у Маруси, видимо, происходит какой-то сбой в прошивке. Она начинает жаловаться оленю на жизнь. «Меня Егор Туалетов обидел! 😭». И олень, представьте себе, олень, который только что съел соль, вдруг говорит: «ЧТО?! Он посмел обидеть ДЕВУШКУ?! Надо срочно набить ему морду! Веди меня к нему!»
Что?! Олень — радикальный феминист?! Олень — борец за права женщин?! Олень — член движения #MeToo?! И он готов идти бить морду механику по имени Егор Туалетов! Это же просто… гениально! Это лучший сюжетный поворот в истории видеоигр!
И вот, Маруся, вместо того, чтобы решать свои проблемы сама, ведёт оленя к механику. Олень идёт за ней, как верный пёс. Как телохранитель. Как её личный Дмитрий Нагиев. И вот они приходят к механику. И олень, этот благородный зверь, вызывает Туалетова на дуэль. Дуэль века! Человек против оленя! Молот против рогов!
И что делает Егор? Он, будучи адекватным человеком, вступает в бой. И побеждает. Он убивает оленя. И что делает Маруся? Правильно! Она извиняется! «Прости меня, Егор. Я была неправа». Стоп! Ты привела оленя, который хотел набить ему морду, а теперь ты извиняешься?! Ты использовала благородное животное, как разменную монету, а теперь, когда твой план провалился, ты идёшь на попятную?!
Логика Маруси проста: если олень победил — она с героем. Если механик победил — она с механиком. И в том, и в другом случае, она при делах. Она — серый кардинал. Она — кукловод. Она — вершительница судеб. Но по факту, она просто… неадекватная.
И вот олень мёртв. Механик дал ей свой номер телефона. И говорит: «Проехали. Запиши мой телефон, авось пригодится». «Авось пригодится»! Егор, ты даже не представляешь, насколько ей пригодится. Потому что Маруся — это не просто девушка. Это ходячая катастрофа. И твой номер телефона — это не номер телефона. Это номер спасательной службы, которую вызовут на место происшествия, когда Маруся снова попадёт в какую-нибудь передрягу.
А олень… Олень стал жертвой. Жертвой Марусиной глупости и эгоизма. Он поверил в то, что он — рыцарь. А оказался просто мясом в этой драме. И, что самое печальное, его смерть ничему Марусю не научила. Она как была эгоисткой, так и осталась. Только теперь у неё есть телефон механика.
Механик Егор Туалетов, или «Чё те надо, овца?»[править]
Поговорим о Егоре. Егор Туалетов — механик. Он живёт у чёрта на рогах, в каком-то отдалённом квартале. Он чинит машины. Он, судя по всему, неплохой специалист. И он — единственный адекватный NPC в этой игре. Потому что он единственный, кто видит Марусю насквозь.
Первое, что он ей говорит: «Чё те надо, овца?» Не «Здравствуйте, милая леди». Не «Чем могу помочь?» А именно: «Чё те надо, овца?». И это — идеальная реакция. Потому что Маруся действительно овца. Она приходит к нему с каким-то туманным намерением «познакомиться». Она не знает, зачем она пришла. Она не знает, чего она хочет. Она просто пришла, потому что он мужчина и он в списке её потенциальных жертв.
Она спрашивает его: «У тебя есть жена или девушка?» А он: «Не твоё дело». И он прав! Это действительно не её дело! Она его видит в первый раз, а уже лезет в личную жизнь! Она, видите ли, «должна это знать»! Зачем? Чтобы понять, есть ли у неё шанс. Шанс на что? На отношения? На секс? На брак? На то, чтобы он её содержал?
Механик — единственный, кто ставит её на место. Он говорит: «Я не NPC, а живой человек, так что ничем помочь не могу!» Это — прорыв четвёртой стены. Это — крик души разработчика. «Я не NPC, я живой человек!» Это заявление, которое слышит каждая вторая женщина на сайтах знакомств.
Маруся, конечно, не понимает. Она думает: «Как это — не NPC? Все вокруг — NPC! Есть я — главная героиня, а есть все остальные — фон, декорации, массовка. Они должны мне помогать, обслуживать меня, развлекать меня и, желательно, вступать со мной в интимную связь по моему первому требованию». А механик ей говорит: «Нет. Я живой. У меня есть свои дела. Своя жизнь. И ты в неё не входишь».
И это — самое главное противостояние в игре. Противостояние между эгоцентричным миром Маруси, где она — пуп земли, и суровой реальностью, где она — просто овца, которая лезет не в своё дело. И механик — главный защитник этой реальности. Он — последний бастион здравого смысла. Он — герой нашего времени.
Но, как мы знаем, даже он в итоге сдаётся. После случая с оленем, он даёт ей свой номер. Он, возможно, чувствует вину. Он, возможно, видит, что она — не просто овца, а заблудшая овца. И он решает дать ей шанс. «Авось пригодится». И он, сам того не зная, подписал себе приговор. Потому что Маруся обязательно этим воспользуется.
И, надо сказать, сцена с оленем — это единственный момент, когда Маруся вызывает жалость. Она стоит, смотрит на мёртвого оленя и понимает, что натворила. Она понимает, что её план провалился. Она понимает, что механик оказался сильнее. И она извиняется. Искренне? Возможно. Но, зная Марусю, скорее всего, это очередная манипуляция. Потому что, как только она получает номер телефона, её печаль тут же испаряется. У неё теперь есть контакт. Цель достигнута. Олень мёртв, да здравствует новый поклонник!
Клубная жизнь, или медляк с текилой и ЗППП[править]
Вернёмся в клуб. В этот чудный мир музыки, огней и разбитых надежд. Маруся попала туда благодаря кавказцу. Точнее, благодаря его приглашению. И вот она внутри. И что она делает? Конечно же, она напивается. И не просто напивается, а в хлам. Потому что Маруся не умеет пить. Она умеет только создавать проблемы.
И вот она сидит с кавказцем, пьёт текилу и слушает местные хиты. Хиты, кстати — отдельная тема. Это песни, которые крутят в клубе. И они, мягко говоря, специфические. «Трахнул я одну ТП — оказалась РСП». Шедевр. Просто пушкинская строка. Я рыдаю. «Трахнул я её ЛП — подхватил ЗППП». Это же готовый гимн для кожно-венерологического диспансера! «Получилось тупо — чешется залупа!» Это же надо так точно передать суть!
И вот Маруся под эти чудные напевы общается с кавказцем. И выясняется, что кавказец — это не ходячий стереотип. У него нет акцента. Он просто «угорал». Он — обычный человек. Он — личность. И он, как и все в этом городе, не тот, кем кажется.
Маруся не замечает подвоха. Она пьёт, танцует, требует групповушку. «А потом там меня трахнут, я надеюсь? Я бы не отказалась от групповушки!» И тут — бинго! Полиция. «Девушка, вы пьяны, пройдёмте на выход». Всё. Клуб окончен. Маруся в чёрном списке. Навеки. Во всех клубах города.
Как ей это удалось? Как можно так быстро и эффективно испортить себе репутацию? Вопрос риторический. Маруся — это генератор хаоса. Она приходит в место — и место начинает рушиться. Она была у механика — механик стал врагом. Она была в клубе — клуб внёс её в чёрный список. Она была в лесу — погиб олень. Она была у памятника — памятник, наверное, покраснел от стыда.
И вот, после клуба, у неё на руках: лимон, соль и чувство глубокого неудовлетворения. Соль — для оленя. Лимон — для места, которое некоторые олени любят лизать. Чувство неудовлетворения — для дальнейших подвигов. И она, как настоящий солдат, продолжает свой путь. Путь к отцу. К хлебу. К истине. Которая, как мы знаем, где-то рядом. В очереди.
Стоп. А кавказец? Он же вроде приглашал её в гости в кавказский квартал. «Там тебя всегда накормят и напоят». И Маруся, конечно же, решит этим воспользоваться. Потому что ей всегда нужно, чтобы её накормили и напоили. А если ещё и трахнут, то это будет просто идеальный день.
И она идёт в гости. В кавказский квартал. И там, как вы понимаете, её ждут новые приключения. Приключения с запахом кавказской кухни и разочарования.
Кавказская кухня, или «Всем гамарджоба, барев дзес и салам алейкум!»[править]
Итак, Маруся идёт в гости к кавказцу. Точнее, к его родственникам. В кавказский квартал. И это — отдельный акт в этой трагикомедии.
Она приходит и заявляет с порога: «Всем гамарджоба, барев дзес и салам алейкум! Ваш родственник сказал, что здесь меня накормят и напоят». Она, видите ли, знает все обычаи. Она — полиглот. Она — эксперт по кавказскому этикету. Она — Маруся.
И её сажают за стол. За стол, полный еды. Там хинкали, вино, шашлыки, харчо. И Маруся… ест. И пьёт. И ест. И пьёт. Она хомячит так, как будто её не кормили 16 лет. Как будто она — та самая дочь, которую бросил отец, ушедший за хлебом.
И самое интересное — она комментирует. «Ам!», «Глыг-глыг!», «Сёрб-сёрб-сёрб!», «Ам-ам-ам-ам-ам!». Это же просто апофеоз. Человек пришёл в гости и жрёт, как будто он пылесос. И она ещё удивляется, что её никто не хочет. Да кто захочет женщину, которая ест, как голодный медведь после спячки?
И вот, она всё съела. Всё выпила. И говорит: «Как-то скромненько, мальчики. Я ожидала большего. Всем пока! 💋». СТОП! Она пришла в гости, её накормили, напоили, а она говорит «скромненько»?! Она ожидала, что ей зажарят целого барана? Она думала, что ради неё будут резать последнего быка? Она считала, что её ждёт пир на весь мир?
И после этого она уходит. Сытая, пьяная и… с чувством, что её обделили. И, как бонус, после такого обжорства у неё возникает естественная потребность. Она хочет в туалет. И не просто хочет, а очень хочет. Потому что вся эта еда, все эти «ам-ам» и «глыг-глыг» — они не усвоились. Они требуют выхода.
И вот тут начинается самая, пожалуй, драматичная и напряжённая часть её приключений. Потому что найти в этом городе работающий туалет — это задача ненамного легче, чем найти отца. И это, как вы понимаете, о многом говорит.
Туалетный квест, или «Спасите, я в сортире!»[править]
Итак, Маруся объелась кавказской кухни. И её организм, не привыкший к такому количеству жирной, острой и вкусной пищи, взбунтовался. Ей нужно в туалет. Срочно. Прямо сейчас. И тут начинается гонка со временем.
По городу разбросаны туалеты. Но, как и всё в этом городе, они не так просты. Это не просто кабинки. Это — ловушки. Это — испытания. Это — проверка на прочность. И Маруся, наша отважная героиня, ищет заветную дверь.
И вот, она находит туалет. Заходит. И… дверь заклинивает. Классика. Она застряла. Она в ловушке. И она начинает звать на помощь. «СПАСИТЕ!»
И вот тут-то и проявляется вся глубина её «связей». Если у неё есть номер механика (а он у неё, конечно же, есть, потому что она — Маруся, и она всегда добивается своего), то она звонит ему. Она зовёт на помощь. И механик, как настоящий герой, приходит. И вытаскивает её.
И что он получает в благодарность? «Давай я тебе отсосу в знак благодарности!» И что он отвечает? «Иди на хуй, пока я тебя в сортире не закрыл!» Гениально! Просто гениально! Это лучший ответ на лучшее предложение. Он не просто отказывается. Он угрожает ей. Он ставит её на место. Он — единственный, кто понимает, что иметь дело с Марусей — себе дороже.
Но, если номера механика нет, то Марусю ждёт печальная участь. Она задыхается в туалете. Она умирает. Одна. В окружении мух и… сами понимаете, чего. И это — заслуженный финал. Потому что нельзя быть такой дурой. Нельзя столько есть. Нельзя столько пить. И нельзя надеяться на то, что кто-то придёт и спасёт тебя, если ты сама не в состоянии справиться с элементарными вещами.
Туалетный квест — это кульминация Марусиной беспомощности. Она не может найти отца. Она не может перевести бабушку. Она не может завести отношения. И она не может даже сходить в туалет. Она — инвалид по жизни. Она — человек, который нуждается в постоянной опеке. И то, что она ещё жива — это просто чудо. Или, возможно, проклятие. Проклятие, которое она несёт всем, кто с ней рядом.
И вот, после туалета, она, конечно же, получает туалетную бумагу. И делает из лимона… лимон-тампон. И покрывает голову туалетной бумагой. Чтобы пройти в церковь. В церковь! Потому что без туалетной бумаги на голове её туда не пустят. Логика? Абсурд? Да, это Маруся.
Религиозный экстаз и финальный аккорд, или «Квадрат — большой наш брат»[править]
Итак, Маруся решает сходить в церковь. Зачем? Ну, наверное, чтобы замолить грехи. Или чтобы найти там отца. Или чтобы просто убить время. Причины не важны. Важно то, что в церковь её не пускают без «покрытой головы». И она решает, что лучший головной убор — это туалетная бумага. А чтобы всё это выглядело «эстетично», она добавляет лимон. В качестве… аксессуара. Но не на голову. А в… себя. Потому что она не может просто так взять и не засунуть куда-нибудь лимон.
И вот она, с бумагой на голове и лимоном в… там, заходит в церковь. И попадает на службу. И слушает проповедь. Проповедь о Священном Квадране. О том, что «Простой Квадрат — Большой Наш Брат». О том, что «нет ничего лучше квадрата». О том, что «круглую Землю мы измеряем квадратными единицами».
Это — религия Квадрата. Религия, которая идеально вписывается в этот квадратный город. И Маруся, с её квадратной головой, с её квадратными мыслями, с её квадратными желаниями — идеальная прихожанка. Она верит в Квадрат. Потому что Квадрат — это просто. Квадрат — это понятно. Квадрат — это она сама.
И в этой церкви, под эти квадратные песнопения, Маруся, возможно, впервые в жизни чувствует себя на своём месте. Она — часть чего-то большего. Она — винтик в квадратном механизме. Она — не просто Маруся, а Маруся-прихожанка. Маруся-верующая. Маруся-с-бумагой-на-голове-и-лимоном-в… там.
Но, конечно, и тут не обходится без приключений. Потому что из неё начинает «пахнуть рыбой в лимонном соусе». И её просят удалиться. И она уходит. И, видимо, оставляет свой лимон-тампон где-то в церкви. Или, может, забирает с собой. Не важно. Важно то, что церковь она тоже «пометила».
После церкви её ждёт финал. Тот самый финал, ради которого всё затевалось. Встреча с отцом.
И вот она находит его. В очереди. В очереди за хлебом. Он стоит там 16 лет. Он не может дождаться. Очередь огромная. И он в ней. Стоит. С хлебом. Не доходя до дома.
И Маруся, увидев его, кричит: «ПАПА?!» А он: «МАРУСЯ?!» И начинается трогательная сцена. Но, зная Марусю, даже эта сцена превращается в фарс. Она начинает врать. Что она «тихая, скромная и добрая». Что она «переводит бабушек через дорогу». Что она «ходит в церковь». Ага. Конечно. Мы-то знаем, как она переводит бабушек. И как она ходит в церковь. С лимоном в… да.
И тут, в очереди, находится свидетель. Вампир. Да, вампир. Который, видимо, тоже стоит в очереди за хлебом. И он начинает говорить правду. Что Маруся — «та самая шлю…». Но она, не будь дурой, тут же накачивает бицепс и говорит: «Что, простите? У меня бицепс зачесался, я не расслышала». И вампир, испугавшись её бицепса, говорит: «Я говорю: шлю привет тебе и твоему отцу!» И папаша, конечно же, в восторге. Он не видит, что его дочь — просто хулиганка с манией величия. Он видит только красивую, сильную девушку.
И вот финал. Отец с хлебом. Маруся в слезах. Мама дома. Идиллия. Победа. Титры.
Но, если задуматься, что мы видим? Мы видим, что Маруся нашла отца не благодаря своим талантам. Она нашла его, просто дойдя до нужного магазина. Она не проявила ни ума, ни смекалки, ни доброты. Она просто шла по квестовым маркерам, как заведённая. И все её «подвиги» — это просто череда ошибок, глупостей и случайностей.
Она — героиня поневоле. Точнее, не героиня, а просто… главная героиня. Которая по воле сценария должна найти отца. И она его находит. Не потому, что она этого заслужила. А потому, что так прописано в коде.
И вот отец вернулся. С хлебом. Через 16 лет. И что он скажет? Наверное, «Ну и денёк». Или «Я забыл, зачем уходил». Или «А где мой суп?» А Маруся, наверное, скажет: «Папа, у меня есть номер телефона механика». И всё начнётся сначала.
Эта игра не о поиске отца. Эта игра о том, как одна неадекватная девушка способна разрушить жизни всех, кто её окружает. Она — как вирус. Она проникает в город, заражает его своим безумием и оставляет после себя хаос, разрушения и труп оленя.
И знаете, что самое смешное? Что эта девушка, Маруся — она есть в каждом из нас. В каждом из нас есть та самая часть, которая хочет, чтобы на нас обратили внимание. Которая готова на всё ради лайка. Которая готова притворяться «хорошей девочкой», чтобы получить своё. Которая готова вести оленя на смерть, лишь бы добиться своего.
И именно поэтому этот квест так цепляет. Не потому, что он смешной. А потому, что он — правдивый. Потому что Маруся — это мы. Только без трусов, забытых где-то в клубе.
Волшебное заклинание для создания мира и оживления Маруси[править]
import os
import sys
import random
import shutil
import time
import textwrap
import math
import unicodedata
world = {}
V_states = {}
H_states = {}
px, py = 0, 0
player_msg = ""
roses_collected = 0
ribbons_collected = 0
bouquets_collected = 0
lemon = 0
salt = 0
toilet_paper = 0 # 🧻
monument_met = False
monument_coords = None
shovel = 0 # 🥄
holes_dug = 0 # количество выкопанных ямок
women_defeated_count = 0 # сколько 👩 побеждено в драке
women_npcs = [] # координаты всех 👩 возле магазина
option_block_active = False # показывать ли опции
current_woman_coords = None # координаты соседней 👩
grandma = {
'x': 0,
'y': 0,
'state': 'none', # none, waiting, crossing, going_home, dead
'under': '🔹',
'crossing_dir': (0, 0),
'start_x': 0, # начальная позиция бабушки
'start_y': 0
}
mechanic = None
car = {}
stop_signs = set()
buses = []
district_names = {}
district_colors = {}
sheeps = []
grass_cells = set()
# Новые глобальные переменные для кавказца и клуба
new_npc = None
club_entrance = None
club_invited = False
club_visited = False
club_blacklist = False
pending_invite = False
feast_visited = False
# Глобальные переменные для оленя
deer = None # объект оленя (если следует за героиней)
player_in_bus = False
# Переменная для отслеживаемой цели (компас)
tracked_target = None # может быть 'grandma', 'monument', 'new_npc', 'mechanic', 'shop', 'hut' или None
# Множество клеток автобусных дорог
bus_road_cells = set()
# Новые глобальные словари для названий проспектов
prospect_names_h = {} # B -> название для горизонтальных
prospect_names_v = {} # B -> название для вертикальных
# Туалетные переменные
toilet_patience = None # None - нет нужды, число - осталось терпения
mechanic_killed_deer = False
player_is_clown = False
# Новые переменные для превращения в эльфа
player_is_elf = False
lemon_tampon = False
# Новые переменные для могильщиков
gravediggers = [] # список словарей {'x','y','under','state','cross_dir','steps_crossed'}
coffin_coords = None
gravedigger_triggered = False
gravedigger_move_countdown = 0
church_visited_global = False
gravedigger_block_dialogue_shown = False # был ли показан диалог преграждения пути
gravedigger_dialogue_shown = False # был ли показан основной диалог с могильщиками
# Новый словарь для хранения исходных клеток под NPC очереди и 👩
queue_npcs_under = {}
# Координаты магазина с очередью NPC (для компаса)
shop_with_queue_coords = None
# Геометрический центр квартала с хижинами (для компаса)
hut_center = None
def setup_districts():
names = [
"Ктулховский", "Дартовский", "Норрисовский", "Пупкинский",
"Чердачный", "Дровосексуальный", "Навуходопоносорский", "Ясос-Бибский",
"Пердолётный", "Котобутербродный", "Новосибирский", "Снюсоедский",
"Асбестовый", "Квадранский", "Абсурдоляндский", "Ёпрстский",
"Цедрейтерский", "Полуторагодовалый", "Спринтерский", "Убитоенотский",
"Сиконский", "Анциклопедийный", "Черненковский", "Насубарупоедский"
]
random.shuffle(names)
colors = ['\033[91m', '\033[92m', '\033[93m', '\033[94m', '\033[95m', '\033[96m']
for r in range(5):
for c in range(5):
if c == 2 and r == 2:
district_names[(c, r)] = "ЦЕНТРАЛЬНЫЙ"
else:
district_names[(c, r)] = names.pop().upper()
used_colors = set()
if c > 0:
used_colors.add(district_colors[(c-1, r)])
if r > 0:
used_colors.add(district_colors[(c, r-1)])
available_colors = [col for col in colors if col not in used_colors]
district_colors[(c, r)] = random.choice(available_colors)
def get_district_index(coord):
if -111 <= coord <= -69: return 0
if -66 <= coord <= -24: return 1
if -21 <= coord <= 21: return 2
if 24 <= coord <= 66: return 3
if 69 <= coord <= 111: return 4
return None
def get_district(x, y):
c = get_district_index(x)
r = get_district_index(y)
if c is None or r is None:
return None
return (c, r)
def getch():
try:
if os.name == 'nt':
import msvcrt
ch = msvcrt.getwch()
if ch in ('\x00', '\xe0'):
ch2 = msvcrt.getwch()
if ch2 == 'H': return 'up'
if ch2 == 'P': return 'down'
if ch2 == 'K': return 'left'
if ch2 == 'M': return 'right'
return ch.lower()
else:
import tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
if ch == '\x1b':
ch2 = sys.stdin.read(1)
if ch2 == '[':
ch3 = sys.stdin.read(1)
if ch3 == 'A': return 'up'
if ch3 == 'B': return 'down'
if ch3 == 'D': return 'left'
if ch3 == 'C': return 'right'
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch.lower()
except Exception:
pass
return ''
def is_V_road(Bx, By):
if Bx in (-13, 12) and (By <= -13 or By > 12):
return True
if -13 <= Bx <= 12 and -13 <= By <= 12:
return V_states.get((Bx, By), False)
return False
def is_H_road(Bx, By):
if By in (-13, 12) and (Bx <= -13 or Bx > 12):
return True
if -13 <= Bx <= 12 and -13 <= By <= 12:
return H_states.get((Bx, By), False)
return False
def get_road_type(x, y):
bx = (x + 3) % 9
by = (y + 3) % 9
Bx = (x + 3) // 9
By = (y + 3) // 9
if bx < 7 and by < 7:
return None
elif bx >= 7 and by < 7:
return '||' if is_V_road(Bx, By) else None
elif bx < 7 and by >= 7:
return '🟰' if is_H_road(Bx, By) else None
else:
top = is_V_road(Bx, By)
bottom = is_V_road(Bx, By + 1)
left = is_H_road(Bx, By)
right = is_H_road(Bx + 1, By)
roads_count = sum([top, bottom, left, right])
if roads_count == 0:
return None
elif top and bottom and not left and not right:
return '||'
elif left and right and not top and not bottom:
return '🟰'
else:
return '🔘'
def cell_rng(x, y, tag):
return random.Random(hash((x, y, tag))).random()
def is_road_adjacent(x, y):
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
if get_road_type(x + dx, y + dy) is not None:
return True
return False
def is_building(x, y):
if get_road_type(x, y) is not None or is_road_adjacent(x, y):
return False
return cell_rng(x, y, "building") < 0.2
def is_stop_sign(x, y):
return (x, y) in stop_signs
def is_disco_ball(x, y):
return x in (-90, -45, 0, 45, 90) and y in (-90, -45, 0, 45, 90) and (x != 0 or y != 0)
def is_tree(x, y):
if is_stop_sign(x, y): return False
if is_disco_ball(x, y): return False
if get_road_type(x, y) is not None or is_road_adjacent(x, y): return False
if abs(x) > 114 or abs(y) > 114: return False
if is_building(x, y): return False
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: continue
nx, ny = x + dx, y + dy
if is_building(nx, ny) or is_disco_ball(nx, ny):
return False
return cell_rng(x, y, "tree") < 0.4
def is_far_from_road(x, y, radius=4):
"""Проверяет, что в радиусе radius клеток нет дороги (расстояние >= radius+1)."""
for dx in range(-radius, radius+1):
for dy in range(-radius, radius+1):
if dx == 0 and dy == 0:
continue
if get_road_type(x+dx, y+dy) is not None:
return False
return True
def generate_cell(x, y):
if is_stop_sign(x, y): return '🛑'
if is_disco_ball(x, y): return '🪩'
road = get_road_type(x, y)
if road: return road
if is_road_adjacent(x, y): return '🔸'
if abs(x) > 114 or abs(y) > 114:
# Лес за городом на достаточном удалении от дорог
if is_far_from_road(x, y):
r = cell_rng(x, y, "country")
if r < 0.4:
return '🌲'
elif r < 0.41: # 1% вероятность
return '🦌'
else:
return '▪️'
else:
if cell_rng(x, y, "country") < 0.4: return '🌲'
return '▪️'
bx_mod = (x + 3) % 9
by_mod = (y + 3) % 9
Bx_idx = (x + 3) // 9
By_idx = (y + 3) // 9
if bx_mod < 7 and by_mod < 7:
is_outer_edge = (Bx_idx in (-12, 12) or By_idx in (-12, 12))
if is_outer_edge:
top_road = is_H_road(Bx_idx, By_idx - 1)
bottom_road = is_H_road(Bx_idx, By_idx)
left_road = is_V_road(Bx_idx - 1, By_idx)
right_road = is_V_road(Bx_idx, By_idx)
if top_road and bottom_road and left_road and right_road:
if 1 <= bx_mod <= 5 and 1 <= by_mod <= 5:
lx = bx_mod - 1
ly = by_mod - 1
if ly == 2 or lx == 2: return '▪️'
else: return '🏡'
else:
return '▪️'
if is_building(x, y): return '🏢'
has_adj_building_or_disco = False
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: continue
nx, ny = x + dx, y + dy
if is_building(nx, ny) or is_disco_ball(nx, ny):
has_adj_building_or_disco = True
break
if has_adj_building_or_disco: break
if not has_adj_building_or_disco:
if cell_rng(x, y, "tree") < 0.4:
if x == 0 and y == 0:
return '🔹'
return '🌳'
has_adj_tree = False
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: continue
if is_tree(x + dx, y + dy):
has_adj_tree = True
break
if has_adj_tree: break
if not has_adj_tree:
if abs(x) <= 111 and abs(y) <= 111:
if cell_rng(x, y, "rose") < 0.2: return '🌹'
return '🔹'
def generate_area(cx, cy):
for y in range(cy - 17, cy + 18):
for x in range(cx - 17, cx + 18):
if (x, y) not in world:
world[(x, y)] = generate_cell(x, y)
def init_city():
setup_districts()
global V_states, H_states, stop_signs, grandma, mechanic, sheeps, grass_cells, monument_coords, car
global new_npc, club_entrance, club_invited, club_visited, club_blacklist, pending_invite, feast_visited
global deer, bus_road_cells, prospect_names_h, prospect_names_v
global toilet_patience, mechanic_killed_deer, player_is_clown, toilet_paper
global player_is_elf, lemon_tampon
global gravediggers, coffin_coords, gravedigger_triggered, gravedigger_move_countdown, church_visited_global, shovel
global gravedigger_block_dialogue_shown, gravedigger_dialogue_shown
global queue_npcs_under, shop_with_queue_coords
global holes_dug, women_defeated_count, women_npcs, option_block_active, current_woman_coords
global hut_center
toilet_patience = None
mechanic_killed_deer = False
player_is_clown = False
toilet_paper = 0
player_is_elf = False
lemon_tampon = False
gravediggers = []
coffin_coords = None
gravedigger_triggered = False
gravedigger_move_countdown = 0
church_visited_global = False
shovel = 0
holes_dug = 0
women_defeated_count = 0
women_npcs = []
option_block_active = False
current_woman_coords = None
gravedigger_block_dialogue_shown = False
gravedigger_dialogue_shown = False
queue_npcs_under = {} # очищаем словарь
shop_with_queue_coords = None # сбрасываем координаты магазина
hut_center = None
edges_V = {}
edges_H = {}
for y in range(-12, 13):
edges_V[(-13, y)] = True
edges_V[(12, y)] = True
for x in range(-12, 13):
edges_H[(x, -13)] = True
edges_H[(x, 12)] = True
guaranteed_V = [-8, -3, 2, 7]
for bx in guaranteed_V:
for y in range(-12, 13): edges_V[(bx, y)] = True
guaranteed_H = [-8, -3, 2, 7]
for by in guaranteed_H:
for x in range(-12, 13): edges_H[(x, by)] = True
for x in range(-12, 12):
for y in range(-12, 13):
if random.random() < 0.8: edges_V[(x, y)] = True
for x in range(-12, 13):
for y in range(-12, 12):
if random.random() < 0.8: edges_H[(x, y)] = True
while True:
degrees = {}
for (x, y) in edges_V:
degrees[(x, y-1)] = degrees.get((x, y-1), 0) + 1
degrees[(x, y)] = degrees.get((x, y), 0) + 1
for (x, y) in edges_H:
degrees[(x-1, y)] = degrees.get((x-1, y), 0) + 1
degrees[(x, y)] = degrees.get((x, y), 0) + 1
pruned_any = False
for node, deg in degrees.items():
if deg <= 1:
x, y = node
if (x, y+1) in edges_V:
del edges_V[(x, y+1)]
pruned_any = True
elif (x, y) in edges_V:
del edges_V[(x, y)]
pruned_any = True
elif (x+1, y) in edges_H:
del edges_H[(x+1, y)]
pruned_any = True
elif (x, y) in edges_H:
del edges_H[(x, y)]
pruned_any = True
if not pruned_any: break
visited_V = set()
visited_H = set()
queue = [(-13, -13)]
visited_nodes = {(-13, -13)}
while queue:
curr_x, curr_y = queue.pop(0)
if (curr_x, curr_y) in edges_V and (curr_x, curr_y) not in visited_V:
visited_V.add((curr_x, curr_y))
nxt = (curr_x, curr_y-1)
if nxt not in visited_nodes:
visited_nodes.add(nxt)
queue.append(nxt)
if (curr_x, curr_y+1) in edges_V and (curr_x, curr_y+1) not in visited_V:
visited_V.add((curr_x, curr_y+1))
nxt = (curr_x, curr_y+1)
if nxt not in visited_nodes:
visited_nodes.add(nxt)
queue.append(nxt)
if (curr_x, curr_y) in edges_H and (curr_x, curr_y) not in visited_H:
visited_H.add((curr_x, curr_y))
nxt = (curr_x-1, curr_y)
if nxt not in visited_nodes:
visited_nodes.add(nxt)
queue.append(nxt)
if (curr_x+1, curr_y) in edges_H and (curr_x+1, curr_y) not in visited_H:
visited_H.add((curr_x+1, curr_y))
nxt = (curr_x+1, curr_y)
if nxt not in visited_nodes:
visited_nodes.add(nxt)
queue.append(nxt)
V_states.clear()
H_states.clear()
for k in visited_V: V_states[k] = True
for k in visited_H: H_states[k] = True
# Заполняем множество автобусных дорог
bus_road_cells.clear()
for B in [-8, -3, 2, 7]:
# Горизонтальные дороги
for x in range(-111, 112):
bus_road_cells.add((x, B*9 + 4))
bus_road_cells.add((x, B*9 + 5))
# Вертикальные дороги
for y in range(-111, 112):
bus_road_cells.add((B*9 + 4, y))
bus_road_cells.add((B*9 + 5, y))
# Названия проспектов
prospect_names_list = [
"20-ЛЕТИЯ АБСУРДОПЕДИИ",
"МОДЕРАЦИИ",
"ХЕРОХИТО",
"АБСУРДОТЕЧНЫЙ",
"24 ФЕВРАЛЯ",
"КАК-ПРАВИЛЬНЫЙ",
"ПИТОНА",
".".join(str(random.randint(0, 255)) for _ in range(4)) # рандомный IP
]
random.shuffle(prospect_names_list)
# Горизонтальные проспекты (B)
horiz_bs = [-8, -3, 2, 7]
vert_bs = [-8, -3, 2, 7]
for i, b in enumerate(horiz_bs):
prospect_names_h[b] = prospect_names_list[i]
for i, b in enumerate(vert_bs):
prospect_names_v[b] = prospect_names_list[4 + i]
stop_signs = set()
x_vals = [-69, -66, -24, -21, 21, 24, 66, 69]
y_vals = [-69, -66, -24, -21, 21, 24, 66, 69]
for n in range(-4, 5):
base = 27 * n
for x in x_vals: stop_signs.add((x, base))
for y in y_vals: stop_signs.add((base, y))
for y in range(-114, 115):
for x in range(-114, 115):
world[(x, y)] = generate_cell(x, y)
visited_block_cells = set()
candidate_blocks = []
road_chars = {'||', '🟰', '🔘'}
for y_pos in range(-111, 112):
for x_pos in range(-111, 112):
if (x_pos, y_pos) not in visited_block_cells and world.get((x_pos, y_pos)) not in road_chars:
queue_block = [(x_pos, y_pos)]
visited_block_cells.add((x_pos, y_pos))
block = []
q_idx = 0
while q_idx < len(queue_block):
cx, cy = queue_block[q_idx]
q_idx += 1
block.append((cx, cy))
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cx + dx, cy + dy
if -111 <= nx <= 111 and -111 <= ny <= 111:
if (nx, ny) not in visited_block_cells and world.get((nx, ny)) not in road_chars:
visited_block_cells.add((nx, ny))
queue_block.append((nx, ny))
if len(block) > 49:
has_disco = any(world.get((bx, by)) == '🪩' for bx, by in block)
contains_start = (0, 0) in block
if not has_disco and not contains_start:
candidate_blocks.append(block)
if candidate_blocks:
chosen_block = random.choice(candidate_blocks)
S = set()
for cx, cy in chosen_block:
if world.get((cx, cy)) in ['🔸', '🛑']: S.add((cx, cy))
H_set = set()
G_set = set()
for cx, cy in chosen_block:
if (cx, cy) not in S:
touches_S = False
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if (cx + dx, cy + dy) in S:
touches_S = True
break
if touches_S: H_set.add((cx, cy))
else: G_set.add((cx, cy))
for cx, cy in H_set: world[(cx, cy)] = '🛖'
for cx, cy in G_set: world[(cx, cy)] = '🌱'
grass_cells = G_set
if len(G_set) > 0:
num_sheep = min(10, len(G_set))
sheep_pos = random.sample(list(G_set), num_sheep)
for sx, sy in sheep_pos:
sheeps.append({'x': sx, 'y': sy})
world[(sx, sy)] = '🐑'
# Вычисляем геометрический центр квартала
sum_x = sum(x for x, y in chosen_block)
sum_y = sum(y for x, y in chosen_block)
n = len(chosen_block)
hut_center = (round(sum_x / n), round(sum_y / n))
candidate_monument_blocks = []
for Bx in range(-11, 12):
for By in range(-11, 12):
if is_H_road(Bx, By - 1) and is_H_road(Bx, By) and is_V_road(Bx - 1, By) and is_V_road(Bx, By):
start_x, start_y = Bx * 9 - 3, By * 9 - 3
has_disco = False
for y_pos in range(start_y, start_y + 7):
for x_pos in range(start_x, start_x + 7):
if world.get((x_pos, y_pos)) == '🪩':
has_disco = True
break
if has_disco: break
has_sheep_stuff = False
for y_pos in range(start_y, start_y + 7):
for x_pos in range(start_x, start_x + 7):
if world.get((x_pos, y_pos)) in ['🛖', '🌱', '🐑']:
has_sheep_stuff = True
break
if has_sheep_stuff: break
# Исключаем блок, если он содержит стартовую клетку (0,0)
contains_start = (start_x + 1 <= 0 <= start_x + 5) and (start_y + 1 <= 0 <= start_y + 5)
if not has_disco and not has_sheep_stuff and not contains_start:
candidate_monument_blocks.append((Bx, By))
if candidate_monument_blocks:
mbx, mby = random.choice(candidate_monument_blocks)
start_x, start_y = mbx * 9 - 3, mby * 9 - 3
layout = ["🌴🌴💠🌴🌴", "🌴💠💠💠🌴", "💠💠🗿💠💠", "🌴💠💠💠🌴", "🌴🌴💠🌴🌴"]
for i in range(5):
for j in range(5):
world[(start_x + 1 + j, start_y + 1 + i)] = layout[i][j]
monument_coords = (start_x + 3, start_y + 3)
for y_pos in range(-114, 115):
for x_pos in range(-114, 115):
if world.get((x_pos, y_pos)) == '🌳':
if world.get((x_pos + 1, y_pos)) == '🌳' and world.get((x_pos + 2, y_pos)) == '🌳':
if random.random() < 0.3:
world[(x_pos, y_pos)] = '🔔'
world[(x_pos + 1, y_pos)] = '🕌'
world[(x_pos + 2, y_pos)] = '🔔'
continue
if world.get((x_pos, y_pos + 1)) == '🌳' and world.get((x_pos, y_pos + 2)) == '🌳':
if random.random() < 0.3:
world[(x_pos, y_pos)] = '🔔'
world[(x_pos, y_pos + 1)] = '🕌'
world[(x_pos, y_pos + 2)] = '🔔'
district_ranges = [(-111, -69), (-66, -24), (-21, 21), (24, 66), (69, 111)]
for c in range(5):
for r in range(5):
x_min, x_max = district_ranges[c]
y_min, y_max = district_ranges[r]
buildings = []
for y_pos in range(y_min, y_max + 1):
for x_pos in range(x_min, x_max + 1):
if world.get((x_pos, y_pos)) == '🏢':
buildings.append((x_pos, y_pos))
if buildings:
bx, by = random.choice(buildings)
world[(bx, by)] = '🚾'
visited_cells = set()
road_chars_for_stores = {'||', '🟰', '🔘', '🛑'}
for y_pos in range(-114, 115):
for x_pos in range(-114, 115):
if (x_pos, y_pos) not in visited_cells and world.get((x_pos, y_pos)) not in road_chars_for_stores:
queue_stores = [(x_pos, y_pos)]
visited_cells.add((x_pos, y_pos))
block_buildings = []
q_idx = 0
while q_idx < len(queue_stores):
cx, cy = queue_stores[q_idx]
q_idx += 1
if world.get((cx, cy)) == '🏢': block_buildings.append((cx, cy))
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cx + dx, cy + dy
if -114 <= nx <= 114 and -114 <= ny <= 114:
if (nx, ny) not in visited_cells and world.get((nx, ny)) not in road_chars_for_stores:
visited_cells.add((nx, ny))
queue_stores.append((nx, ny))
m_buildings = len(block_buildings)
if m_buildings >= 5:
n_stores = m_buildings // 5
for bx, by in random.sample(block_buildings, n_stores):
world[(bx, by)] = '🏪'
# ======= НОВЫЙ КОД: очередь к магазину =======
shops = [(x, y) for (x, y), cell in world.items() if cell == '🏪']
obstacles_for_walk = {'🏢','🌳','🌲','🪩','🚾','🏪','👵','🏡','🧑🔧','🕌','🔔','🛖','🐑','🌴','🗿','🧔','🦌','⚰️','👷'}
def is_walkable(x, y):
if not (-114 <= x <= 114 and -114 <= y <= 114):
return False
if get_road_type(x, y) is not None:
return False
if world.get((x, y), '') in obstacles_for_walk:
return False
return True
best_shop = None
best_dir = None
min_dist = float('inf')
dirs = [(0,-1),(0,1),(-1,0),(1,0)]
for shop in shops:
mx, my = shop
# Проверка 4 соседей
if not all(is_walkable(mx+dx, my+dy) for dx, dy in dirs):
continue
# Собираем все валидные направления (проходимость и не проспект)
valid_dirs = []
for dir in dirs:
dx, dy = dir
valid = True
for i in range(1, 9):
nx, ny = mx + dx*i, my + dy*i
if not is_walkable(nx, ny):
valid = False
break
if (nx, ny) in bus_road_cells:
valid = False
break
if valid:
valid_dirs.append(dir)
if not valid_dirs:
continue
# Проверяем условие: ни один NPC не попадает в квадрат 3x3 вокруг (0,0)
chosen_dir = None
for dir in valid_dirs:
dx, dy = dir
# Все клетки, которые займут NPC
npc_cells = []
for i in range(1, 9):
npc_cells.append((mx + dx*i, my + dy*i))
# остальные три направления по одной клетке
for other_dir in dirs:
if other_dir == dir:
continue
ox, oy = other_dir
npc_cells.append((mx + ox, my + oy))
# Проверяем, не попадает ли кто-то в запретную зону (|x|<=1 and |y|<=1)
in_forbidden = False
for nx, ny in npc_cells:
if abs(nx) <= 1 and abs(ny) <= 1:
in_forbidden = True
break
if not in_forbidden:
chosen_dir = dir
break
if chosen_dir is None:
continue # все валидные направления дают попадание в стартовую зону
dist = abs(mx) + abs(my)
if dist < min_dist:
min_dist = dist
best_shop = shop
best_dir = chosen_dir
if best_shop is not None and best_dir is not None:
shop_with_queue_coords = best_shop # сохраняем координаты магазина с очередью
mx, my = best_shop
dx, dy = best_dir
# Размещаем очередь NPC
queue_npcs = ['😩','😡','😴','😤','🥸','🤓','🤠','😎'] # индекс 0 - дальняя
for i in range(1, 9):
nx, ny = mx + dx*i, my + dy*i
# Сохраняем исходную клетку под NPC
queue_npcs_under[(nx, ny)] = world.get((nx, ny), '🔹')
world[(nx, ny)] = queue_npcs[8 - i] # i=1 -> индекс 7 = 😎, i=8 -> индекс 0 = 😩
# Размещаем 👩 с остальных трёх сторон
for other_dir in dirs:
if other_dir == best_dir:
continue
ox, oy = other_dir
nx, ny = mx + ox, my + oy
# Сохраняем исходную клетку под NPC
queue_npcs_under[(nx, ny)] = world.get((nx, ny), '🔹')
world[(nx, ny)] = '👩'
women_npcs.append((nx, ny)) # добавляем координаты 👩
# ======= КОНЕЦ НОВОГО КОДА =======
grandma_candidates = []
bus_Bs = [-8, -3, 2, 7]
sidewalk_coords = []
for b in bus_Bs:
sidewalk_coords.extend([b * 9 + 3, b * 9 + 6])
for x_pos in range(-114, 115):
for y_pos in range(-114, 115):
if world.get((x_pos, y_pos)) == '🔸':
is_h = (y_pos in sidewalk_coords)
is_v = (x_pos in sidewalk_coords)
if is_h and (x_pos % 9 == 0 and x_pos % 27 != 0): grandma_candidates.append((x_pos, y_pos))
elif is_v and (y_pos % 9 == 0 and y_pos % 27 != 0): grandma_candidates.append((x_pos, y_pos))
if grandma_candidates:
gx, gy = random.choice(grandma_candidates)
grandma['x'], grandma['y'] = gx, gy
grandma['start_x'], grandma['start_y'] = gx, gy # сохраняем начальные координаты
grandma['state'] = 'waiting'
grandma['under'] = world.get((gx, gy), '🔸')
world[(gx, gy)] = '👵'
candidate_blocks_mech = []
for Bx in range(-12, 13):
for By in range(-12, 13):
if Bx in (-12, 12) or By in (-12, 12):
if is_H_road(Bx, By - 1) and is_H_road(Bx, By) and is_V_road(Bx - 1, By) and is_V_road(Bx, By):
candidate_blocks_mech.append((Bx, By))
if candidate_blocks_mech:
mbx, mby = random.choice(candidate_blocks_mech)
mechanic = {'x': mbx * 9, 'y': mby * 9, 'met': False}
mechanic['under'] = world.get((mechanic['x'], mechanic['y']), '🔹')
world[(mechanic['x'], mechanic['y'])] = '🧑🔧'
# Car initialization
min_dist = None
nearest_sidewalks = []
for wx in range(-21, 22):
for wy in range(-21, 22):
if world.get((wx, wy)) == '🔸':
dist = abs(wx) + abs(wy)
if min_dist is None or dist < min_dist:
min_dist = dist
nearest_sidewalks = [(wx, wy)]
elif dist == min_dist:
nearest_sidewalks.append((wx, wy))
if nearest_sidewalks:
sx, sy = random.choice(nearest_sidewalks)
car = {'x': sx, 'y': sy, 'type': random.choice(['🚗', '🚘']), 'speed': 0, 'dir': (0, 0), 'in_car': False}
else:
car = {'x': 0, 'y': 1, 'type': '🚗', 'speed': 0, 'dir': (0, 0), 'in_car': False}
# Поиск подходящих 🪩 и спавн 🧔
eligible_discos = []
for (x, y), cell in list(world.items()):
if cell == '🪩':
adj_blue = 0
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
if world.get((x+dx, y+dy)) == '🔹':
adj_blue += 1
if adj_blue >= 3:
eligible_discos.append((x, y))
if eligible_discos:
club_entrance = random.choice(eligible_discos)
# Найти соседние 🔹
blue_neighbors = []
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = club_entrance[0]+dx, club_entrance[1]+dy
if world.get((nx, ny)) == '🔹':
blue_neighbors.append((nx, ny))
if blue_neighbors:
spawn_pos = random.choice(blue_neighbors)
new_npc = {'x': spawn_pos[0], 'y': spawn_pos[1], 'under': '🔹', 'met': False, 'invited': False, 'disappear_next': False}
world[(spawn_pos[0], spawn_pos[1])] = '🧔'
def get_bus_state_from_step(rect, B_val, step):
if rect == 'H':
y_top = B_val * 9 + 4
y_bottom = B_val * 9 + 5
if 0 <= step <= 74:
x, y, dx, dy, is_turn = 111 - step * 3, y_top, -1, 0, (step == 74)
else:
x, y, dx, dy, is_turn = -111 + (step - 75) * 3, y_bottom, 1, 0, (step == 149)
return x, y, dx, dy, is_turn
else:
x_left = B_val * 9 + 4
x_right = B_val * 9 + 5
if 0 <= step <= 74:
x, y, dx, dy, is_turn = x_left, -111 + step * 3, 0, 1, (step == 74)
else:
x, y, dx, dy, is_turn = x_right, 111 - (step - 75) * 3, 0, -1, (step == 149)
return x, y, dx, dy, is_turn
def update_bus_coords(bus):
x, y, dx, dy, is_turn = get_bus_state_from_step(bus['rect'], bus['B_val'], bus['step'])
bus['x'], bus['y'], bus['dx'], bus['dy'], bus['is_turn'] = x, y, dx, dy, is_turn
def get_bus_path(bus):
path = []
if bus['is_turn']:
nx, ny, _, _, _ = get_bus_state_from_step(bus['rect'], bus['B_val'], (bus['step'] + 1) % 150)
path.append((nx, ny))
else:
for i in range(1, 4): path.append((bus['x'] + bus['dx'] * i, bus['y'] + bus['dy'] * i))
return path
def spawn_buses():
global buses
buses = []
routes_list = []
for By in [-8, -3, 2, 7]: routes_list.append(('H', By))
for Bx in [-8, -3, 2, 7]: routes_list.append(('V', Bx))
for rect, B_val in routes_list:
route_offset = random.randint(0, 149)
for i in range(6):
step = (route_offset + i * 25) % 150
bus = {'rect': rect, 'B_val': B_val, 'step': step, 'pair_id': (rect, B_val), 'type': '🚌' if rect == 'H' else '🚍'}
update_bus_coords(bus)
buses.append(bus)
def move_buses(ignore_player=False):
global buses, px, py, player_msg, grandma, car, deer, gravediggers
routes = list(set([bus['pair_id'] for bus in buses]))
random.shuffle(routes)
moving = {r: True for r in routes}
current_cells = {r: set() for r in routes}
intended_cells = {r: set() for r in routes}
for bus in buses:
r = bus['pair_id']
current_cells[r].add((bus['x'], bus['y']))
for cell in get_bus_path(bus): intended_cells[r].add(cell)
changed = True
while changed:
changed = False
for i in range(len(routes)):
rA = routes[i]
if not moving[rA]: continue
must_stop = False
for j in range(len(routes)):
if i == j: continue
rB = routes[j]
if not moving[rB]:
if intended_cells[rA].intersection(current_cells[rB]):
must_stop = True
break
else:
if i > j and intended_cells[rA].intersection(intended_cells[rB]):
must_stop = True
break
if must_stop:
moving[rA] = False
changed = True
hit_player = False
hit_car = False
deer_hit = False
buses_to_remove = []
# Сначала проверяем столкновения с могильщиками на пути автобусов
gravediggers_to_remove = []
for bus in buses:
if moving[bus['pair_id']]:
path = get_bus_path(bus)
# Проверяем, есть ли могильщик на пути автобуса
for g in gravediggers:
if (g['x'], g['y']) in path:
# Могильщик сбит
world[(g['x'], g['y'])] = '🥄' # оставляем лопату
gravediggers_to_remove.append(g)
if player_msg:
player_msg += "\nМогильщика насмерть сбил автобус!"
else:
player_msg = "Могильщика насмерть сбил автобус!"
break
# Также проверяем текущую клетку автобуса (на случай наложения)
for g in gravediggers:
if (g['x'], g['y']) == (bus['x'], bus['y']):
world[(g['x'], g['y'])] = '🥄'
if g not in gravediggers_to_remove:
gravediggers_to_remove.append(g)
if player_msg:
player_msg += "\nМогильщика насмерть сбил автобус!"
else:
player_msg = "Могильщика насмерть сбил автобус!"
# Удаляем сбитых могильщиков
for g in gravediggers_to_remove:
if g in gravediggers:
gravediggers.remove(g)
for bus in buses:
if moving[bus['pair_id']]:
path = get_bus_path(bus)
if not ignore_player and not car['in_car'] and (px, py) in path:
hit_player = True
if bus not in buses_to_remove: buses_to_remove.append(bus)
if (car['x'], car['y']) in path:
hit_car = True
if bus not in buses_to_remove: buses_to_remove.append(bus)
if grandma['state'] in ['waiting', 'crossing', 'going_home'] and (grandma['x'], grandma['y']) in path:
grandma['state'] = 'dead'
world[(grandma['x'], grandma['y'])] = '🎗️'
msg_add = "😱 Бабушку насмерть сбил автобус! 😱"
if player_msg and msg_add not in player_msg: player_msg += "\n" + msg_add
elif not player_msg: player_msg = msg_add
# Проверка столкновения автобуса с оленем
if deer is not None and (deer['x'], deer['y']) in path:
deer_hit = True
if deer is not None:
world[(deer['x'], deer['y'])] = deer['under']
deer = None
if not ignore_player:
msg = "Автобус насмерть сбил оленя! 🤣"
if player_msg: player_msg += "\n" + msg
else: player_msg = msg
# Проверка статического оленя (если deer == None, но в мире есть 🦌)
for cell in path:
if world.get(cell) == '🦌' and deer is None:
deer_hit = True
world[cell] = '▪️' if is_far_from_road(cell[0], cell[1]) else '🌲'
if not ignore_player:
msg = "Автобус насмерть сбил оленя! 🤣"
if player_msg: player_msg += "\n" + msg
else: player_msg = msg
break
bus['step'] = (bus['step'] + 1) % 150
update_bus_coords(bus)
for b in buses_to_remove:
if b in buses:
buses.remove(b)
return hit_player or hit_car
def is_last_stop(bus):
sim_bus = bus.copy()
while not sim_bus['is_turn']:
sim_bus['step'] = (sim_bus['step'] + 1) % 150
update_bus_coords(sim_bus)
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if (sim_bus['x'] + dx, sim_bus['y'] + dy) in stop_signs: return False
return True
def exit_bus(player_bus):
global px, py, player_msg, player_in_bus
player_in_bus = False
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if (player_bus['x'] + dx, player_bus['y'] + dy) in stop_signs:
px, py = player_bus['x'] + dx, player_bus['y'] + dy
player_msg = "Вы вышли из автобуса."
return
px, py = player_bus['x'], player_bus['y']
player_msg = "Вы вышли из автобуса."
def run_bus_animation(player_bus):
global px, py, player_msg, player_in_bus, tracked_target
player_in_bus = True
draw(px, py, in_bus=True, hide_hud=True)
time.sleep(1.0)
player_msg = ""
just_boarded = True
while True:
time.sleep(0.2)
move_buses(ignore_player=True)
px, py = player_bus['x'], player_bus['y']
generate_area(px, py)
at_stop = False
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if (px + dx, py + dy) in stop_signs:
at_stop = True
break
if at_stop and not just_boarded:
is_last = is_last_stop(player_bus)
while True:
if is_last: player_msg = "Остановка (Конечная). Нажмите 1, чтобы выйти."
else: player_msg = "Остановка. 1 - Ехать дальше, 2 - Выйти."
draw(px, py, in_bus=True, hide_hud=False)
ch = getch()
# Обработка клавиш-цифр для компаса
if ch in ['3','4','5','6','7','8','0']:
if ch == '3': tracked_target = 'grandma'
elif ch == '4': tracked_target = 'monument'
elif ch == '5': tracked_target = 'new_npc'
elif ch == '6': tracked_target = 'mechanic'
elif ch == '7': tracked_target = 'shop'
elif ch == '8': tracked_target = 'hut'
elif ch == '0': tracked_target = None
# Перерисовать экран, чтобы показать индикатор
draw(px, py, in_bus=True, hide_hud=False)
continue
if is_last:
if ch in ['1', '!', ' ', '\r', '\n', 'q', 'й', '\x1b']:
exit_bus(player_bus)
return
else:
if ch in ['1', '!', ' ', '\r', '\n']:
player_msg = ""
just_boarded = True
break
elif ch in ['2', '@', '"', 'q', 'й', '\x1b']:
exit_bus(player_bus)
return
else:
if not at_stop: just_boarded = False
draw(px, py, in_bus=True, hide_hud=True)
def is_kad(x, y):
"""Проверяет, находится ли точка на КАД (окружной дороге)."""
if (abs(x) == 112 or abs(x) == 113) and abs(y) <= 113:
return True
if (abs(y) == 112 or abs(y) == 113) and abs(x) <= 113:
return True
return False
def draw(cx, cy, dead=False, in_bus=False, hide_hud=False):
global tracked_target, player_is_clown, player_is_elf, gravediggers, coffin_coords, shovel
global option_block_active, hut_center
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
GRID_WIDTH = 30
UI_WIDTH = 44
# Определяем, что выводить над сеткой (район, КАД, проспекты)
display_items = [] # список кортежей (текст, цвет или None)
if is_kad(cx, cy):
display_items.append(("КАД", None))
else:
# Проверяем проспекты
prospects = []
# Горизонтальные проспекты: координата y в диапазоне B*9+3 .. B*9+6 при |x|<=111
for b in [-8, -3, 2, 7]:
if abs(cx) <= 111 and b*9+3 <= cy <= b*9+6:
prospects.append(("H", b))
# Вертикальные проспекты: координата x в диапазоне B*9+3 .. B*9+6 при |y|<=111
for b in [-8, -3, 2, 7]:
if abs(cy) <= 111 and b*9+3 <= cx <= b*9+6:
prospects.append(("V", b))
if prospects:
for orient, b in prospects:
if orient == 'H':
name = prospect_names_h[b]
else:
name = prospect_names_v[b]
display_items.append((f"ПРОСПЕКТ {name}", None))
else:
# Обычный район
dist = get_district(cx, cy)
if dist:
c, r = dist
raw_text = f"{district_names[(c, r)]} РАЙОН"
color = district_colors[(c, r)]
display_items.append((raw_text, color))
total_lines = 22
if display_items:
total_lines += 3 # всегда добавляем 3 строки (тексты + пустые)
v_pad = max(0, (term_lines - total_lines) // 2)
grid_h_pad = max(0, (term_cols - GRID_WIDTH) // 2)
ui_h_pad = max(0, (term_cols - UI_WIDTH) // 2)
grid_pad_str = " " * grid_h_pad
ui_pad_str = " " * ui_h_pad
bus_map = { (bus['x'], bus['y']): bus['type'] for bus in buses }
print("\n" * v_pad, end="")
# Выводим названия (район/КАД/проспекты) и пустые строки
if display_items:
for raw_text, color in display_items:
if color:
pad = max(0, (term_cols - len(raw_text)) // 2)
print(" " * pad + color + raw_text + '\033[0m')
else:
pad = max(0, (term_cols - len(raw_text)) // 2)
print(" " * pad + raw_text)
# Добавляем пустые строки, чтобы всего было 3
num_empty = 3 - len(display_items)
for _ in range(num_empty):
print()
else:
pass
# Сетка
for y in range(cy - 7, cy + 8):
row = []
for x in range(cx - 7, cx + 8):
if x == cx and y == cy:
if in_bus:
row.append(bus_map.get((x, y), '🚌'))
elif car['in_car']:
row.append('💥' if dead else car['type'])
elif dead:
row.append('💀')
elif player_is_clown:
row.append('🤡')
elif player_is_elf:
row.append('🧝')
else:
row.append('👩🦰')
elif (x, y) in bus_map:
row.append(bus_map[(x, y)])
elif x == car['x'] and y == car['y'] and not car['in_car']:
row.append(car['type'])
elif deer is not None and x == deer['x'] and y == deer['y']:
row.append('🦌')
elif (x, y) == coffin_coords:
row.append('⚰️')
elif any(g['x'] == x and g['y'] == y for g in gravediggers):
row.append('👷')
else:
row.append(world.get((x, y), '🔹'))
print(grid_pad_str + "".join(row))
print()
UI_LINES_TOTAL = 6
ui_lines = []
if not hide_hud:
if car['in_car']:
ui_lines.append("Управление авто: Н,И,П,О/WASD")
ui_lines.append("Газ/Поворот/Тормоз(Пробел) | E/У - Выход")
dir_map = {(0,-1): '⬆️', (1,0): '⬇️', (0,1): '⬇️', (-1,0): '⬅️'}
dir_str = dir_map.get(car['dir'], '') if car['speed'] > 0 else ''
ui_lines.append(f"Скорость: {car['speed']}/5 Направление: {dir_str}")
else:
ui_lines.append("Управление: Н,И,П,О или Стрелки/WASD")
ui_lines.append("Р/Пробел - Стоять | Q/Esc - Выход")
# Новая строка: если есть лопата и стоим на ▪️, показать копать
if shovel > 0 and world.get((cx, cy)) == '▪️':
ui_lines.append("У/Е - КОПАТЬ")
items = []
if roses_collected > 0: items.append(f"🌹: {roses_collected}")
if ribbons_collected > 0: items.append(f"🎗️: {ribbons_collected}")
if bouquets_collected > 0: items.append(f"💐: {bouquets_collected}")
if lemon_tampon:
items.append("🍋 в ({})")
elif lemon > 0:
items.append(f"🍋: {lemon}")
if salt > 0: items.append(f"🧂: {salt}")
if toilet_paper > 0: items.append(f"🧻: {toilet_paper}")
if shovel > 0: items.append(f"🥄: {shovel}")
if items:
ui_lines.append(f"Инвентарь: {' | '.join(items)}")
# Если есть нужда в туалете, выводим сообщение и счётчик
if toilet_patience is not None and toilet_patience > 0:
orange = '\033[38;5;208m'
reset = '\033[0m'
ui_lines.append(f"{orange}ВЫ ХОТИТЕ В ТУАЛЕТ!{reset}")
ui_lines.append(f"🚽 Терпение: {toilet_patience}")
# Добавление индикатора направления, если включён и цель существует
if tracked_target is not None:
tx = ty = None
emoji = ""
if tracked_target == 'grandma':
if grandma['state'] not in ['none','dead']:
tx, ty = grandma['x'], grandma['y']
emoji = '👵'
elif tracked_target == 'monument':
if monument_coords is not None:
tx, ty = monument_coords
emoji = '🗿'
elif tracked_target == 'new_npc':
if new_npc is not None:
tx, ty = new_npc['x'], new_npc['y']
emoji = '🧔'
elif tracked_target == 'mechanic':
if mechanic is not None:
tx, ty = mechanic['x'], mechanic['y']
emoji = '🧑🔧'
elif tracked_target == 'shop':
if shop_with_queue_coords is not None:
tx, ty = shop_with_queue_coords
emoji = '🏪'
elif tracked_target == 'hut':
if hut_center is not None:
tx, ty = hut_center
emoji = '🛖'
if tx is not None and ty is not None:
dx = tx - cx
dy = ty - cy
# Вычисляем угол от севера (ось Y вниз, поэтому -dy)
angle = math.degrees(math.atan2(dx, -dy))
angle = (angle + 360) % 360
# Определяем сектор (8 направлений)
sector = int(((angle + 22.5) % 360) // 45)
# Стрелки в нужном виде
arrows = ['⬆️','↗️','➡️','↘️','⬇️','↙️','⬅️','↖️']
arrow = arrows[sector]
ui_lines.append(f"{emoji}{arrow}")
ui_lines.append("-" * UI_WIDTH)
else:
ui_lines.extend(["", "", ""])
if player_msg:
for msg_line in player_msg.split('\n'):
wrapped_msg = textwrap.wrap(msg_line, width=UI_WIDTH)
ui_lines.extend(wrapped_msg)
# Добавляем опции, если активен блок
if option_block_active:
ui_lines.append("1 — подраться с 👩")
ui_lines.append("2 — позвонить 🧑🔧")
ui_lines.append("9 — убить 👩 лопатой")
while len(ui_lines) < UI_LINES_TOTAL:
ui_lines.append("")
for line in ui_lines:
print(ui_pad_str + line.ljust(UI_WIDTH))
def show_splash_screen():
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
splash_art = [
" ▪️▪️▪️👑▪️▪️▪️",
" ▪️ 🎀🏵️🏵️🏵️🎀▪️",
" ▪️🏵️ 👁️🌟👁️🏵️▪️",
" ▪️🏵️💢 👃💢🏵️▪️",
" ▪️🏵️🌟 👄🌟🏵️▪️",
" ▪️▪️🔶🔶🔶▪️▪️",
" 🫲🔶▪️♥️▪️🔶🤳",
" ▪️▪️▪️🔶▪️▪️▪️",
" ▪️▪️▪️☘️▪️▪️▪️",
" ▪️▪️🔶▪️🔶▪️▪️",
" ▪️▪️🔶▪️▪️🔶▪️",
" ▪️▪️👠▪️▪️▪️👠",
"",
"===================",
"= МАРУСЯ-НЕТАКУСЯ =",
"==================="
]
v_pad = max(0, (term_lines - len(splash_art)) // 2)
h_pad = max(0, (term_cols - 19) // 2)
print("\n" * v_pad, end="")
for line in splash_art: print(" " * h_pad + line)
time.sleep(5)
def show_prison_screen():
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
scene = [
"🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱",
"🧱▪️▪️▪️▪️▪️▪️▪️▪️▪️🧱",
"🧱▪️▪️▪️######▪️▪️▪️🧱",
"🧱▪️👮▪️##👩🦰##▪️👮▪️🧱",
"🧱▪️▪️▪️######▪️▪️▪️🧱",
"🧱▪️▪️▪️▪️▪️▪️▪️▪️▪️🧱",
"🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱",
"",
"\033[91mТы села в тюрьму за тяжкое преступление!",
"Это тебе не GTA!\033[0m"
]
v_pad = max(0, (term_lines - len(scene)) // 2)
h_pad = max(0, (term_cols - 21) // 2)
print("\n" * v_pad, end="")
for line in scene:
if "\033" in line:
clean_len = len(line) - 9
print(" " * max(0, (term_cols - clean_len) // 2) + line)
else:
print(" " * h_pad + line)
time.sleep(5)
def play_mechanic_dialogue():
global player_msg, px, py
dialogue = [
("🧑🔧: Чё те надо, овца?", "👩🦰: Может, познакомимся для начала?"),
("🧑🔧: Я Егор Туалетов, меня в городе знают все уважаемые люди.", "👩🦰: А я Маруся, очень приятно."),
("🧑🔧: Это тебе очень приятно, а мне не очень.", "👩🦰: У тебя есть жена или девушка?"),
("🧑🔧: Не твоё дело.", "👩🦰: Очень даже моё. Ты почему живёшь у чёрта на рогах? Я еле нашла тебя."),
("🧑🔧: Да отъебись ты от меня! Я не NPC, а живой человек, так что ничем помочь не могу!", "👩🦰: Это мы ещё посмотрим...")
]
for lines in dialogue:
player_msg = lines[0] + "\n" + lines[1]
for _ in range(25):
time.sleep(0.2)
move_buses(ignore_player=True)
generate_area(px, py)
draw(px, py, hide_hud=False)
player_msg = ""
def play_club_scene():
global player_msg, px, py, bouquets_collected, roses_collected, ribbons_collected
global club_visited, club_blacklist, lemon, salt
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
club_art = [
"💈🌟🌟🌟🌟🪩🌟🌟🌟🌟💈",
"💈▪️🎶🎶🎶🎶🎶🎶🎶▪️💈",
"💈🔊▪️▪️▪️▪️▪️▪️▪️▪️💈",
"💈▪️💃🕺👫🤸🕴️🕺💃▪️💈",
"💈▪️👩👨▪️🥴▪️👱👱♀️▪️💈",
"💈▪️▪️▪️👩🦰🤝🧔▪️▪️▪️💈",
"💈🤢▪️🫗🚬🚬🚬🪳▪️🤮💈",
"🚧🚧🚧🚧👮👮👮🚧🚧🚧🚧"
]
segments = [
("🎵Трахнул я одну ТП —\nОказалась РСП.🎵",
"👩🦰: Как тебя зовут, красавчик?\n🧔: Ясурам, а тебя?"),
("🎵Трахнул я её ЛП —\nПодхватил ЗППП!🎵",
"👩🦰: А я Маруся.\n🧔: Так чей это был букет?"),
("🎵Получилось тупо —\nЧешется залупа!🎵",
"👩🦰: А где твой каУказский акЬцент, Уася?\n🧔: Нет у меня акцента, это я угорал просто."),
("🎵Так что надо, братцы,\nНам предохраняться!🎵",
"👩🦰: Ясно, угостишь меня текилой?\n🧔: Угощу, конечно. Знаешь, как её пить?"),
("🎵Трахнул я альтушку в рот\nИ спустил ей на живот.🎵",
"👩🦰: Не учи мать ебаться, угощай давай!\n🧔: Может, закусишь чем-нибудь?"),
("🎵Заказал ей мидии —\nПолучил хламидии!🎵",
"👩🦰: Зачем?! Это же не чистый спирт!\n🧔: А ты сама дойдёшь до дома?"),
("🎵Получилось тупо —\nЧешется залупа!🎵",
"👩🦰: Не боись, я за рулём! Ик!\n🧔: Если что — заходи к нам в гости в кавказский квартал,\nтам тебя всегда накормят и напоят."),
("🎵Так что надо, братцы,\nНам предохраняться!🎵",
"👩🦰: А потом там меня трахнут, я надеюсь?\nЯ бы не отказалась от групповушки!\n👮: Девушка, вы пьяны, пройдёмте на выход.")
]
art_pad = max(0, (term_cols - 22) // 2)
for seg_idx, (lyrics, dialogue) in enumerate(segments):
os.system('cls' if os.name == 'nt' else 'clear')
print("\n" * ((term_lines - 10) // 2))
lyrics_lines = lyrics.split('\n')
for line in lyrics_lines:
print(" " * ((term_cols - len(line)) // 2) + line)
print()
for line in club_art:
print(" " * art_pad + line)
print()
dialogue_lines = dialogue.split('\n')
for line in dialogue_lines:
print(" " * ((term_cols - len(line)) // 2) + line)
time.sleep(5)
club_visited = True
club_blacklist = True
lemon = 1
salt = 1
player_msg = "Вы получили лимон и соль!\nТеперь вы в чёрном списке всех клубов города."
def play_feast_scene():
global feast_visited, player_msg, toilet_patience
board = [
['▪️', '▪️', '▪️', '▪️', '👩🦰', '▪️', '▪️', '▪️', '▪️'],
['▪️', '——', '——', '——', '——', '——', '——', '——', '▪️'],
['🧔♂️', '🥟', '🍷', '🔹', '🔹', '🔹', '🍷', '🥟', '🧔♂️'],
['🧔♂️', '🥟', '🍷', '🔹', '🍖', '🔹', '🍷', '🥟', '🧔♂️'],
['🧔♂️', '🥟', '🍷', '🔹', '🔹', '🔹', '🍷', '🥟', '🧔♂️'],
['🧔♂️', '🥟', '🍷', '🔹', '🍲', '🔹', '🍷', '🥟', '🧔♂️'],
['🧔♂️', '🥟', '🍷', '🔹', '🔹', '🔹', '🍷', '🥟', '🧔♂️'],
['🧔♂️', '🥟', '🍷', '🔹', '🥩', '🔹', '🍷', '🥟', '🧔♂️'],
['🧔♂️', '🥟', '🍷', '🔹', '🔹', '🔹', '🍷', '🥟', '🧔♂️'],
['▪️', '——', '——', '——', '——', '——', '——', '——', '▪️'],
['▪️', '▪️', '🪵', '▪️', '▪️', '▪️', '🪵', '▪️', '▪️'],
['▪️', '▪️', '🪵', '▪️', '▪️', '▪️', '🪵', '▪️', '▪️']
]
def draw_f(b, msg=""):
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
board_lines = ["".join(row) for row in b]
msg_lines = msg.split('\n') if msg else []
total_lines = len(board_lines) + (2 + len(msg_lines) if msg else 0)
v_pad = max(0, (term_lines - total_lines) // 2)
pad = max(0, (term_cols - 18) // 2)
print("\n" * v_pad, end="")
for line in board_lines:
print(" " * pad + line)
if msg:
print()
for line in msg_lines:
print(" " * max(0, (term_cols - len(line)) // 2) + line)
msg1 = "👩🦰: Всем гамарджоба, барев дзес и салам алейкум!\nВаш родственник сказал,\nчто здесь меня накормят и напоят."
draw_f(board, msg1)
time.sleep(5)
msg2 = "🧔♂️: Мой брат говорил о тебе, но мы не ждали гостей...\n👩🦰: Я ваши обычаи знаю, так что не надо ля-ля!\nМаруся трапезничать желает!"
draw_f(board, msg2)
time.sleep(5)
board[0][4] = '▪️'
board[2][4] = '👩🦰'
px_f, py_f = 4, 2
msg = ""
while True:
draw_f(board, msg)
left = sum(1 for row in board for cell in row if cell in ['🥟', '🍷', '🍖', '🍲', '🥩'])
if left == 0:
break
ch = getch()
dx, dy = 0, 0
if ch in ['н', 'y', 'w', 'ц', 'up']: dy = -1
elif ch in ['и', 'b', 's', 'ы', 'down']: dy = 1
elif ch in ['п', 'g', 'a', 'ф', 'left']: dx = -1
elif ch in ['о', 'j', 'd', 'в', 'right']: dx = 1
if dx == 0 and dy == 0:
continue
nx, ny = px_f + dx, py_f + dy
if 0 <= nx < 9 and 0 <= ny < 12:
target = board[ny][nx]
if target not in ['——', '🧔♂️', '▪️', '🪵']:
board[py_f][px_f] = '🔹'
px_f, py_f = nx, ny
board[py_f][px_f] = '👩🦰'
if target == '🥟': msg = "👩🦰: Ам!"
elif target == '🍷': msg = "👩🦰: Глыг-глыг!"
elif target == '🍲': msg = "👩🦰: Сёрб-сёрб-сёрб!"
elif target in ['🍖', '🥩']: msg = "👩🦰: Ам-ам-ам-ам-ам!"
else: msg = ""
win_msg = "👩🦰: Как-то скромненько, мальчики.\nЯ ожидала большего. Всем пока! 💋"
draw_f(board, win_msg)
time.sleep(5)
feast_visited = True
toilet_patience = 25 # появляется нужда в туалете
player_msg = ""
# Новая функция для сцены в церкви (эльф)
def play_church_scene(enter_x, enter_y):
global player_is_elf, lemon_tampon, car, px, py, player_msg, lemon
global church_visited_global, gravediggers, coffin_coords, gravedigger_triggered, gravedigger_move_countdown
church_art = [
"▪️▪️▪️▪️▪️▪️▪️▪️▪️▪️▪️",
"👳▪️▪️▪️✝️▪️✝️▪️✝️▪️▪️",
"🧕🧕▪️▪️🕯▪️🕯️▪️🕯️▪️▪️",
"👳👳👳▪️▪️▪️▪️▪️▪️▪️▪️",
"🧕🧕🧕🧕▪️▪️▪️▪️▪️🧙▪️",
"👳👳👳👳👳▪️▪️▪️▪️🪵🪵",
"🧕🧕🧕🧕🧕🧝▪️▪️▪️🪵🪵",
"☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️"
]
texts = [
"🧙: Сыны и дочери Абслаха!\nПозвольте прочитать вам древний абсят,\nпоражающий ёмкостью и лаконичностью!",
"🧙: Простой\nКвадрат —\nБольшой\nНаш Брат.\nИ одного\nЛюби Его!\nБудь рад!",
"🧙: Здесь Священный Квадран учит нас,\nчто нет ничего лучше квадрата.\nДаже круглую Землю мы измеряем\nквадратными единицами.\nДаже наш огромный город — это квадрат,\nразделённый на квадратное число\nквадратных районов!",
"🧙: А теперь пару слов о гигиене.\nСыны и дочери Абслаха, от кого из вас\nпахнет рыбой в лимонном соусе?\n🧝: Ой, мне нужно срочно припудрить носик..."
]
for stage, text in enumerate(texts):
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
art_lines = church_art
text_lines = text.split('\n')
total_lines = len(art_lines) + 1 + len(text_lines)
v_pad = max(0, (term_lines - total_lines) // 2)
h_pad = max(0, (term_cols - max(len(line) for line in art_lines)) // 2)
print("\n" * v_pad, end="")
for line in art_lines:
print(" " * h_pad + line)
print()
for tline in text_lines:
print(" " * max(0, (term_cols - len(tline)) // 2) + tline)
time.sleep(5)
# Восстановление
px, py = enter_x, enter_y
player_is_elf = False
lemon_tampon = False
lemon = 0
car['x'] = 1000
car['y'] = 1000
car['in_car'] = False
player_msg = ("Вы достали лимон, завернули в туалетную\n"
"бумагу и спрятали за углом!\n"
"\033[38;5;208mКто-то угнал ваш автомобиль!\033[0m")
# После церкви спавним гроб и могильщиков
church_visited_global = True
spawn_gravediggers()
def spawn_gravediggers():
global coffin_coords, gravediggers, world, grandma
if grandma['start_x'] is None or grandma['start_y'] is None:
return
coffin_coords = (grandma['start_x'], grandma['start_y'])
world[coffin_coords] = '⚰️'
# Ищем соседние клетки тротуара (🔸) для могильщиков
adjacent_sidewalk = []
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = coffin_coords[0]+dx, coffin_coords[1]+dy
if -114 <= nx <= 114 and -114 <= ny <= 114 and world.get((nx,ny)) == '🔸':
adjacent_sidewalk.append((nx,ny))
# Нужно два могильщика
if len(adjacent_sidewalk) >= 2:
# Выбираем две клетки
pos1, pos2 = adjacent_sidewalk[0], adjacent_sidewalk[1]
gravediggers = [
{'x': pos1[0], 'y': pos1[1], 'under': '🔸', 'state': 'idle', 'cross_dir': None, 'steps_crossed': 0},
{'x': pos2[0], 'y': pos2[1], 'under': '🔸', 'state': 'idle', 'cross_dir': None, 'steps_crossed': 0}
]
world[pos1] = '👷'
world[pos2] = '👷'
else:
# fallback: если не хватает тротуара, просто разместим на соседних свободных клетках
candidates = []
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = coffin_coords[0]+dx, coffin_coords[1]+dy
if -114 <= nx <= 114 and -114 <= ny <= 114 and world.get((nx,ny)) not in ['🏢','🌳','🌲','🪩','🚾','🏪','👵','🏡','🧑🔧','🕌','🔔','🛖','🐑','🌴','🗿','🧔','⚰️','👷']:
candidates.append((nx,ny))
if len(candidates) >= 2:
pos1, pos2 = candidates[0], candidates[1]
gravediggers = [
{'x': pos1[0], 'y': pos1[1], 'under': world.get(pos1,'🔹'), 'state': 'idle', 'cross_dir': None, 'steps_crossed': 0},
{'x': pos2[0], 'y': pos2[1], 'under': world.get(pos2,'🔹'), 'state': 'idle', 'cross_dir': None, 'steps_crossed': 0}
]
world[pos1] = '👷'
world[pos2] = '👷'
def play_gravedigger_dialogue():
global player_msg, px, py, gravedigger_triggered, gravedigger_move_countdown
segments = [
"👩🦰: А вы чего здесь яйца чешете?\n👷: Да вот бабку автобус сбил,\nа мы не знаем, где её закопать.",
"👩🦰: Ясно, как насчёт МЖМ?\n👷: ОК, только за презиками сгоняем."
]
for seg in segments:
player_msg = seg
# Рандомная длительность: от 3 до 5 секунд с шагом 0.2
# => количество кадров от 15 до 25 (включительно)
n_frames = random.randint(15, 25)
for _ in range(n_frames):
time.sleep(0.2)
move_buses(ignore_player=True)
generate_area(px, py)
draw(px, py, hide_hud=False)
player_msg = ""
gravedigger_triggered = True
gravedigger_move_countdown = 2
def get_cross_direction(sidewalk_cell):
# Определяем направление перехода через дорогу для тротуара
x, y = sidewalk_cell
# Если тротуар горизонтальный (y на линии B*9+3 или B*9+6)
for b in [-8,-3,2,7]:
if y == b*9+3:
return (0,1) # вниз к дороге
if y == b*9+6:
return (0,-1) # вверх к дороге
# Вертикальный тротуар
for b in [-8,-3,2,7]:
if x == b*9+3:
return (1,0) # вправо
if x == b*9+6:
return (-1,0) # влево
return (0,0)
def is_cell_free_for_gravedigger(x, y, exclude_player=False):
"""Проверяет, свободна ли клетка для могильщика."""
if not (-114 <= x <= 114 and -114 <= y <= 114):
return False
# Проверяем препятствия
cell = world.get((x, y), '')
if cell in ['🏢','🌳','🌲','🪩','🚾','🏪','🏡','🕌','🔔','🛖','🌴','🗿','⚰️','👷','🦌','🐑']:
return False
# Проверяем занятость другими могильщиками
for g in gravediggers:
if g['x'] == x and g['y'] == y:
return False
# Проверяем занятость игроком
if not exclude_player and (x == px and y == py):
return False
# Проверяем автобусы
for b in buses:
if b['x'] == x and b['y'] == y:
return False
# Проверяем машину
if car['x'] == x and car['y'] == y and not car['in_car']:
return False
return True
def update_gravediggers():
global gravediggers, gravedigger_triggered, gravedigger_move_countdown, player_msg, coffin_coords, gravedigger_block_dialogue_shown
if not gravedigger_triggered or not gravediggers:
return
# Уменьшаем счётчик после диалога
if gravedigger_move_countdown > 0:
gravedigger_move_countdown -= 1
if gravedigger_move_countdown == 0:
# Устанавливаем состояние crossing и направление
cross_dir = get_cross_direction(coffin_coords)
for g in gravediggers:
g['state'] = 'crossing'
g['cross_dir'] = cross_dir
g['steps_crossed'] = 0
return # ещё не двигаемся
# Сначала обрабатываем возможное удаление (despawn_next)
to_remove = []
for g in gravediggers:
if g['state'] == 'despawn_next':
# Восстанавливаем клетку и удаляем
world[(g['x'], g['y'])] = g['under']
to_remove.append(g)
if player_msg:
player_msg += "\nМогильщик зашёл в дом!"
else:
player_msg = "Могильщик зашёл в дом!"
for g in to_remove:
if g in gravediggers:
gravediggers.remove(g)
if not gravediggers:
return
# Сначала собираем занятые клетки всеми могильщиками
occupied = set()
for g in gravediggers:
occupied.add((g['x'], g['y']))
for g in gravediggers[:]: # итерируем по копии
if g['state'] == 'crossing':
dx, dy = g['cross_dir']
nx, ny = g['x']+dx, g['y']+dy
# Проверяем, можно ли шагнуть прямо
if (nx,ny) == (px,py):
# Игрок преграждает путь, пробуем обойти
# Направления обхода: перпендикулярные к cross_dir
side_dirs = [(dy, dx), (-dy, -dx)] # например для (0,1) -> (1,0), (-1,0)
moved = False
for sdx, sdy in side_dirs:
sx, sy = g['x'] + sdx, g['y'] + sdy
if (sx, sy) == (px,py): continue # не пытаемся встать на игрока
if is_cell_free_for_gravedigger(sx, sy, exclude_player=True):
# Перемещаемся в сторону
world[(g['x'], g['y'])] = g['under']
g['under'] = world.get((sx, sy), '🔹')
world[(sx, sy)] = '👷'
g['x'], g['y'] = sx, sy
# Если первый раз, выводим диалог
if not gravedigger_block_dialogue_shown:
gravedigger_block_dialogue_shown = True
msg = "👷: Пропусти, чего стала?\n👩🦰: Может, давай без презиков?\n👷: Ага, чтобы потом залупа чесалась,\nкак в известной песне?"
if player_msg:
player_msg += "\n" + msg
else:
player_msg = msg
moved = True
break
if not moved:
# Если обойти не удалось, остаёмся на месте
continue
# Не увеличиваем steps_crossed, так как это боковой шаг
# Обновим occupied для следующего могильщика
occupied.add((g['x'], g['y']))
continue
# Если прямая клетка свободна, проверяем как обычно
if (nx,ny) in occupied or not is_cell_free_for_gravedigger(nx, ny):
continue
# Перемещаемся прямо
world[(g['x'], g['y'])] = g['under']
g['under'] = world.get((nx, ny), '🔹')
world[(nx, ny)] = '👷'
g['x'], g['y'] = nx, ny
g['steps_crossed'] += 1
if g['steps_crossed'] >= 3:
g['state'] = 'to_building'
occupied.add((nx, ny))
elif g['state'] == 'to_building':
# Проверяем, рядом ли здание
adjacent_building = False
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
if world.get((g['x']+dx, g['y']+dy)) == '🏢':
adjacent_building = True
break
if adjacent_building:
g['state'] = 'despawn_next'
continue
# BFS до ближайшего 🏢
start = (g['x'], g['y'])
q = [start]
came_from = {start: None}
target = None
while q:
cx, cy = q.pop(0)
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = cx+dx, cy+dy
if (nx,ny) == (px,py): continue # не проходим через игрока
if (nx,ny) in occupied: continue
if not (-114 <= nx <= 114 and -114 <= ny <= 114): continue
if get_road_type(nx,ny) is not None: continue
if (nx,ny) in came_from: continue
cell = world.get((nx,ny), '')
if cell in ['🏢','🚾','🏪','👵','🪩','🧔','🏡','🛖','🌴','🗿','⚰️','👷','🦌','🐑']:
if cell == '🏢':
target = (cx,cy)
q = []
break
continue
came_from[(nx,ny)] = (cx,cy)
q.append((nx,ny))
if target:
break
if target and came_from[target] is not None:
curr = target
while came_from[curr] != start:
curr = came_from[curr]
nx, ny = curr
# Проверяем, не занята ли эта клетка игроком (если вдруг)
if (nx,ny) == (px,py):
# Игрок опять преградил, пробуем обход как в crossing
# Но здесь направление к цели может быть любым. Попробуем все 4 направления
moved = False
for sdx, sdy in [(-1,0),(1,0),(0,-1),(0,1)]:
sx, sy = g['x'] + sdx, g['y'] + sdy
if (sx, sy) == (px,py): continue
if is_cell_free_for_gravedigger(sx, sy, exclude_player=True):
world[(g['x'], g['y'])] = g['under']
g['under'] = world.get((sx, sy), '🔹')
world[(sx, sy)] = '👷'
g['x'], g['y'] = sx, sy
if not gravedigger_block_dialogue_shown:
gravedigger_block_dialogue_shown = True
msg = "👷: Пропусти, чего стала?\n👩🦰: Может, давай без презиков?\n👷: Ага, чтобы потом залупа чесалась,\nкак в известной песне?"
if player_msg:
player_msg += "\n" + msg
else:
player_msg = msg
moved = True
break
if moved:
continue
else:
continue # не можем сходить, остаёмся
# Обычный шаг по BFS
if (nx,ny) in occupied or not is_cell_free_for_gravedigger(nx, ny):
continue
world[(g['x'], g['y'])] = g['under']
g['under'] = world.get((nx, ny), '🔹')
world[(nx, ny)] = '👷'
g['x'], g['y'] = nx, ny
occupied.add((nx, ny))
# Проверяем соседство со зданием после хода
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
if world.get((g['x']+dx, g['y']+dy)) == '🏢':
adjacent_building = True
break
if adjacent_building:
g['state'] = 'despawn_next'
def try_deer_interaction():
"""Проверяет взаимодействие героини с оленем (если есть соль)."""
global deer, salt, player_msg, mechanic
# Если олень уже следует, ничего не делаем
if deer is not None:
return
# Ищем соседнего оленя по ортогонали
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = px + dx, py + dy
if world.get((nx, ny)) == '🦌' and salt > 0:
if mechanic is not None and mechanic['met']:
# Удаляем статического оленя, создаём следующего за героиней
# Точка спавна: клетка, где стоял олень, станет '▪️' при уходе
deer = {'x': nx, 'y': ny, 'under': '▪️', 'following': True}
world[(nx, ny)] = '🦌' # олень остаётся на месте до первого хода
salt = 0
player_msg = ("🦌: Какая вкусная соль — как в Питере!\n"
"А почему ты плачешь?\n"
"👩🦰: Меня Егор Туалетов обидел! 😭\n"
"🦌: ЧТО?! Он посмел обидеть ДЕВУШКУ?!\n"
"Надо срочно набить ему морду!\n"
"Веди меня к нему!")
else:
# Олень слизывает соль и остаётся на месте
salt = 0
player_msg = "Олень слизал у вас всю соль!"
break
def move_deer_if_following(old_px, old_py):
"""Перемещает оленя на предыдущую позицию героини, если он рядом."""
global deer, px, py, car, player_in_bus
if deer is None or not deer['following']:
return
if car['in_car'] or player_in_bus:
return
# Олень двигается только если он был рядом с героиней до её хода
if abs(old_px - deer['x']) + abs(old_py - deer['y']) != 1:
return
# Если героиня не двигалась (old == new), олень тоже стоит
if old_px == px and old_py == py:
return
# Целевая клетка - старая позиция героини
tx, ty = old_px, old_py
# Проверка, что целевая клетка свободна (не стена, не занята)
cell = world.get((tx, ty), '')
obstacles = ['🏢','🌳','🌲','🪩','🚾','🏪','👵','🏡','🧑🔧','🕌','🔔','🛖','🐑','🌴','🗿','🧔','🦌','⚰️','👷']
if cell in obstacles or any(b['x'] == tx and b['y'] == ty for b in buses) or (tx == px and ty == py):
return
# Перемещаем оленя
old_deer_x, old_deer_y = deer['x'], deer['y']
# Восстанавливаем клетку под оленем
world[(old_deer_x, old_deer_y)] = deer['under']
# Сохраняем содержимое целевой клетки
deer['under'] = world.get((tx, ty), '')
# Перемещаем
deer['x'], deer['y'] = tx, ty
world[(tx, ty)] = '🦌'
def check_trio_revenge():
"""Проверяет, находятся ли героиня, олень и механик в ряд из трёх клеток."""
global deer, px, py, mechanic, player_msg
if deer is None or not deer['following'] or mechanic is None:
return False
positions = [(px, py), (deer['x'], deer['y']), (mechanic['x'], mechanic['y'])]
# Проверяем горизонталь: все y одинаковы и x идут подряд
ys = [p[1] for p in positions]
xs = [p[0] for p in positions]
if len(set(ys)) == 1:
min_x, max_x = min(xs), max(xs)
if max_x - min_x == 2 and all(x in xs for x in range(min_x, max_x+1)):
return True
# Проверяем вертикаль: все x одинаковы и y идут подряд
if len(set(xs)) == 1:
min_y, max_y = min(ys), max(ys)
if max_y - min_y == 2 and all(y in ys for y in range(min_y, max_y+1)):
return True
return False
def play_revenge_scene():
"""25-секундный диалог, анимация и финальное сообщение."""
global deer, player_msg, px, py, mechanic, mechanic_killed_deer
# 25-секундный диалог (5 сегментов по 5 секунд)
segments = [
("👩🦰: Что, мудак, допизделся?\n🧑🔧: Опять ты, рыжая шлюха? А рядом кто?"),
("👩🦰: Это настоящий мужчина.\nСейчас он будет бить тебе ебало.\n🧑🔧: Вот этот лизун — настоящий мужчина? 🤣"),
("🦌: Сейчас же извинись перед девушкой!\n🧑🔧: А то что будет, клоун? Забодаешь меня?"),
("🦌: Тебя родители не учили, что девушек нельзя обижать?\n🧑🔧: Лол, воспитатель нашёлся! 🤣"),
("🦌: Ты не мужчина! Я вызываю тебя на дуэль!\n🧑🔧: Значит, ты выбрал смерть.")
]
for seg in segments:
player_msg = seg
for _ in range(25):
time.sleep(0.2)
move_buses(ignore_player=True)
generate_area(px, py)
draw(px, py, hide_hud=False)
player_msg = ""
# Анимация (6 кадров по 0.5 секунды)
frames = [
["🌲▪️▪️🧑🔧▪️▪️▪️▪️🌲",
"🌲▪️▪️🌲▪️🦌▪️▪️🌲"],
["🌲▪️▪️🧑🔧▪️▪️▪️▪️🌲",
"🌲▪️▪️🌲🦶🦌▪️▪️🌲"],
["🌲▪️▪️🧑🔧▪️▪️▪️▪️🌲",
"🌲▪️▪️🌲🦶💢🦌▪️🌲"],
["🌲▪️▪️🧑🔧▪️▪️▪️▪️🌲",
"🌲▪️▪️🌲▪️▪️▪️🦌🌲"],
["🌲▪️▪️🧑🔧▪️▪️▪️▪️🌲",
"🌲▪️▪️🌲▪️▪️▪️🩸🌲"],
["🌲▪️▪️🧑🔧▪️▪️▪️▪️🌲",
"🌲▪️▪️🌲▪️▪️▪️☠️🌲"]
]
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
max_row_len = max(len(row) for frame in frames for row in frame)
h_pad = max(0, (term_cols - max_row_len) // 2)
v_pad = max(0, (term_lines - len(frames[0])) // 2)
for frame in frames:
os.system('cls' if os.name == 'nt' else 'clear')
print("\n" * v_pad, end="")
for row in frame:
print(" " * h_pad + row)
time.sleep(0.5)
# Олень исчезает, клетка становится '▪️'
if deer is not None:
world[(deer['x'], deer['y'])] = '▪️'
deer = None
# Механик убил оленя
mechanic_killed_deer = True
# Финальное сообщение
player_msg = ("👩🦰: Прости меня, Егор. Я была неправа.\n"
"🧑🔧: Проехали. Запиши мой телефон, авось пригодится.")
def play_toilet_scene(enter_from_coords, toilet_coords):
"""Сцена туалета. Возвращает True, если героиня спасена, False если умерла."""
global toilet_patience, mechanic, px, py, player_msg, toilet_paper, player_is_clown, mechanic_killed_deer
toilet_x, toilet_y = toilet_coords
enter_x, enter_y = enter_from_coords
toilet_art = [
"🧱🧱🧱🧱🧱🧱🧱",
"🧱▪️🪰▪️🪰▪️🧱",
"🧱🪰▪️🪰▪️🪰🧱",
"🧱▪️🪰▪️🪰💩🧱",
"🚪👩🦰▪️🪰💩🚽🧱",
"🪹🪹🪹🪹🪹🪹🪹"
]
def draw_toilet_art(art, msg=None, msg_color=None):
os.system('cls' if os.name == 'nt' else 'clear')
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
art_lines = art
total_lines = len(art_lines)
if msg:
total_lines += 2 + msg.count('\n') + 1
v_pad = max(0, (term_lines - total_lines) // 2)
h_pad = max(0, (term_cols - max(len(line) for line in art_lines)) // 2)
print("\n" * v_pad, end="")
for line in art_lines:
print(" " * h_pad + line)
if msg:
print()
for mline in msg.split('\n'):
if msg_color:
print(" " * max(0, (term_cols - len(mline)) // 2) + msg_color + mline + '\033[0m')
else:
print(" " * max(0, (term_cols - len(mline)) // 2) + mline)
msg1 = "👩🦰: Фух, вот это я насрала, даже в унитаз не влезло.\nОй, дверь заклинило, СПАСИТЕ!"
draw_toilet_art(toilet_art, msg1)
time.sleep(5)
if mechanic_killed_deer:
msg2 = "👩🦰: Алло, Егор, выручай, я в туалете застряла!"
draw_toilet_art(toilet_art, msg2)
time.sleep(5)
toilet_patience = None
toilet_paper += 1
if mechanic:
world[(mechanic['x'], mechanic['y'])] = '▪️'
else:
mechanic = {'met': True}
mechanic['under'] = world.get((enter_x, enter_y), '🔹')
mechanic['x'] = enter_x
mechanic['y'] = enter_y
mechanic['under'] = world.get((enter_x, enter_y), '🔹')
world[(enter_x, enter_y)] = '🧑🔧'
placed = False
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = enter_x + dx, enter_y + dy
if -114 <= nx <= 114 and -114 <= ny <= 114 and world.get((nx, ny)) == '🔹':
px, py = nx, ny
placed = True
break
if not placed:
for radius in range(2, 5):
for dx in range(-radius, radius+1):
for dy in range(-radius, radius+1):
if abs(dx) + abs(dy) == radius:
nx, ny = enter_x + dx, enter_y + dy
if -114 <= nx <= 114 and -114 <= ny <= 114 and world.get((nx, ny)) == '🔹':
px, py = nx, ny
placed = True
break
if placed: break
if placed: break
if not placed:
px, py = enter_x + 1, enter_y
player_msg = "👩🦰: Ты мой герой! 💋\nДавай я тебе отсосу в знак благодарности!\n🧑🔧: Иди на хуй, пока я тебя в сортире не закрыл!"
return True
else:
toilet_art_dead = [
"🧱🧱🧱🧱🧱🧱🧱",
"🧱▪️🪰▪️🪰▪️🧱",
"🧱🪰▪️🪰▪️🪰🧱",
"🧱▪️🪰▪️🪰💩🧱",
"🚪💀▪️🪰💩🚽🧱",
"🪹🪹🪹🪹🪹🪹🪹"
]
brown = '\033[38;5;94m'
msg_death = "ВЫ ЗАДОХНУЛИСЬ В ТУАЛЕТЕ!"
draw_toilet_art(toilet_art_dead, msg_death, brown)
time.sleep(5)
player_is_clown = False
return False
def visible_length(s):
"""Возвращает длину строки для центрирования (учитывая только символы)."""
return len(s)
def center_print(s, term_cols):
"""Печатает строку по центру с учётом ширины терминала."""
print(" " * max(0, (term_cols - visible_length(s)) // 2) + s)
def play_father_reunion_scene():
"""Сцена встречи с отцом в очереди за хлебом. Завершает игру победой."""
global px, py
term_cols, term_lines = shutil.get_terminal_size(fallback=(80, 24))
# Сцена с очередью
queue_art = [
"▪️▪️▪️▪️▪️▪️▪️‼️▪️▪️🍞🍞🍞",
"🧑💼🧑🦲🧑✈️🤱🧟🧛👨🦳🍞🙎♀️▪️🍞🍞🍞",
"▪️▪️▪️▪️▪️▪️💞‼️▪️▪️🍞🍞🍞",
"▪️▪️▪️▪️▪️▪️👩🦰‼️▪️▪️🍞🍞🍞",
"▪️▪️▪️▪️▪️▪️▪️‼️▪️▪️🍞🍞🍞"
]
dialogues = [
"👩🦰: ПАПА?!\n👨🦳: МАРУСЯ?!",
"👩🦰: Где ты был 16 лет?\n👨🦳: Я ушёл за хлебом, разве мама тебе не сказала?",
"👩🦰: Но за 16 лет ты так и не вернулся!\n👨🦳: Во всём городе работает лишь 1 магазин,\nвсе эти годы я стоял в очереди.",
"👨🦳: Ты лучше расскажи о себе.\nКогда я видел тебя в последний раз,\nвоспитательница детского садика жаловалась,\nчто ты лазишь мальчикам в трусики!\n👩🦰: Это было давно и неправда.\nСейчас я тихая, скромная и добрая,\nна досуге перевожу бабушек через дорогу,\nа ещё хожу в церковь.",
"👨🦳: А жених у тебя есть?\nТы уже целовалась с кем-нибудь?\n👩🦰: Ты что, разве можно целоваться до свадьбы?!\nЯ не пью, не курю, не матерюсь\nи уж точно ни с кем не целуюсь!",
"🧛: Э, мужик, очередь не задерживай!\nЯ тут ещё со времён СССР стою!\nЗабирай хлеб и вали отсюда!\n💪👩🦰: Будьте так любезны не разговаривать\nс моим отцом в таком тоне...",
"🧛: Я тебя знаю! Ты та самая шлю...\n💪👩🦰: Что, простите? У меня бицепс зачесался,\nя не расслышала.",
"🧛: Я говорю: шлю привет тебе и твоему отцу!\n👨🦳: Ого, какая ты сильная, Маруся!\nТы в качалку ходишь?",
"💪👩🦰: Нет, это я гектар земли вскопала.\nПросто не люблю сидеть без дела.\n👨🦳: Все видели, какая моя дочка умница?",
"🤱: Мужчина, имейте совесть!\nЯ всю жизнь провела в очереди!\nЯ даже родила в очереди!\nИ забеременела тоже в очереди!\nНашли время для болтовни!\n👨🦳: Всё, мы уже уходим."
]
# Показываем очередь и диалоги по 5 секунд
for dialogue in dialogues:
os.system('cls' if os.name == 'nt' else 'clear')
art_lines = queue_art
max_art_width = max(visible_length(line) for line in art_lines)
art_pad = max(0, (term_cols - max_art_width) // 2)
dialogue_lines = dialogue.split('\n')
total_height = len(art_lines) + 1 + len(dialogue_lines)
v_pad = max(0, (term_lines - total_height) // 2)
print("\n" * v_pad, end="")
for line in art_lines:
print(" " * art_pad + line)
print()
for line in dialogue_lines:
print(" " * max(0, (term_cols - visible_length(line)) // 2) + line)
time.sleep(5)
# Финальная сцена
os.system('cls' if os.name == 'nt' else 'clear')
final_art = [
"▪️▪️▪️🛑▪️▪️▪️",
"▪️▪️🛑🛑🛑▪️▪️",
"▪️🛑🛑🛑🛑🛑▪️",
"🛑🛑🛑🛑🛑🛑🛑",
"🪵🔸🔸🔸🔸🔸🪵",
"🪵🔸🔸🖼️🔸🔸🪵",
"🪵🔸🔸🔸🔸🔸🪵",
"🚪👨🦳🍞🔸👩🦰👩🦳🪵",
"🪹🪹🪹🪹🪹🪹🪹"
]
green = '\033[92m'
reset = '\033[0m'
victory_msg = green + "НЕВЕРОЯТНО! ОТЕЦ ВЕРНУЛСЯ В СЕМЬЮ С ХЛЕБОМ!" + reset
trophy_art = [
"==========",
"= ПОБЕДА =",
"=========="
]
max_art_width = max(visible_length(line) for line in final_art)
art_pad = max(0, (term_cols - max_art_width) // 2)
total_height = len(final_art) + 3 + len(trophy_art)
v_pad = max(0, (term_lines - total_height) // 2)
print("\n" * v_pad, end="")
for line in final_art:
print(" " * art_pad + line)
print()
center_print(victory_msg, term_cols)
print()
for line in trophy_art:
center_print(line, term_cols)
time.sleep(5)
def main():
global px, py, player_msg, buses, roses_collected, ribbons_collected, bouquets_collected, grandma, mechanic, sheeps, monument_met, car
global new_npc, club_entrance, club_invited, club_visited, club_blacklist, pending_invite, lemon, salt, deer, player_in_bus, tracked_target, feast_visited
global toilet_patience, mechanic_killed_deer, player_is_clown, toilet_paper
global player_is_elf, lemon_tampon, gravediggers, coffin_coords, gravedigger_triggered, gravedigger_move_countdown, church_visited_global, shovel
global gravedigger_block_dialogue_shown, gravedigger_dialogue_shown
global queue_npcs_under, shop_with_queue_coords
global holes_dug, women_defeated_count, women_npcs, option_block_active, current_woman_coords
global hut_center
show_splash_screen()
init_city()
world[(0, 0)] = '🔹'
spawn_buses()
while True:
generate_area(px, py)
draw(px, py)
ch = getch()
# Сброс сообщения в начале цикла
player_msg = ""
# Сброс опций перед каждой итерацией
option_block_active = False
current_woman_coords = None
# Проверка соседства с NPC очереди ТОЛЬКО если игрок не в машине и не в автобусе
if not car['in_car'] and not player_in_bus:
adjacent_npc = False
adjacent_woman = False
for dx_npc, dy_npc in [(-1,0),(1,0),(0,-1),(0,1)]:
nx_npc, ny_npc = px + dx_npc, py + dy_npc
cell = world.get((nx_npc, ny_npc))
if cell in ['😩','😡','😴','😤','🥸','🤓','🤠','😎']:
adjacent_npc = True
if cell == '👩':
adjacent_woman = True
current_woman_coords = (nx_npc, ny_npc)
break # достаточно одной женщины
if adjacent_woman and women_defeated_count == 0:
option_block_active = True
if not player_msg:
player_msg = "👩🦰: Мне только спросить.\n👩: Куда без очереди, шаболда?\nДумаешь, самая умная?"
elif adjacent_npc and not player_msg:
player_msg = "👩🦰: Мне только спросить."
else:
option_block_active = False
# Обработка клавиш-цифр для компаса (до выхода и движения)
if ch in ['3','4','5','6','7','8','0']:
if ch == '3': tracked_target = 'grandma'
elif ch == '4': tracked_target = 'monument'
elif ch == '5': tracked_target = 'new_npc'
elif ch == '6': tracked_target = 'mechanic'
elif ch == '7': tracked_target = 'shop'
elif ch == '8': tracked_target = 'hut'
elif ch == '0': tracked_target = None
draw(px, py)
continue
# Обработка опций, если активны
if option_block_active and ch in ['1','2','9']:
if ch == '1': # подраться с 👩
if holes_dug < 25:
player_msg = "👩 вырубила вас кастетом и затоптала до смерти! 👠"
option_block_active = False # сбрасываем опции
draw(px, py, dead=True)
time.sleep(3)
break
else:
if current_woman_coords:
wx, wy = current_woman_coords
world[(wx, wy)] = queue_npcs_under.get((wx, wy), '🔹')
women_npcs.remove((wx, wy))
women_defeated_count += 1
player_msg = "Вы вырубили 👩 мускулистой мозолистой рукой\nи затоптали до смерти! 💪"
option_block_active = False
current_woman_coords = None
continue
elif ch == '2': # позвонить 🧑🔧
if not mechanic_killed_deer:
player_msg = "У вас нет номера 🧑🔧!"
else:
player_msg = "🧑🔧: Не впутывай меня в свои блядские разборки!"
option_block_active = False
draw(px, py)
time.sleep(2)
player_msg = ""
continue
elif ch == '9': # убить 👩 лопатой
if shovel == 0:
player_msg = "У вас нет лопаты!"
option_block_active = False
draw(px, py)
time.sleep(2)
player_msg = ""
continue
else:
player_is_clown = True
if current_woman_coords:
wx, wy = current_woman_coords
world[(wx, wy)] = '💀'
# Заменяем всех остальных NPC очереди на 🤣
for (nx, ny) in queue_npcs_under.keys():
if (nx, ny) != (wx, wy) and world.get((nx, ny)) in ['👩','😩','😡','😴','😤','🥸','🤓','🤠','😎']:
world[(nx, ny)] = '🤣'
player_msg = "🤣х10: Рыжая, рыжая, конопатая, убила девушку лопатою!\n\n🤡 Ты попалась на детскую дразнилку\nи опозорилась на весь город!"
option_block_active = False # сбрасываем опции
draw(px, py)
time.sleep(3)
break
if ch in ['q', 'й', '\x03'] and not car['in_car']: break
if ch in ['q', 'й', '\x03'] and car['in_car'] and ch == '\x03': break
dx, dy = 0, 0
move_intent = (0, 0)
is_brake = False
player_acted = False
if ch in ['н', 'y', 'w', 'ц', 'up']:
dy, move_intent = -1, (0, -1)
elif ch in ['и', 'b', 's', 'ы', 'down']:
dy, move_intent = 1, (0, 1)
elif ch in ['п', 'g', 'a', 'ф', 'left']:
dx, move_intent = -1, (-1, 0)
elif ch in ['о', 'j', 'd', 'в', 'right']:
dx, move_intent = 1, (1, 0)
elif ch in ['р', 'h', ' ', 'r']:
dx, dy, is_brake = 0, 0, True
player_acted = True
# Обработка отложенного приглашения
if pending_invite:
if new_npc:
world[(new_npc['x'], new_npc['y'])] = new_npc['under']
new_npc = None
if bouquets_collected > 0:
bouquets_collected -= 1
club_invited = True
player_msg = "Кавказец вошёл в ночной клуб и приглашает тебя внутрь!"
pending_invite = False
old_px, old_py = px, py
if car['in_car']:
if ch in ['e', 'у', 'E', 'У', 'е', 'Е']:
if car['speed'] > 0:
player_msg = "Выйти из автомобиля можно только после полной остановки!"
else:
is_avenue = False
if abs(car['x']) <= 111 and abs(car['y']) <= 111:
for b in [-8, -3, 2, 7]:
if car['x'] in [b*9+4, b*9+5] or car['y'] in [b*9+4, b*9+5]:
is_avenue = True
break
if is_avenue:
player_msg = "Нельзя оставлять автомобиль на проезжей части проспекта!"
else:
exit_cells = []
if car['type'] == '🚗': exit_cells = [(car['x'], car['y'] - 1), (car['x'], car['y'] + 1)]
else: exit_cells = [(car['x'] - 1, car['y']), (car['x'] + 1, car['y'])]
valid_exits = []
obstacles = ['🏢', '🌳', '🌲', '🪩', '🚾', '🏪', '👵', '🏡', '🧑🔧', '🕌', '🔔', '🛖', '🐑', '🌴', '🗿', '🧔', '🦌', '⚰️', '👷', '👩', '😩', '😡', '😴', '😤', '🥸', '🤓', '🤠', '😎']
for ex, ey in exit_cells:
tc = world.get((ex, ey), '')
if tc not in obstacles and not any(b['x'] == ex and b['y'] == ey for b in buses):
if (ex, ey) in bus_road_cells and abs(ex) <= 111 and abs(ey) <= 111:
continue
valid_exits.append((ex, ey))
if valid_exits:
px, py = random.choice(valid_exits)
if world.get((px, py)) == '🌹':
roses_collected += 1
world[(px, py)] = '🔹'
player_msg = "Вы сорвали розу!"
elif world.get((px, py)) == '🥄':
shovel += 1
world[(px, py)] = get_road_type(px, py) or '🔹'
player_msg = "Вы подобрали лопату!"
else:
player_msg = "Вы вышли из автомобиля."
car['in_car'] = False
player_acted = True
if toilet_patience is not None and toilet_patience > 0:
toilet_patience -= 1
if toilet_patience == 0:
player_msg = "💩 Ты обосралась и опозорилась на весь город!"
if not car['in_car'] and not player_in_bus:
player_is_clown = True
draw(px, py)
time.sleep(3)
break
hit_bus = move_buses()
if hit_bus:
if player_msg: player_msg += "\n"
if car['in_car']:
player_msg += "💥 Вы попали в аварию и погибли!"
world[(car['x'], car['y'])] = '💥'
else:
player_msg += "💥 Вас сбил автобус!"
draw(px, py, dead=True)
break
continue
else:
bus_road_blocked = False
for ex, ey in exit_cells:
tc = world.get((ex, ey), '')
if (ex, ey) in bus_road_cells and abs(ex) <= 111 and abs(ey) <= 111 and tc not in obstacles and not any(b['x'] == ex and b['y'] == ey for b in buses):
bus_road_blocked = True
break
if bus_road_blocked:
player_msg = "Вы не можете выйти на проезжую часть проспекта!"
else:
player_msg = "Выход из автомобиля заблокирован препятствиями!"
continue
if move_intent != (0, 0) or is_brake:
steps = 0
if is_brake:
car['speed'] = max(0, car['speed'] - 2)
steps = car['speed']
else:
if car['speed'] == 0:
car['speed'] = 1
car['dir'] = move_intent
car['type'] = '🚗' if move_intent[1] == 0 else '🚘'
steps = 1
elif move_intent == car['dir']:
car['speed'] = min(5, car['speed'] + 1)
steps = car['speed']
elif move_intent == (-car['dir'][0], -car['dir'][1]):
if car['speed'] > 0:
player_msg = "Вы не можете поехать в обратном направлении до полной остановки!"
else:
if car['speed'] <= 3:
car['dir'] = move_intent
car['type'] = '🚗' if move_intent[1] == 0 else '🚘'
car['speed'] = 1
steps = 1
else:
player_msg = "Вы не можете повернуть на большой скорости!"
hit_npc = False
crashed = False
rose_crushed = False
deer_hit = False
old_car_x, old_car_y = car['x'], car['y']
for _ in range(steps):
nx = car['x'] + car['dir'][0]
ny = car['y'] + car['dir'][1]
tc = world.get((nx, ny), '')
obstacles = ['🏢', '🌳', '🌲', '🪩', '🚾', '🏪', '🏡', '🕌', '🔔', '🛖', '🌴', '🗿', '⚰️', '👷']
npcs = ['👵', '🧑🔧', '🧔', '👩', '😩', '😡', '😴', '😤', '🥸', '🤓', '🤠', '😎']
if tc == '🦌' or (deer is not None and deer['x'] == nx and deer['y'] == ny):
if deer is not None and deer['x'] == nx and deer['y'] == ny:
world[(deer['x'], deer['y'])] = '▪️'
deer = None
else:
world[(nx, ny)] = '▪️'
deer_hit = True
car['x'], car['y'] = nx, ny
continue
hit_static_bus = any(b['x'] == nx and b['y'] == ny for b in buses)
if tc in obstacles or hit_static_bus:
car['x'], car['y'] = nx, ny
crashed = True
if hit_static_bus:
for b in buses[:]:
if b['x'] == nx and b['y'] == ny:
buses.remove(b)
break
elif tc in npcs:
car['x'], car['y'] = nx, ny
hit_npc = True
if tc == '👵':
grandma['state'] = 'dead'
world[(nx, ny)] = grandma['under']
elif tc == '🧑🔧':
world[(nx, ny)] = mechanic['under']
mechanic = None
elif tc == '🧔':
world[(nx, ny)] = new_npc['under']
new_npc = None
elif tc in ['👩', '😩', '😡', '😴', '😤', '🥸', '🤓', '🤠', '😎']:
# Восстанавливаем исходную клетку из словаря
world[(nx, ny)] = queue_npcs_under.get((nx, ny), '🔹')
else:
car['x'], car['y'] = nx, ny
if tc == '🌹':
rose_crushed = True
world[(nx, ny)] = '🔹'
elif tc == '🥄':
shovel += 1
world[(nx, ny)] = get_road_type(nx, ny) or '🔹'
player_msg = "Вы подобрали лопату!"
px, py = car['x'], car['y']
if car['x'] != old_car_x or car['y'] != old_car_y:
player_acted = True
if toilet_patience is not None and toilet_patience > 0:
toilet_patience -= 1
if toilet_patience == 0:
player_msg = "💩 Ты обосралась и опозорилась на весь город!"
if not car['in_car'] and not player_in_bus:
player_is_clown = True
draw(px, py)
time.sleep(3)
break
if deer_hit:
player_msg = "Вы насмерть сбили оленя!"
elif rose_crushed:
player_msg = "Вы раздавили розу(ы)!"
if crashed:
world[(car['x'], car['y'])] = '💥'
player_msg = "Вы попали в аварию и погибли!"
draw(px, py, dead=True)
break
if hit_npc and not crashed:
player_msg = "Вы сбили человека насмерть!"
draw(px, py)
time.sleep(3)
show_prison_screen()
break
else:
# Новая проверка: копание лопатой (🥄) на клетке '▪️'
if ch in ['e', 'у', 'E', 'У', 'е', 'Е'] and shovel > 0 and world.get((px, py)) == '▪️':
world[(px, py)] = '🕳️'
holes_dug += 1
player_msg = "Вы выкопали ямку, но ничего не нашли!"
player_acted = True
dx, dy = 0, 0 # предотвращаем дальнейшее движение
if (dx, dy) != (0, 0):
if grandma['state'] == 'crossing' and (dx, dy) != grandma['crossing_dir']:
player_msg = "Вы не можете бросить бабушку!"
dx, dy = 0, 0
if (dx, dy) != (0, 0):
nx, ny = px + dx, py + dy
if world.get((nx, ny)) == '🪩':
if club_blacklist:
player_msg = "Вы в чёрном списке во всех клубах города!"
elif club_invited and (nx, ny) == club_entrance:
enter_from = (px, py)
play_club_scene()
px, py = enter_from
continue
else:
player_msg = "Фейсконтроль ночного клуба не пускает внутрь кого попало!"
elif nx == car['x'] and ny == car['y']:
if car['type'] == '🚗' and dx == 0 and dy in [-1, 1]:
car['in_car'] = True
px, py = car['x'], car['y']
car['speed'] = 0
player_acted = True
elif car['type'] == '🚘' and dy == 0 and dx in [-1, 1]:
car['in_car'] = True
px, py = car['x'], car['y']
car['speed'] = 0
player_acted = True
else:
player_msg = "В этот автомобиль можно сесть только сбоку!" if car['type'] == '🚘' else "В этот автомобиль можно сесть только сверху или снизу!"
else:
bus_at_target = next((b for b in buses if b['x'] == nx and b['y'] == ny), None)
is_on_stop = (px, py) in stop_signs
boarded = False
if bus_at_target and is_on_stop:
if is_last_stop(bus_at_target):
player_msg = "На конечной остановке войти нельзя! Перейдите через дорогу."
else:
player_msg = "Вы вошли в автобус!"
px, py = nx, ny
player_bus = bus_at_target
boarded = True
if boarded:
run_bus_animation(player_bus)
continue
elif not bus_at_target:
target_cell = world.get((nx, ny), '')
if target_cell == '🚾' and toilet_patience is not None and toilet_patience > 0:
saved = play_toilet_scene((old_px, old_py), (nx, ny))
if saved:
continue
else:
break
elif target_cell == '⚰️' or target_cell == '👷':
player_msg = "Сюда нельзя наступать!"
elif target_cell == '🥄':
# Подбираем лопату
shovel += 1
world[(nx, ny)] = get_road_type(nx, ny) or '🔹'
px, py = nx, ny
player_acted = True
if toilet_patience is not None and toilet_patience > 0:
toilet_patience -= 1
if toilet_patience == 0:
player_msg = "💩 Ты обосралась и опозорилась на весь город!"
if not car['in_car'] and not player_in_bus:
player_is_clown = True
draw(px, py)
time.sleep(3)
break
player_msg = "Вы подобрали лопату!"
elif target_cell == '🏪':
# Проверяем, является ли магазин ключевым (с очередью)
if (nx, ny) == shop_with_queue_coords:
play_father_reunion_scene()
break # завершаем игру
else:
player_msg = "Магазин закрыт!"
elif target_cell not in ['🏢', '🌳', '🌲', '🪩', '🚾', '🏪', '👵', '🏡', '🧑🔧', '🕌', '🔔', '🛖', '🐑', '🌴', '🗿', '🧔', '🦌', '⚰️', '👷', '👩', '😩', '😡', '😴', '😤', '🥸', '🤓', '🤠', '😎']:
px, py = nx, ny
player_acted = True
if toilet_patience is not None and toilet_patience > 0:
toilet_patience -= 1
if toilet_patience == 0:
player_msg = "💩 Ты обосралась и опозорилась на весь город!"
if not car['in_car'] and not player_in_bus:
player_is_clown = True
draw(px, py)
time.sleep(3)
break
if target_cell == '🌹':
roses_collected += 1
world[(px, py)] = '🔹'
player_msg = "Вы сорвали розу!"
elif target_cell == '🎗️':
ribbons_collected += 1
world[(px, py)] = get_road_type(px, py) or '🔹'
player_msg = "Вы подобрали ленточку!"
else:
if target_cell == '🛖':
if club_visited and not feast_visited:
play_feast_scene()
continue
elif target_cell == '🕌':
if player_is_elf:
enter_x, enter_y = px, py
play_church_scene(enter_x, enter_y)
continue
else:
player_msg = "В церковь нельзя с непокрытой головой!"
elif target_cell == '🚾': player_msg = "Вам сейчас не нужно в туалет!"
# Взаимодействия с NPC и памятником (только вне автомобиля)
if not car['in_car'] and not player_in_bus:
moved_dx = px - old_px
moved_dy = py - old_py
if mechanic and not mechanic['met']:
if max(abs(px - mechanic['x']), abs(py - mechanic['y'])) <= 1:
mechanic['met'] = True
player_acted = True
play_mechanic_dialogue()
old_px, old_py = px, py
moved_dx, moved_dy = 0, 0
if new_npc and not new_npc['met']:
if abs(px - new_npc['x']) + abs(py - new_npc['y']) == 1:
new_npc['met'] = True
player_acted = True
if bouquets_collected == 0:
player_msg = "👩🦰: Познакомимся, красавчик? Пойдём на дискотеку?\n🧔: Думаешь, если я кавказец, то поведусь на первую встречную?"
else:
player_msg = "🧔: Вах, какой дэвущка! Зачэм тебе этот жялкий веник? Это жьиних купиль, да? Викинь его, айда в клюб — потанцуем, жи есть!"
pending_invite = True
old_px, old_py = px, py
moved_dx, moved_dy = 0, 0
if monument_coords:
if abs(px - monument_coords[0]) + abs(py - monument_coords[1]) == 1:
player_acted = True
if not player_is_elf and toilet_paper > 0 and lemon > 0:
player_is_elf = True
toilet_paper = 0
lemon_tampon = True
player_msg = "Надо бы сменить имидж... ЭВРИКА!\nЯ обмотаю волосы туалетной бумагой!\nХоп-хоп-хоп — готово! А куда засунуть лимон,\nчтобы я не ходила с ним, как дура?\nПридумала: ЛИМОН-ТАМПОН! Оп!"
else:
if not monument_met:
monument_met = True
msg1 = "👩🦰: Вау, это же памятник Василию Пупкину! Надо срочно сделать селфи, но не с пустыми же руками!"
if player_msg: player_msg += "\n" + msg1
else: player_msg = msg1
if ribbons_collected > 0 and roses_collected > 2 and roses_collected % 2 != 0:
roses_collected = 0
ribbons_collected = 0
bouquets_collected += 1
msg2 = "👩🦰: ЭВРИКА! Я сделаю букет из роз и ленточки! Хоп-хоп-хоп — готово! Так, улыбочку! Щёлк! А фото выложу в Нельзяграм и получу кучу лайков! 😍"
if player_msg: player_msg += "\n" + msg2
else: player_msg = msg2
# Проверка соседства с NPC из очереди (не женщин)
# Этот блок больше не нужен, так как обработан выше
# Но оставим для совместимости с другими NPC
adjacent_npc = False
for dx_npc, dy_npc in [(-1,0),(1,0),(0,-1),(0,1)]:
nx_npc, ny_npc = px + dx_npc, py + dy_npc
if world.get((nx_npc, ny_npc)) in ['😩','😡','😴','😤','🥸','🤓','🤠','😎']:
adjacent_npc = True
break
if adjacent_npc and not player_msg:
player_msg = "👩🦰: Мне только спросить."
# Проверка взаимодействия с могильщиком (начало диалога)
for g in gravediggers:
if g['state'] == 'idle' and abs(px - g['x']) + abs(py - g['y']) == 1:
if world.get((px, py)) == '🔸' and not gravedigger_dialogue_shown:
player_acted = True
play_gravedigger_dialogue()
gravedigger_dialogue_shown = True
old_px, old_py = px, py
moved_dx, moved_dy = 0, 0
break # только один диалог за раз
if grandma['state'] == 'crossing':
if (moved_dx, moved_dy) == grandma['crossing_dir'] and (moved_dx, moved_dy) != (0, 0):
world[(grandma['x'], grandma['y'])] = grandma['under']
grandma['x'] += moved_dx
grandma['y'] += moved_dy
grandma['under'] = world.get((grandma['x'], grandma['y']), '🔹')
world[(grandma['x'], grandma['y'])] = '👵'
if grandma['under'] == '🔸':
grandma['state'] = 'going_home'
player_msg = "👵: Спасибо, внучка. Я всем расскажу, какая ты хорошая девочка."
elif grandma['state'] == 'going_home':
adj_building = False
for bx, by in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if world.get((grandma['x'] + bx, grandma['y'] + by)) in ['🏢', '🏡']:
adj_building = True
break
if adj_building:
world[(grandma['x'], grandma['y'])] = grandma['under']
grandma['state'] = 'none'
player_msg = "Бабушка благополучно добралась домой!"
else:
q = [(grandma['x'], grandma['y'])]
came_from = {(grandma['x'], grandma['y']): None}
target = None
dist_idx = get_district_index(grandma['x']), get_district_index(grandma['y'])
while q:
cx, cy = q.pop(0)
for bx, by in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cx + bx, cy + by
if (nx, ny) == (px, py): continue
if not (-114 <= nx <= 114 and -114 <= ny <= 114): continue
if (get_district_index(nx), get_district_index(ny)) != dist_idx: continue
if get_road_type(nx, ny) is not None: continue
if (nx, ny) not in came_from:
cell = world.get((nx, ny))
if cell in ['🏢', '🏡']:
target = (cx, cy)
came_from[(nx, ny)] = (cx, cy)
q = []
break
elif cell not in ['🏪', '🚾', '🛑', '👵', '🪩', '🧔', '⚰️', '👷']:
came_from[(nx, ny)] = (cx, cy)
q.append((nx, ny))
if target:
curr = target
if curr != (grandma['x'], grandma['y']):
while came_from[curr] != (grandma['x'], grandma['y']):
curr = came_from[curr]
world[(grandma['x'], grandma['y'])] = grandma['under']
grandma['x'], grandma['y'] = curr
grandma['under'] = world.get((grandma['x'], grandma['y']), '🔹')
world[(grandma['x'], grandma['y'])] = '👵'
if grandma['state'] == 'waiting':
if world.get((px, py)) == '🔸' and abs(px - grandma['x']) + abs(py - grandma['y']) == 1:
cross_dir = None
if grandma['y'] % 9 == 3: cross_dir = (0, 1)
elif grandma['y'] % 9 == 6: cross_dir = (0, -1)
elif grandma['x'] % 9 == 3: cross_dir = (1, 0)
elif grandma['x'] % 9 == 6: cross_dir = (-1, 0)
target_g = (grandma['x'] + cross_dir[0] * 3, grandma['y'] + cross_dir[1] * 3)
target_p = (px + cross_dir[0] * 3, py + cross_dir[1] * 3)
if (car['x'], car['y']) not in (target_g, target_p):
player_msg = "👩🦰: Давайте я переведу Вас через дорогу.\n👵: Спасибо, внучка."
grandma['state'] = 'crossing'
grandma['crossing_dir'] = cross_dir
player_acted = True
# Проверка взаимодействия с оленем
try_deer_interaction()
# Движение оленя после игрока
if player_acted:
move_deer_if_following(old_px, old_py)
if check_trio_revenge():
play_revenge_scene()
continue
# Пересчет опций после возможного перемещения
if not car['in_car'] and not player_in_bus:
adjacent_woman_now = False
current_woman_coords = None
for dx_npc, dy_npc in [(-1,0),(1,0),(0,-1),(0,1)]:
nx_npc, ny_npc = px + dx_npc, py + dy_npc
if world.get((nx_npc, ny_npc)) == '👩' and women_defeated_count == 0:
adjacent_woman_now = True
current_woman_coords = (nx_npc, ny_npc)
break
if adjacent_woman_now:
option_block_active = True
if not player_msg:
player_msg = "👩🦰: Мне только спросить.\n👩: Куда без очереди, шаболда?\nДумаешь, самая умная?"
else:
option_block_active = False
# Движение овец
for sheep in sheeps:
valid_moves = []
for sdx, sdy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = sheep['x'] + sdx, sheep['y'] + sdy
if (nx, ny) in grass_cells and world.get((nx, ny)) == '🌱' and (nx, ny) != (px, py):
valid_moves.append((nx, ny))
if valid_moves:
nx, ny = random.choice(valid_moves)
world[(sheep['x'], sheep['y'])] = '🌱'
sheep['x'], sheep['y'] = nx, ny
world[(nx, ny)] = '🐑'
# Обновляем могильщиков после хода игрока
if player_acted and church_visited_global:
update_gravediggers()
# Двигаем автобусы только если игрок сделал ход
if player_acted:
hit_bus = move_buses()
if hit_bus:
if player_msg: player_msg += "\n"
if car['in_car']:
player_msg += "💥 Вы попали в аварию и погибли!"
world[(car['x'], car['y'])] = '💥'
else:
player_msg += "💥 Вас сбил автобус!"
draw(px, py, dead=True)
break
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
passИгровая комната |
||
|---|---|---|
| Собранные игрушки | Second Hand · SimCity 2000 · QTE · Wikigacha · Весёлый работодатель · Казино «Флэш Рояль» · Маленький человечек в большой квартире: Яичница · Отшей блондинку · Пустыня · Путешествие куда-нибудь и не только · Сказка про белого бычка | |
| Разобранные игрушки | AI Adventures · Бездна · Интерактивное кино · Крестики-нолики · Самый сложный экзамен · Симулятор динозавра | |
| Игрушечный питон | Велогрибник: Заводской Дрифт · Велогрибник 2.0: Лесное Метро · Проф. Волкгонагалл: Лесная Трансфигурация (версия 37.0) · Маруся-Нетакуся | |
| Ещё чего: Портал «Игры Абсурдопедии» | Компьютерные игры | Ролевая игра Абсурдопедии | ||
