игра «Гопник-2»

Приступ «гопникомании»

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

Исходный код оригинальной игры, по всей видимости, канул в лету, но мне когда-то повезло наткнуться на неоконченный порт этой игры под Линукс. С тех пор я ей иногда занимаюсь.

Пиксельный портрет мужчины в кепке с сигаретой и надписью «ГОПНИК-2», подзаголовками «ОКОРЕНИЕ», «УЛЬТУРНОЙ», «ТОЛИЦЫ» на тёмном фоне.
Заставка оригинальной игры, взятая из текстового файла описания

Недавно в мой репозиторий пришёл некий активный гражданин и навёл там суеты, за что я ему благодарен. В частности, он подчеркнул кое-какие изъяны в балансе, плюс оказалось, что в процессе перевода кода с сишного printf на модуль Си++ fmt, я поломал буферизацию в Винде, которую когда-то чинил простым макросом.

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

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

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

16 комментариев

Марковские цепи

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

Но вставлять в игру, занимающую около мегабайта, любую, даже самую маленькую БЯМ (большую языковую модель) — странная идея. Размер возрастёт в десятки раз, а фана будет — чуть.

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

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

У меня была идея скормить ей достаточное количество разговоров из книг «Слова пацана» и «Казанский феномен: миф и реальность», чтобы получать какой-то осмысленный диалог. К сожалению, лодка надежд разбилась о примитивность алгоритма.

Конечно, я посидел над этом идеей всего один вечер, но, кажется, качественного скачка не произойдёт, сколько не сиди. Удачные диалоги встречаются, но чаще всего видно, что модель мира там отсутствует полностью.

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

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

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

7 комментариев

PVS-Studio и «Гопник-2»

Скриншот VS Code: файл run.cpp и вкладка PVS-Studio с предупреждениями V690, V614, V601, V786, V547, V801, V815.

Много читал об инструменте статического анализ PVS-Studio — на «Хабре» постоянно выходят статьи с разбором ошибок, которые он помогает найти. Выглядит действительно очень здорово. Несмотря на неизбежные ложные срабатывания, находится много такого, что пропустил программист.

PVS-Studio — платный инструмент, но утром за завтраком мне стало интересно, нет ли там какой-нибудь триальной версии, чтобы можно было на практике познакомиться с тем как он настраивается и чем может быть полезным. Оказалось, что у него есть восьмидневная бесплатная лицензия и я решил глянуть что он сможет найти в коде «Гопника-2».

Нашлось несколько интересных ошибок. На скриншоте одна из них. Как видно, str_enemy_amount проверяется два раза, тогда как после первого условия там гарантировано ноль. Очевидно автор этого куска кода его скопипастил, но забыл во втором куске исправить enemy на hero.

Я бегло посмотрел все ошибки, которые он мне выдал, надо будет внимательно посмотреть каждую, но как будто бы всё по делу, ноль ложных срабатываний.

Оставить комментарий

Накрыло «Гопником-2»

Что-то мой закрывшийся, было, гештальт опять открылся — думал, что меня этот старинный текстовый квест уже не интересует, ан нет, вчера что-то совершенно не спалось, я снова расчехлил игру «Гопник-2» и играл в неё до поздней ночи.

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

Скриншот текстового квеста: описание стычки банд и списки участников «Ваша басота» (зелёные) и «Их басота» (красные) с уровнями.
Меня позвали на «стрелку» в игре «Гопник-2», если бы не шокер, я бы не выжил

Оставить комментарий

«Гопник-2»: убрал printf

Закончил убирать функцию printf, сегодня вколотил последний гвоздь в крышку её гроба. Я почему-то наивно полагал, что на этом моя работа по избавлению от char * будет окончена, но не учёл, что в коде довольно много (больше 150) других мест, где ещё используется этот тип.

В процессе замены встретилось вот такое. Загадка, всё ли хорошо в следующем коде?

std::cout << fmt::format(" {}(+{})", main_hero->inv[i].name, main_hero->inv[i].armo)

Мой компилятор считает, что всё, но сборка на «Гитхабе» падает с ошибкой «Binding packed field error».

Получается, я по неведомой причине не могу обратиться по ссылке на поле внутри упакованной структуры (fmt::format принимает параметры по ссылке).

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

const auto name = main_hero->inv[i].name;
const auto armo = main_hero->inv[i].armo;

std::cout << fmt::format(" {}(+{})", name, armo);

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

Я бы понял, если бы «Гитхаб» собирал мне бинарники для какой-то экзотической архитектуры, где есть какие-то особенности адресации, но тут такого ведь и в помине нет.

2 комментария

«Гопник-2»: что нового?

Можно подумать, что я совсем забросил заниматься игрой «Гопник-2», про которую ещё недавно так много писал, но это не так. Просто ничего особо интересного я там пока не делаю, — в основном довольно механически заменяю сишные строки (char *) на сиплюсплюсный класс std::string.

Недавно появившиеся контрибьюторы добавили мне в проект библиотеку fmt, что упростило работу, — во-первых, теперь не приходится костылить собственные обёртки, чтобы чем-то заменить сишную функцию форматирования printf, а во-вторых, из-за гибкости fmt можно оставлять какие-то места почти нетронутыми, если сходу не удаётся перевести их на новые рельсы. Удобно.

Осталось примерно 260 замен, не так много, было около 400 или около того.

Скриншот терминала: вывод теста gop2_tests с сообщениями о займе у пацанов и понтовости, завершающийся зелёным «All tests passed (12 assertions in 1 test case)».
Тесты прошли успешно

Ещё ребята постарались добавить мне тесты. Я ещё не смотрел, но планирую ознакомиться с тем как это делает. Так как на Си++ я раньше не писал, то совершенно без понятия как они тут выглядят, любопытно.

Оставить комментарий

«Гопник-2»: контрибьюторы

Фантастика! У меня на проекте буквально за эти выходные появилось ещё два контрибьютора.

deadem перевёл проект на сборку cmake, принёс библиотеку форматирования fmt и приделал к нему CI «Гитхаба», теперь сборка идёт прямо там. kartaris убрал fmt в зависимости, принёс менеджер пакетов conan и дописал код для использования в нём fmt.

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

Графики активности bolknote, deadem, kartaris (2011–2023): коммиты и изменения строк
Активность по контрибьюторам в проекте на «Гитхабе»

Оставить комментарий

Си++: форматированный вывод

Си++, похоже, язык для воспитания героев — создаём трудности на ровном месте и начинаем их преодолевать. В моём случае началось всё с разумной, как мне до сих пор кажется, идеи — заменить сишные безобразные строки char * на прекрасный класс std::string из Си++.

В процессе в комментариях мне написали, что вместо него надо использовать std::string_view, чтобы избежать копирования. Так я узнал, что за строки в Си++ отвечают не один класс, а четыре — есть ещё базовые классы, с приставкой base.

Заодно пришлось перейти на стандарт 2017 года, так как до строк с семантикой перемещения додумались только недавно.

Всё шло хорошо, пока я не дошёл до форматирования текста. В Си этим занимается функция printf, а Си++ есть метод std::format, но вот беда — он появился только в стандарте 2020 года и его ещё нет ни в одном из компиляторов, которые есть у меня под рукой.

Пошёл на поклон к ЧатГПТ, который выдал мне такую обёртку:

template<typename... Args>
std::string string_format(const std::string format, Args... args) {
    int size_s = std::snprintf(nullptr, 0, format.data(), args ...) + 1; // Extra space for '\0'
    if (size_s <= 0) {
        throw std::runtime_error("Error during formatting.");
    }

    auto size = static_cast<size_t>( size_s );
    std::unique_ptr<char[]> buf(new char[size]);
    std::snprintf(buf.get(), size, format.data(), args ...);
    return {buf.get(), buf.get() + size - 1}; // We don't want the '\0' inside
}

Это так называемый «шаблонная функция». Насколько я пока понимаю принцип, компилятор смотрит с каким типами параметров вызывается эта функция, и делает их столько штук, сколько есть комбинаций вызовов.

Так как файлы с кодом друг друга не видят, чтобы шаблонная функция «знала» с какими параметрами её вызывают, её надо помещать в заголовочный файл (🤦🏻‍♂️), либо указать в коде все необходимые прототипы вручную (🤦🏻‍♂️🤦🏻‍♂️🤦🏻‍♂️).

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

На этом этапе я выяснил, что мой компилятор под «Виндоуз» ничего не знает про std::string_view, а компилятор под «Мак» не может скомпилировать код выше вот с такой прекрасной ошибкой:

comm/../utils.hpp:30:60: error: cannot pass object of non-trivial type 'std::string' through variadic function; call will abort at runtime [-Wnon-pod-varargs]
    int size_s = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...) + 1;

Мне бы хотелось, чтобы код собирался под оба компилятора, поэтому я снова пошёл к ЧатГПТ и вернулся вот с таким прекрасным кодом:

template<typename... Args>
std::string string_format(const std::string& format, Args&&... args) {
    auto size_s = std::snprintf(nullptr, 0, format.c_str(), 
                                 std::decay_t<Args>(args).c_str()...) + 1; // Extra space for '\0'
    if (size_s <= 0) {
        throw std::runtime_error("Error during formatting.");
    }

    auto size = static_cast<size_t>( size_s );
    std::unique_ptr<char[]> buf(new char[size]);
    std::snprintf(buf.get(), size, format.c_str(), 
                  std::decay_t<Args>(args).c_str()...);
    return {buf.get(), buf.get() + size - 1}; // We don't want the '\0' inside
}

Тут происходит какое-то чёрное кодунство в котором я ещё не разобрался. Но главное, что код заработал. Правда опять только под одним компилятором, второму не понравилось следующее:

comm/../utils.hpp:32:58: error: request for member 'c_str' in 'args#0', which is of non-class type 'std::decay_t<char*>' {aka 'char*'}
   std::decay_t<Args>(args).c_str()...) + 1;

К счастью, решилось это просто — надо было всего лишь убрать .c_str().

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

Теперь я понимаю за что программистам на Си++ платят такие деньги — это доплата за нервную работу.

12 комментариев

Бесплатный CLion?

Два дня до момента, когда моя среда разработки, где я мучаю Си++, превратится в тыкву. С преподавательской лицензией меня обломали, попробую другой путь — податься на бесплатную лицензию как бесплатный проект.

Казалось бы дело в шляпе, но там надо показать регулярные коммиты за три месяца, а у меня пока ещё и месяц не прошёл. Но я придумал план, надёжный, как швейцарские часы, — два месяца рефакторинга. Буду перетаскивать очевидные сишные части на Си++. Начал со строк — переписываю сишные указатели на нормальный std::string.

Придётся, видимо, пока взять в качестве редактора VSCode, вот только он не IDE. Ну а какой выбор? В процессе знакомлюсь с тем, что умеет Си++. Смотрите какая прелесть:

std::ostream &operator<<(std::ostream &os, const Colors &color) {
    static const std::map <Colors, std::string> colorNames = {
            {RESET,   "39;49m"},
            {BLUE,    "01;34m"},
            {GREEN,   "01;32m"},
            {CYAN,    "01;36m"},
            {RED,     "01;31m"},
            {MAGENTA, "01;35m"},
            {YELLOW,  "01;33m"},
            {WHITE,   "01;37m"},
            {BLACK,   "01;30m"},
    };

    os << "\033[" << colorNames.at(color);
    return os;
}

В Си это была большая функция с оператором switch, которая выводила на экран последовательности для переключения цвета, её надо было вызывать отдельно каждый раз. На Си++ всё удобнее:

std::cout << YELLOW << mess[2] << std::flush;

Рраз и строка жёлтая. YELLOW тут у меня — значение перечисляемого типа Colors.

15 комментариев

«Гопник-2»: демотивировался

Что-то меня неожиданно жёстко демотивировала фраза об окончании испытательного периода, которая начала недавно появляться в среде разработки, где я разрабатываю «Гопника-2». Когда её вижу, делать ничего не хочется. Кажется, зачем что-то начинать, если скоро период окончится и я не смогу дальше писать код.

Окно предупреждения: пробная версия CLion истекает через 3 дня. Предложено купить лицензию. Внизу видна строка состояния редактора.

Покупать мне её не хочется — всё-таки там приличные деньги, а проект-то у меня некоммерческий. Есть способы получить её бесплатно, один из них — податься как преподаватель. Я преподаватель высшей школы ИТИС КФУ, поэтому попробовал, не знаю, дадут ли, но попытка не пытка.

Дополнено: не дали:

Thank you for applying for the JetBrains Product Pack for Students.
We regret to inform you that we wouldn’t be able to provide a license due to EU and/or US export restrictions.
JetBrains as a company has to comply with all EU and US regulations and eliminate all possible risks coming out of it.

16 комментариев

«Гопник-2»: переход на нормальный Си++11

Как я писал прежде, когда я адаптировал «Гопника-2» для запуска под «Виндоузом», столкнулся с тем, что не все тамошние компиляторы игру собирали. Не хватало двух функций — strdup и _fileno. Проблема решилось переключением компилятора на стандарт gnu++11, но меня это беспокоило — вдруг найдётся компилятор, который его не поддерживает.

Тут у нас в Татарстане выдались длинные выходные, — в праздник «Ураза-байрам» у нас выходной, я решил этим воспользоваться и поковырять эту тему подробнее.

Вызов _fileno у меня использовался для получения номера дескриптора входного потока, так что тут всё решилось просто, — для этого есть специальная константа STDIN_FILENO. А вот с strdup пришлось повозиться.

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

Первым делом пошёл на поклон к «ЧатГПТ», спросил совета. Советы не заработали, но дали направление мысли.

Первая мысль была забахать макрос, который заменял бы собой strdup в проблемном компиляторе, а остальные не трогал. Тут надо сказать, что под «Виндой» далеко не во всех наборах утилит компиляторы не содержат функцию strdup, например в msys2 она есть.

Мне хотелось как-то отличать между собой обычный mingw без strdup от того, который внутри msys2. «ЧатГПТ» тут не помог, хоть и подсунул несколько макросов на пробу, которых в действительности не существует.

Пришлось пошарить по исходникам. Оказалось, есть ряд макросов, которые присутствуют в msys2, но отсутствуют в msys. Из них я выбрал __MINGW64_VERSION_MAJOR:

#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
#define strdup(s) (__extension__({ const char* __s = (s); size_t __l = strlen(__s) + 1; char* __d = (char*)malloc(__l);\
__d ? (char*)memcpy(__d, __s, __l) : nullptr; }))
#endif

Тут используется расширение, которое позволяет писать многострочные макросы. Хотя этот вариант мне и нравится, но опять используется не нормальный Си++, а расширение компилятора.

Вторая мысль, на которую меня навела нейросеть — использовать слабые символы языка Си++. Это совершенно новая для меня штука, поэтому нужно небольшое пояснение. Прежде всего для меня же, чтобы я в будущем не забыл что это вообще такое.

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

Слабые символы используются для библиотек, чтобы пользователь мог заменить некоторые сущности, если они его не устраивают.

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

«ЧатГПТ», к сожалению, помочь не смог, — сначала подсовывал нерабочий код, а потом зациклился в своих советах. Поэтому пришлось разбираться самостоятельно.

Всё оказалось не так уж и сложно. Сначала в заголовочном файле определяем прототип функции:

#ifdef __MINGW32__
char *strdup(const char *s1);
#endif

Я его спрятал под условие, чтобы мой редактор перестал ругаться на избыточное определение, но всё работает и без этого.

Потом в самом коде создаём слабый символ для strdup:

#ifdef __MINGW32__
#pragma weak my_strdup
char *strdup(const char *src) {
    size_t len = strlen(src) + 1;
    char *s = (char *) malloc(len);
    return s == nullptr ? nullptr : (char *) memcpy(s, src, len);
}
#endif

Слабый символ я определяю через директиву #pragma weak, у которой надо указать имя слабого символа, причём оно должно отличаться от имени функции. Я пока не до конца понял зачем это нужно делать, но без этого не работает.

Кстати, #pragma weak тоже расширение, но оно легко заменяется на атрибут weak, надо только будет изучить его синтаксис, я не успел этого сделать.

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

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

Дополнено: в итоге все ситуации я побороть не смог и откатился на самое простое решение со своей собственной реализацией strdup.

Оставить комментарий

«Гопник-2»: новости

Может показаться, что после релиза «Гопника-2» я игрой больше не занимался, так как ничего про неё не пишу.

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

Текст битвы в игре: игрок наносит урон шоком, получает ответные удары, побеждает врага, получает 90 качков опыта; в конце — «А у Нарка был косячок. Пиво победителю!
Новая штука в игре — шокер, можно бить противника током

Я долго не понимал как во внутреннем АПИ игры формируются прайс-листы, но теперь разобрался и сделал, как и планировал, на новой станции «Чёрная речка» ларёк. В нём можно покрепиться семечками и шавермой (мы же в Питере), а так же купить батарейки для шокера — нового игрового предмета.

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

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

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

2 комментария

«Гопник-2»: версия 1.24

У меня тут небольшая веха в игре «Гопник-2» — вышел первый релиз, который я считаю стабильным. По этому поводу я даже собрал бинарники под все основные платформы. Для тех, кто не боится качать запускаемые файлы из интернета, выложил их на странице релиза.

Там универсальный бинарник под МакОС и статически собранные файлы под 64-битные «Виндоуз» и «Линукс». Ссылка для скачивания выше, а ниже скриншот, в нём ссылки не работают.

Релиз 1.24: исправлен баг с «Чёрной речкой», герой не может драться при побеге, сборка под Android; доступны бинарники для Linux, macOS, Windows и исходники.

Оставить комментарий

«Гопник-2»: теперь «Андроид»

В полку не совсем обычных платформ, где я попробовал собрать «Гопника-2», прибыло. В этот раз, пока обедал, листал экраны на смартфоне и взгляд упал на терминал Termux, который я когда-то ставил, чтобы иметь возможность зайти экстренно куда-нибудь по работе.

Решил попробовать скомпилируется ли. В принципе проблем почти не возникло. Всё необходимое поставил командой pkg install make clang git, после чего выписал проект и поправил путь до заголовочного файла — в «Андроиде» он находится в другом месте.

Скриншот текстовой RPG: игрок «bolk» в Купчине, уровень 0, здоровье 35/35, точность 30%, пива и бабла нет; начал битву с Гопником 1 уровня, ударил на 33 урона, враг промахался.
Так выглядит запущенный «Гопник-2», запущенный в терминале Termux под «Андроидом»

Правки в репозиторий внёс, теперь всё будет собираться ещё и под «Андроид». Проверять в будущем я не буду, вряд ли кто-то будет собирать для себя, но проверить было интересно.

4 комментария

«Гопник-2»: фан-клуб

Тем временем, у игры «Гопник-2», с которой я вожусь почти каждый день в последнее время, отрос небольшой фан-клуб. Самый заметный его участник, bairog, прошёл всю игру вдоль и поперёк, завёл мне десяток багов, которые я постепенно исправил и даже начал писать игре краткое описание. За что ему огромное спасибо!

Когда будет готово, надо будет это должным образом оформить, снабдить скриншотами и перенести в вики проекта.

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

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

Оставить комментарий

«Гопник-2»: разные mingw

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

Сильнее всего меня удивило в процессе, что поведение mingw, — инструментария для компиляции приложений под «Виндоуз», различается под разными операционными системами.

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

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

Во-первых, под «Виндой» отсутствовала функция strdup, интернет ничего не подсказал, но я случайно решил эту проблему, установив стандарт языка в GNU C++11: -std=gnu++11.

Во-вторых, почему отсутствовали-то ряд важных констант. Cледуя совету на «Стековерфлоу», я просто определил их нужными значениями.

В-третьих, функцию usleep пришлось заменить на Sleep из windows.h.

После этих правок игра начала собираться под «Виндой» тоже.

Недавно прочитал в одной из статей на Хабре, что «язык Си++ достаточно сложен, чтобы позволить нам писать на нём просто», как же это верно! Неожиданно много времени занимает борьба с какими-то вещами, которые, как мне кажется, должны просто работать.

10 комментариев

«Гопник-2» на Haiku

В 2000-е у меня был друг, который фанател от «BeOS». Послушать его, так это лучшая система на Земле, где всё сразу продумано, нет родовых травм и легаси. Судя по деталям, которыми он сыпал в своих рассказах, во многом так и было, — многие вещи, которые бесили в других операционках, там были сделаны на удивление хорошо.

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

А мне тут что-то до того понравилось водружать «Гопника» на всё новые и новые операционные системы, что я решил пока не останавливаться, и, порывшись в памяти, вспомнил про BeOS, установил себе на виртуалку Haiku и попробовать собрать игру.

Скриншот терминала с текстовой игрой «гопник2: gop2» на рабочем столе Haiku; виден диалог, выбор роли (подтсан, отморозок и др.), уровень сложности и начало боя с беспредельщиком.
«Гопник-2», запущенный под Haiku (наследницей BeOS), собралось без правок, но с приключениями

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

Вот как выглядит рецепт компиляции и запуска:

pkgman install gcc make haiku_devel
git clone https://github.com/bolknote/gopnik2.git
cd gopnik2
make
./gop2

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

Оставить комментарий

«Гопник-2»: UTF-8

Продолжаю писать про портирование «Гопника-2» под «Виндоуз», ибо там ещё есть о чём рассказать.

Я уже очень давно не работаю под этой операционной системой, поэтому некоторые обычные, возможно, для многих вещи для меня являются открытием. Когда я начинал процесс портирования игры под эту платформу, я был уверен, что столкнусь в трудностями совершенно в других частях кода. Одной из неожиданностей была плохая работа «Виндоуз» с кодировкой ЮТФ-8.

Если вывод в консоль удалось победить сравнительно легко, то со вводом возникли проблемы — вызов SetConsoleCP(CP_UTF8), который должен был помочь, не помог. В интернете пишут, что он, по всей видимости, не реализован — просто не делает ничего.

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

Рекомендуемый специалистами способ — переключить консоль в режим ввода кодировки ЮТФ-16, а потом перекодировать полученную строку в нужную кодировку. «Виндозу» ЮТФ-16 родная, поэтому проблем вообще никаких не возникает.

#ifdef __MINGW32__
        int wlen = 100;
        int save = _setmode(_fileno(stdin), _O_U16TEXT);
        wchar_t *wstr = (wchar_t *) malloc(wlen * sizeof(wchar_t));
        fgetws(wstr, wlen, stdin);

        int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, 0, 0, 0, 0);
        user_name = (char *) malloc(len);
        WideCharToMultiByte(CP_UTF8, 0, wstr, -1, user_name, len, 0, 0);
        _setmode(_fileno(stdin), save);

        free(wstr);
#endif

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

Оставить комментарий

«Гопник-2»: буферизация

Как я писал уже сегодня, мне пришлось вчера выключить буферизацию вывода, так как текст визуально «застревал» на экране. Добавление fflush проблему решало, но не будешь же выставлять этот вызов везде, где есть вывод, это несколько сотен мест.

В общем, вчера я сделал попроще, а сегодня пришла в голову более удачная идея обернуть printf в макрос:

// включаем буферизацию вывода
setvbuf(stdout, nullptr, _IOFBF, 1000);
// делаем макрос со сбросом буфера
#define PRINTF(...) { printf (__VA_ARGS__); fflush(stdout); }

Работает просто отлично, вывод текста стал выглядеть комфортно.

6 комментариев

«Гопник-2»: адаптация под Windows

В итоге, вчерашняя мысль адаптировать «Гопника-2» под Виндоуз меня не отпустила. Открыл вечером «ЧатГПТ» и попробовал с его помощью сделать первые шаги.

Несмотря на то, что «Чат» у меня последней, четвёртой версии, всё равно, даже на простых примерах он допускает совершенно детские ошибки, — не подключает нужные заголовочные файлы (мы программировали на Си), упускает важные части.

ChatGPT объясняет, что код на C с Unix-вызовами не переносится на Windows, но предлагает аналог через conio.h: функцию get_key с _setmode и _getch для чтения символа без эха.
Пытаемся программировать на пару с «ЧатГПТ-4»; подсказанный пример не скомпилируется

На скриншоте у меня как раз такой пример — он не скомпилируется из-за отсутствия констант и не содержит условия для обработки комбинации Ctrl+C, как было в том коде, который я попросил «ЧатГПТ» переписать.

Вот как в итоге стал выглядеть этот кусок после нескольких уточняющих вопросов и гуглежа:

#ifdef __MINGW32__
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode = 0;
    GetConsoleMode(hStdin, &mode);
    if (echo) {
        mode &= ~ENABLE_ECHO_INPUT;
    } else {
        mode |= ENABLE_ECHO_INPUT;
    }

    SetConsoleMode(hStdin, mode);

    int ch = _getch();
    if (ch == 0x03) // Ctrl+C
    {
        printf("Ctrl+C hit, exiting...\n");
        gracefulexit();
    }

    return ch;
#endif

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

#ifdef __MINGW32__
    SetConsoleCP(CP_UTF8);
    SetConsoleOutputCP(CP_UTF8);
    setvbuf(stdout, nullptr, _IONBF, 0);

    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD mode = 0;
    GetConsoleMode(handle, &mode);
    SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
#endif

Пришлось полностью выключить буферизацию, чтобы нормально отрабатывал printf с UTF-8. Работает на троечку — вывод текста стал подтормаживать, такое себе.

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

Из заметных багов — игра падает при попытке загрузки сохранения и совсем не работает ввод русских букв. Второе мне нужно только одном месте (где запрашивается ввод имени игрока), так что есть надежда это починить. Остальное как будто работает.

2 комментария

← 1 2 →