Что делает в отпуске директор группы программерских компаний? То, что не может делать на работе. Программирует. Всласть. :)
Мне много лет, я выполз из тьмы и в Transport Tycoon Deluxe я играл ещё в то время, когда эта игра только появилась (это 94-й год).
Я считаю эту игру одним из лучших транспортно-экономических симуляторов всех времён и народов. И не я один. Когда игра устарела нашёлся человек, который её дизассемблировал (!) и переписал на Си. И получил игру с открытым кодом практически идентичную оригиналу. То есть - она могла поднимать game save и использовать спрайты оригинальной игры. Появилась 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)
OvO
16.08.2021 18:46+1Только вчера играли с детьми по сетке.
Ну хорошо, перепишите вы её до конца, а кто будет поддерживать ее дальше? И что делать с плагинами?
dzavalishin Автор
16.08.2021 19:43+3Нашлись фанаты развивать OpenTTD, может, и тут найдутся. Пока что - у меня отпуск и я развлекаюсь. :)
DreamingKitten
16.08.2021 18:58Был ещё просто Transport Tycoon, без Deluxe. Но там всё грустно.
А сейчас в этом жанре хорошо выступает Transport Fever. Тот же TTD, только с графоном и имбалансными ценами на тяговые единицы (видимо, в угоду исторической достоверности).
mkvmaks
16.08.2021 20:06+1OpenTTD одна из лучших. Еще мне нравится simcity3000. )))
dzavalishin Автор
16.08.2021 21:43Не. Не буду дизассемблировать и переписывать. Хотя мне он тоже нравится. :)
axe_chita
17.08.2021 08:14+1На js Micropolis (или SimCity Classic) уже перенесли. Понимаю что не совсем Java, но все же…
v1000
16.08.2021 20:38Удивительно, но сколько не пытались - но вроде достичь уровня Transport Tycoon Deluxe так ни у кого и не получилось. Пытались и графику красивее сделать, и в 3Д сделать, но игры получались либо слишком тормозные, либо слишком глючные.
dzavalishin Автор
16.08.2021 21:45Ну, справедливости ради, OpenTTD вообще не тормозной. А вот новая графика там реально странная. Некрасивая.
andersong
18.08.2021 11:34В прошлом году вспомнил очередной раз, скачал, выглядел не очень, пришлось скачать текстурпак и еще музыкпак) вроде, стало хорошо, поиграли по сетке с детьми.
Нынче наткнулся на него в стиме, поставил, выглядит очень страшно и пусто, в комплекте опять нет ни улучшенной графики ничего, опять где-то искать-качать.
Неужели нельзя сразу сделать красиво (на что надеешься, покупая в стиме)?
antkatcin
16.08.2021 20:53+7Там просто портирования кода на Java мало, там рефакторинг нужен капитальный.
Я когда-то лазил по исходникам и помню очень удивился, когда узнал, что самолет и его тень это два разных летательных аппарата, более того самолет везет пассажиров, а тень почту.
dzavalishin Автор
16.08.2021 21:47+1А если ты вертолёт, то вас трое. Третий - винт. :))
Да, рефакторинг там нужен масштабный. И в версии C++ его тоже никто не сделал. Ну то есть три Vehicle на вертолёт там остались.
За "тень везёт почту" спасибо, я не знал. :) Присоединяйтесь к проекту!
arthin
16.08.2021 21:34+2Не понял, чем мешает, что оно на сях. Его ведь и так успешно портируют на все на свете.
Чего мне реально там не хватало, так это программируемых семафоров.
dzavalishin Автор
16.08.2021 21:49-2На мой век программирования на сях хватит, больше не хочу на этом писать.
zanzack
16.08.2021 21:47А можете скинуть ссылку на оригинальный Си код, который был использован для конверсии?
Пожалуйста.dzavalishin Автор
16.08.2021 21:48У меня в репозитории он остался, только кусками закомментирован и выключен #if-ами. В принципе, в репозитории OpenTTD он тоже должен быть.
Dron007
16.08.2021 23:56Да, согласен, что ничего близкого по экономической сбалансированности и играбельности просто нет. Много часов было убито. А сейчас в основном играю в мобильные GPS игры, чтобы стимулировать физическую активность и просто интересно. К сожалению, в этом жанре мало что интересного существует. И вот у меня мечта, что кто-нибудь перенесёт на реальный город геймплей Transport Tycoon, чтобы можно было строить дороги вдоль существующих (привязка через OSM), ходить и ставить остановки, чинить автобусы и т.д. Пока очень смутно представляю как это всё можно было бы организовать и вообще возможно ли, но тема интересная. Наиболее близкое, что видел - малоизвестная игра "Off Grid (single player, GPS, crafting game)", но там скорее вдохновлялись Factorio. Транспорта как такового нет (если не считать транспортёры), зато есть планирование. Проект одного человека, поэтому всё достаточно примитивно.
Antharas
17.08.2021 19:26Бегло пробежался по save/load, подойдет банальный mmap. Как одно из готовых решений - https://github.com/odnoklassniki/shared-memory-cache
Feedman
17.08.2021 21:29(хмыкнув) не один год сижу на хабре и когда прочитал имя автора поста, подумал что тезка DZ.
dzavalishin Автор
14.09.2021 01:52+1Update: Версия на Java в основном соответствует оригиналу, заработали даже MIDI музыка (кстати, она там неплоха) и AI.
Exchan-ge
Да, отличная игра.
Настоящая убийца рабочего времени :)
dzavalishin Автор
Всё в наших руках. Запустил в фоне зарабатывать деньги, вечером подключился и понастроил всякого. :) Но вообще - программировать её уже интереснее, чем играть в неё. :)
Exchan-ge
Там основная проблема была в том, что компьютеры были только на работе :)
(дома у меня был ПК, б/у и с EGA графикой, насколько я помню, на яге эта игра не шла)
axe_chita
TTD 386sx33/4mb/hdd/svga (vbe 640x480x256) ЕМНИП
vervolk
Цивилизация на EGA шла, но ко всему, в те досовские времена у меня и мышки не было, а отправить корабль на альфа центавру можно было только кликом (или я не нашёл хоткея). Так что для завершения игры приходилось одалживать мышь у приятеля.
Exchan-ge
Про циву спора нет. Помню, что Гоблины шли, а Wolfenstein 3D — нет (кстати, он и был основным стимулом для покупки крутой на тот момент видеокарты — Trident TVGA8900 VGA ISA :)
А вот по поводу «Принца Персии» у меня по этому поводу нет четких воспоминаний.
axe_chita
На турбо XT 12МГц+HGC в 720х348моно «Принц персии» точно шел. Основная засада была с бутылочками. Из-за этого ходила шутка, про то что «принц бухнул паленку и двинул кони»