Что делает в отпуске директор группы программерских компаний? То, что не может делать на работе. Программирует. Всласть. :)

Мне много лет, я выполз из тьмы и в Transport Tycoon Deluxe я играл ещё в то время, когда эта игра только появилась (это 94-й год).

Оригинальный Transport Tycoon
Оригинальный Transport Tycoon

Я считаю эту игру одним из лучших транспортно-экономических симуляторов всех времён и народов. И не я один. Когда игра устарела нашёлся человек, который её дизассемблировал (!) и переписал на Си. И получил игру с открытым кодом практически идентичную оригиналу. То есть - она могла поднимать game save и использовать спрайты оригинальной игры. Появилась OpenTTD.

OpenTTD
OpenTTD

OPenTTD развивается до сих пор и, в целом, это совершенно фантастический успех опенсорс сообщества. С ней всё хорошо. Кроме одного. Разработчики OpenTTD до сих пор удерживают внутри игры совместимость со старой игрой из 90-х. И это превращает код игры в изрядный треш. Ну то есть, например, состояние клетки игрового поля до сих пор хранится в виде группы бит определённого байта (с именем от m1 до m7, чтобы не было скучно). Не struct { ... int tileType : 4 ... } и type = tile.tileType, нет! - GB(_m[tile].m1, 4, 4), и только так. То есть, по сути, игра написана на ассемблере, обёрнутом в Си.

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

Ну и вот. Я решился взять этот вес.

Способ конвертирования кода был избран наиболее прямолинейный. Берём исходник на Си, кидаем его в файл ТоЖеИмя.java и открываем в эклипсе. Сначала купируем истерику компилятора снося все взятия адресов объектов, обращения через * и доступ к полям через ->, дальше код превращается из полностью красного в пятнистый. Идём по пятнам красного и исправляем.

Чтобы понимать масштабы бедствия - версия, от которой я стартовал - порядка 4 мб исходных текстов, порядка 150 тыс строк. Я сразу отрезал часть кода, которой решил точно не заниматься - музыка, звуки, save/load (логика реализации этой части невыносима из си) и ещё несколько некритичных и сделанных слишком по-сишному частей.

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

Несколько слов о том, каковы основные сложности конверсии. Я сознательно взял именно версию кода на Си (проект уже переписан на ++), полагая, что так будет проще. Думаю, что не прогадал.

Булевы и целые

Си не видит разницы между boolean и int. Java строга в этом плане, и пришлось пополнить код сотнями if( -> if( 0 != (... - впрочем, кое где это потом помогло выловить неприятности.

Беззнаковые целые разных размеров

В Яве нет unsigned. В основном это не проблема, потому что код тянулся ещё с 16-битных времён и оперировал больше байтами и 16-битными словами. Для него обычный int вполне беззнаков в рамках его потребностей. Но были неприятные места. Отмечу, что там где в оригинальном коде применялся uint8, применять явский байт нельзя - он знаковый. Ну или можно, но 0xFF & обязателен (при этом происходит преобразование в int и обрезание верха, так что результат верный). Я не осознал этой проблемы на старте и уже позже, во время отладки пришлось почти везде по коду искать и убивать переменные и поля типа byte и преобразования к нему. Впрочем, нашлись и обратные примеры - когда требовался именно знаковый байт. Например, смещение спрайта от точки отрисовки хранится в байте со знаком. Хранить можно и в int, но при чтении байта из оригинальной таблицы знак надо сохранить.

Использование оператора запятая

В Яве его тоже упразднили, и никто особо не заметил бы, но в коде OpenTTD пасся какой-то яростный фанат этого оператора. Применял он его не без изящества, оцените:

( bonus += 10, age > 10 ) || ( bonus += 20, age > 5 ) || ( bonus += 40, age > 2 ) || ( bonus += 100, true )

Красиво? Заменяется на switch с fall through. Но есть и идиотские примеры. Типа такого:

if (!tile.IsTileType(TileTypes.MP_RAILWAY) || ((dir = 0, tile.getMap().m5 != 1) && (dir = 1, _tile.getMap().m5 != 2)))

return Cmd.return_cmd_error(Str.STR_1005_NO_SUITABLE_RAILROAD_TRACK);

Их расшивать бывает трудно.

Пойнтеры в параметрах функций для возврата значений

Внезапно, лечатся очень легко.

modify( int[] x ) { x[0]++; }

Аллокация локальных объектов

Поскольку взятия адресов я сношу ещё перед началом работы с кодом, неясно, надо ли аллоцировать локальный объект. Пример:

NPFFindStationOrTileData fstd; ... NPFFillWithOrderData(&fstd, v);

Должно быть

NPFFindStationOrTileData fstd = new NPFFindStationOrTileData();
...
NPFFillWithOrderData(fstd, v);

На практике проблемы вообще нет, компилятор сообщает о том, что переменная не инициализирована.

Перечислимые типы

Тут у Явы всё отлично, но это отлично неприменимо по-сишному. В Си константы из enum - это целые. И можно flags & FLG_USED и будет всё как надо. В итоге почти все enum были заменены на просто целые константы.

Нет макросов

А в Си их применяют часто. Где-то (в инициализации массивов, например) за макросы сошли статические методы - если метод можно посчитать при компиляции, Ява принимает его при инициализации массивов. Но, к примеру, конструкцию BitOps.SB(tile.getMap().m2, 0, 4, new_ground); , которая позволяет модифицировать биты у произвольного поля, пришлось переделывать повсеместно.

Работа с памятью

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

Ну, понятно, что вызов free() мы просто удаляем. Но из-за отсутствия сборки мусора программы на Си местами вынуждены жутко извращаться в плане управления аллокацией и это местами заставляет программиста извращаться ещё жутче в коде, чтобы построить такие структуры, которые можно собрать при ручном управлении памятью. Любимый хит - указатель something **next, который смотрел на поле next последней собранной структуры, чтобы потом из более позднего вызова туда в прошлое впилить указатель на свежесозданный объект.

Все такие вещи я детально анализировал, сносил под корень и заменял на нормальные списки, хеш-таблицы и прочие человеческие инструменты работы со структурами данных. Иногда это упрощало код кардинально. Раз в 10.

В целом сам процесс доведения кода для безошибочного с точки зрения компилятора Ява был муторным, но на 90% - элементарным. Трудно было с goto. Применялись они редко, но иезуитски - код на 10 страниц с пятью вложенными циклами и switch/if, и три перехода. Куда-нибудь на вторую страницу внутри цикла. Местами это решалось выносом части кода в функции (там, где переход был применён для повторного использования кода), а местами требовалась очень серьёзная перереботка. Иногда приходилось просто добавлять булевы переменные для виртуального обхода части кода. Увы, в правильности всех подобных конверсий я пока не уверен.

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

Комментарии (28)


  1. Exchan-ge
    16.08.2021 18:36

    Transport Tycoon Deluxe я играл ещё в то время, когда эта игра только появилась (это 94-й год).


    Да, отличная игра.
    Настоящая убийца рабочего времени :)


    1. dzavalishin Автор
      16.08.2021 18:45

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


      1. Exchan-ge
        16.08.2021 19:57

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


        Там основная проблема была в том, что компьютеры были только на работе :)
        (дома у меня был ПК, б/у и с EGA графикой, насколько я помню, на яге эта игра не шла)


        1. axe_chita
          17.08.2021 07:58

          TTD 386sx33/4mb/hdd/svga (vbe 640x480x256) ЕМНИП


        1. vervolk
          17.08.2021 13:42

          Цивилизация на EGA шла, но ко всему, в те досовские времена у меня и мышки не было, а отправить корабль на альфа центавру можно было только кликом (или я не нашёл хоткея). Так что для завершения игры приходилось одалживать мышь у приятеля.


          1. Exchan-ge
            17.08.2021 18:31

            Цивилизация на EGA шла


            Про циву спора нет. Помню, что Гоблины шли, а Wolfenstein 3D — нет (кстати, он и был основным стимулом для покупки крутой на тот момент видеокарты — Trident TVGA8900 VGA ISA :)
            А вот по поводу «Принца Персии» у меня по этому поводу нет четких воспоминаний.


            1. axe_chita
              18.08.2021 10:03

              На турбо XT 12МГц+HGC в 720х348моно «Принц персии» точно шел. Основная засада была с бутылочками. Из-за этого ходила шутка, про то что «принц бухнул паленку и двинул кони»


  1. OvO
    16.08.2021 18:46
    +1

    Только вчера играли с детьми по сетке.

    Ну хорошо, перепишите вы её до конца, а кто будет поддерживать ее дальше? И что делать с плагинами?


    1. dzavalishin Автор
      16.08.2021 19:43
      +3

      Нашлись фанаты развивать OpenTTD, может, и тут найдутся. Пока что - у меня отпуск и я развлекаюсь. :)


  1. DreamingKitten
    16.08.2021 18:58

    Был ещё просто Transport Tycoon, без Deluxe. Но там всё грустно.

    А сейчас в этом жанре хорошо выступает Transport Fever. Тот же TTD, только с графоном и имбалансными ценами на тяговые единицы (видимо, в угоду исторической достоверности).


  1. mkvmaks
    16.08.2021 20:06
    +1

    OpenTTD одна из лучших. Еще мне нравится simcity3000. )))


    1. dzavalishin Автор
      16.08.2021 21:43

      Не. Не буду дизассемблировать и переписывать. Хотя мне он тоже нравится. :)


      1. axe_chita
        17.08.2021 08:14
        +1

        На js Micropolis (или SimCity Classic) уже перенесли. Понимаю что не совсем Java, но все же…


  1. v1000
    16.08.2021 20:38

    Удивительно, но сколько не пытались - но вроде достичь уровня Transport Tycoon Deluxe так ни у кого и не получилось. Пытались и графику красивее сделать, и в 3Д сделать, но игры получались либо слишком тормозные, либо слишком глючные.


    1. dzavalishin Автор
      16.08.2021 21:45

      Ну, справедливости ради, OpenTTD вообще не тормозной. А вот новая графика там реально странная. Некрасивая.


      1. andersong
        18.08.2021 11:34

        В прошлом году вспомнил очередной раз, скачал, выглядел не очень, пришлось скачать текстурпак и еще музыкпак) вроде, стало хорошо, поиграли по сетке с детьми.
        Нынче наткнулся на него в стиме, поставил, выглядит очень страшно и пусто, в комплекте опять нет ни улучшенной графики ничего, опять где-то искать-качать.
        Неужели нельзя сразу сделать красиво (на что надеешься, покупая в стиме)?


  1. antkatcin
    16.08.2021 20:53
    +7

    Там просто портирования кода на Java мало, там рефакторинг нужен капитальный.

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


    1. dzavalishin Автор
      16.08.2021 21:47
      +1

      А если ты вертолёт, то вас трое. Третий - винт. :))

      Да, рефакторинг там нужен масштабный. И в версии C++ его тоже никто не сделал. Ну то есть три Vehicle на вертолёт там остались.

      За "тень везёт почту" спасибо, я не знал. :) Присоединяйтесь к проекту!


  1. arthin
    16.08.2021 21:34
    +2

    Не понял, чем мешает, что оно на сях. Его ведь и так успешно портируют на все на свете.

    Чего мне реально там не хватало, так это программируемых семафоров.


    1. dzavalishin Автор
      16.08.2021 21:49
      -2

      На мой век программирования на сях хватит, больше не хочу на этом писать.


  1. zanzack
    16.08.2021 21:47

    А можете скинуть ссылку на оригинальный Си код, который был использован для конверсии?
    Пожалуйста.


    1. dzavalishin Автор
      16.08.2021 21:48

      У меня в репозитории он остался, только кусками закомментирован и выключен #if-ами. В принципе, в репозитории OpenTTD он тоже должен быть.


  1. Dron007
    16.08.2021 23:56

    Да, согласен, что ничего близкого по экономической сбалансированности и играбельности просто нет. Много часов было убито. А сейчас в основном играю в мобильные GPS игры, чтобы стимулировать физическую активность и просто интересно. К сожалению, в этом жанре мало что интересного существует. И вот у меня мечта, что кто-нибудь перенесёт на реальный город геймплей Transport Tycoon, чтобы можно было строить дороги вдоль существующих (привязка через OSM), ходить и ставить остановки, чинить автобусы и т.д. Пока очень смутно представляю как это всё можно было бы организовать и вообще возможно ли, но тема интересная. Наиболее близкое, что видел - малоизвестная игра "Off Grid (single player, GPS, crafting game)", но там скорее вдохновлялись Factorio. Транспорта как такового нет (если не считать транспортёры), зато есть планирование. Проект одного человека, поэтому всё достаточно примитивно.


  1. Antharas
    17.08.2021 19:26

    Бегло пробежался по save/load, подойдет банальный mmap. Как одно из готовых решений - https://github.com/odnoklassniki/shared-memory-cache


    1. dzavalishin Автор
      17.08.2021 21:28

      Там проблема не в файл записать, а массово сериализовать объекты…


  1. Feedman
    17.08.2021 21:29

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


    1. dzavalishin Автор
      17.08.2021 21:29

      Ничто человеческое людям не чуждо. :)


  1. dzavalishin Автор
    14.09.2021 01:52
    +1

    Update: Версия на Java в основном соответствует оригиналу, заработали даже MIDI музыка (кстати, она там неплоха) и AI.