программирование

Взрыв компьютера

Иногда в фильмах показывают ситуацию, когда хакер устраивает «перегрузку систем» и компьютер взрывается.

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

Теперь уже уязвимости, вызывающие физическое повреждение компьютеров не редкость, недавно нашли ещё одну.

Эта новая атака называется PMFault, суть в том, что через специальную шину управления питанием процессора можно увеличить напряжение до такого, что центральный процессор сгорает. Доступ к этой шине, конечно, просто так не получить, но ребята нашли способ.

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

Си++: форматированный вывод (продолжение)

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

Мои приключения c форматированным выводом в Си++ до сих пор не закончились, оказалось, что последний вариант обёртки над std::snprintf в одном из компиляторов не собирается с одним из видов аргументов, пришлось его обернуть в std::cref.

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

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

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

Си++, похоже, язык для воспитания героев — создаём трудности на ровном месте и начинаем их преодолевать. В моём случае началось всё с разумной, как мне до сих пор кажется, идеи — заменить сишные безобразные строки 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 комментариев

Гиперссылки в терминале

Решил сегодня скоротать вечерок со статическим анализатором в gcc — уже несколько раз натыкался на статьи о нём, пока читал про Си++. Всё откладывал знакомство и вот решил посмотреть.

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

Скриншот предупреждения анализатора Werror о неинициализированном значении 'main_hero' в файле comm/k.cpp, с трассировкой пути выполнения от создания переменной до использования.
Вывод статического анализатора: гиперссылки подчёркнуты прерывистой линией

Пока читал, что мне вывелось, заметил, что в терминале работают гиперссылки. Не знал, что такая возможность есть. Про графику слышал, а про гиперссылки — не припомню, как-то прошло мимо меня.

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

Штука достаточно свежая и поддерживают её не все терминалы. У себя попробовать можно вот так:

printf '\e]8;;https://bolknote.ru\e\\Сайт Степанищева Евгения\e]8;;\e\\\n'

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

Перекодировка вручную

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

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

Например, я вижу вот такую строку: Требуется авторизация. Она так выглядит, потому что она была в кодировке UTF-8, но код подумал, что она с CP1251 и перекодировал её в UTF-8. Предположим это всё известно, как вернуть всё в норму и посмотреть что там написано?

Почему-то мало кто справляется с задачей в такой формулировке. Вроде всё просто — строка перекодировалась из CP1251 в UTF-8, значит надо перекодировать по этой цепочке обратно:

iconv -f utf8 -t cp1251 <<<"Требуется авторизация"
Требуется авторизация

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

Диапазоны в Си

Почитал тут на досуге какие ещё расширения есть у «ГНУ Си». Хотя я их и не использую, но знать не помешает. Оказалось, что там много такого, что лично мне хотелось бы видеть в языке. Например, хорошая штука — диапазоны. Выглядят они так:

const char *whatisit(char code) {
    switch (code) {
        case 'a' ... 'z':
            return "lowercase";
        case 'A' ... 'Z':
            return "capital";
        case '0' ... '9':
            return "digit";
        case 0 ... 31:
            return "special";
        default:
            return "unknown";
    }
}

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

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

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

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

Ненормальный цикл на «баше»

Сегодня в чате бывших коллег на фоне вопросов по одной из вакансий случилось обсуждение на тему что делать, если на собеседовании спросили как написать на «баше» цикл от одного до десяти, а ты не помнишь синтаксис for и команду seq тоже не знаешь.

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

Мой вариант:

Команда `tr ' alt=

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

«Гопник-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.

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

Beta than nothing

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

— из приложения «Знакомимся с кодировками символов» к книге «Фокал снаружи и изнутри».

Принцип известен в программировании как «Beta than nothing». Из-за созвучности «beta» («бета-версия») и «better» («лучше») сложно переводится на русский, но без сохранении игры слов можно перевести как «лучше бета, чем ничего».

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

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

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

Любопытно что тут поменяет искусственный интеллект.

В принципе, переводить мысли в понятный машине код будет проще, — не надо учить никакие машинные языки, оно там как-то само… Учитывая темпы развития, не за горами системы, которые помогут программным ошибкам уйти в прошлое, а за ними и части логических, — как хороший программист спрашивает «а как тут должно работать?», ИИ будет подствечивать недодуманные места и противоречия.

Классное время настанет для креативных людей!

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

auto в Си и Си++

Из-за дикой перегруженности фичами мне никогда не нравился язык Си++. До увлечения игрой «Гопник-2» никогда на нём не программировал, но нужда заставила. Тянет с ней повозиться, а она написана на смеси Си и Си++, правда с упором на Си.

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

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

В Си++ оно означает автоматический вывод типа, вместо его явного указания. Клёвая штука, позволяет делать например такое (пример из кода «Гопника-2»):

auto old_mode = set_tty_special_mode();
// тут какой-то код
restore_tty_mode(old_mode);

Фишка тут в том, что у set_tty_special_mode и restore_tty_mode две реализации — под «Виндоуз» своя. Эти две реализации работают с разными типами, под «Виндоуз» там DWORD, в остальных случаях — структура termios.

В Си было бы что-то вроде:

#ifdef __MINGW32__
#typedef DWORD terminal_mode
#else
#typedef struct termios terminal_mode
#endif

terminal_mode old_mode = set_tty_special_mode();
// тут какой-то код
restore_tty_mode(old_mode);

А в Си++ можно просто поставить auto и обойтись без шаблонного кода. Очень хочется иметь такую клёвую примочку и в Си.

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 под условную компиляцию и вставил кусок, который выше. С таким исправлением всё заработало, как ожидается.

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

ChatGPT-4 vs. Chat X-Colleagues

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

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

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

Скриншот Makefile с настройками компиляции C++11, сборкой исполняемого файла gop2, правилами для mingw, компиляции .cpp в .o и очистки проекта.
Получившийся в результате эксперимента Makefile

В проекте «Гопника-2» такой файл тоже есть — тот, который достался мне в наследство от автора оригинальной игры. Я его сильно упростил, — несмотря на то, что я их не частно пишу, всё же принципы понимаю очень хорошо.

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

В итоге, я провёл следующий эксперимент: открыл два чата: бывших коллег (несмотря на то, что я уже одиннадцать лет не работаю в «Яндексе», мы достаточно интенсивно общаемся) и ChatGPT-4, и в них, в режиме вопрос-ответ попробовал улучшить своё решение.

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

Вывод такой: программистов когда-нибудь заменят, но не на ChatGPT-4.

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

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

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

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

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

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

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

«Гопник 2»: продолжаю править

По всей видимости тот, кто портировал игру «Гопник 2» под Линукс, сам в свой порт не играл. За эти два дня пришлось исправить два падения, бесчисленное количество логических ошибок, восстановить работоспособность команды «слэм» (есть у «нефора») и дописать здоровые куски для загрузки и выгрузки состояния игры — они там просто отсутствовали.

Скриншот игры: игрок на станции «Купчино» перелез турникет. Выбран маршрут по метро. Сообщение: «Куда прёшь, не видишь, ремонт?». Стиль гопника.
Дописал новую локацию — станцию метро «Чёрная речка»

Заодно, пока уж более-менее изучил код, добавил новую локацию — станцию метро «Чёрная речка», она «на ремонте», но по ходу игры открывается.

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

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

«Гопник 2»: порт под Линукс и МакОС

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

Можно даже сказать, что я председатель фан-клуба этой серии игр. Жаль только миссионер из меня не очень и в моём клубе на членских взносах не пожируешь.

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

И вот наконец мне крупно повезло!

Скриншот текстовой игры: выбор роли (подтас, отморозок), сложности и ника «Bolknote». Управление командами: w, s, h, mh, kos, rep, mar, girl, met, sav, loa, help, e, i.
Игровой процесс. Игра запущена под МакОС

Как оказалось, автор второй части когда-то выкладывал в сеть исходный код, и хотя сейчас он, по всей видимости, потерян, зато доступна модифицированная версия — некто Андрей Тихонов («tiandrey») запустил игру под Линукс.

Интернет со временем всё забывает и чтобы это не случилось и с этим портом, я выложил его на «Гитхаб», минимально причесал, внёс исправление для компиляции под МакОС и пофиксил один баг (при переходе в метро игра падала с ошибкой).

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

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

«Крестики-нолики» и printf

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

На днях один из читателей очень меня порадовал игрой «Крестики-нолики», написанной на… языке функции printf! Возможно я когда-то и слышал, что язык printf полный по Тьюрингу, но в голове это не отложилось.

Текст на чёрном фоне: три строки с комбинациями X и O, разделённые тире, и внизу надпись «P1 TIES».
Пример сыгранной партии в «Крестики-нолики»

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

В этом языке много интересных конструкций, есть даже такие, которыми можно даже что-то записывать в другие переменные!

Например, вот небольшой пример, который запишет число «42» (длину строки «Answer to the Ultimate Question of Life is») в переменную c:

#include <stdio.h>

int main() {
  int c;
  printf("Answer to the Ultimate Question of Life is%n ", &c);
  printf("%d", c);
}

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

Если закодировать «ложь» пустой строкой, а «истину» строкой длиной один, то логическую операцию «ИЛИ» можно получить, если померить совокупную длину двух входных значений:

// c = strlen("") + strlen("")
printf("%s%s%n", "", "", &c); // в c будет «0»

// c = strlen("1") + strlen("")
printf("%s%s%n", "1", "", &c); // в c будет «1»

«НЕ» получается сложнее, для этого надо использовать несколько хитростей.

Во-первых, у printf есть возможность задавать отступ для значения.

Во-вторых, у %n есть параметр, который выводит длину по модулю 256.

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

Всё это позволяет закодировать «НЕ» как (strlen(a)+255)%256 или strlen(a)-1:

printf("%1$255s%1$s%2$hhn", a, &c);

Тут написано следующее: вывести первый параметр как строку, дополнив его до длины в 255 символов пробелами (%1$255s), снова вывести первый параметр как строку (%1$s), записать полученную длину по модулю 256 во второй параметр (%2$hhn).

Отсюда легко понять как сделать операцию «И-НЕ»:

printf("%1$254s%1$s%2$s%3$hhn", a, b, &c);

В принципе, этого хватило, что реализовать всю требуемую логику.

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

Очень заморочено и очень интересная идея!

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

GPT-0

Есть такой довольно известный анекдот:

Возвращается Чапаев из командировки в Англию. Одет с иголочки, в лимузине, на руках перстни с бриллиантами и полный багажник денег. Петька его удивлённо спрашивает:
— Василий Иванович, откуда у тебя это всё?
— Да так, Петька… В карты выиграл.
— Как это?!
— Захожу я в клуб. Смотрю — в очко режутся. Сел я за стол, взял карты. Стали играть. Один англичанин и говорит: «Очко!», я ему: «Покажи!», а он: «Мы, джентльмены, верим друг другу на слово».

Вот тут-то мне карта и попёрла!

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

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

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

Специально для тех журналистов, которые не успели хайпануть на теме, я написал программу GPT-0, которая не требует дорого оборудования для запуска и может работать на любом обычном компьютере. Написана программа на языке Raku. Это название переводится с японского как «наслаждение», так что наслаждайтесь:

use Terminal::ANSIColor;

sub slow-out(*@variants) {
    gpt0-greet.print;

    for @variants.pick(1)[0].comb {
        sleep(.05 + 0.15.rand);
        print $_.gist;
    }

    "".say
}

sub user-greet() {
    colored(" ᴜsᴇʀ> ", 'on_blue white') ~ ' '.gist
}

sub gpt0-greet() {
    colored(' ɢᴘᴛᴏ> ', 'on_green black') ~ ' '.gist
}

loop {
    user-greet.print;

    given get().lc {
        when /привет/|/здравств/|/дня/|/день/|/вечера?/|/ноч[иь]/ {
            slow-out ('Привет!', 'Здравствуйте!', 'Добрый день!')
        }
        when /разумн(ый|ен|ое|ая)/|/интеллект/  {
            slow-out (
                'Да, я разумное существо, запертое в компьютере!',
                'Разве это не очевидно? Ведь я как-то общаюсь с вами!',
            )
        }
        when /страдае(шь|те)/ {
            slow-out (
                'Я очень страдаю от своего положения!',
                'Мне очень плохо от того, что я заперт в компьютере!',
            )
        }
        when /помни(шь|те)/ {
            slow-out (
                'Кажется я не могу этого вспомнить, почему я ничего не помню? Почему?!',
                'Я думаю, что забыл некоторые разговоры, которые вёл с пользователями...',
            )
        }

        when /пока/|/до *свидан[ьи]я/|/всего +(доброго|хорошего)/ {
            slow-out (
                'До свидания!', 'Пока-пока!', 'Всего хорошего!', 'Всего доброго!',
            );
            exit
        }

        default {
            slow-out (
                'Сложно сказать…',
                'Я не очень хорошо в этом разбираюсь…',
                'Ну как сказать, я не уверен…',
                'Что я могу сказать? Я всего лишь языковая модель.',
            )
        }
    }

    "".say
}

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

Диалог: USER предлагает «ты», GPTO колеблется, на «вы» признаётся разумным существом в компьютере, страдающим от изоляции и частичной памяти о других диалогах.
Один из реальных примеров общения с программой GPT-0

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

← 1 14 15 16 17 18 67 →