История о велосипеде

    imageИногда программисты изобретают свои «велосипеды», но действительно ли это плохо, особенно если творение не отличимо от оригинала, а автор получает бесценный опыт?

    Эта история началась чуть более года назад с появлением у моей супруги современного смартфона. Она попросила найти пасьянс «Косынку», такой же как был когда-то на настольной Windows. Просмотрев десяток программ, я был неприятно удивлен — почти везде было неудобное управление, спорные картинки карт, море дополнительных наворотов вроде 250 пасьянсов в одном, установки фотографии на рубашку и блекджека с блудницами. В результате, выбрали один из более-менее годных вариантов и на какое-то время об этом забыли.
    Пролетел год и я начал писать программы для мобильных. При этом ребром стал вопрос кросс-платформенности, отладки in-app покупок, подготовки контента под разные платформы. Была мысль сделать для пробы пера «Тетрис» или очередной калькулятор, но все же в качестве «велосипеда» я выбрал «Косынку». Идейным стрежнем проекта стало максимально точное повторение старого, доброго пасьянса из набора игр Windows.


    Контент


    imageПервая проблема возникла с графикой: в интернете полно рисунков карт, многие из них бесплатны, но почти все довольно вычурны, странно смотрятся на мобильных экранах, да и просто не похожи на старые карты из cards.dll. Карты для Windows попиксельно рисовала талантливый дизайнер Сюзан Кэр в 1989м году, в 16-ти цветном режим. При желании их легко найти в сети, но использовать наврядли можно без нарушений IP. Гораздо интереснее оказались карты, используемые в некоторых играх мира Linux — они распостранялись под GPL лицензией, хоть и выглядели немного странно, с черно-белыми старшими картами (фигурами). Автором этих карт оказался тов. Oxymoron, на сайте которого лежат сами картинки и скрипт их сборки. Он нарисовал собственные цифры и иконки мастей, а фигуры взял из неизвестного источника, но довольно похожие на оригинал. Я немного доработал скрипт, чтобы он сохранял карты в PNG, добавил прозрачность, а заодно раскрасил вручную старшие карты. Для иконки туза пик была нарисована собственная стилизованная картинка, по аналогии с оригиналом напоминающая кельтский узор. Она же и стала иконкой программы.

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

    Геймплей


    imageКак оказалось, геймплей в «Косынке» не так уж и прост за счет подсчета очков, таймера, выдачи по одной/три карты, а заодно и режима Vegas. К примеру, игра на деньги с выдачей по одной карте (т.е. без возможности переворачивать колоду), заметно отличается от стандартной: ее цель перенести как можно больше карт к тузам и набрать более $42, чтобы выйти из минуса и начать следующую партию, если пасьянс не сложился. В игре же на счет перенос карты из колоды «вниз», а потом к тузам дает 15 очков, в то время как сразу к тузам — только 10. Естественно, задокументировано это очень условно, потому пришлось загнать оригинальную игру в wine и провести полноценный ресерч.

    Код


    Бизнес логику программы я написал на чистой Java 1.6, без использования сторонних библиотек, Android, Swing и пр. Для Android эта часть собирается в виде отдельной библиотеки и подключается к GUI проекту без особых проблем, а вот для других ОС я решил идти нестандартным путем и не заниматься портированием «в лоб» на другие языки. После некоторой обработки напильником проект Sharpen смог преобразовать этот код в чистый C#, что открыло путь к реализации под iOS c помощью MonoTouch, а в будующем и для Windows Phone 7/8. Простейшие юнит-тесты показали, что логика работы не изменилась, трансляция прошла успешно. В среде MonoTouch я без особых нюансов реализовал отображение GUI, отладил игру на эмуляторе и iPod Touch и отправил на одобрение в iTunes Store. Настройки программы я реализовал с помощью внешнего биндинга к KGN.InAppSettings, споткнувшись о некоторые мелочи вроде отсутствия поддержки thumb для внешних библиотек.

    Проблемы


    imageПосле недели аппрува в Apple, моя «Классическая Косынка» появилась в iTunes Store. Я ожидал критики графики или возможных лицензионных споров, но в первый же день все отзывы были однозначны: игра вылетает сразу при запуске. Игру из магазина я убрал и начал изучать проблемы. У моих знакомых игра запускалась отлично, эмулятор и iPod Touch 4g тоже не показывали никаких сбоев. Понадобилось несколько дней и помощь форумчан, чтобы найти первый серьезный баг: в региональных стандартах iOS для Европы и Украины указан десятичный разделитель «точка», а в России это уже «запятая». Соответственно, преобразование float в строку работало на одних устройствах и крашилось на других. В течение недели после отправки на аппрув, оказалось, что игра еще и крашилась при запуске в альбомном режиме. На iPod и iPhone все программы стартуют в портретной ориентации, потому обнаруживается это только на iPad, которого у меня не было. На ре-аппрув ушла еще неделя, за которорую игра окончательно пропала из категории «Новое» и успешно утонула за пределами Top 1000. К тому же, иконка с прозрачным фоном, отлично принятая в Android Market, была похабно обработана скриптами Apple и стала откровенно не симпатичной.

    High-definition


    imageК тому моменту, как проект заработал нормально, были заменены иконки и пр., у меня появился новый iPad, где я обнаружил, что разрешения карт откровенно не хватает для 9.7" экрана, да и на Retina Display «прыгающие карты» откровенно тормозят. Борьба с лагами заняла пару дней, за которые я успел основательно породниться с Core Animation, а вот для улучшения разрешения пришлось нанять дизайнера. Результатом его работы стали цифры, буквы и масти высокого разрешения, максимально похожие по стилю на оригинал. Фигуры я увеличил с помощью Photo Zoom Pro, чтобы не потерять аутентичность пиксельной графики. Вышло не идеально, но все же довольно удачно.

    На эту работу было потрачено немало усилий, потому было логично выпустить отдельно HD версию, а заодно добавить внутреигровую покупку в обычную версию. С реализацией In-App Purchases под MonoTouch оказалось не все просто, а чтобы смотрелось это органично, я решил избавиться от KGN.InAppSettings и сделать единый интерфейс для настроек и покупок. Технические детали я тут описывать не буду, но набралось их немало, включая мартышкин труд по созданию собственных Settings, идентичных системным, борьба с отсутствием документации к StoreKit для Mono, разные баги с отладкой покупок и пр.

    Профит


    Игра не стала шедевром, не попала в топы, да и наврядли окупит стоимость разработки и работу дизайнера, но все же теперь у меня на всех устройствах (включая и смартфон, с которого все началось) есть «Косынка», максимально похожая на оригинал, ставший родным еще со времен Windows 3.1. Опыт написания портируемого кода на Java и переноса его на C# оказался просто бесценным — подобным методом моей командой портировано уже несколько крупных Android проектов на iOS, а за счет отладки на небольшой игре получилось неплохо изучить подводные грабли MonoTouch и вообще разработки под iOS, а заодно написать некое количество полезных библиотек. Может кто-то и посмеется над этой историей, но как по мне, «велосипед» вышел весьма годным, а время было потрачено совсем не зря.

    Post Scriptum


    Я специально избегал углубления в подробности портирования и работы с MonoTouch, потому что каждая из них достойна отдельного топика. Рано или поздно соберусь с силами и выложу на суд общественности свой класс для In-Game Settings, менеджер покупок и пр.
    Ссылки на проект я не выкладываю, чтобы не посчитали рекламой.
    Если кому нужен обновленный скрипт генерации карт, пишите на scanwords@runserver.net, он является derivative work от GPL проекта, потому выложу без проблем.
    Поделиться публикацией

    Комментарии 22

      +3
      > Соответственно, преобразование float в строку работало на одних устройствах и крашилось на других.

      Сколько раз уже вспотыкался об эти грабли, так что на автомате tryparse :)
        +1
        InvariantCulture msdn.microsoft.com/en-us/library/bh4863by?

        float f = float.Parse(s, CultureInfo.InvariantCulture);
        
          +1
          именно. вариант с tryparse не даст краша, но и результата не даст в половине случаев.
          а чтобы наверняка, лучше так:

          float f = float.Parse(s.Replace(",","."), CultureInfo.InvariantCulture);

            +1
            Речь о MonoTouch, как минимум.

            Вон, kekekeks собирал Mono под Maemo и напоролся:
            http://mono.1490590.n4.nabble.com/Problems-with-InvariantCulture-on-Maemo-td3439657.html

            image

            Лучше сделаю двойной tryparse.
            –1
            Для человека не писавшего софт для смартов этот краш выглядит как минимум дико О_о
              +2
              Это не дико, это особенность. Вполне разумно, что при парсинге из строки учитываются культурные особенности, что в .Net, что в ObjC это происходит. Причём по умолчанию берётся текущая локаль системы, вот это как раз надо знать и понимать. Из-за этого незнания и случилась ошибка. При разборе пользовательского ввода, это логично (например разделитель на цифровой клавиатуре тоже зависит от локали) для парсинга данных из сети стоит точно указывать локаль. (натыкался на то, что популярный код, который строит маршруты на карте парсит данные с текущей локалью, на европейских телефонах все было нормально, на телефоне с российской локалью возникали проблемы.) Тоже самое происходит с датами.
              Вывод: не стоит забывать в каком интернациональном мире мы живём.
            +4
            Читать о таких велосипедах — всегда удовольствие :) — делать что-то по готовому во всех отношениях примеру — тоже удовольствие. Никаких тебе изменяющихся ТЗ, правок в GUI, и прочих затягивающих срок разработки вещей.
              0
              Паука и сапера старых еще портируйте и совсем круто будет, тем более что для паука уже много есть в солитере.
                +2
                И скиньте пожалуйста ссылку в ЛС. А еще лучше поставьте в сообщение. Интернет состоит из ссылок, желание некоторых админов бороться со спамом в последние годы начало походить на маразм, когда авторы БОЯТСЯ ставить ссылки в собственных текстах. С этим надо бороться.
              0
              Скажите, какое мнение составилось о Sharpen?
                0
                Довольно мощный инструмент, но слабовато документирован и требует полной валидности конвертируемого кода. Не очень дружит с дженериками, а для нормального переноса коллекций надо писать конфиг. Некоторые вещи сделаны странно, например, java.lang.String.split преобразуется в Sharpen.Runtime.Split(str1, str2). Вместо Shapen.Runtime можно подставлять имя своего обработчика, но сам метод придется реализовывать вручную.
                Интеграция в Eclipse и запуск конверсии тоже немного хромают, но это можно пережить.
                Еще есть версия от Xamarin, которой они якобы смогли весь Android перевести в C#, но я о ней узнал поздновато и еще не изучал.
                0
                Спасибо!
                Мои 0,99 вам.
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    игра не предупреждает, если ситуация патовая, т.е. никаких возможных комбинаций выиграть нету. Добавите?

                    Я подумаю, но вообще наврядли — не было этого в оригинале, а я не хочу отсебятничать, уж извините.
                    А вот аналог правой кнопки скорее всего добавлю, чтобы автоматом делать возможный ход.
                  • НЛО прилетело и опубликовало эту надпись здесь
                      +1
                      Я одинаково хорошо знаю C# и Java, потому через некоторое время исследования MonoDroid пришел к выводу, что полезность этого продукта для меня довольно сомнительна. В первую очередь, MonoTouch/MonoDroid не предоставляют универсального подхода к GUI, а только обертки вокруг системных библиотек. Т.е. надо точно так же делать Activity, View и XML Layouts на MonoDroid как и в чистой Java, а на MonoTouch работать с UIApplication, UIViewController и UIView как и в ObjC. Синтаксис же у С# и Java очень похож, производительность разработки/программы выходит одинаковая, но при этом IDE MonoDevelop заметно проигрывает по удобству Eclipse. Не маловажный момент и в том, что Android документации для Java на порядок больше, включая исходники системы.
                      В конце-концов, MonoDroid добавляет к каждой программе груз в 3-4 мегабайта своих библиотек. Для сравнения, версия «Косынки» под Android занимает 125кб.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          А чем обусловлен ваш «переезд»? Я просто на C# немнго в Unity3D пишу, хочу перевести пару приложений с android на ios, вот думаю, стоит ObjC пилить или все таки Mono использовать, многие уверяют, что Mono для серьезных дел не подходит :(
                          • НЛО прилетело и опубликовало эту надпись здесь
                              +1
                              ObjC все равно придётся подучить, хотя не знаю насколько это критично для игр. Я встречал ошибку, которая в monodevelop просто выдавала ObjC ошибку и stacktrace
                        0
                        Пожалуй, оставлю здесь одну (но хорошую) ссылку на «велосипедные» карточные игры на вебе — www.razlozhi.ru/

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