-Поиск по дневнику

Поиск сообщений в rss_rss_hh_full

 -Подписка по e-mail

 

 -Постоянные читатели

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 1

Habrahabr








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

[Перевод - recovery mode ] Angular vs. React vs. Vue: Сравнение 2017

Понедельник, 18 Сентября 2017 г. 22:25 + в цитатник
Synoptic вчера в 22:25 Разработка

Angular vs. React vs. Vue: Сравнение 2017

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



Итак, как же нам выбрать? Список плюсов и минусов никогда не повредит. Проделаем это в стиле моей предыдущей статьи, “9 шагов: выбор технологического стэка для вашего веб-приложения”.


Прежде чем начнем — SPA или нет?


Сперва вы должны принять четкое решение, нужно ли вам одностраничное веб-приложение(SPA), или вы предпочли бы многостраничный подход. Больше на эту тему в моем посте “Одностраничные веб-приложения(SPA) против Многостраничных веб-приложений(MPA)”(скоро выйдет, следите за обновлениями в Twitter).


Участники сегодняшнего состязания: Angular, React and Vue


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


Вопросы, которые мы сегодня рассмотрим:


  • Насколько зрелые эти фреймворки/библиотеки?
  • Будут ли эти фреймворки существовать еще какое-то время?
  • Насколько велики и полезны сообщества вокруг них?
  • Насколько легко найти разработчиков под каждый из этих фреймворков?
  • Каковы основные идеи этих фреймворков?
  • Насколько просто использовать эти фреймворки для небольших и крупных веб-приложений?
  • Какова кривая обучения для каждого из фреймворков?
  • Какую производительность вы ожидаете от этих фреймворков?
  • Где вы можете подробнее узнать, что у них под капотом?
  • Как начать разрабатывать с выбранным фреймворком?

На старт, внимание, марш!


Жизненный цикл и стратегические соображения



React vs. Angular vs. Vue


1.1 Немного истории


Angular — это JavaScript-фреймворк, основанный на TypeScript. Разработанный и поддерживаемый Google, он описывается как “Супергеройский JavaScript MVW фреймворк”. Angular(известный также как “Angular 2+”, “Angular 2” или “ng2”) — переписанный, по большей части несовместимый преемник AngularJS(известный как “Angular.js” или “AngularJS 1.x”). Хотя AngularJS(старый) был впервые выпущен в октябре 2010-го, он до сих пор получает багфиксы и т.д. Новый Angular(без JS) был представлен как версия №2 в сентябре 2016. Последний мажорный релиз — версия 4, так как версию 3 пропустили. Angular используется Google, Wix, weather.com, healthcare.gov и Forbes(в соответствии с madewithangular, stackshare и libscore.com).


React характеризуется как “JavaScript-библиотека для создания пользовательских интерфейсов”. Впервые выпущенный в марте 2013-го, React разработан и поддерживается Facebook-ом, который использует React-компоненты на нескольких страницах(однако, не являясь одностраничным веб-приложением). В соответствии с этой статьей Криса Кордла, React в Facebook применяется значительно шире, чем Angular в Google. React также используют в Airbnb, Uber, Netflix, Twitter, Pinterest, Reddit, Udemy, Wix, Paypal, Imgur, Feedly, Stripe, Tumblr, Walmart и других(в соотв. с Facebook, stackshare и libscore.com).


В данный момент Facebook работает над выпуском React Fiber. Это изменит React под капотом — в результате, рендеринг должен значительно ускориться — но обратная совместимость сохранится после изменений. В Facebook рассказывали об этих изменениях на их конференции для разработчиков в апреле 2017-го, также была опубликована неофициальная статья о новой архитектуре. Предположительно, React Fiber будет выпущен вместе с React 16.


Vue — один из самых быстроразвивающихся JS-фреймворков в 2016-м. Vue описывает себя как “Интуитивный, Быстрый и Интегрируемый MVVM для создания интерактивных интерфейсов”. Впервые он был выпущен в феврале 2014-го бывшим сотрудником Google Эваном Ю(кстати, Эван тогда написал интересный пост про маркетинговую деятельность и цифры в первую неделю после старта). Это был неплохой успех, особенно учитывая, что Vue привлекает столько внимания будучи театром одного актера, без поддержки крупной компании. На данный момент, у Эвана есть команда из дюжины основных разработчиков. В 2016 была выпущена вторая версия. Vue используют Alibaba, Baidu, Expedia, Nintendo, GitLab. Список более мелких проектов можно найти на madewithvuejs.com.


И Angular, и Vue доступны под лицензией MIT, в то время как React — под BSD3-license. Есть много обсуждений по поводу патентного файла. Джеймс Аид(бывший инженер Facebook) объясняет причины и историю, лежащую за этим файлом: Патент Facebook касается распространения их кода при сохранении возможности защитить себя от патентных исков. Файл патента обновлялся единожды и некоторые люди утверждают, что React можно использовать, если ваша компания не собирается подавать в суд на Facebook. Можете ознакомиться с обсуждением вокруг этого Github issue. Я не являюсь адвокатом, поэтому вы сами должны решить, создает ли лицензия React проблемы для вас или вашей компании. Есть еще много статей на эту тему: Дэннис Уолш пишет, почему вам не стоит бояться. Рауль Крипалани предостерегает от использования в стартапах, у него также есть обзор в формате “изложение мыслей”. Также существует недавнее оригинальное заявление от Facebook на эту тему: “Разъяснение лицензии React”.


1.2 Core development


Как было отмечено ранее, Angular и React поддерживаются и используются крупными компаниями. Facebook, Instagram и Whatsapp используют их на своих страницах. Google использует их во многих своих проектах: например, новый пользовательский интерфейс Adwords был реализован с помощью Angular и Dart. Опять же, Vue разрабатывается группой лиц, чья работа поддерживается через Patreon и другие средства спонсирования. Решайте сами, хорошо это или плохо. Маттиас Гёцке считает, что небольшая команда Vue — это плюс, потому что это ведет к более чистому коду / API, и меньшему оверинженерингу.


Посмотрим на статистику: на странице команды Angular перечислено 36 человек, у Vue — 16 человек, у React страницы команды нет. На GitHub-е у Angular больше 25 000 звезд и 463 контрибьютора, у React — больше 70 000 звезд и 1000 контрибьюторов, и у Vue почти 60 000 звезд и лишь 120 контрибьюторов. Можете также проверить страничку “Github Stars History for Angular, React and Vue”. Опять же, Vue, похоже, очень хорошо развивается. В соответствии с bestof.js, за последние три месяца Angular 2 получал в среднем 31 звезду в день, React — 74 звезды, Vue — 107 звезд.



A Github Stars History для Angular, React и Vue (Источник)


Апдейт: Спасибо Полу Хеншелю за то, что указал на npm trends. Они показывают количество скачиваний для данных npm-пакетов и даже полезнее, как чистый взгляд на звезды GitHub.



Количество скачиваний для заданных npm-пакетов в течение двух лет


1.3 Жизненный цикл на рынке


Angular, React и Vue сложно сравнить в Google Trends из-за их разнообразных имен и версий. Одним из способов получить приблизительные значения может быть поиск в категории “Internet & technologies”. Вот результат:



Ну, что ж. Vue до 2014 года не существовало — значит, тут что-то не так. La Vue по-французски — “вид”, “взгляд”, или “мнение”. Может быть, дело в этом. Сравнение “VueJS” с “Angular” или “React” несправедливо, так как у VueJS почти нет результатов, которые можно сравнить с остальными.


В таком случае, попробуем кое-что другое. Technology Radar от ThoughtWorks дает хорошее представление о том, как технологии эволюционируют в течение времени. Redux находится на стадии принятия(принятия в проектах!) и он был бесценен на ряде проектов ThoughtWorks. Vue.js на стадии испытаний(испытайте!). Его описывают, как легковесную и гибкую альтернативу Angular с более низкой кривой обучения. Angular 2 находится на стадии оценки — он успешно используется командами ThoughtWorks, но пока не имеет настоятельных рекомендаций.


В соответствии с последним опросом StackOverflow 2017, React любят 67% опрошенных разработчиков, а AngularJS — 52%. “Отсутствие интересу к продолжению разработки” регистрирует большие значения для AngularJS(48%) в сравнении с React(33%). Vue не входит в первую десятку ни в одном из случаев. Далее, есть опрос statejs.com, сравнивающий “front-end фреймворки”. Самые интересные факты: React и Angular обладают 100%-й известностью, Vue незнаком 23%-м опрошенных людей. Касательно “удовлетворенности”, React набрал 92% для варианта “использовал бы снова”, Vue — 89%, Angular 2 — только 65%.


Как насчет другого опроса об удовлетворенности пользователей? Эрик Элиотт запустил один в октябре 2016-го, чтобы оценить Angular 2 и React. Лишь 38% опрошенных людей использовали бы Angular 2 снова, в то время как 84% использовали бы снова React.


1.4 Долгосрочная поддержка и миграции


API React-а достаточно стабилен, как заявляет об этом Facebook в своих принципах проектирования. Существуют также скрипты, помогающие мигрировать с вашего текущего API на новый: попробуйте react-codemod. Миграции достаточно просты и здесь нет такой вещи(и необходимости в ней), как LTS версии. В этом посте на Reddit люди отмечают, что апгрейд на самом деле никогда не был проблемой. Команда React написала пост об их схеме версионирования. Когда они добавляют предупреждение об устаревании, они оставляют его для остальной части текущей релизной версии до того момента, пока поведение не будет изменено в следующей мажорной версии. Планов выпустить новую мажорную версию нет — v14 выпущена в октябре 2015-го, v15 опубликована в апреле 2016-го, а у v16 пока нет даты релиза. Обновление не должно стать проблемой, как недавно заметил один из основных разработчиков React.


Что касается Angular, есть пост о версионировании и релизах Angular начиная с релиза v2. Будет одно мажорное обновление каждые шесть месяцев и период устаревания(deprecation period), как минимум шесть месяцев(два мажорных релиза). Существуют экспериментальные API, помеченные в документации более коротким периодом устаревания. Официального анонса нет, но в соответствии с этой статьей, команда Angular анонсировала LTS версии начиная с Angular 4. Они будут поддерживаться как минимум год после следующего мажорного релиза. Это значит, что Angular 4 будет поддерживаться багфиксами и важными патчами как минимум до сентября 2018-го. В большинстве случаев, обновление со второй до четвертой версии Angular также просто, как обновление зависимостей Angular. Angular также предоставляет руководство с информацией о том, какие понадобятся изменения в дальнейшем.


Процесс обновления с Vue 1.x до 2.0 должен быть простым для небольшого приложения — команда разработчиков утверждает, что 90% API осталось без изменений. Имеется приятный инструмент для диагностики обновления и помощи во время миграции, работающий из консоли. Один из разработчиков отметил, что обновление с v1 до v2 до сих пор не приносит особого удовольствия на больших приложениях. К сожалению, roadmap-а для следующего мажорного релиза или информации о планах создания LTS версий нет.


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


1.5 Кадровые ресурсы и найм


Если у вас есть HTML-разработчики, которые не хотят углубляться в JavaScript, вам лучше выбрать Angular или Vue. React повлечет за собой увеличение количества JavaScript-а(позже мы поговорим об этом).


У вас есть дизайнеры, работающие в непосредственной близости с кодом? Пользователь “pier25” отмечает в своем Reddit-посте, что выбирать React имеет смысл, если вы работаете в Facebook, где каждый разработчик — супергерой. В реальном мире, вы не всегда найдете дизайнера, способного модифицировать JSX — по существу, работать с HTML-шаблонами будет намного проще.


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


Angular также хорош, если у вас есть разработчики с ООП-бэкграундом или те, кто не любят JavaScript. Чтобы заострить на этом внимание, цитата Манеша Чанда:


“Я не JavaScript-девелопер. Мой бэкграунд — создание крупномасштабных корпоративных систем с использованием “настоящих” платформ для разработки ПО. Я начинал в 1997-м, разрабатывая приложения на C, C++, Pascal, Ada и Fortran. (...) Я могу точно сказать, что JavaScript для меня просто белиберда. Будучи MVP в Microsoft и экспертом, я хорошо понимаю TypeScript. Я также не рассматриваю Facebook, как компанию-разработчика ПО. Однако, Google и Microsoft уже крупнейшие инноваторы в этой сфере. Я чувствую себя более комфортно, работая с продуктом, у которого есть хорошая поддержка от Google или Microsoft. Также (...) с моим бэкграундом, я знаю, что у Microsoft даже большие планы для TypeScript”

Ну… Видимо, я должен упомянуть, что Манеш Чанд является региональным директором в Microsoft.


Сравнение React, Angular и Vue


2.1 Компоненты


Все обсуждаемые фреймворки основаны на компонентах. Компонент получает что-то на вход и после внутренних вычислений возвращает отрендеренный шаблон UI(область входа / выхода с сайта или элемент списка to-do) на выходе. Определенные компоненты должно быть легко переиспользовать на веб-странице или в других компонентах. Например, у вас мог бы быть компонент сетки(состоящий из шапки и нескольких компонентов для рядов) с разнообразными свойствами(колонки, информация о шапке, data rows, и т.д.) и возможностью переиспользования этого компонента с разными данными на другой странице. Вот всеобъемлющая статья о компонентах на тот случай, если вам захочется изучить их получше.


И React, и Vue превосходно подходят для работы с “тупыми” компонентами: небольшие функции, не имеющие состояния, которые получают что-то на вход и возвращают элементы на выходе.


2.2 TypeScript vs ES6 vs. ES5


React фокусируется на использовании JavaScript ES6. Vue использует JavaScript ES5 либо ES6.


Angular зависит от TypeScript. Это дает большую консистентность в примерах и в опенсорсных проектах(примеры React можно найти в ES5 или в ES6). Это также вводит такие понятия, как декораторы или статическая типизация. Статическая типизация полезна для инструментов для анализа кода, типа автоматического рефакторинга, перехода к определению и т.д. — они должны уменьшить количество багов в приложении, хотя консенсус по поводу их использования, определенно, не достигнут. Эрик Элиот не согласен с этим в своей статье “Шокирующий секрет статических типов”. Дэниэл С Вонг говорит, что использование статических типов не вредит и что хорошо иметь разработку через тестирование(TDD) и статическую типизацию одновременно.


Вам, вероятно, следует знать о том, что вы можете использовать Flow, чтобы включить проверку типов в React. Это инструмент для статической проверки типов, разработанный Facebook для JavaScript. Flow также можно интегрировать в VueJS.


Если вы пишете код на TypeScript, вы уже не пишете стандартный JavaScript. Несмотря на рост, у TypeScript до сих пор крошечное число пользователей, по сравнению со всем языком JavaScript. Один из рисков может заключаться в том, что вы будете двигаться в неверном направлении, поскольку TypeScript может — хотя и вряд ли — исчезнуть со временем. Помимо того, TypeScript создает приличный оверхед на проектах(на обучение) — можете больше почитать об этом в Сравнении Angular 2 и React от Эрика Элиотта.


Апдейт: Джеймс Рейвенскрофт написал к этой статье комментарий о том, что у TypeScript первоклассная поддержка JSX — компоненты можно легко проверить на соответствие типу. Так что, если вам нравится TypeScript и вы хотите использовать React, это не должно быть проблемой.


2.3 Шаблоны — JSX или HTML


React нарушает устоявшиеся best practices. Десятилетиями разработчики пытались разделить шаблоны и встроенную джаваскриптовую логику, но в JSX они опять перемешаны. Может быть, это звучит ужасно, но вам следует послушать речь Питера Ханта “React: Переосмысление best practices”(от октября 2013-го). Он указывает на то, что разделение шаблонов и логики — это просто разделение технологий, а не ответственности. Вы должны создавать компоненты вместо шаблонов. Компоненты переиспользуемы, интегрируемы и удобны для unit-тестирования.


JSX — это опциональный препроцессор с HTML-подобным синтаксисом, который затем компилируется в JavaScript. Отсюда некоторые странности — например, вам нужно использовать className вместо class, потому что последний является в JavaScript зарезервированным ключевым словом. JSX — большое преимущество для разработки, так как у вас все будет в одном и том же месте, а также быстрее будут работать автокомплит и проверки на стадии компиляции. Когда вы допускаете ошибку в JSX, React не компилирует код и выводит номер строки, в которой допущена ошибка. Angular 2 тихо падает в рантайме(возможно, этот аргумент некорректен, если вы пользуетесь AOT с Angular).


JSX подразумевает, что все в React = JavaScript — он используется и для JSX-шаблонов, и для логики. Кори Хаус указывает в своей статье от января 2016-го: “Angular 2 продолжает внедрять ‘JS’ в HTML. React внедряет ‘HTML’ в JS”. Это хорошо, потому что JavaScript мощнее, чем HTML.


Шаблоны в Angular представляют собой усовершенствованный HTML со специальным языком Angular(штуки, вроде ngIf или ngFor). В то время, как React требует знания JavaScript, Angular заставляет вас учить специфичный для Angular синтаксис.


Vue предлагает “однофайловые компоненты”. Это похоже на компромисс относительно разделения ответственности — шаблоны, скрипты и стили в одном файле, но в трех различных, упорядоченных секциях. Это значит, что вы получаете подсветку синтаксиса, поддержку CSS и возможность легко использовать препроцессоры типа Jade или SCSS. Я прочитал в других статьях, что JSX проще дебажить, потому что Vue не показывает синтаксические ошибки в HTML. Это не так, поскольку Vue конвертирует HTML в render-функции — поэтому ошибки показываются без проблем(Спасибо Винициусу Рейзу за комментарий и поправки!).


Примечание: Если вам нравится задумка с JSX и вам хочется использовать его в Vue, вы можете использовать babel-plugin-transform-vue-jsx.


2.4 Фреймворки против библиотек


Angular — больше фреймворк, чем библиотека, так как предоставляет убедительные предложения о том, как ваше приложение должно быть структурировано, а также имеет больше функционала из коробки. Angular — это “цельное решение” — включены “батарейки” и вам предоставляется возможность удобного старта. Вам не нужно проводить анализ библиотек, решать вопрос с роутингом и т.п. — вы можете просто начать работать.


React и Vue, с другой стороны, универсально гибки. Их библиотеки можно совмещать с любого рода пакетами(их достаточно много для React в npm, но у Vue пакетов меньше, так как он еще достаточно молод). Используя React, вы можете даже заменить саму библиотеку на ее API-совместимую альтернативу, такую как Inferno. С большей гибкостью, однако, появляется большая ответственность — для React не существует правил и и ограничительных рекомендаций. Каждый проект требует принятия решения относительно его архитектуры, и все может легко пойти не так, как планировалось.


С другой стороны, Angular поставляется с запутанным клубком систем для сборки, бойлерплейтов, линтеров и других пожирателей времени, с которыми придется иметь дело. Это верно и для React в случае использования стартер-китов или бойлерплейтов. Конечно, они очень полезны, React работает из коробки, и это, может быть для вас способ его изучить. Иногда разнообразие инструментов, необходимых для работы в JavaScript-окружении называют “усталостью от JavaScript”. Вот статья Эрика Клеммонса, который сказал следующее:


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

Vue кажется самым чистым и легким из трех фреймворков. У GitLab есть пост о принятии решения в пользу Vue.js(октябрь 2016-го):


Vue.js прекрасно поставляется отлично сбалансированным в плане того, что он может сделать для вас и того, что вам нужно делать самостоятельно. (...) Vue.js всегда находится в пределах досягаемости; прочная, но гибкая сетка готова помочь сохранить эффективность вашего программирования и свести связанные с DOM страдания к минимуму.

Им нравится простота и легкость использования — исходный код очень читабелен и документация или внешние библиотеки не нужны. Все достаточно просто. Vue.js “не делает далекоидущих предположений о большей части чего-либо”. Имеется также подкаст о решении GitLab.


Другой пост о переходе на Vue от Pixeljets. React “был большим шагом вперед для мира JS в плане state-awareness, и он показал множеству людей реальное функциональное программирование хорошим, практичным способом”. Одним из серьезных минусов React по сравнению с Vue является проблема разбиения компонентов на более мелкие компоненты из-за ограничений JSX. Вот цитата из статьи:


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

На Hacker news и Reddit есть интересные дискуссии об этом посте — в наличии аргументы от недовольных и дальнейших сторонников Vue.


2.5 Управление состоянием и связывание данных


Разрабатывать UI сложно, так как состояния присутствуют везде — данные меняются с течением времени, что влечет за собой увеличение сложности. Определенные способы работы с состоянием оказывают большую помощь, когда приложение разрастается и становится более сложным. Для небольших приложений это может быть перебор, и что-то типа Vanilla JS было бы достаточно.


Как это работает? Компоненты описывают UI в определенный момент времени. Когда данные изменяются, фреймворк перерисовывает UI-компонент целиком — отображаемые данные всегда актуальны. Мы может назвать эту идею “UI как функция”.


React часто работает в паре с Redux. Redux описывает себя в трех фундаментальных принципах:


  • Единственный источник правды
  • Состояние доступно только для чтения
  • Изменения делаются с помощью чистых функций

Другими словами: состояние приложения целиком находится в дереве объектов внутри единого хранилища. Это помогает отлаживать приложение и кое-какая функциональность становится проще для реализации. Состояние в read-only режиме и может быть изменено только через “экшены”, чтобы избежать состояния гонки(также полезно для отладки). “Редьюсеры” пишутся, чтобы указать, как “экшены” могут трансформировать состояние.


Большая часть туториалов и бойлерплейтов включают в себя Redux, но вы можете использовать React без него(или вам может быть вообще не нужен Redux в вашем проекте). Redux добавляет сложность и достаточно серьезные ограничения для вашего кода. Если вы изучаете React, вам стоит подумать об изучении чистого React перед тем, как вы перейдете к Redux. Вам определенно стоит прочесть “Вам может быть не нужен Redux” Дэна Абрамова.


Некоторые разработчики предлагают использовать Mobx вместо Redux. Можете думать о нем, как о “автоматическом Redux”, который упрощает использование и понимание с самого начала. Если хотите посмотреть, вам стоит начать с введения. Вы можете также почитать это полезное сравнение Redux и MobX от Робина. Тот же автор также предлагает информацию о переходе с Redux на MobX. Этот список полезен, если вы хотите проверить другие Flux-библиотеки. И, если вы пришли из мира MVC, вы захотите прочесть статью “Философия Redux (если вы уже знаете MVC)” Михаила Левковского.


Vue можно использовать с Redux, но он предлагает Vuex как свое собственное решение.


Большое отличие между React и Angular — одно- или двустороннее связывание. Двустороннее связывание в Angular меняет модель состояния, когда элемент пользовательского интерфейса(к примеру, поле ввода) обновляется. React идет только одним путем: сначала обновляет модель и затем отрисовывает элемент. Подход Angular чище в коде и проще для реализации разработчиком. Подход React позволяет получить лучшее понимание о том, что происходит с данными, потому что поток данных течет лишь в одном направлении(это делает отладку проще).


Оба подхода имеют плюсы и минусы. Вам нужно понять эти идеи и определить, влияют ли они на ваше решение о выборе фреймворка. И статья “Двустороннее связывание: Angular 2 и React”, и этот вопрос на StackOverflow предоставляют хорошие объяснения. Здесь вы можете найти интерактивные примеры кода(возрастом в 3 года, только для Angular 1 и React). Последнее, но тем не менее важное: Vue поддерживает и одностороннее, и двустороннее связывание(одностороннее по-умолчанию).


Если хотите почитать больше, имеется длинная статья о различных типах состояния и управлении состоянием в Angular-приложениях(от Виктора Савкина).


2.6 Другие концепции программирования


Angular включает в себя dependency injection, паттерн, в котором один объект(сервис) предоставляет зависимости другому объекту(клиенту). Это дает большую гибкость и более чистый код. Статья “Понять dependency injection” в деталях объясняет эту идею.


Паттерн Модель-Вид-Контроллер(MVC) делит проект на три части — модель, вид и контроллер. У Angular, как у MVC-фреймворка, имеется поддержка MVC из коробки. У React есть лишь V — что будет M и C нужно решить вам самим.


2.7 Гибкость и переход к микросервисам


Вы можете работать с React или Vue просто добавляя JavaScript-библиотеку к исходникам. Это невозможно с Angular, потому что он использует TypeScript.


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


Как отмечает Кори Хаус:


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

Некоторые люди также используют React для не-SPA вебсайтов(например, для сложных форм или мастеров). Даже Facebook использует React не для главной страницы, а, скорее, для определенных страниц и возможностей.


2.8 Размер и производительность


Обратная сторона функциональности: Angular довольно-таки раздут. Размер сжатого gzip-ом файла — 143кб, по сравнению с 23кб Vue и 43кб React.


И у React, и у Vue есть Virtual DOM, который должен увеличивать производительность. Если вам интересно, можете почитать об отличиях между Virtual DOM и DOM, а также о реальных преимуществах Virtual DOM в React. Один из авторов Virtual DOM также отвечает на вопросы, связанные с производительностью на StackOverflow.


Чтобы проверить производительность, я посмотрел великолепный js-framework-benchmark. Вы можете сами скачать и запустить его или посмотреть на интерактивную таблицу с результатами. Перед тем, как проверить результаты, вам нужно знать, что фреймворки обманывают бенчмарки — такие замеры производительности не должны быть основой принятия решений.



Производительность Angular, React и Vue (Источник)



Выделение памяти в Мб (Источник)


Чтобы подытожить: у Vue прекрасная производительность и лучшее распределение памяти, но все фреймворки действительно довольно рядом друг с другом, по сравнению с особенно медленными или особенно быстрыми фреймворками(типа Inferno). Еще раз: тесты производительности следует рассматривать как побочные данные, не для вынесения вердикта.


2.9 Тестирование


Facebook использует Jest, чтобы тестировать свой код на React. Здесь есть сравнение Jest и Mocha, и есть статья о том, как использовать Enzyme с Mocha. Enzyme — библиотека для тестирования, написанная на JavaScript, используемая Airbnb(в сочетании с Jest, Karma и другими тест-раннерами). Если вы хотите почитать побольше, имеются старые статьи про тестирование в React(здесь и здесь).


Далее, существует Jasmine, как тестовый фреймворк для Angular 2. В статье Эрика Элиотта говорится о том, что Jasmine “приводит к миллионам способов написания тестов и утверждений, нуждающихся в тщательном прочтении каждого из них, чтобы понять, что он делает”. Вывод также раздут и труден для чтения. Есть несколько информативных статей про интеграцию Angular 2 с Karma и Mocha. Также есть старое видео(от 2015-го) о тестовых стратегиях в Angular 2.


У Vue есть недостатки в тестировании, но Эван написал в своем превью от 2017-го, что команда планирует поработать над этим. Они рекомендуют использовать Karma. Vue работает с Jest, также есть тестовая утилита avoriaz.


2.10 Универсальные и нативные приложения


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


И React, и Angular поддерживают нативную разработку. У Angular есть NativeScript(при поддержке Telerik) для нативных приложений и Ionic Framework для гибридных приложений. С React вы можете попробовать react-native-renderer, чтобы разрабатывать кроссплатформенные приложения для iOS или Android, или react-native для нативных приложений. Большое число приложений(включая Facebook; за подробностями на Showcase) сделаны с помощью react-native.


JavaScript-фреймворки рендерят страницы на клиенте. Это плохо для воспринимаемой производительности, пользовательского опыта в целом и SEO. Серверный рендеринг — это плюс. У всех трет фреймворков имеются библиотеки, чтобы помочь с этим. Для React это next.js, у Vue есть nuxt.js, и у Angular есть… Angular Universal.


2.11 Кривая обучения


У Angular, определенно, крутая кривая обучения. Он обладает всеобъемлющей документацией, но иногда это может вас расстраивать, потому что ряд вещей сложнее, чем кажется. Даже если вы глубоко понимаете JavaScript, вам нужно изучить, что происходит под капотом фреймворка. Вначале установка магическая, предлагает большое число встроенных пакетов и кода. Это можно рассматривать, как минус из-за большой, сразу существующей экосистемы, которую вам нужно со временем изучить. С другой стороны, это может и хорошо в определенных ситуациях, поскольку многие решения уже приняты. С React вам, скорее всего, придется принять множество внушительных решений относительно использования third-party библиотек. Существует 16 различных flux-пакетов для управления состоянием, которые можно выбрать для React.


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


Некоторые люди утверждают, что то, что они сделали на React было бы написано лучше с использованием Vue. Если вы — неопытный JavaScript разработчик — или в последние 10 лет работали в основном с jQuery — вам стоит подумать об использовании Vue. Сдвиг парадигмы более выражен при переходе на React. Vue выглядит больше как чистый JavaScript в тоже время привнося некоторые новые идеи: компоненты, событийная модель и однонаправленный data-flow. Также у него небольшой размер.


Тем временем, у Angular и React свой собственный подход к вещам. Они могут мешать вам, потому что вам нужно приспосабливаться под них, чтобы заставить их работать. Это может быть вредно из-за меньшей гибкости и крутой кривой обучения. Это также может быть и полезно, поскольку вас заставляют изучать правильные подходы в процессе изучения технологии. С Vue вы можете работать в стиле старомодного JavaScript. Это может быть проще вначале, но может стать проблемой в долгосрочной перспективе, если все сделать не верно.


Когда речь заходит об отладке, плюс React и Vue в меньшем количестве магии. Охота на баги проще, потому что есть лишь несколько мест, куда нужно смотреть, и у стектрейсов лучше видны отличия между собственным кодом и исходниками библиотеки. Работающие с React сообщают, что им никогда не требовалось читать исходный код библиотеки. Однако, отлаживая ваш код вашего приложения на Angular, вам часто нужно отлаживать внутренности Angular, чтобы понять лежащую в его основе модель. С другой стороны, сообщения об ошибках должны стать более чистыми и информативными начиная с Angular 4.


2.12 Под капотом Angular, React и Vue


Хотите проверить исходники самостоятельно? Хотите увидеть, как все складывается?


Возможно, вам стоит сначала проверить вот эти GitHub-репозитории: React(github.com/facebook/react), Angular(github.com/angular/angular) и Vue(github.com/vuejs/vue).


Как выглядит синтаксис? ValueCoders сравнивают синтаксис Angular, React и Vue.


Хорошо бы также увидеть все на продакшене — вместе с исходным кодом, на котором все основано. На TodoMVC есть список из десятков вариантов одного и того же Todo-приложения, написанного на различных JavaScript-фреймворках — можете сравнить решения на Angular, React и Vue. RealWorld разрабатывает реальное приложение(клон Medium) и у них есть готовые решения для Angular(4+) и React(с Redux). Для Vue работа в процессе.


Также есть некоторые реальные приложения, на которые вы можете посмотреть. Решения на базе React:



Приложения на Angular:



А также решения на Vue:



Заключение


Выберите фреймворк сейчас


И React, и Angular, и Vue достаточно круты, и никого из них нельзя поставить сильно выше остальных. Доверьтесь своему нутру. Эта последний кусочек развлекательного цинизма может помочь вам принять решение:


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

Это преувеличение, конечно, но в нем возможно есть доля правды. Да, в экосистеме JavaScript есть много лишнего шума. Может быть, вы найдете множество более привлекательных альтернатив во время ваших поисков — постарайтесь не быть ослепленным новейшим блестящим фреймворком.


Что я должен выбрать?


  • Если вы работаете в Google: Angular
  • Если вы любите TypeScript: Angular(или React)
  • Если вам нужно руководство, структура и рука помощи: Angular
  • Если вы работаете в Facebook: React
  • Если вам нравится гибкость: React
  • Если вы любите большие экосистемы: React
  • Если вам нравится выбирать из десятков пакетов: React
  • Если вы любиле JS и подход “все-есть-JavaScript”: React
  • Если вам нравится действительно чистый код: Vue
  • Если вы хотите простейшая кривую обучения: Vue
  • Если вы хотите самый легковесный фреймворк: Vue
  • Если вам хочется разделения ответственности в пределах одного файла: Vue
  • Если вы работаете один или в небольшой команде: Vue(или React)
  • Если ваше приложение имеет тенденцию разрастаться: Angular(или React)
  • Если вы хотите иметь большой пул девелоперов: Angular или React
  • Если вы работаете с дизайнерами и вам нужны чистые HTML-файлы: Angular или Vue
  • Если вам нравится Vue, но пугает ограниченная экосистема: React
  • Если вы не можете решить, изучите сначала React, затем Vue, затем Angular

Итак, вы приняли решение?



Дааа, вы его приняли!


Отлично! Читайте о том, как начать разрабатывать с Angular, React или Vue (скоро, подписывайтесь на меня в Twitter для обновлений).


Дополнительные ресурсы


React JS, Angular & Vue JS — Quickstart & Comparison (восьмичасовое введение и сравнение трех фреймворков)
Angular vs. React (vs. Vue) — the DEAL breaker (короткое, но превосходное сравнение от Доминика Т)
Angular 2 vs. React — the ultimate dance off (неплохое сравнение от Эрика Элиотта)
React vs. Angular vs. Ember vs. Vue.js (сравнение трех фреймворков в форме заметок от Гекана Сари)
React vs. Angular (понятное сравнение двух фреймворков)
Can Vue fight for the Throne with React? (приятное сравнение с большим количеством примеров кода)
10 reasons, why I moved from Angular to React (еще одно неплохое сравнение от Робина Вируча)
All JavaScript frameworks are terrible (большая заметка обо всех основных фреймворках от Мэтта Берджесса)

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/338068/


Метки:  

Постъядерный караван в 35 килобайт

Понедельник, 18 Сентября 2017 г. 20:15 + в цитатник
Zoolander сегодня в 20:15 Разработка

Постъядерный караван в 35 килобайт

    35 килобайт минифицированного кода на обычном JavaScript, семь городов, пустоши, радиоактивные гекконы, съедобные кактусы, встречные караваны и бандиты. Что это? Это небольшая игра, которая запускается в браузере. Ее принципы довольно просты для повторения и в самой примитивной версии ее можно воссоздать, наверное, на любом устройстве, если там есть устройство вывода и генератор случайных чисел. Но сегодня я хочу рассказать, как я реализовал ее для современных браузеров.

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

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



    Возможности игры


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

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

    Функционал можно легко расширить, харизму и другие настоящие ролевые факторы добавить самим. Даже если вы не хотите программировать — можно просто открыть файлы с набором событий в Notepad и добавить новые события, изменить баланс или даже полностью переписать мир и лор, превратив путешествие по постъядерной пустыне в приключения караванщика Лютика в стране эльфов. Или в странствия космического корабля между разными звездными системами (правда, придется выбросить съедобные кактусы, попадающиеся по дороге).

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

    Основная идея и логика программы


    Базовая идея игры очень проста — мы запускаем бесконечный цикл, внутри которого выполняется четыре базовых операции:
    1. Движение к заданной точке
    2. Отсчет дней
    3. Потребление еды
    4. Проверка на вероятность для события, которое нас ждет в пустоши

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

    Как это происходит? Очень просто — каждое событие просто меняет числовые параметры каравана или мира, а затем сообщает об этом в лог. И вот тут возникает магия — параметры могут быть одними и теми же, но сообщать можно о совершенно разных причинах. Минус несколько единиц еды? Это могут быть нападения крыс, выпадение радиоактивных осадков или голодных бродяг. Плюс несколько единиц еды? Значит, ваши люди нашли съедобный кактус, раскопали в придорожных руинах довоенные консервы или нашли на дороге замечательные кожаные сапоги с мягкой подошвой.

    Первая версия, с которой я начал экспериментировать, выглядела чистой иллюстрацией описанного алгоритма:



    Я сделал этот прототип как ремейк игры про орегонский караван из этого туториала.

    Одномерный прототип — караван для js13kGames


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



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

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

    Список изменений
    1. Многоразовое путешествие — игра не прекращается с достижением цели
    2. Двухмерная карта мира и города
    3. Другой сеттинг мира и никакой дизентерии
    4. Бандиты могут вступать в переговоры и наниматься
    5. Добавлены товары, которые автоматически продаются и покупаются при достижении города
    6. Модульная система — логика на базе плагинов и наборы событий в отдельных файлах



    Движок каравана и архитектура


    Игра, как уже было сказано, сделана на чистом JavaScript без использования сторонних библиотек (вы можете сами добавить их, если сочтете нужным). Для отображения карты мира и интерфейса используется обычный HTML и CSS. Чтобы изменять их, используются базовые операции с DOM и классическая операция document.getElementById

    Пример отображения количества игроков в караване
    this.view = {}; // объект для хранения элементов DOM
    this.view.crew = document.getElementById('game-stat-crew'); // находим элемент при запуске игры
    // ...
    this.view.crew.innerHTML = world.crew; //  записываем число людей в караване как обычный html 
    



    WorldState — модель мира


    Мир в игре — это класс WorldState. Он хранит в себе все важные параметры и не содержит никакой логики. Логику мы привяжем потом, за счет плагинов.

    function WorldState(stats) {
        this.day = 0;           // текущий день, с десятичными долям
        this.crew = stats.crew; // количество людей
        this.oxen = stats.oxen; // количество быков
        this.food = stats.food; // запасы еды
        this.firepower = stats.firepower; // единиц оружия
        this.cargo = stats.cargo;   // товаров для торговли
        this.money = stats.money;   //деньги
    
        // лог событий, содержит день, описание и характеристику
        //  { day: 1, message: "Хорошо покушали", goodness: Goodness.positive}
        this.log = [];
    
        // координаты каравана, пункта отправления и назначения
        this.caravan = { x: 0, y: 0};
        this.from = {x: 0, y: 0};
        this.to = {x: 0, y: 0};
    
        this.distance = 0; // сколько всего пройдено
    
        this.gameover = false;  // gameover
        this.stop = false;    // маркер для обозначения того, что караван стоит
        this.uiLock = false; // маркер для блокировки интерфейса
    }
    


    Game — создание мира и игровой цикл


    Игровой цикл запускается и управляется объектом Game. Этот же объект создает мир. Обратите внимание на поле plugins — по умолчанию это пустой массив. Game ничего не знает о плагинах, кроме двух вещей — у них должна быть функция инициализации init(world) и функция обновления update.

    Game = {
        plugins: [],  // генераторы событий, 
    };
    
    Game.init = function () {
        // создаем мир по стартовому состоянию которое хранится в отдельном файле
        // в объекте StartWorldState в директории data
        this.world = new WorldState(StartWorldState);
    
        var i;
        for (i = 0; i < this.plugins.length; i++) {
            this.plugins[i].init(this.world);
        }
    };
    
    // добавление плагинов
    Game.addPlugin = function (plugin) {
        this.plugins.push(plugin);
    };
    
    // игровой цикл
    Game.update = function () {
        if (this.world.gameover) return; // никаких действий
        var i;
        for (i = 0; i < this.plugins.length; i++) {
            this.plugins[i].update();
        }
    };
    
    Game.resume = function () {
        this.interval = setInterval(this.update.bind(this), GameConstants.STEP_IN_MS);
    };
    
    Game.stop = function () {
        clearInterval(this.interval);
    };
    
    Game.restart = function () {
        this.init();
        this.resume();
    };
    


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

    Ешь, живи, двигайся — CorePlugin


    Самые базовые действия каравана — перемещение, отсчет времени и потребление пищи — реализованы в объекте CorePlugin:

    исходный код CorePlugin
    CorePlugin = {};
    
    CorePlugin.init = function (world) {
        this.world = world; // запоминаем world
        this.time = 0; // общее время с начала игры, в миллисекундах
        this.dayDelta = GameConstants.STEP_IN_MS / GameConstants.DAY_IN_MS; // сколько дней в одном шаге игру
        this.lastDay = -1;  // отслеживаем наступление нового дня
        this.speedDelta = Caravan.FULL_SPEED - Caravan.SLOW_SPEED; // разница между полной и минимальной скоростью
    };
    
    CorePlugin.update = function () {
        if (this.world.stop) return; // если стоим - никаких изменений
        this.time += GameConstants.STEP_IN_MS; // увеличение времени
        this.world.day = Math.ceil(this.time / GameConstants.DAY_IN_MS); // текущий день, целый
    
        // Движение каравана в зависимости от того, сколько дней прошло
        this.updateDistance(this.dayDelta, this.world);
    
        // события связанные с наступлением нового дня
        if (this.lastDay < this.world.day) {
            this.consumeFood(this.world);
            this.lastDay = this.world.day;
        }
    };
    
    // еда выдается один раз в день
    CorePlugin.consumeFood = function (world) {
        world.food -= world.crew * Caravan.FOOD_PER_PERSON;
        if (world.food < 0) {
            world.food = 0;
        }
    };
    
    // обновить пройденный путь в зависимости от потраченного времени в днях
    CorePlugin.updateDistance = function (dayDelta, world) {
        var maxWeight = getCaravanMaxWeight(world);
        var weight = getCaravanWeight(world);
    
        // при перевесе - Caravan.SLOW_SPEED
        // при 0 весе - Caravan.FULL_SPEED
        var speed = Caravan.SLOW_SPEED + (this.speedDelta) * Math.max(0, 1 - weight/maxWeight);
    
        // расстояние, которое может пройти караван при такой скорости
        var distanceDelta = speed * dayDelta;
    
        // вычисляем расстояние до цели
        var dx = world.to.x - world.caravan.x;
        var dy = world.to.y - world.caravan.y;
    
        // если мы находимся около цели - останавливаемся
        if(areNearPoints(world.caravan, world.to, Caravan.TOUCH_DISTANCE)){
            world.stop = true;
            return;
        }
    
        // до цели еще далеко - рассчитываем угол перемещения
        // и получаем смещение по координатам
        var angle = Math.atan2(dy, dx);
        world.caravan.x += Math.cos(angle) * distanceDelta;
        world.caravan.y += Math.sin(angle) * distanceDelta;
        world.distance += distanceDelta;
    };
    
    // регистрируем плагин в игре
    Game.addPlugin(CorePlugin);
    



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

    Наш базовый плагин отсчитывает время в миллисекундах, переводит их в дни, а затем обновляет дистанцию и запасы еды. В принципе, объект плагина просто должен содержать функции init(world) и update(), а делать он может что угодно. Можно даже просто вызывать какую-нибудь другую игру на HTML5 или создавать диалоговое окно.

    Чтобы подключить плагин, надо добавить его код между определением объекта Game и первым вызовом Game.restart(). Примерно так, как это сделано сейчас в index.html:

    
    
    
    
    
    
    
    


    Итак, как сделать игру про караван


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

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

    Существующие плагины можно отключать (убирая их исходный код из index.html или закомментировав строку с Game.addPlugin(SomePlugin) в конце их кода). Они ничего не знают друг о друге и просто меняют модель мира или интерфейс игры.

    Ну и последний вариант, для писателей — просто открывать файлы в директории data и редактировать описания событий и константы. Хотя это те же JavaScript-исходники, они довольны просты для изменений. Особенно тексты. Чтобы доказать это, я вкратце расскажу, как устроены другие плагины в текущей версии.

    Случайные события


    Все примитивные случайные события лежат в файле data/RandomEvents.js в переменной RandomEvents в таком формате:

    var RandomEvents = [
        {
            goodness: Goodness.negative,
            stat: 'crew',
            value: -4,
            text: 'На караван напал смертокогть! Людей: -$1'
        },
        {
            goodness: Goodness.negative,
            stat: 'food',
            value: -10,
            text: 'Кротокрысы на привале сожрали часть еды. Пропало пищи: -$1'
        },
      {
            goodness: Goodness.positive,
            stat: 'money',
            value: 15,
            text: 'У дороги найден мертвый путешественник. На теле найдены монеты. Денег: +$1'
        },
        {
            goodness: Goodness.positive,
            stat: 'crew',
            value: 2,
            text: 'Вы встретили одиноких путников, которые с радостью хотят присоединиться к вам. Людей: +$1'
        },
    


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

    Первое поле — goodness- означает позитивную, негативную и нейтральную окраску сообщения в логе. Второе поле — stat — содержит название параметра WorldState, который должен меняться. Value — это среднее значение для изменения этого параметра. Последнее поле — должно содержать любой произвольный текст, описывающий произошедшее. Вместо символом $1 в текст будет подставлено реальное изменение параметра, которое выпадет в игре.

    Случайные события проверяются на выпадение в объекте RandomEventPlugin и радуют взгляд игрока в логе:



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

    Как написать или отредактировать диалог


    За диалоговую систему отвечает объект DialogWindow. Если вы заглянете в его исходный код, то увидите мутанта, который находит в HTML-коде нужный div-элемент и привязывает к нему общий обработчик кликов мышкой. Идея заключается в том, что когда мы просим этот объект показать нам новый диалог, мы передаем ему массив наших диалогов. И обработчик нажатия на конкретный выбор описывается в конкретном диалоге в таком формате:

    var DeathDialogs = {
        "start": {
            icon: "images/pic_death.jpg", // ссылка на url картинки
            title: "Погибший в пустоши", // заголовок диалога
            desc: "",                                // статический текст диалога
            desc_action: function (world, rule) { // функция для создания вычисляемого текста диалога
                var desc = " Причина смерти: "+rule.text+". Вы сумели пройти "+Math.floor(world.distance) + " миль и накопить "+Math.floor(world.money) + " денег";
                desc += "Может быть, следующим караванщикам повезет больше?"
                return desc;
            },
            choices:[  // массив выборов
                {
                    text: 'Начать новую игру', // текст на кнопке
                    action: function () { return "stop"; }  // функция, возвращающая тег следующего диалога
                }
            ]
        },
    };
    


    В диалогах смерти только один вариант, описанный как поле «start». Но таких вариантов может быть бесконечно много. К примеру, в диалогах бандитов я реализовал 12 развилок. Как происходит переход между ними? Наш универсальный объект DialogWindow при вызове функции show сохраняет у себя список переданных диалогов и показывает тот, который определен в поле «start».

    При отображении очередного диалога его массив choices отображается как набор кнопок, в атрибуты которых записывается номер выбора. А все функции action из choices записываются во внутренний массив dialogActions. При клике мышкой на кнопке выбора универсальный обработчик определяет номер функции в dialogActions и вызывает ее, попутно передавая два аргумента, которые мы решили использовать в этом диалоге. Таким образом, в диалогах с бандитами функция action в конкретном choice может принимать состояние мира (world) и описание текущих бандитов (bandits). А в диалогах к другим плагинам — другие параметры. Да можно и вообще без них, особенно, если смысл выбора — просто закончить диалог, как при геймувере.



    Чтобы диалог закончился и игрок вернулся к карте мира, надо, чтобы функция action в объекте choice возвращала один из зарезервированных тегов «finish»,«exit»,«stop». Вообще смысл этой функции в том, чтобы возвращать имя следующей развилки. Но до этого заветного return-a можно и порой нужно вставить любую логику, любые вычисления, которые позволят выбрать следующую развилку — «run», «fight» или, быть может, даже «love».

    Как вызвать диалог из плагина


    В любой момент времени в update любого работающего плагина можно вызвать диалог следующим образом:

        // ... где-то в недрах update у объекта-плагина
        // останавливаем караван, аналог паузы
        world.stop = true; 
       // просим показать диалог с набором развилок из DeathDialogs
       // в развилки будут передаваться аргументы world и rule
        DialogWindow.show(DeathDialogs, world, rule, this);
    


    Также в плагине должна быть реализована функция onDialogClose — этот коллбэк будет вызываться после закрытия диалога. Пример из плагина, определяющего наступление смерти:
    DeathCheck.onDialogClose = function () {
        Game.restart();
    };
    


    Краткое описание существующих плагинов


    В текущей версии игры используются следующие плагины:

    Map2DPlugin — перемещение каравана по карте. Поиск городов, которые задаются в index.html как обычные div с параметрами top и left. Здесь же определяется прибытие в город и происходит автоматическая торговля.

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

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

    DropPlugin — плагин перегруза. В прототипе из туториала про орегонский караван игра сама автоматически сбрасывала вещи — сначала оружие, а потом еду. Это было не очень комфортно и вызывало недоумение — как так-то? Ведь с оружием можно добыть еду, а «жареным мясом нельзя убить врага» (с) один известный стрим по Fallout 4. Так что я решил сделать диалог, в котором ты просто выбираешь, от чего избавиться.

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

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

    Небольшие советы по текущей игре и балансу


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

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

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

    Ссылки и дистрибутивы


    Я использовал графику с лицензией Creative Common 0 с ресурсов pixabay.org и opengameart.org, так что и графика, и код распространяется на этих условиях — свободное копирование и использование, без каких-либо обязательств.

    Исходный код можно взять с GitHub или скачать zip-архивом отсюда. Первый вариант предпочтительнее, так как чаще обновляется.

    Для тестирования даже на локальном компьютере достаточно открыть index.html в браузере — там не используются функции, которым обязательно требуется сервер.

    Живой билд игры можно потестить здесь. Верстка рассчитана на обычные мониторы, не на мобильные экраны.
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/336724/


    Метки:  

    Постъядерный караван в 35 килобайт

    Понедельник, 18 Сентября 2017 г. 20:15 + в цитатник
    Zoolander сегодня в 20:15 Разработка

    Постъядерный караван в 35 килобайт

      35 килобайт минифицированного кода на обычном JavaScript, семь городов, пустоши, радиоактивные гекконы, съедобные кактусы, встречные караваны и бандиты. Что это? Это небольшая игра, которая запускается в браузере. Ее принципы довольно просты для повторения и в самой примитивной версии ее можно воссоздать, наверное, на любом устройстве, если там есть устройство вывода и генератор случайных чисел. Но сегодня я хочу рассказать, как я реализовал ее для современных браузеров.

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

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



      Возможности игры


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

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

      Функционал можно легко расширить, харизму и другие настоящие ролевые факторы добавить самим. Даже если вы не хотите программировать — можно просто открыть файлы с набором событий в Notepad и добавить новые события, изменить баланс или даже полностью переписать мир и лор, превратив путешествие по постъядерной пустыне в приключения караванщика Лютика в стране эльфов. Или в странствия космического корабля между разными звездными системами (правда, придется выбросить съедобные кактусы, попадающиеся по дороге).

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

      Основная идея и логика программы


      Базовая идея игры очень проста — мы запускаем бесконечный цикл, внутри которого выполняется четыре базовых операции:
      1. Движение к заданной точке
      2. Отсчет дней
      3. Потребление еды
      4. Проверка на вероятность для события, которое нас ждет в пустоши

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

      Как это происходит? Очень просто — каждое событие просто меняет числовые параметры каравана или мира, а затем сообщает об этом в лог. И вот тут возникает магия — параметры могут быть одними и теми же, но сообщать можно о совершенно разных причинах. Минус несколько единиц еды? Это могут быть нападения крыс, выпадение радиоактивных осадков или голодных бродяг. Плюс несколько единиц еды? Значит, ваши люди нашли съедобный кактус, раскопали в придорожных руинах довоенные консервы или нашли на дороге замечательные кожаные сапоги с мягкой подошвой.

      Первая версия, с которой я начал экспериментировать, выглядела чистой иллюстрацией описанного алгоритма:



      Я сделал этот прототип как ремейк игры про орегонский караван из этого туториала.

      Одномерный прототип — караван для js13kGames


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



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

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

      Список изменений
      1. Многоразовое путешествие — игра не прекращается с достижением цели
      2. Двухмерная карта мира и города
      3. Другой сеттинг мира и никакой дизентерии
      4. Бандиты могут вступать в переговоры и наниматься
      5. Добавлены товары, которые автоматически продаются и покупаются при достижении города
      6. Модульная система — логика на базе плагинов и наборы событий в отдельных файлах



      Движок каравана и архитектура


      Игра, как уже было сказано, сделана на чистом JavaScript без использования сторонних библиотек (вы можете сами добавить их, если сочтете нужным). Для отображения карты мира и интерфейса используется обычный HTML и CSS. Чтобы изменять их, используются базовые операции с DOM и классическая операция document.getElementById

      Пример отображения количества игроков в караване
      this.view = {}; // объект для хранения элементов DOM
      this.view.crew = document.getElementById('game-stat-crew'); // находим элемент при запуске игры
      // ...
      this.view.crew.innerHTML = world.crew; //  записываем число людей в караване как обычный html 
      



      WorldState — модель мира


      Мир в игре — это класс WorldState. Он хранит в себе все важные параметры и не содержит никакой логики. Логику мы привяжем потом, за счет плагинов.

      function WorldState(stats) {
          this.day = 0;           // текущий день, с десятичными долям
          this.crew = stats.crew; // количество людей
          this.oxen = stats.oxen; // количество быков
          this.food = stats.food; // запасы еды
          this.firepower = stats.firepower; // единиц оружия
          this.cargo = stats.cargo;   // товаров для торговли
          this.money = stats.money;   //деньги
      
          // лог событий, содержит день, описание и характеристику
          //  { day: 1, message: "Хорошо покушали", goodness: Goodness.positive}
          this.log = [];
      
          // координаты каравана, пункта отправления и назначения
          this.caravan = { x: 0, y: 0};
          this.from = {x: 0, y: 0};
          this.to = {x: 0, y: 0};
      
          this.distance = 0; // сколько всего пройдено
      
          this.gameover = false;  // gameover
          this.stop = false;    // маркер для обозначения того, что караван стоит
          this.uiLock = false; // маркер для блокировки интерфейса
      }
      


      Game — создание мира и игровой цикл


      Игровой цикл запускается и управляется объектом Game. Этот же объект создает мир. Обратите внимание на поле plugins — по умолчанию это пустой массив. Game ничего не знает о плагинах, кроме двух вещей — у них должна быть функция инициализации init(world) и функция обновления update.

      Game = {
          plugins: [],  // генераторы событий, 
      };
      
      Game.init = function () {
          // создаем мир по стартовому состоянию которое хранится в отдельном файле
          // в объекте StartWorldState в директории data
          this.world = new WorldState(StartWorldState);
      
          var i;
          for (i = 0; i < this.plugins.length; i++) {
              this.plugins[i].init(this.world);
          }
      };
      
      // добавление плагинов
      Game.addPlugin = function (plugin) {
          this.plugins.push(plugin);
      };
      
      // игровой цикл
      Game.update = function () {
          if (this.world.gameover) return; // никаких действий
          var i;
          for (i = 0; i < this.plugins.length; i++) {
              this.plugins[i].update();
          }
      };
      
      Game.resume = function () {
          this.interval = setInterval(this.update.bind(this), GameConstants.STEP_IN_MS);
      };
      
      Game.stop = function () {
          clearInterval(this.interval);
      };
      
      Game.restart = function () {
          this.init();
          this.resume();
      };
      


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

      Ешь, живи, двигайся — CorePlugin


      Самые базовые действия каравана — перемещение, отсчет времени и потребление пищи — реализованы в объекте CorePlugin:

      исходный код CorePlugin
      CorePlugin = {};
      
      CorePlugin.init = function (world) {
          this.world = world; // запоминаем world
          this.time = 0; // общее время с начала игры, в миллисекундах
          this.dayDelta = GameConstants.STEP_IN_MS / GameConstants.DAY_IN_MS; // сколько дней в одном шаге игру
          this.lastDay = -1;  // отслеживаем наступление нового дня
          this.speedDelta = Caravan.FULL_SPEED - Caravan.SLOW_SPEED; // разница между полной и минимальной скоростью
      };
      
      CorePlugin.update = function () {
          if (this.world.stop) return; // если стоим - никаких изменений
          this.time += GameConstants.STEP_IN_MS; // увеличение времени
          this.world.day = Math.ceil(this.time / GameConstants.DAY_IN_MS); // текущий день, целый
      
          // Движение каравана в зависимости от того, сколько дней прошло
          this.updateDistance(this.dayDelta, this.world);
      
          // события связанные с наступлением нового дня
          if (this.lastDay < this.world.day) {
              this.consumeFood(this.world);
              this.lastDay = this.world.day;
          }
      };
      
      // еда выдается один раз в день
      CorePlugin.consumeFood = function (world) {
          world.food -= world.crew * Caravan.FOOD_PER_PERSON;
          if (world.food < 0) {
              world.food = 0;
          }
      };
      
      // обновить пройденный путь в зависимости от потраченного времени в днях
      CorePlugin.updateDistance = function (dayDelta, world) {
          var maxWeight = getCaravanMaxWeight(world);
          var weight = getCaravanWeight(world);
      
          // при перевесе - Caravan.SLOW_SPEED
          // при 0 весе - Caravan.FULL_SPEED
          var speed = Caravan.SLOW_SPEED + (this.speedDelta) * Math.max(0, 1 - weight/maxWeight);
      
          // расстояние, которое может пройти караван при такой скорости
          var distanceDelta = speed * dayDelta;
      
          // вычисляем расстояние до цели
          var dx = world.to.x - world.caravan.x;
          var dy = world.to.y - world.caravan.y;
      
          // если мы находимся около цели - останавливаемся
          if(areNearPoints(world.caravan, world.to, Caravan.TOUCH_DISTANCE)){
              world.stop = true;
              return;
          }
      
          // до цели еще далеко - рассчитываем угол перемещения
          // и получаем смещение по координатам
          var angle = Math.atan2(dy, dx);
          world.caravan.x += Math.cos(angle) * distanceDelta;
          world.caravan.y += Math.sin(angle) * distanceDelta;
          world.distance += distanceDelta;
      };
      
      // регистрируем плагин в игре
      Game.addPlugin(CorePlugin);
      



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

      Наш базовый плагин отсчитывает время в миллисекундах, переводит их в дни, а затем обновляет дистанцию и запасы еды. В принципе, объект плагина просто должен содержать функции init(world) и update(), а делать он может что угодно. Можно даже просто вызывать какую-нибудь другую игру на HTML5 или создавать диалоговое окно.

      Чтобы подключить плагин, надо добавить его код между определением объекта Game и первым вызовом Game.restart(). Примерно так, как это сделано сейчас в index.html:

      
      
      
      
      
      
      
      


      Итак, как сделать игру про караван


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

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

      Существующие плагины можно отключать (убирая их исходный код из index.html или закомментировав строку с Game.addPlugin(SomePlugin) в конце их кода). Они ничего не знают друг о друге и просто меняют модель мира или интерфейс игры.

      Ну и последний вариант, для писателей — просто открывать файлы в директории data и редактировать описания событий и константы. Хотя это те же JavaScript-исходники, они довольны просты для изменений. Особенно тексты. Чтобы доказать это, я вкратце расскажу, как устроены другие плагины в текущей версии.

      Случайные события


      Все примитивные случайные события лежат в файле data/RandomEvents.js в переменной RandomEvents в таком формате:

      var RandomEvents = [
          {
              goodness: Goodness.negative,
              stat: 'crew',
              value: -4,
              text: 'На караван напал смертокогть! Людей: -$1'
          },
          {
              goodness: Goodness.negative,
              stat: 'food',
              value: -10,
              text: 'Кротокрысы на привале сожрали часть еды. Пропало пищи: -$1'
          },
        {
              goodness: Goodness.positive,
              stat: 'money',
              value: 15,
              text: 'У дороги найден мертвый путешественник. На теле найдены монеты. Денег: +$1'
          },
          {
              goodness: Goodness.positive,
              stat: 'crew',
              value: 2,
              text: 'Вы встретили одиноких путников, которые с радостью хотят присоединиться к вам. Людей: +$1'
          },
      


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

      Первое поле — goodness- означает позитивную, негативную и нейтральную окраску сообщения в логе. Второе поле — stat — содержит название параметра WorldState, который должен меняться. Value — это среднее значение для изменения этого параметра. Последнее поле — должно содержать любой произвольный текст, описывающий произошедшее. Вместо символом $1 в текст будет подставлено реальное изменение параметра, которое выпадет в игре.

      Случайные события проверяются на выпадение в объекте RandomEventPlugin и радуют взгляд игрока в логе:



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

      Как написать или отредактировать диалог


      За диалоговую систему отвечает объект DialogWindow. Если вы заглянете в его исходный код, то увидите мутанта, который находит в HTML-коде нужный div-элемент и привязывает к нему общий обработчик кликов мышкой. Идея заключается в том, что когда мы просим этот объект показать нам новый диалог, мы передаем ему массив наших диалогов. И обработчик нажатия на конкретный выбор описывается в конкретном диалоге в таком формате:

      var DeathDialogs = {
          "start": {
              icon: "images/pic_death.jpg", // ссылка на url картинки
              title: "Погибший в пустоши", // заголовок диалога
              desc: "",                                // статический текст диалога
              desc_action: function (world, rule) { // функция для создания вычисляемого текста диалога
                  var desc = " Причина смерти: "+rule.text+". Вы сумели пройти "+Math.floor(world.distance) + " миль и накопить "+Math.floor(world.money) + " денег";
                  desc += "Может быть, следующим караванщикам повезет больше?"
                  return desc;
              },
              choices:[  // массив выборов
                  {
                      text: 'Начать новую игру', // текст на кнопке
                      action: function () { return "stop"; }  // функция, возвращающая тег следующего диалога
                  }
              ]
          },
      };
      


      В диалогах смерти только один вариант, описанный как поле «start». Но таких вариантов может быть бесконечно много. К примеру, в диалогах бандитов я реализовал 12 развилок. Как происходит переход между ними? Наш универсальный объект DialogWindow при вызове функции show сохраняет у себя список переданных диалогов и показывает тот, который определен в поле «start».

      При отображении очередного диалога его массив choices отображается как набор кнопок, в атрибуты которых записывается номер выбора. А все функции action из choices записываются во внутренний массив dialogActions. При клике мышкой на кнопке выбора универсальный обработчик определяет номер функции в dialogActions и вызывает ее, попутно передавая два аргумента, которые мы решили использовать в этом диалоге. Таким образом, в диалогах с бандитами функция action в конкретном choice может принимать состояние мира (world) и описание текущих бандитов (bandits). А в диалогах к другим плагинам — другие параметры. Да можно и вообще без них, особенно, если смысл выбора — просто закончить диалог, как при геймувере.



      Чтобы диалог закончился и игрок вернулся к карте мира, надо, чтобы функция action в объекте choice возвращала один из зарезервированных тегов «finish»,«exit»,«stop». Вообще смысл этой функции в том, чтобы возвращать имя следующей развилки. Но до этого заветного return-a можно и порой нужно вставить любую логику, любые вычисления, которые позволят выбрать следующую развилку — «run», «fight» или, быть может, даже «love».

      Как вызвать диалог из плагина


      В любой момент времени в update любого работающего плагина можно вызвать диалог следующим образом:

          // ... где-то в недрах update у объекта-плагина
          // останавливаем караван, аналог паузы
          world.stop = true; 
         // просим показать диалог с набором развилок из DeathDialogs
         // в развилки будут передаваться аргументы world и rule
          DialogWindow.show(DeathDialogs, world, rule, this);
      


      Также в плагине должна быть реализована функция onDialogClose — этот коллбэк будет вызываться после закрытия диалога. Пример из плагина, определяющего наступление смерти:
      DeathCheck.onDialogClose = function () {
          Game.restart();
      };
      


      Краткое описание существующих плагинов


      В текущей версии игры используются следующие плагины:

      Map2DPlugin — перемещение каравана по карте. Поиск городов, которые задаются в index.html как обычные div с параметрами top и left. Здесь же определяется прибытие в город и происходит автоматическая торговля.

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

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

      DropPlugin — плагин перегруза. В прототипе из туториала про орегонский караван игра сама автоматически сбрасывала вещи — сначала оружие, а потом еду. Это было не очень комфортно и вызывало недоумение — как так-то? Ведь с оружием можно добыть еду, а «жареным мясом нельзя убить врага» (с) один известный стрим по Fallout 4. Так что я решил сделать диалог, в котором ты просто выбираешь, от чего избавиться.

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

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

      Небольшие советы по текущей игре и балансу


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

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

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

      Ссылки и дистрибутивы


      Я использовал графику с лицензией Creative Common 0 с ресурсов pixabay.org и opengameart.org, так что и графика, и код распространяется на этих условиях — свободное копирование и использование, без каких-либо обязательств.

      Исходный код можно взять с GitHub или скачать zip-архивом отсюда. Первый вариант предпочтительнее, так как чаще обновляется.

      Для тестирования даже на локальном компьютере достаточно открыть index.html в браузере — там не используются функции, которым обязательно требуется сервер.

      Живой билд игры можно потестить здесь. Верстка рассчитана на обычные мониторы, не на мобильные экраны.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/336724/


      Метки:  

      GeekUniversity открывает набор на факультет разработки игр

      Понедельник, 18 Сентября 2017 г. 18:59 + в цитатник
      mary_arti сегодня в 18:59 Разработка

      GeekUniversity открывает набор на факультет разработки игр



        В нашем онлайн-университете для программистов открылся новый факультет разработки игр. За год обучения студенты научатся писать игры на C#, достигнув уровня middle.

        GeekUniversity — совместный образовательный проект Mail.Ru Group и IT-портала GeekBrains. Программу обучения и спецкурсы для факультета разрабатывают Avito, Альфа-банк, МТС, Тинькофф, DeliveryClub.

        Чтобы поступить на факультет разработки игр, необходимо сдать тест по теории языка C#. Тем, кто не справится с вступительным испытанием, предложат пройти подготовительные курсы — восемь занятий, за которые можно освоить объектно-ориентированное программирование и базовые конструкции C#. Программа факультета рассчитана на год, а интенсивность занятий составляет три-четыре раза в неделю. В ходе учебы студенты реализуют ряд проектов, которые смогут добавить в свое портфолио.

        В рамках обучения студенты освоят C#, SQL, классические алгоритмы и структуры данных, пройдут курсы по дополненной реальности, архитектурам и шаблонам проектирования, а также изучат основы 3D-моделирования — это позволит им создавать простые модели и переносить их в Unity3D. В ходе курса учащиеся разработают свою первую игру на C# Unity3D – «Танки» (желающие смогут предложить и реализовать собственный игровой проект), создадут мобильный 3D-шутер, спроектируют и реализуют многопользовательскую стратегию.

        Оставить заявку и получить подробную консультацию можно здесь.
        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/338200/


        Метки:  

        GeekUniversity открывает набор на факультет разработки игр

        Понедельник, 18 Сентября 2017 г. 18:59 + в цитатник
        mary_arti сегодня в 18:59 Разработка

        GeekUniversity открывает набор на факультет разработки игр



          В нашем онлайн-университете для программистов открылся новый факультет разработки игр. За год обучения студенты научатся писать игры на C#, достигнув уровня middle.

          GeekUniversity — совместный образовательный проект Mail.Ru Group и IT-портала GeekBrains. Программу обучения и спецкурсы для факультета разрабатывают Avito, Альфа-банк, МТС, Тинькофф, DeliveryClub.

          Чтобы поступить на факультет разработки игр, необходимо сдать тест по теории языка C#. Тем, кто не справится с вступительным испытанием, предложат пройти подготовительные курсы — восемь занятий, за которые можно освоить объектно-ориентированное программирование и базовые конструкции C#. Программа факультета рассчитана на год, а интенсивность занятий составляет три-четыре раза в неделю. В ходе учебы студенты реализуют ряд проектов, которые смогут добавить в свое портфолио.

          В рамках обучения студенты освоят C#, SQL, классические алгоритмы и структуры данных, пройдут курсы по дополненной реальности, архитектурам и шаблонам проектирования, а также изучат основы 3D-моделирования — это позволит им создавать простые модели и переносить их в Unity3D. В ходе курса учащиеся разработают свою первую игру на C# Unity3D – «Танки» (желающие смогут предложить и реализовать собственный игровой проект), создадут мобильный 3D-шутер, спроектируют и реализуют многопользовательскую стратегию.

          Оставить заявку и получить подробную консультацию можно здесь.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338200/


          Метки:  

          [Из песочницы] Что такое dinghy или как ускорить docker

          Понедельник, 18 Сентября 2017 г. 18:39 + в цитатник
          jesprider сегодня в 18:39 Разработка

          Что такое dinghy или как ускорить docker



          Однажды я заглянул на Хабр, чтобы посмотреть как разработчики используют динги (dinghy) и вообще ускоряют работу докера на маке. На моё удивление по запросу динги я нашёл ровно ноль статей. Было бы нечестно не упомянуть, что тот же запрос вывел 4 комментария. С другой стороны этот факт не изменил картины в целом.

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

          • Производительность докера на osx
          • Запуск нескольких контейнеров, которые работают на порте 80

          Под катом более подробное описание вышеперечисленных проблем, а так же способы их решения.

          Проблема 1: низкая производительность докера на маке


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

          Docker toolbox



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

          В моём случае тулбокс оказался достаточно медленным. Данный инструмент хорошо подходит для запуска «статических» контейнеров. Когда же нужно интенсивно разрабатывать приложение, необходимо создавать mounted volumes (уже давно пытаюсь найти русский эквивалент, есть идеи?) вашей рабочей директории, то есть директории с кодом вашего приложения. И вот тут начинаются проблемы. Выяснив, что тулбокс не использует ни NFS ни rsync, я стал искать другие решения.

          Преимущества:
          — легко устанавливается и настраивается
          Недостатки:
          — медленный при разработке больших проектов

          Docker for mac




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

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

          Преимущества:
          — ещё легче устанавливается и настраивается
          Недостатки:
          — ужасно медленный
          — только одна виртуальная машина, настраиваемся самим приложением

          docker-osx-dev


          Хорошее решение для ускорения работы на локальных машинах при работе с тулбоксом. Docker-osx-dev использует rsync, что значительно ускоряет отправку изменений в контейнер. Недостатком данного решения можно назвать «раздутый» размер контейнера, поскольку сами файлы копируются в контейнер.

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

          Преимущества:
          — значительно ускоряет перенос файлов в контейнер
          Недостатки:
          — висящий процесс
          — раздувает размер контейнера

          Dinghy


          Dinghy — это надстройка над докер машиной (docker-machine), которая включает в себя NFS и proxy (о котором мы поговорим чуть позже, cейчас нас интересует проблема производительности). А насколько мне известно, ничего быстрее NFS (в данном контексте) ещё не придумали.

          Теперь, вместо создания и использования дефолтной или бут ту докер виртуальной машины, мы создаём динги машину. По умолчанию в ней уже будет включен NFS. Если вы не хотите использовать прокси, то можно его отключить в настройках динги, поставив соответствующий флаг в true:

          cat ~/.dinghy/preferences.yml
          :preferences:
            :proxy_disabled: false
            :fsevents_disabled: false
            :create:
              provider: virtualbox
              disk: 30000
          

          Недостаток: поскольку вы явным образом импортируете переменные окружения (например export DOCKER_MACHINE_NAME=dinghy и пр.), то использование обычных докер машин параллельно с динги может принести много хлопот.

          Преимущества:
          — скорость работы
          — никаких дополнительных процессов
          Недостатки:
          — возможно потребуются дополнительные конфиги (docker-compose.yml)
          — конфликт DOCKER_MACHINE_NAME с обычной докер машиной

          Проблема 2: приложения на 80 порту


          После того, как мы ускорили работу своей рабочей среды, может появиться ещё одна проблема: необходимость иметь 2 и более контейнеров, работающие на 80 порту. Что за проблема, просто взять и заэкспойзить другой порт, — может сказать внимательный читатель. Действительно, зачастую данного решения бывает достаточно. Но иногда, конфигурация проектов достаточно сложна и запутана. В этих случаях необходимо иметь именно 80 порты.

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

          169b86af85da        codekitchen/dinghy-http-proxy:2.5   "/app/docker-entry..."    4 hours ago         Up 4 hours          0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 19322/tcp, 0.0.0.0:19322->19322/udp   dinghy_http_proxy
          

          Этот контейнер слушает на 80 порту и принимает все запросы на себя. Внутри этого контейнера имеется nginx сервер, который автоматически создаёт виртуальные сервера исходя из вашего docker-compose файла. Всё, что вам нужно, указать hostname в этом файле. Далее, при обращении на данный хост, динги найдёт нужную запись и перенаправит на нужный контейнер. Профит.

          Заключение


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

          Если у вас возник интерес по техническим деталям, оставляйте комментарии, постараюсь на всё ответить.

          Спасибо за внимание.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338198/


          Метки:  

          [Из песочницы] Что такое dinghy или как ускорить docker

          Понедельник, 18 Сентября 2017 г. 18:39 + в цитатник
          jesprider сегодня в 18:39 Разработка

          Что такое dinghy или как ускорить docker



          Однажды я заглянул на Хабр, чтобы посмотреть как разработчики используют динги (dinghy) и вообще ускоряют работу докера на маке. На моё удивление по запросу динги я нашёл ровно ноль статей. Было бы нечестно не упомянуть, что тот же запрос вывел 4 комментария. С другой стороны этот факт не изменил картины в целом.

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

          • Производительность докера на osx
          • Запуск нескольких контейнеров, которые работают на порте 80

          Под катом более подробное описание вышеперечисленных проблем, а так же способы их решения.

          Проблема 1: низкая производительность докера на маке


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

          Docker toolbox



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

          В моём случае тулбокс оказался достаточно медленным. Данный инструмент хорошо подходит для запуска «статических» контейнеров. Когда же нужно интенсивно разрабатывать приложение, необходимо создавать mounted volumes (уже давно пытаюсь найти русский эквивалент, есть идеи?) вашей рабочей директории, то есть директории с кодом вашего приложения. И вот тут начинаются проблемы. Выяснив, что тулбокс не использует ни NFS ни rsync, я стал искать другие решения.

          Преимущества:
          — легко устанавливается и настраивается
          Недостатки:
          — медленный при разработке больших проектов

          Docker for mac




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

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

          Преимущества:
          — ещё легче устанавливается и настраивается
          Недостатки:
          — ужасно медленный
          — только одна виртуальная машина, настраиваемся самим приложением

          docker-osx-dev


          Хорошее решение для ускорения работы на локальных машинах при работе с тулбоксом. Docker-osx-dev использует rsync, что значительно ускоряет отправку изменений в контейнер. Недостатком данного решения можно назвать «раздутый» размер контейнера, поскольку сами файлы копируются в контейнер.

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

          Преимущества:
          — значительно ускоряет перенос файлов в контейнер
          Недостатки:
          — висящий процесс
          — раздувает размер контейнера

          Dinghy


          Dinghy — это надстройка над докер машиной (docker-machine), которая включает в себя NFS и proxy (о котором мы поговорим чуть позже, cейчас нас интересует проблема производительности). А насколько мне известно, ничего быстрее NFS (в данном контексте) ещё не придумали.

          Теперь, вместо создания и использования дефолтной или бут ту докер виртуальной машины, мы создаём динги машину. По умолчанию в ней уже будет включен NFS. Если вы не хотите использовать прокси, то можно его отключить в настройках динги, поставив соответствующий флаг в true:

          cat ~/.dinghy/preferences.yml
          :preferences:
            :proxy_disabled: false
            :fsevents_disabled: false
            :create:
              provider: virtualbox
              disk: 30000
          

          Недостаток: поскольку вы явным образом импортируете переменные окружения (например export DOCKER_MACHINE_NAME=dinghy и пр.), то использование обычных докер машин параллельно с динги может принести много хлопот.

          Преимущества:
          — скорость работы
          — никаких дополнительных процессов
          Недостатки:
          — возможно потребуются дополнительные конфиги (docker-compose.yml)
          — конфликт DOCKER_MACHINE_NAME с обычной докер машиной

          Проблема 2: приложения на 80 порту


          После того, как мы ускорили работу своей рабочей среды, может появиться ещё одна проблема: необходимость иметь 2 и более контейнеров, работающие на 80 порту. Что за проблема, просто взять и заэкспойзить другой порт, — может сказать внимательный читатель. Действительно, зачастую данного решения бывает достаточно. Но иногда, конфигурация проектов достаточно сложна и запутана. В этих случаях необходимо иметь именно 80 порты.

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

          169b86af85da        codekitchen/dinghy-http-proxy:2.5   "/app/docker-entry..."    4 hours ago         Up 4 hours          0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 19322/tcp, 0.0.0.0:19322->19322/udp   dinghy_http_proxy
          

          Этот контейнер слушает на 80 порту и принимает все запросы на себя. Внутри этого контейнера имеется nginx сервер, который автоматически создаёт виртуальные сервера исходя из вашего docker-compose файла. Всё, что вам нужно, указать hostname в этом файле. Далее, при обращении на данный хост, динги найдёт нужную запись и перенаправит на нужный контейнер. Профит.

          Заключение


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

          Если у вас возник интерес по техническим деталям, оставляйте комментарии, постараюсь на всё ответить.

          Спасибо за внимание.
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338198/


          Метки:  

          Результаты опроса «Каким бы вы хотели видеть Toster.ru?»

          Понедельник, 18 Сентября 2017 г. 18:13 + в цитатник
          toster сегодня в 18:13 Управление

          Результаты опроса «Каким бы вы хотели видеть Toster.ru?»

            Всем привет от команды Тостера! Работая над улучшением нашего сервиса, мы постоянно изучаем данные веб-аналитики, собираем обратную связь от пользователей через службу поддержки или через вопросы, которые задают по тегу «Toster.ru». А когда нам нужно принять более сложное решение или поглубже разобраться в поведении и предпочтениях пользователей, мы проводим опросы. Сегодня как раз хотим поделиться с вами результатами одного из опросов, который провели совсем недавно.

            Мы спросили пользователей о некоторых принципиальных нововведениях, которые планируем ввести на сервисе, а также попросили сравнить Toster.ru и Stackoverflow.com по ряду параметров. В опросе приняли участие более 2.5 тысяч человек, из которых две трети являются разработчиками (причём 39% имеют уровень квалификации Senior или Lead, 38% — Middle, и 23% — Junior или Intern).



            Новые возможности Тостера


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



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

            Пользуясь случаем, мы предложили несколько вариантов разделения вопросов по сложности:



            С небольшим отрывом победил вариант разделения вопросов по сложностям от Intern до Senior. То есть в том же стиле, в каком принято сейчас делить самих разработчиков по их квалификации.

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



            Двигаемся дальше. Следующая часто озвучиваемая пользователями просьба — дать возможность ставить вопросам, ответам и комментариям отрицательную оценку. Через опрос мы решили узнать, насколько эта функция нужна широкому кругу пользователей:



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

            Так что о введении отрицательных отметок мы ещё хорошенько подумаем. Для начала посмотрим — вдруг введение разделения вопросов по вопросов избавить от желания минусовать?



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



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

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



            Дальше мы поинтересовались, хотели бы пользователи увидеть на «Тостере» конкурсные вопросы с денежным призом, который получал бы тот, чей ответ был первым признан решением?



            Тут пользователи оказались более единодушны: более половины встретили эту идею тепло, лишь каждому пятому эта затея не понравилась.



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



            В принципе похожий механизм уже реализован на Stackoverflow, но с той лишь разницей, что в виде награды выступают очки рейтинга, а не деньги, мы же хотели пойти чуть дальше.



            Интеграция «Тостера» с другими сервисами TM


            Мы всё время думаем о том, как увеличить интеграцию между «Тостером» и другими ресурсами TM. Мы также постоянно слышим об этом от пользователей. В опросе мы решили уточнить этот момент уже в более конкретных и осязаемых формах. И получили следующие ответы:

            • 67% опрошенных хотело бы иметь возможность получить полноценный аккаунт на Хабрахабре за свою активность на «Тостере»;
            • 56% согласились с тем, что за должную активность на «Тостере» было бы неплохо получить возможность бесплатно пользоваться сервисом «Фрилансим»: размещать заказы или откликаться на них;
            • 46% согласилось с тем, что клёво иметь возможность не просто помогать людям находить решения, но и, в качестве бонуса, бесплатно размещать вакансии на Моём круге!
            • Против таких возможных бонусов высказалось не более 10%.

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



            Тостер и StackOverflow


            Поскольку нас частенько сравнивают со Stackoverflow, мы решили не упускать возможности и поинтересовались у пользователей, в чём преимущества «Тостера» над Stackoverflow и наоборот, в чём преимущества Stackoverflow над «Тостером»?

            Большинство (порядка 40%) считает, что задать вопрос или дать ответ одинаково просто (сложно) на обоих ресурсах. Мнение оставшихся разделилось так, что вдвое большее число ответивших считают, что проще задать вопрос или дать ответ на «Тостере». А вот аудиторию Stackoverflow считают более дружелюбной, нежели на «Тостере».

            Вопрос о преимуществах «Тостера» и StackOverflow мы оставили открытым, дав возможность самостоятельной формулировки. Не будем приводить здесь все ответы, ведь их огромное множество (и спасибо вам за это!), но топ самых популярных ответов выглядит так:

            Основные преимущества «Тостера»:

            • Русский язык
            • Более приятный интерфейс
            • Возможность создавать дискуссионные вопросы
            • Более широкая тематика
            • Можно просматривать все вопросы
            • Локализация (специфичный софт вроде 1С)
            • Рассылка дайджеста (это нас удивило)
            • Низкий порог входа

            Основные преимущества SO:

            • Более сложные вопросы

            • Большая аудитория
            • Больше готовых решений
            • Отсутствие дискуссий
            • Лучшая модерация
            • Англоязычное коммьюнити

            Мы ни в коем случае не стремимся сделать из Тостера второй StackOverflow, но из ответов мы увидели, что у нашего сервиса есть свои убедительные преимущества, которые мы будем развивать и поддерживать дальше. Прежде всего, мы и дальше будем стараться создавать место, где IT-специалисты смогут получать ответы на самые разные вопросы, касающиеся их деятельности: как простые, так и сложные; как имеющие однозначное решение, так и затрагивающие более широкую дискуссионную проблематику или обмен опытом.
            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/338196/


            Метки:  

            Результаты опроса «Каким бы вы хотели видеть Toster.ru?»

            Понедельник, 18 Сентября 2017 г. 18:13 + в цитатник
            toster сегодня в 18:13 Управление

            Результаты опроса «Каким бы вы хотели видеть Toster.ru?»

              Всем привет от команды Тостера! Работая над улучшением нашего сервиса, мы постоянно изучаем данные веб-аналитики, собираем обратную связь от пользователей через службу поддержки или через вопросы, которые задают по тегу «Toster.ru». А когда нам нужно принять более сложное решение или поглубже разобраться в поведении и предпочтениях пользователей, мы проводим опросы. Сегодня как раз хотим поделиться с вами результатами одного из опросов, который провели совсем недавно.

              Мы спросили пользователей о некоторых принципиальных нововведениях, которые планируем ввести на сервисе, а также попросили сравнить Toster.ru и Stackoverflow.com по ряду параметров. В опросе приняли участие более 2.5 тысяч человек, из которых две трети являются разработчиками (причём 39% имеют уровень квалификации Senior или Lead, 38% — Middle, и 23% — Junior или Intern).



              Новые возможности Тостера


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



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

              Пользуясь случаем, мы предложили несколько вариантов разделения вопросов по сложности:



              С небольшим отрывом победил вариант разделения вопросов по сложностям от Intern до Senior. То есть в том же стиле, в каком принято сейчас делить самих разработчиков по их квалификации.

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



              Двигаемся дальше. Следующая часто озвучиваемая пользователями просьба — дать возможность ставить вопросам, ответам и комментариям отрицательную оценку. Через опрос мы решили узнать, насколько эта функция нужна широкому кругу пользователей:



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

              Так что о введении отрицательных отметок мы ещё хорошенько подумаем. Для начала посмотрим — вдруг введение разделения вопросов по вопросов избавить от желания минусовать?



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



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

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



              Дальше мы поинтересовались, хотели бы пользователи увидеть на «Тостере» конкурсные вопросы с денежным призом, который получал бы тот, чей ответ был первым признан решением?



              Тут пользователи оказались более единодушны: более половины встретили эту идею тепло, лишь каждому пятому эта затея не понравилась.



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



              В принципе похожий механизм уже реализован на Stackoverflow, но с той лишь разницей, что в виде награды выступают очки рейтинга, а не деньги, мы же хотели пойти чуть дальше.



              Интеграция «Тостера» с другими сервисами TM


              Мы всё время думаем о том, как увеличить интеграцию между «Тостером» и другими ресурсами TM. Мы также постоянно слышим об этом от пользователей. В опросе мы решили уточнить этот момент уже в более конкретных и осязаемых формах. И получили следующие ответы:

              • 67% опрошенных хотело бы иметь возможность получить полноценный аккаунт на Хабрахабре за свою активность на «Тостере»;
              • 56% согласились с тем, что за должную активность на «Тостере» было бы неплохо получить возможность бесплатно пользоваться сервисом «Фрилансим»: размещать заказы или откликаться на них;
              • 46% согласилось с тем, что клёво иметь возможность не просто помогать людям находить решения, но и, в качестве бонуса, бесплатно размещать вакансии на Моём круге!
              • Против таких возможных бонусов высказалось не более 10%.

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



              Тостер и StackOverflow


              Поскольку нас частенько сравнивают со Stackoverflow, мы решили не упускать возможности и поинтересовались у пользователей, в чём преимущества «Тостера» над Stackoverflow и наоборот, в чём преимущества Stackoverflow над «Тостером»?

              Большинство (порядка 40%) считает, что задать вопрос или дать ответ одинаково просто (сложно) на обоих ресурсах. Мнение оставшихся разделилось так, что вдвое большее число ответивших считают, что проще задать вопрос или дать ответ на «Тостере». А вот аудиторию Stackoverflow считают более дружелюбной, нежели на «Тостере».

              Вопрос о преимуществах «Тостера» и StackOverflow мы оставили открытым, дав возможность самостоятельной формулировки. Не будем приводить здесь все ответы, ведь их огромное множество (и спасибо вам за это!), но топ самых популярных ответов выглядит так:

              Основные преимущества «Тостера»:

              • Русский язык
              • Более приятный интерфейс
              • Возможность создавать дискуссионные вопросы
              • Более широкая тематика
              • Можно просматривать все вопросы
              • Локализация (специфичный софт вроде 1С)
              • Рассылка дайджеста (это нас удивило)
              • Низкий порог входа

              Основные преимущества SO:

              • Более сложные вопросы

              • Большая аудитория
              • Больше готовых решений
              • Отсутствие дискуссий
              • Лучшая модерация
              • Англоязычное коммьюнити

              Мы ни в коем случае не стремимся сделать из Тостера второй StackOverflow, но из ответов мы увидели, что у нашего сервиса есть свои убедительные преимущества, которые мы будем развивать и поддерживать дальше. Прежде всего, мы и дальше будем стараться создавать место, где IT-специалисты смогут получать ответы на самые разные вопросы, касающиеся их деятельности: как простые, так и сложные; как имеющие однозначное решение, так и затрагивающие более широкую дискуссионную проблематику или обмен опытом.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/338196/


              Метки:  

              Эксперимент: действительно ли все разбираются в дизайне?

              Понедельник, 18 Сентября 2017 г. 17:51 + в цитатник
              Danya_Baranov сегодня в 17:51 Дизайн

              Эксперимент: действительно ли все разбираются в дизайне?

                image

                Мнение о дизайне есть у каждого. «Это плохой лого». «Это хороший лого». «Этот дизайн столько не стоит». Таких комментариев полно под любой новостью о дизайне. Как люди, которые не являются профессионалами, могут так легко делать выводы по одному взгляду?
                Я решил провести небольшой эксперимент, чтобы проверить, действительно ли все так хорошо разбираются в дизайне, как им кажется.

                Опрос: почему логотип Polska Moto такой успешный?


                image

                Чтобы понять, действительно ли люди могут сделать правильные выводы, глядя на дизайн, мы создали опрос. Мы попросили наших подписчиков описать впечатление от неизвестного в России логотипа «Polska Moto».

                image
                Эта компания, вопреки ожиданиям, невероятно быстро заняла лидирующие позиции на рынке мототехники. Их логотип в Польше можно увидеть в каждом мотосалоне:

                image

                Как это ни удивительно, большинство действительно поняло, почему компания с таким логотипом пришла к успеху. В их ответах я прочел много лестных слов об удачном сочетании форм и цветов, например:
                «Яркие цвета и простые формы обеспечили легкость восприятия и просмотра логотипа, округлость притянула взгляд и внимание своей совершенной, без изъянов и излишеств формой. Просто, увлекательно и с намеком на совершенство». [М, 17, маркетолог-программист]
                Это очень приятно, особенно если учесть, что этот логотип сделал лично я за пару минут. А ведь я даже не дизайнер! Тем не менее 57% опрошенных связали успех моей вымышленной фирмы «Polska Moto» именно с логотипом. Они искали причины в сочетании цветов, похожести на Майкрософт, простоте и нестандартности:
                «Возможно, повлияла идея гармоничного сочетания разных элементов — четырех основных цветов, круга и квадрата, при этом логотип остался мягким и скругленным, глаз ни за что не цепляется». [Ж, 23, интернет-маркетинг]

                Почему логотип Polska Moto такой провальный?


                Возможно, я действительно сделал очень хороший логотип и все эти мнения объективные? Чтобы это проверить, я разделил опрашиваемых на две группы по сто человек с помощью сервиса «Qualtrics». Первой группе я показал свой логотип Polska Moto с историей успеха, а другой группе тот же самый лого, но с противоположной легендой:

                image

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

                Как отличить хороший дизайн от плохого: мнение опрашиваемых


                image

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

                Обе группы заметили схожесть с Microsoft. Хорошо это или плохо?

                Конечно, плохо:
                Логотип не отражает сущности компании, он безлик и похож на сотни других. Первая ассоциация с ним — Microsoft, а не мотоиндустрия. [Ж, 26, фрилансер]
                Цвета логотипа слишком заезжены. Вызывают сильную ассоциацию с Microsoft или Google. [М, 18 лет, —]
                … или все-таки хорошо?
                Минималистичность, геометрия, цветовая гамма соответствует корпорации Майкрософт, что изначально внушает уверенность. [Ж, 17, маркетолог]
                Ассоциация с Windows / Google (цвета и геометрия) [Ж, 22, юрист ]
                Группы по-разному восприняли факт «несоответствия сфере»:
                Скучный лого, никак не намекают на связь с мотопроизводством. Цвета не соответствуют специализации (слишком ярко).
                Не понятно чем занимается компания. Это скорее похоже на сферу развлечений. [Ж, 23, графический дизайнер]
                Яркий логотип, не построенный на стереотипных идеях о мотоциклах. При этом внешне создаётся вид престижного и важного бренда. [М, 24, дизайнер-фрилансер]
                … логотип не заезженный и не похож на другие логотипы производителей авто-механики или авто. [Ж, 16, программист]
                Мнения разошлись и насчет цветовой гаммы логотипа:
                Слишком несерьёзно выглядит сочетание цветов [М, 22, —]
                Я считаю, что все детали помешали успеху компании. Форма и цвета больше подходят логотипу цирка [Ж, 30, дизайн]
                Логотип напоминает калейдоскоп и вызывает этим приятные воспоминая из детства. Этим располагает к себе. [Ж, 30, техник строитель / домохозяйка]
                Простота логотипа и его броскость (яркость) легко запоминались пользователями и угадывались [М, 25, инженер-программист]

                На удивление, множество разногласий вызвал квадрат по центру логотипа. Группа с «разорившейся компанией» видела проблему именно в нем:
                … Меня крайне смущает квадрат в центре. [Ж, 24, дизайн]
                Мне кажется, этот контур квадрата здесь абсолютно лишний… [Ж, 19, студент издательского дела]
                Из-за квадрата [Ж, 32, дизайн]
                Однако группа с «успешной компанией» нашла в квадрате глубинный смысл:
                В интернете ходит картинка с тремя кругами: дорого, долго, качественно. И на пересечении их результат. Я думаю в квадрате они тоже выделили что у них все и сразу :) [М, 21, разработчик]
                …4 стихии мира (кружок — сам логотип) и многогранность этого мира, так как внутри квадрат. Для мототехники это важно. [Ж, 21, логист и переводчик]
                Опрашиваемые даже не смогли определиться, простой это логотип или сложный:
                … он визуально сложный [М, 24, Веб]
                Перегруженность деталями и цветами [М, 20, студент-программист]
                Лого создан на основе простых форм и цветов [Ж, 25, дизайнер полиграфии]

                Простой, яркий, запоминающийся логотип [Ж, 23, педагог]

                Какой ответ правильный?


                image

                Понятно – большинство увидело то, что захотело увидеть. Кто-то понял, что логотип не настоящий и потроллил нас в ответ. Но хочется узнать отгадку — хороший это дизайн или плохой? Стала бы компания с таким лого успешной или нет?

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

                На самом деле, в нашем вопросе не больше смысла, чем в: «Почему компания с такой техподдержкой стала успешной?». Или: «Почему компания с таким руководителем отдела снабжения разорилась?». Тут нужна куча уточняющих вопросов: «А насколько большую роль играла техподдержка?», «А как шли дела у отдела снабжения?». И только с помощью массы дополнительной информации можно сделать не то что выводы, а просто скромные предположения.

                Вернемся к дизайну: команда Логомашины сделала сотни логотипов для самых разных компаний. Мы видели, как проекты становятся известными или пропадают без следа. Но ни мы, и никто другой не скажет вам, как связан дизайн логотипа и успех фирмы. Что бы изменилось, если бы на логотипе Эппл яблоко было не надкусано? Никто не знает. А если бы Эппл оставила свой старый логотип? Все бы изменилось, но никто не скажет, как именно.

                image

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

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

                Выводы


                image

                В дизайне очень мало объективного. Если бы мы спросили: «Почему эта программа не работает?», — большинство бы честно сказало: «Я не знаю, я не программист». А разработчики бы открыли код и нашли ошибку, если она есть.

                Но когда мы спрашиваем: «Почему этот дизайн не работает?», — мнение есть у каждого. И на это мнение очень легко повлиять.
                Поэтому все фокус-группы и опросы «Как вам мой логотип?» не состоятельны — люди с умным видом рассуждают о том, в чем вообще никто не разбирается. Одни и те же признаки можно трактовать двояко — перед вами простой и запоминающийся логотип или просто примитивный?

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

                Простой пример — клиент разочаровался в логотипе, потому что, цитата: «Показал всем знакомым, никому не понравилось». Все бы ничего, но он показывал дизайн со словами: «Мне какое-то говно нарисовали, что думаешь?». Вывод из всей истории простой: большая часть мнений о хорошем и плохом дизайне основаны только на субъективных ощущениях. А ощущения создаются не только из чувства вкуса и предрассудков, но и из «эффекта ореола» — мнения окружающих и других факторов.

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

                Если хотите поучаствовать в наших экспериментах, посмотреть трансляции из офиса и узнать что-то новое — подпишитесь на Логомашину в ВК. И, как всегда, удачи вам и вашим проектам!

                Эксперимент провел Данила Баранов, контент-менеджер Логомашины.
                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/338192/


                Метки:  

                Эксперимент: действительно ли все разбираются в дизайне?

                Понедельник, 18 Сентября 2017 г. 17:51 + в цитатник
                Danya_Baranov сегодня в 17:51 Дизайн

                Эксперимент: действительно ли все разбираются в дизайне?

                  image

                  Мнение о дизайне есть у каждого. «Это плохой лого». «Это хороший лого». «Этот дизайн столько не стоит». Таких комментариев полно под любой новостью о дизайне. Как люди, которые не являются профессионалами, могут так легко делать выводы по одному взгляду?
                  Я решил провести небольшой эксперимент, чтобы проверить, действительно ли все так хорошо разбираются в дизайне, как им кажется.

                  Опрос: почему логотип Polska Moto такой успешный?


                  image

                  Чтобы понять, действительно ли люди могут сделать правильные выводы, глядя на дизайн, мы создали опрос. Мы попросили наших подписчиков описать впечатление от неизвестного в России логотипа «Polska Moto».

                  image
                  Эта компания, вопреки ожиданиям, невероятно быстро заняла лидирующие позиции на рынке мототехники. Их логотип в Польше можно увидеть в каждом мотосалоне:

                  image

                  Как это ни удивительно, большинство действительно поняло, почему компания с таким логотипом пришла к успеху. В их ответах я прочел много лестных слов об удачном сочетании форм и цветов, например:
                  «Яркие цвета и простые формы обеспечили легкость восприятия и просмотра логотипа, округлость притянула взгляд и внимание своей совершенной, без изъянов и излишеств формой. Просто, увлекательно и с намеком на совершенство». [М, 17, маркетолог-программист]
                  Это очень приятно, особенно если учесть, что этот логотип сделал лично я за пару минут. А ведь я даже не дизайнер! Тем не менее 57% опрошенных связали успех моей вымышленной фирмы «Polska Moto» именно с логотипом. Они искали причины в сочетании цветов, похожести на Майкрософт, простоте и нестандартности:
                  «Возможно, повлияла идея гармоничного сочетания разных элементов — четырех основных цветов, круга и квадрата, при этом логотип остался мягким и скругленным, глаз ни за что не цепляется». [Ж, 23, интернет-маркетинг]

                  Почему логотип Polska Moto такой провальный?


                  Возможно, я действительно сделал очень хороший логотип и все эти мнения объективные? Чтобы это проверить, я разделил опрашиваемых на две группы по сто человек с помощью сервиса «Qualtrics». Первой группе я показал свой логотип Polska Moto с историей успеха, а другой группе тот же самый лого, но с противоположной легендой:

                  image

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

                  Как отличить хороший дизайн от плохого: мнение опрашиваемых


                  image

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

                  Обе группы заметили схожесть с Microsoft. Хорошо это или плохо?

                  Конечно, плохо:
                  Логотип не отражает сущности компании, он безлик и похож на сотни других. Первая ассоциация с ним — Microsoft, а не мотоиндустрия. [Ж, 26, фрилансер]
                  Цвета логотипа слишком заезжены. Вызывают сильную ассоциацию с Microsoft или Google. [М, 18 лет, —]
                  … или все-таки хорошо?
                  Минималистичность, геометрия, цветовая гамма соответствует корпорации Майкрософт, что изначально внушает уверенность. [Ж, 17, маркетолог]
                  Ассоциация с Windows / Google (цвета и геометрия) [Ж, 22, юрист ]
                  Группы по-разному восприняли факт «несоответствия сфере»:
                  Скучный лого, никак не намекают на связь с мотопроизводством. Цвета не соответствуют специализации (слишком ярко).
                  Не понятно чем занимается компания. Это скорее похоже на сферу развлечений. [Ж, 23, графический дизайнер]
                  Яркий логотип, не построенный на стереотипных идеях о мотоциклах. При этом внешне создаётся вид престижного и важного бренда. [М, 24, дизайнер-фрилансер]
                  … логотип не заезженный и не похож на другие логотипы производителей авто-механики или авто. [Ж, 16, программист]
                  Мнения разошлись и насчет цветовой гаммы логотипа:
                  Слишком несерьёзно выглядит сочетание цветов [М, 22, —]
                  Я считаю, что все детали помешали успеху компании. Форма и цвета больше подходят логотипу цирка [Ж, 30, дизайн]
                  Логотип напоминает калейдоскоп и вызывает этим приятные воспоминая из детства. Этим располагает к себе. [Ж, 30, техник строитель / домохозяйка]
                  Простота логотипа и его броскость (яркость) легко запоминались пользователями и угадывались [М, 25, инженер-программист]

                  На удивление, множество разногласий вызвал квадрат по центру логотипа. Группа с «разорившейся компанией» видела проблему именно в нем:
                  … Меня крайне смущает квадрат в центре. [Ж, 24, дизайн]
                  Мне кажется, этот контур квадрата здесь абсолютно лишний… [Ж, 19, студент издательского дела]
                  Из-за квадрата [Ж, 32, дизайн]
                  Однако группа с «успешной компанией» нашла в квадрате глубинный смысл:
                  В интернете ходит картинка с тремя кругами: дорого, долго, качественно. И на пересечении их результат. Я думаю в квадрате они тоже выделили что у них все и сразу :) [М, 21, разработчик]
                  …4 стихии мира (кружок — сам логотип) и многогранность этого мира, так как внутри квадрат. Для мототехники это важно. [Ж, 21, логист и переводчик]
                  Опрашиваемые даже не смогли определиться, простой это логотип или сложный:
                  … он визуально сложный [М, 24, Веб]
                  Перегруженность деталями и цветами [М, 20, студент-программист]
                  Лого создан на основе простых форм и цветов [Ж, 25, дизайнер полиграфии]

                  Простой, яркий, запоминающийся логотип [Ж, 23, педагог]

                  Какой ответ правильный?


                  image

                  Понятно – большинство увидело то, что захотело увидеть. Кто-то понял, что логотип не настоящий и потроллил нас в ответ. Но хочется узнать отгадку — хороший это дизайн или плохой? Стала бы компания с таким лого успешной или нет?

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

                  На самом деле, в нашем вопросе не больше смысла, чем в: «Почему компания с такой техподдержкой стала успешной?». Или: «Почему компания с таким руководителем отдела снабжения разорилась?». Тут нужна куча уточняющих вопросов: «А насколько большую роль играла техподдержка?», «А как шли дела у отдела снабжения?». И только с помощью массы дополнительной информации можно сделать не то что выводы, а просто скромные предположения.

                  Вернемся к дизайну: команда Логомашины сделала сотни логотипов для самых разных компаний. Мы видели, как проекты становятся известными или пропадают без следа. Но ни мы, и никто другой не скажет вам, как связан дизайн логотипа и успех фирмы. Что бы изменилось, если бы на логотипе Эппл яблоко было не надкусано? Никто не знает. А если бы Эппл оставила свой старый логотип? Все бы изменилось, но никто не скажет, как именно.

                  image

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

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

                  Выводы


                  image

                  В дизайне очень мало объективного. Если бы мы спросили: «Почему эта программа не работает?», — большинство бы честно сказало: «Я не знаю, я не программист». А разработчики бы открыли код и нашли ошибку, если она есть.

                  Но когда мы спрашиваем: «Почему этот дизайн не работает?», — мнение есть у каждого. И на это мнение очень легко повлиять.
                  Поэтому все фокус-группы и опросы «Как вам мой логотип?» не состоятельны — люди с умным видом рассуждают о том, в чем вообще никто не разбирается. Одни и те же признаки можно трактовать двояко — перед вами простой и запоминающийся логотип или просто примитивный?

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

                  Простой пример — клиент разочаровался в логотипе, потому что, цитата: «Показал всем знакомым, никому не понравилось». Все бы ничего, но он показывал дизайн со словами: «Мне какое-то говно нарисовали, что думаешь?». Вывод из всей истории простой: большая часть мнений о хорошем и плохом дизайне основаны только на субъективных ощущениях. А ощущения создаются не только из чувства вкуса и предрассудков, но и из «эффекта ореола» — мнения окружающих и других факторов.

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

                  Если хотите поучаствовать в наших экспериментах, посмотреть трансляции из офиса и узнать что-то новое — подпишитесь на Логомашину в ВК. И, как всегда, удачи вам и вашим проектам!

                  Эксперимент провел Данила Баранов, контент-менеджер Логомашины.
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/338192/


                  Метки:  

                  [Из песочницы] Генерация родословного дерева на основе данных Wikipedia

                  Понедельник, 18 Сентября 2017 г. 17:41 + в цитатник
                  fonkost сегодня в 17:41 Разработка

                  Генерация родословного дерева на основе данных Wikipedia

                  В этой статье я хочу показать, как с помощью фреймворка Selenium Webdriver можно, исходя из данных Wikipedia, составить генеалогическое древо заданной персоны (например, легендарного основателя первой династии русских правителей Рюрика).

                  В статье будет рассказано, как определить имя персоны, вычислить ссылки на страницы детей персоны, а также будет построен алгоритм генерации генеалогического древа.
                  Я буду использовать Java, Selenium Webdriver и Chrome. Chrome, потому что он быстрее остальных браузеров, а так как переход по урлу — самое затратное по времени операция в программе, то выбор браузера заметнее всего сказывается на времени. Можно вообще отказаться от браузера и использовать, скажем PhantomJs, но его сложнее дебажить. Поэтому я остановился на Chrome.

                  В первую очередь создадим тест, проверяющий, что браузер корректно запустился и что при переходе по урлу https://ru.wikipedia.org/wiki/Рюрик открывается страница с заголовком «Рюрик — Википедия»:

                  @BeforeClass
                  public static void Start() {
                      driver = DriverHelper.getDriver();
                  }
                  
                  @Test
                  public void testGetDriver() {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/%D0%A0%D1%8E%D1%80%D0%B8%D0%BA");
                      assertTrue(driver.getTitle().equals("Рюрик — Википедия"));
                  }
                  
                  @AfterClass
                  public static void Stop() {
                      driver.quit();
                  }
                  

                  Создаем класс DriverHelper со статичным методом getDriver(), чтобы проект скомпилился и тест прошёл успешно:

                  public final class DriverHelper{
                      private static final int TIMEOUT = 30;
                  
                      public static WebDriver getDriver() {
                          WebDriver driver = new ChromeDriver();
                          driver.manage().window().maximize();
                          driver.manage().timeouts().implicitlyWait(TIMEOUT, TimeUnit.SECONDS);
                          return driver;
                      }
                  }
                  

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

                  Создание класса Person


                  Перейдем к созданию класса Person, в котором будет храниться информация о персоне, а также к созданию класса страницы персоны на Wikipedia PersonPage.

                  В классе Person пока будут только два поля – name и url. В качестве name будем использовать полное имя человека, без разделения на Фамилию, Имя, Отчество, т.к. большинство представителей династии не будут иметь фамилии, зато будут иметь прозвища, титулы и порядковые имена.

                  Url будет указывать на страницу Wikipedia, посвященную данному человеку.
                  Создаем тест, проверяющий формирование персоны:

                  @Test
                  public void testGetPerson() throws Exception {
                      PersonPage page = new PersonPage(driver);
                      Person person = page.getPerson("https://ru.wikipedia.org/wiki/Владимир_Александрович");
                      assertTrue(person.getName().equals("Владимир Александрович"));
                      assertTrue(person.getUrl().equals(
                          "https://ru.wikipedia.org/wiki/
                          %D0%92%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80_
                          %D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B2%D0%B8%D1%87"));
                  }
                  

                  testGetPerson() не компилится. Нам нужно разработать страницу PersonPage, чтобы определить имя и страницу человека. Url мы определяем по url текущей страницы, а имя – по текстовому содержимому тэга с идентификатором firstHeading. Метод getPerson():

                  public Person getPerson(String url) throws MalformedURLException {
                      driver.navigate().to(url);
                  
                      String name = getName();
                  
                      Person person = new Person(driver.getCurrentUrl());
                      person.setName(name);
                      return person;
                  }
                  
                  private String getName() throws MalformedURLException {
                      String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();
                      return namePage;
                  }
                  

                  Перепрогоняем тесты – позеленели.
                  Отдельно стоит упомянуть, почему переопределяется url, хотя он передается в качестве параметра: дело в том, что в Wikipedia одной персоне может быть посвящено несколько страниц, которые редиректятся на одну. В результате, если использовать исходные урлы, то возможно возникновение дубликатов, когда существует несколько персон с «разными» урлами, которые фактически являются одной персоной. Поэтому в качестве урла используется тот урл, на который редиректятся все остальные.

                  Например: страница https://ru.wikipedia.org/wiki/Ярослав_Мудрый перенаправляется на https://ru.wikipedia.org/wiki/Ярослав_Владимирович_Мудрый, а страница https://ru.wikipedia.org/wiki/Андрей_Боголюбский — на https://ru.wikipedia.org/wiki/Андрей_Юрьевич_Боголюбский

                  Определение детей персоны


                  Попробуем определить детей персоны, которые имеют свои страницы в Wikipedia.
                  Для начала напишем тест для определения детей Рюрика (точнее одного — Игоря):

                  @Test
                  public void testGetChildrenUrl() throws Exception {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Рюрик");
                      PersonPage page = new PersonPage(driver);
                      List children = page.getChildrenUrl();
                      assertTrue(children.size() == 1);
                      Person person = children.get(0);
                      assertTrue(person.getUrl().equals("https://ru.wikipedia.org/wiki/
                          %D0%98%D0%B3%D0%BE%D1%80%D1%8C_
                          %D0%A0%D1%8E%D1%80%D0%B8%D0%BA%D0%BE%D0%B2%D0%B8%D1%87"));
                  }
                  

                  Чтобы тест прошел успешно, нужно добавить на страницу PersonPage метод определения урлов детей человека:

                  public List getChildrenUrl() throws MalformedURLException {
                      List childrenLinks = driver.findElements(
                          By.xpath("//table[contains(@class, 'infobox')]//tr[th[.='Дети:']]//a"));
                      List children = new ArrayList();
                      for (WebElement link : childrenLinks) {
                          Person person = new Person(link.getAttribute("href"));
                          children.add(person);
                      }
                      return children;
                  }
                  

                  Пока что игнорируем то, что детям могут быть не посвящены страницы в Wikipedia и поэтому они не имеют ссылок на свои страницы. Например, как в случае с детьми Владимира Ярославича (князя галицкого). Также игнорируем то, что информация о потомках может располагаться в основной области, как, например, на странице Марии Добронеги или на странице Святослава Всеволодовича (князя трубчевского).

                  Добавляем тесты, проверяющие корректность определения детей для персоны.
                  Как было оговорено выше, пока предполагаем, что Владимир Ярославич (князь галицкий) и Мария Добронега детей не имели, а Владимир Святославич имел 16 детей, хотя Wikipedia утверждает, что у него было ещё 5 неизвестных по имени дочерей.

                  @Test
                  public void testChildrenSize() throws Exception {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Рюрик");
                      PersonPage page = new PersonPage(driver);
                      List children = page.getChildrenUrl();
                      assertTrue(children.size() == 1);
                  
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Святославич");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 16);
                  
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Ярославич_(князь_галицкий)");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 0);
                  
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Мария_Добронега");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 0);
                  }
                  

                  В класс Person добавим поля для уникального идентификатора персоны (int id) и списка детей персоны (List children), в котором будут храниться идентификаторы детей.
                  Разработаем метод добавления идентификатора ребенка в список детей персоны. Ребенок может быть добавлен в список, только если его там ещё нет.

                  public void setChild(int childId) {
                      if (!children.contains(childId)) {
                          children.add(childId);
                      }
                  }
                  

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

                  Алгоритм поиска потомков


                  Теперь перейдем к самому интересному – разработке алгоритма поиска потомков у заданной персоны. Создадим класс GenerateGenealogicalTree с методом main.

                  Как уже упоминалось, самое затратное по времени — переход по урлу, поэтому нужно минимизировать количество этих переходов. Для этого создадим список персон, в котором будет хранится всё родословное древо. В этом списке запомним индекс текущей персоны — той, на странице которой находимся на данный момент. Все персоны с меньшим индексом считаются «посещенными», а все с бОльшим индексом (+ текущая) — «непосещенными». После того, как был осуществлен переход на страницу текущей персоны и вычислены её основные данные, индекс увеличивается на единицу. Тем самым текущая персона попадает в разряд «посещенных». И остаётся только обойти оставшихся «непосещенных» персон. В каждый момент времени известны те персоны, страницы которых уже были просмотрены.

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

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

                  Алгоритм такой:

                  1. Создается основатель династии на основе заданного урла
                  2. Создается родословное древо на основе основателя династии
                  3. В цикле до тех пор, пока есть «непосещенные» персоны
                  4. Вычисляется персона на основе текущего урла родословного древа. Эта персона устанавливается в качестве текущей.
                  5. Если текущая персона не является дубликатом, то вычисляется и устанавливается список её детей. Все дети добавляются в список.
                  6. Если текущая персона уже встречалась среди «посещенных» персон, то она удаляется.
                  7. Происходит переход к следующей «непосещенной» персоне, которая принимается за «текущую».

                  Код алгоритма:

                  public final class GenerateGenealogicalTree {
                      public static void main(String[] args) throws Exception {
                          String url = getUrl(args);
                          GenealogicalTree tree = getGenealogicalTreeByUrl(url);
                          saveResultAndQuit(tree);
                      }
                  
                      public static GenealogicalTree getGenealogicalTreeByUrl(String url) throws MalformedURLException {
                          WebDriver driver = DriverHelper.getDriver();
                          Person person = new Person(url);
                          GenealogicalTree tree = new GenealogicalTree(person);
                          PersonPage page = new PersonPage(driver);
                          while (tree.hasUnvisitingPerson()) {
                              String currentUrl = tree.getCurrentUrl();
                              Person currentPerson = page.getPerson(currentUrl);
                              tree.setCurrentPerson(currentPerson);
                              if (!tree.isCurrentPersonDeleted()) {
                                  List children = page.getChildrenUrl();
                                  tree.setChildren(children);
                               }
                               tree.updatingCurrentPerson();
                          }
                          driver.quit();
                          return tree;
                      }
                  }
                  

                  Класс GenealogicalTree имеет три поля: List allPersons — список всех представителей родословного древа, int indexCurrentUnvisitedPerson — индекс текущей персоны в списке allPersons, а также boolean isCurrentPersonDeleted — признак того, удалена ли «текущая» персона (т.е. является ли она дубликатом).

                  public final class GenealogicalTree {
                      private List allPersons;
                      private int indexCurrentUnvisitedPerson;
                      private boolean isCurrentPersonDeleted;
                  }
                  

                  Инициализация происходит на основе «родоначальника» династии — первой персоне, потомков которой мы ищем:

                  public GenealogicalTree(Person person) {
                      if (person == null) {
                          throw new IllegalArgumentException("Укажите непустого основателя династии");
                      }
                      allPersons = new ArrayList();
                      allPersons.add(person);
                      indexCurrentUnvisitedPerson = 0;
                      isCurrentPersonDeleted = false;
                  }
                  

                  В этот момент родословное древо состоит из одной текущей «непосещенной» персоны. «Посещенных» персон нет.

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

                  public boolean hasUnvisitingPerson() {
                      return indexCurrentUnvisitedPerson < allPersons.size();
                  }
                  

                  В роли url-а родословного древа выступает url текущей персоны:

                  public String getCurrentUrl() {
                      return allPersons.get(indexCurrentUnvisitedPerson).getUrl();
                  }
                  

                  Метод setCurrentPerson заменяет текущую персону на заданную.

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

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

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

                  public void setCurrentPerson(Person currentPerson) {
                      int indexDuplicate = allPersons.indexOf(currentPerson);
                      if ((0 <= indexDuplicate) && (indexDuplicate < indexCurrentUnvisitedPerson)) {
                          removePerson(indexDuplicate);
                      } else {
                          allPersons.get(indexCurrentUnvisitedPerson).copyMainData(currentPerson);
                          isCurrentPersonDeleted = false;
                      }
                  }
                  

                  Чтобы корректно отработал метод indexOf(Object object) необходимо в классе Person переопределить методы equals(Object object) и hashCode():

                  @Override
                  public boolean equals(Object object) {
                      if ((object == null) || (!(object instanceof Person))) {
                          return false;
                      }
                  
                      Person person = (Person) object;
                      return this.url.equals(person.url);
                  }
                  
                  @Override
                  public int hashCode() {
                      return this.url.hashCode();
                  }
                  

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

                  1. Отцовство достоверно неизвестно. Как, например, в случае со Святополком Окаянным, отцом которого является либо Ярополк Святославич, либо Владимир Святославич
                  2. Оба родителя – потомки Рюрика от разных ветвей. Пример: Глеб Всеславич — потомок Рюрика в 8-м поколении был женат на Анастасии Ярополковне — тоже потомком Рюрика (они четвероюродные брат с сестрой).
                  3. Ошибки на странице: вызывает сомнение, что Всеволод Мстиславич имел сына Володаря Глебовича, родителями которого записаны другие люди, тоже принадлежащие династии Рюриковичей. Вероятнее всего, это просто опечатка в Wikipedia

                  Если эти дубликаты не устранить, то они породят новые повторения, т.к. по всем потомкам дубликатов обход будет производится два раза, а то и три (в случае с Володарем Глебовичем).

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

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

                  После удаления текущая персона помечается удаленной.

                  private void removePerson(int indexDuplicate) {
                      int idRemovedPerson = allPersons.get(indexCurrentUnvisitedPerson).getId();
                      int idDuplicate = allPersons.get(indexDuplicate).getId();
                      for (int i = 0; i < indexCurrentUnvisitedPerson; i++) {
                          Person person = allPersons.get(i);
                          person.replaceChild(idRemovedPerson, idDuplicate);
                      }
                      allPersons.remove(indexCurrentUnvisitedPerson);
                      isCurrentPersonDeleted = true;
                  }
                  

                  В классе Person добавляем метод замены «ребенка»:

                  public void replaceChild(int oldId, int newId) {
                      if (oldId == newId) {
                          return;
                      }
                      if (!children.contains(oldId)) {
                          return;
                      }
                      children.remove((Object) oldId);
                      setChild(newId);
                  }
                  

                  Рассмотрим добавление детей текущей персоне.

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

                  Если не удалена, то пробегаемся по списку, переданному в качестве параметра. Если ребенок уже встречается в родословном древе, то в список детей добавляется идентификатор найденного дубликата. Если ребенок не встречается в родословном древе, то в список детей добавляется его идентификатор, кроме того, сам ребенок добавляется в конец родословного древа, в список «непосещенных» персон.

                  Таким образом через метод setChildren() происходит «наполнение» списка.

                  public void setChildren(List children) {
                      if (isCurrentPersonDeleted) {
                          throw new IllegalArgumentException(
                              "Нельзя установить детей удаленной персоне. Текущая персона уже другая");
                      }
                  
                      for (Person person : children) {
                          int index = allPersons.indexOf(person);
                          int id;
                          if (index >= 0) {
                              id = allPersons.get(index).getId();
                          } else {
                              allPersons.add(person);
                              id = person.getId();
                          }
                          allPersons.get(indexCurrentUnvisitedPerson).setChild(id);
                      }
                  }
                  

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

                  public void updatingCurrentPerson() {
                      if (isCurrentPersonDeleted) {
                          isCurrentPersonDeleted = false;
                      } else {
                          indexCurrentUnvisitedPerson++;
                      }
                  }
                  

                  Обход осуществляется по поколениям: вначале основатель династии (0-е поколение), затем все его дети (1-е поколение) от старшего к младшему (подразумеваем, что именно в таком порядке располагаются урлы в Wikipedia), затем внуки (2-е поколение) (дети старшего сына по старшинству, затем — 2-го сына, и так до самого младшего), правнуки (3-е поколение) и так до самого последнего представителя династии.

                  Естественно, не забываем довести покрытие кода тестами до 100%, чтобы удостовериться, что все работает именно так, как и задумывалось. Описание тестов доступно в javadoc.

                  Отдельно стоит упомянуть вот о чём: класс GenealogicalTree является очень небезопасным и его легко заставить работать некорректно, если использовать вне алгоритма генерации родословного древа (вне GenerateGenealogicalTree). Единственно правильное решение в данной ситуации — перенос данного класса в качестве внутреннего приватного класса для GenerateGenealogicalTree. Но это пока не сделано для удобства тестирования алгоритма.
                  Запускаем программу.

                  Логирование результатов в БД


                  Первый запуск показывает, что мы имеем огромное количество данных, которые надо как-то анализировать, чтобы отсеять заведомо неверные результаты. Забегая вперед сообщу, что на 17 сентября 2017 в Wikipedia нашлось 3448 страниц прямых потомков Рюрика. Легче всего подобный объем информации обрабатывать в БД.

                  В первую очередь развернем локальную базу данных, которую назовем genealogicaltree. Со стандартным пользователем root без пароля. Для взаимодействия с БД будем использовать стандартную библиотеку MySQL JDBC Type 4 driver.

                  А дальше создаем новый класс для взаимодействия с БД и метод для сохранения родословного древа в таблице с заданным именем:

                  public class MySqlHelper {
                      private static final String url = "jdbc:mysql://localhost:3306/genealogicaltree" 
                          + "?serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8";
                      private static final String user = "root";
                      private static final String password = "";
                  
                      private static Connection connection;
                      private static Statement statement;
                      private static ResultSet resultSet;
                  
                      public static void saveTree(String tableName, List tree) throws MalformedURLException {
                          try {
                              connection = DriverManager.getConnection(url, user, password);
                              statement = connection.createStatement();
                  
                              String table = createTable(tableName);
                              statement.executeUpdate(table);
                  
                              for (Person person : tree) {
                                  String insert = insertPerson(tableName, person);
                                  statement.executeUpdate(insert);
                              }
                          } catch (SQLException sqlEx) {
                              sqlEx.printStackTrace();
                          } finally {
                              try {
                                  connection.close();
                              } catch (SQLException se) {
                              }
                              try {
                                  statement.close();
                              } catch (SQLException se) {
                              }
                          }
                      }
                  
                      private static String createTable(String tableName) {
                          StringBuilder sql = new StringBuilder();
                          sql.append("CREATE TABLE " + tableName + " (");
                          sql.append("id INTEGER not NULL, ");
                          sql.append("name VARCHAR(255), ");
                          sql.append("url VARCHAR(2048), ");
                          sql.append("children VARCHAR(255), ");
                          sql.append("PRIMARY KEY ( id ))");
                          return sql.toString();
                      }
                  
                      private static String insertPerson(String tableName, Person person) {
                          StringBuilder sql = new StringBuilder();
                          sql.append("INSERT INTO genealogicaltree." + tableName);
                          sql.append("(id, name, url, nameUrl, children, parents, numberGeneration) \n VALUES (");
                          sql.append(person.getId() + ",");
                          sql.append("'" + person.getName() + "',");
                          sql.append("'" + person.getUrl() + "',");
                          sql.append("'" + person.getChildren() + "',");
                          sql.append(");");
                          return sql.toString();
                      }
                  }
                  

                  Дорабатываем сохранение результатов генерации:

                  private static void saveResultAndQuit(GenealogicalTree tree) throws Exception {
                      Timestamp timestamp = new Timestamp(System.currentTimeMillis());
                      String tableName = "generate" + timestamp.getTime();
                      MySqlHelper.saveTree(tableName, tree.getGenealogicalTree());
                  }
                  

                  Разбор первых результатов


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

                  Разложим ошибки по категориям:

                  1. В список детей попал год (например, 1153 со страницы Ярослава Святославовича)
                  2. Нерусскоязычная статья: Аделаида Французская, дочь короля Франции Людовика VII
                  3. Страница «Внебрачный ребенок», появившаяся от того же Людовика VII
                  4. Внешние страницы наподобие этой, которые попали в список, например, от Галерана IV де Бомона
                  5. «Создание страницы». Например, Анна Юрьевна, дочь туровского князя Юрия Ярославича

                  Доработаем метод getChildrenUrl() определения страниц детей, чтобы исключить заведомо ошибочные. Чтобы не попадала 1 категория, нужно убрать те ссылки, текстовое содержимое которых начинается на цифру. Чтобы не попадала 2 категория, нужно убрать те ссылки, класс которых равен extiw. Чтобы не попадали 3-4 категории, необходимо исключить ссылки, родительский тег которых равен sup (уточняющие ссылки). Чтобы убрать из списка 5 категорию необходимо исключить ссылки, класс которых равен new (создание страницы).

                  Для начала доработаем тест testChildrenSize(), добавив в него проверку всех категории кривых ссылок:

                  driver.navigate().to("https://ru.wikipedia.org/wiki/Ярослав_Святославич");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 3);
                  
                  driver.navigate().to("https://ru.wikipedia.org/wiki/Людовик_VII");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 5);
                  
                  driver.navigate().to("https://ru.wikipedia.org/wiki/Галеран_IV_де_Бомон,_граф_де_Мёлан");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 0);
                  
                  driver.navigate().to("https://ru.wikipedia.org/wiki/Юрий_Ярославич_(князь_туровский)");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 5);
                  

                  Тест предсказуемо красный.

                  Теперь доработаем метод getChildrenUrl():

                  public List getChildrenUrl() throws MalformedURLException {
                      waitLoadPage();
                      List childrenLinks = getChildrenLinks();
                      List children = new ArrayList();
                      for (WebElement link : childrenLinks) {
                          if (DriverHelper.isSup(link)) {
                              continue;
                          }
                          Person person = new Person(link.getAttribute("href"));
                          person.setNameUrl(link.getText());
                          if (person.isCorrectNameUrl()) {
                              children.add(person);
                          }
                      }
                      return children;
                  }
                  
                  private List getChildrenLinks() {
                      List childrenLinks = DriverHelper.getElements(driver,
                          By.xpath("//table[contains(@class, 'infobox')]//tr[th[.='Дети:']]" +
                                  "//a[not(@class='new' or @class='extiw')]"));
                      return childrenLinks;
                  }
                  
                  private void waitLoadPage() {
                      this.driver.findElement(By.cssSelector("#firstHeading"));
                  }
                  
                  public final class DriverHelper {
                      /**
                       * Возвращает список элементов без ожидания их появления.
                  * По умолчанию установлено неявное ожидание - это значит, что если на * странице нет заданных элементов, то пустой результат будет выведен не * сразу, а через таймаут, что приведет к потере времени. Чтобы не терять * время создан этот метод, где неявное ожидание обнуляется, а после поиска * восстанавливается. */ public static List getElements(WebDriver driver, By by) { driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS); List result = driver.findElements(by); driver.manage().timeouts().implicitlyWait(DriverHelper.TIMEOUT, TimeUnit.SECONDS); return result; } public static boolean isSup(WebElement element) { String parentTagName = element.findElement(By.xpath(".//..")).getTagName(); return parentTagName.equals("sup"); } } public class Person { private String nameUrl; public boolean isCorrectNameUrl() { Pattern p = Pattern.compile("^[\\D]+.+"); Matcher m = p.matcher(nameUrl); return m.matches(); } }

                  nameUrl — это наименование ссылки персоны, которое она имеет на странице родителя.
                  Перепрогоняем весь комплект тестов — позеленели.

                  У Рюрика очень много потомков, которым посвящены русскоязычные страницы в Wikipedia, поэтому вначале прогоним программу для Михаила Фёдоровича — первого царя из рода Романовых. Запускаем, ждём окончания и анализируем результаты.

                  Романовы


                  Прогон выдал 383 русскоязычные страницы потомков Михаила Федоровича (потомков с генетической точки зрения, а не с точки зрения принадлежности к роду Романовых, определяемой по мужской линии, которая прервалась ещё в 18 веке на Петре II), среди которых королева Дании, король Испании, король Нидерландов, король Швеции, и все потомки королевы Великобритании Елизаветы II, начиная с наследника британского престола принца Чарлза. Но эта информация, конечно, имеет второстепенное значение к исходной задаче.

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

                  1. Две персоны с именем Владимир Александрович и две персоны с именем Фризо Оранско-Нассауский
                  2. Три персоны с необычным именем Дети Алексея Михайловича, две с — Дети Ивана V, один с — Дети Петра I и ещё три- с Дети Михаила Фёдоровича

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

                  Первое, что бросается в глаза — это то, что данные представители династии потомков после себя не оставили, т.к. умерли маленькими детьми (за исключением, конечно же, дочерей Фризо Оранско-Нассауского, которые ещё не успели вырасти)
                  Поэтому можно смело дорабатывать метод getChildrenUrl(), возвращая пустой список, если текущий url имеет якорь. Это необходимо сделать, чтобы персоне с «якорем» в качестве детей не установились дети её родителя, т.е. собственные братья и сестры, как в первом случае ошибочных записей.

                  public List getChildrenUrl() {
                      waitLoadPage();
                      if (DriverHelper.hasAnchor(driver)) {
                          return new ArrayList();
                      }
                      ...
                  }
                  
                  public final class DriverHelper {
                      ...
                      public static boolean hasAnchor(WebDriver driver) throws MalformedURLException {
                          URL url = new URL(driver.getCurrentUrl());
                          return url.getRef() != null;
                      }
                      ...
                  }
                  

                  Добавляем новый тест, проверяющий, что персона с урлом, содержащим якорь, не имеет детей:

                  @Test
                  public void testEmptyChildrenInPersonWithAnchor() throws Exception {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Александрович");
                      PersonPage page = new PersonPage(driver);
                      List children = page.getChildrenUrl();
                      assertTrue(children.size() == 5);
                  
                      driver.navigate().to(
                          "https://ru.wikipedia.org/wiki/Владимир_Александрович#.D0.A1.D0.B5.D0.BC.D1.8C.D1.8F");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 0);
                  }
                  

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

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

                  Вычисление имени по «якорю»


                  В большинстве подобных случаев имя персоны можно вычислить по «якорю»: имя — это текстовое содержимое тэга с идентификатором, равным значению «якоря».
                  Доработаем метод getName():

                  private String getName() throws MalformedURLException {
                      waitLoadPage();
                      String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();
                  
                      if (!DriverHelper.hasAnchor(driver)) {
                          return namePage;
                      }
                  
                      String anchor = DriverHelper.getAnchor(driver);
                      List list = DriverHelper.getElements(driver, By.id(anchor));
                  
                      if (list.size() == 0) {
                          return namePage;
                      }
                  
                      String name = list.get(0).getText().trim();
                      return name.isEmpty() ? namePage : name;
                  }
                  
                  public final class DriverHelper {
                      ...
                      public static String getAnchor(WebDriver driver) throws MalformedURLException {
                          URL url = new URL(driver.getCurrentUrl());
                          return url.getRef();
                      }
                      ...
                  }
                  

                  Если текущий url содержит «якорь», то необходимо проверить существование на странице элемента с идентификатором, равным «якорю». Его может не существовать, как в случае с Натальей — дочерью Петра I. На странице Петра ссылка содержит уже несуществующий «якорь», который не соответствует «якорю» Натальи.

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

                  Добавление наименования ссылки, родителей и номера поколения


                  Осталось проблема: имя Александра, старшего сына Владимира Александровича, определяется как «Семья». Что с этим делать?!

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

                  Конечно, не забываем покрыть тестами весь доработанный код.

                  Результатом доработки является то, что теперь у персоны есть два «имени», одно из которых уж точно «должно быть информативным». Исключением, по понятным причинам, является родоначальник династии, для которого nameUrl может быть любым (присвоим значение "" для определённости).

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

                  Вот так теперь выглядят урлы с якорями:
                  id name children url urlName
                  8 Пелагея [] ссылка Пелагея
                  9 Марфа [] ссылка Марфа
                  10 Софья [] ссылка Софья
                  15 Анна [] ссылка Анна
                  23 Евдокия (младшая) [] ссылка Евдокия
                  26 Феодора [] ссылка Феодора
                  28 Мария [] ссылка Мария
                  29 Феодосия [] ссылка Феодосия
                  36 Дети Петра I [] ссылка Наталья
                  133 Семья [] ссылка Александр
                  360 Брак и дети [] ссылка Луана Оранско-Нассауская

                  Добавление наименования ссылки не прошло бесследно. Прогон программы для Рюрика неожиданно вылетел с исключением о нарушении инструкции insert на Генрихе II (короле Наварры) из-за того, что nameUrl содержит значение с апострофом — «Генрих II д'Альбре». Доработаем методы setName и setNameUrl в классе Person, сохраняя заданное значение без апострофов.

                  Напомню, что в Wikipedia нашлось около трех с половиной тысяч потомков Рюрика. Если выводить эту информацию в виде таблицы, то получится очень большая страница, которую устанешь прокручивать. Было бы интересно не только видеть всю табличку, но иметь ещё возможность выделить связь заданного представителя с родоначальником династии (т.е. выделить всех предков заданного представителя вплоть до родоначальника). А также знать, каким по счету поколением он является.

                  Чтобы легче было реализовывать эту новую функциональность, добавим в класс Person поля для номера поколения и списка родителей (чтобы легче было построить возрастающую связь):

                  private List parents = new ArrayList();
                  private int numberGeneration = 0;
                  
                  public void setParent(int parent) {
                      parents.add(parent);
                  }
                  
                  public void setNumberGeneration(int numberGeneration) {
                      if (this.numberGeneration == 0) {
                          this.numberGeneration = numberGeneration;
                      }
                  }
                  

                  Родитель в большинстве случаев будет один, но возможны ситуации, когда оба родителя потомки родоначальника династии от разных ветвей и тогда их будет двое. Выше даже был приведен пример ошибки, когда сразу трое «являются» родителями одного и того же человека (первый, второй, третий, их «общий» ребенок — и все Рюриковичи). Конечно, физиологически это невозможно, получается, как в анектоде, но, к сожалению, автоматически определить, кто их них «лишний» невозможно, поэтому придется сохранять всех.

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

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

                  Устанавливать номер поколения и идентификатор родителя будем в методе setChildren(List children) класса GenerateGenealogicalTree:

                  public void setChildren(List children) {
                      if (isCurrentPersonDeleted) {
                          throw new IllegalArgumentException(
                              "Нельзя установить детей удаленной персоне. Текущая персона уже другая");
                      }
                  
                      Person currentPerson = allPersons.get(indexCurrentUnvisitedPerson);
                      int numberGeneration = currentPerson.getNumberGeneration();
                      numberGeneration++;
                      int idParent = currentPerson.getId();
                      for (Person person : children) {
                          int index = allPersons.indexOf(person);
                          int id;
                          if (index >= 0) { // Непервый родитель, номер поколения не трогаем
                              allPersons.get(index).setParent(idParent);
                              id = allPersons.get(index).getId();
                          } else { // Первый родитель
                              person.setNumberGeneration(numberGeneration);
                              person.setParent(idParent);
                              allPersons.add(person);
                              id = person.getId();
                          }
                          currentPerson.setChild(id);
                      }
                  }
                  

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

                  Итоговые результаты


                  Пришло время сформировать несколько родословных деревьев и посмотреть результаты:
                  Адам — первый человек на Земле
                  Чингисхан — величайший завоеватель в истории
                  Романовы
                  Рюриковичи (долго открывается — 3452 персоны).

                  Пояснение к страницам:

                  а) при нажатии на имя открывается страница персоны на Wikipedia
                  б) при нажатии на № открывается страница связи заданной персоны с родоначальником. Например, вот страница, доказывающая, что королева Великобритании Елизавета II является потомком Рюрика в 29 поколении.
                  в) при нажатии на идентификаторы в полях родителей и детей страница прокручивается на строку с этой персоной.

                  Результаты показывают, что, например, последний российский император Николай II был потомком Рюрика в 28 поколении. Более того, все российские императоры, начиная с Петра III и Екатерины II были потомками Рюрика от разных ветвей.

                  Исходный код проекта
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/338190/


                  Метки:  

                  [Из песочницы] Генерация родословного дерева на основе данных Wikipedia

                  Понедельник, 18 Сентября 2017 г. 17:41 + в цитатник
                  fonkost сегодня в 17:41 Разработка

                  Генерация родословного дерева на основе данных Wikipedia

                  В этой статье я хочу показать, как с помощью фреймворка Selenium Webdriver можно, исходя из данных Wikipedia, составить генеалогическое древо заданной персоны (например, легендарного основателя первой династии русских правителей Рюрика).

                  В статье будет рассказано, как определить имя персоны, вычислить ссылки на страницы детей персоны, а также будет построен алгоритм генерации генеалогического древа.
                  Я буду использовать Java, Selenium Webdriver и Chrome. Chrome, потому что он быстрее остальных браузеров, а так как переход по урлу — самое затратное по времени операция в программе, то выбор браузера заметнее всего сказывается на времени. Можно вообще отказаться от браузера и использовать, скажем PhantomJs, но его сложнее дебажить. Поэтому я остановился на Chrome.

                  В первую очередь создадим тест, проверяющий, что браузер корректно запустился и что при переходе по урлу https://ru.wikipedia.org/wiki/Рюрик открывается страница с заголовком «Рюрик — Википедия»:

                  @BeforeClass
                  public static void Start() {
                      driver = DriverHelper.getDriver();
                  }
                  
                  @Test
                  public void testGetDriver() {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/%D0%A0%D1%8E%D1%80%D0%B8%D0%BA");
                      assertTrue(driver.getTitle().equals("Рюрик — Википедия"));
                  }
                  
                  @AfterClass
                  public static void Stop() {
                      driver.quit();
                  }
                  

                  Создаем класс DriverHelper со статичным методом getDriver(), чтобы проект скомпилился и тест прошёл успешно:

                  public final class DriverHelper{
                      private static final int TIMEOUT = 30;
                  
                      public static WebDriver getDriver() {
                          WebDriver driver = new ChromeDriver();
                          driver.manage().window().maximize();
                          driver.manage().timeouts().implicitlyWait(TIMEOUT, TimeUnit.SECONDS);
                          return driver;
                      }
                  }
                  

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

                  Создание класса Person


                  Перейдем к созданию класса Person, в котором будет храниться информация о персоне, а также к созданию класса страницы персоны на Wikipedia PersonPage.

                  В классе Person пока будут только два поля – name и url. В качестве name будем использовать полное имя человека, без разделения на Фамилию, Имя, Отчество, т.к. большинство представителей династии не будут иметь фамилии, зато будут иметь прозвища, титулы и порядковые имена.

                  Url будет указывать на страницу Wikipedia, посвященную данному человеку.
                  Создаем тест, проверяющий формирование персоны:

                  @Test
                  public void testGetPerson() throws Exception {
                      PersonPage page = new PersonPage(driver);
                      Person person = page.getPerson("https://ru.wikipedia.org/wiki/Владимир_Александрович");
                      assertTrue(person.getName().equals("Владимир Александрович"));
                      assertTrue(person.getUrl().equals(
                          "https://ru.wikipedia.org/wiki/
                          %D0%92%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80_
                          %D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B2%D0%B8%D1%87"));
                  }
                  

                  testGetPerson() не компилится. Нам нужно разработать страницу PersonPage, чтобы определить имя и страницу человека. Url мы определяем по url текущей страницы, а имя – по текстовому содержимому тэга с идентификатором firstHeading. Метод getPerson():

                  public Person getPerson(String url) throws MalformedURLException {
                      driver.navigate().to(url);
                  
                      String name = getName();
                  
                      Person person = new Person(driver.getCurrentUrl());
                      person.setName(name);
                      return person;
                  }
                  
                  private String getName() throws MalformedURLException {
                      String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();
                      return namePage;
                  }
                  

                  Перепрогоняем тесты – позеленели.
                  Отдельно стоит упомянуть, почему переопределяется url, хотя он передается в качестве параметра: дело в том, что в Wikipedia одной персоне может быть посвящено несколько страниц, которые редиректятся на одну. В результате, если использовать исходные урлы, то возможно возникновение дубликатов, когда существует несколько персон с «разными» урлами, которые фактически являются одной персоной. Поэтому в качестве урла используется тот урл, на который редиректятся все остальные.

                  Например: страница https://ru.wikipedia.org/wiki/Ярослав_Мудрый перенаправляется на https://ru.wikipedia.org/wiki/Ярослав_Владимирович_Мудрый, а страница https://ru.wikipedia.org/wiki/Андрей_Боголюбский — на https://ru.wikipedia.org/wiki/Андрей_Юрьевич_Боголюбский

                  Определение детей персоны


                  Попробуем определить детей персоны, которые имеют свои страницы в Wikipedia.
                  Для начала напишем тест для определения детей Рюрика (точнее одного — Игоря):

                  @Test
                  public void testGetChildrenUrl() throws Exception {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Рюрик");
                      PersonPage page = new PersonPage(driver);
                      List children = page.getChildrenUrl();
                      assertTrue(children.size() == 1);
                      Person person = children.get(0);
                      assertTrue(person.getUrl().equals("https://ru.wikipedia.org/wiki/
                          %D0%98%D0%B3%D0%BE%D1%80%D1%8C_
                          %D0%A0%D1%8E%D1%80%D0%B8%D0%BA%D0%BE%D0%B2%D0%B8%D1%87"));
                  }
                  

                  Чтобы тест прошел успешно, нужно добавить на страницу PersonPage метод определения урлов детей человека:

                  public List getChildrenUrl() throws MalformedURLException {
                      List childrenLinks = driver.findElements(
                          By.xpath("//table[contains(@class, 'infobox')]//tr[th[.='Дети:']]//a"));
                      List children = new ArrayList();
                      for (WebElement link : childrenLinks) {
                          Person person = new Person(link.getAttribute("href"));
                          children.add(person);
                      }
                      return children;
                  }
                  

                  Пока что игнорируем то, что детям могут быть не посвящены страницы в Wikipedia и поэтому они не имеют ссылок на свои страницы. Например, как в случае с детьми Владимира Ярославича (князя галицкого). Также игнорируем то, что информация о потомках может располагаться в основной области, как, например, на странице Марии Добронеги или на странице Святослава Всеволодовича (князя трубчевского).

                  Добавляем тесты, проверяющие корректность определения детей для персоны.
                  Как было оговорено выше, пока предполагаем, что Владимир Ярославич (князь галицкий) и Мария Добронега детей не имели, а Владимир Святославич имел 16 детей, хотя Wikipedia утверждает, что у него было ещё 5 неизвестных по имени дочерей.

                  @Test
                  public void testChildrenSize() throws Exception {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Рюрик");
                      PersonPage page = new PersonPage(driver);
                      List children = page.getChildrenUrl();
                      assertTrue(children.size() == 1);
                  
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Святославич");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 16);
                  
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Ярославич_(князь_галицкий)");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 0);
                  
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Мария_Добронега");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 0);
                  }
                  

                  В класс Person добавим поля для уникального идентификатора персоны (int id) и списка детей персоны (List children), в котором будут храниться идентификаторы детей.
                  Разработаем метод добавления идентификатора ребенка в список детей персоны. Ребенок может быть добавлен в список, только если его там ещё нет.

                  public void setChild(int childId) {
                      if (!children.contains(childId)) {
                          children.add(childId);
                      }
                  }
                  

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

                  Алгоритм поиска потомков


                  Теперь перейдем к самому интересному – разработке алгоритма поиска потомков у заданной персоны. Создадим класс GenerateGenealogicalTree с методом main.

                  Как уже упоминалось, самое затратное по времени — переход по урлу, поэтому нужно минимизировать количество этих переходов. Для этого создадим список персон, в котором будет хранится всё родословное древо. В этом списке запомним индекс текущей персоны — той, на странице которой находимся на данный момент. Все персоны с меньшим индексом считаются «посещенными», а все с бОльшим индексом (+ текущая) — «непосещенными». После того, как был осуществлен переход на страницу текущей персоны и вычислены её основные данные, индекс увеличивается на единицу. Тем самым текущая персона попадает в разряд «посещенных». И остаётся только обойти оставшихся «непосещенных» персон. В каждый момент времени известны те персоны, страницы которых уже были просмотрены.

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

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

                  Алгоритм такой:

                  1. Создается основатель династии на основе заданного урла
                  2. Создается родословное древо на основе основателя династии
                  3. В цикле до тех пор, пока есть «непосещенные» персоны
                  4. Вычисляется персона на основе текущего урла родословного древа. Эта персона устанавливается в качестве текущей.
                  5. Если текущая персона не является дубликатом, то вычисляется и устанавливается список её детей. Все дети добавляются в список.
                  6. Если текущая персона уже встречалась среди «посещенных» персон, то она удаляется.
                  7. Происходит переход к следующей «непосещенной» персоне, которая принимается за «текущую».

                  Код алгоритма:

                  public final class GenerateGenealogicalTree {
                      public static void main(String[] args) throws Exception {
                          String url = getUrl(args);
                          GenealogicalTree tree = getGenealogicalTreeByUrl(url);
                          saveResultAndQuit(tree);
                      }
                  
                      public static GenealogicalTree getGenealogicalTreeByUrl(String url) throws MalformedURLException {
                          WebDriver driver = DriverHelper.getDriver();
                          Person person = new Person(url);
                          GenealogicalTree tree = new GenealogicalTree(person);
                          PersonPage page = new PersonPage(driver);
                          while (tree.hasUnvisitingPerson()) {
                              String currentUrl = tree.getCurrentUrl();
                              Person currentPerson = page.getPerson(currentUrl);
                              tree.setCurrentPerson(currentPerson);
                              if (!tree.isCurrentPersonDeleted()) {
                                  List children = page.getChildrenUrl();
                                  tree.setChildren(children);
                               }
                               tree.updatingCurrentPerson();
                          }
                          driver.quit();
                          return tree;
                      }
                  }
                  

                  Класс GenealogicalTree имеет три поля: List allPersons — список всех представителей родословного древа, int indexCurrentUnvisitedPerson — индекс текущей персоны в списке allPersons, а также boolean isCurrentPersonDeleted — признак того, удалена ли «текущая» персона (т.е. является ли она дубликатом).

                  public final class GenealogicalTree {
                      private List allPersons;
                      private int indexCurrentUnvisitedPerson;
                      private boolean isCurrentPersonDeleted;
                  }
                  

                  Инициализация происходит на основе «родоначальника» династии — первой персоне, потомков которой мы ищем:

                  public GenealogicalTree(Person person) {
                      if (person == null) {
                          throw new IllegalArgumentException("Укажите непустого основателя династии");
                      }
                      allPersons = new ArrayList();
                      allPersons.add(person);
                      indexCurrentUnvisitedPerson = 0;
                      isCurrentPersonDeleted = false;
                  }
                  

                  В этот момент родословное древо состоит из одной текущей «непосещенной» персоны. «Посещенных» персон нет.

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

                  public boolean hasUnvisitingPerson() {
                      return indexCurrentUnvisitedPerson < allPersons.size();
                  }
                  

                  В роли url-а родословного древа выступает url текущей персоны:

                  public String getCurrentUrl() {
                      return allPersons.get(indexCurrentUnvisitedPerson).getUrl();
                  }
                  

                  Метод setCurrentPerson заменяет текущую персону на заданную.

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

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

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

                  public void setCurrentPerson(Person currentPerson) {
                      int indexDuplicate = allPersons.indexOf(currentPerson);
                      if ((0 <= indexDuplicate) && (indexDuplicate < indexCurrentUnvisitedPerson)) {
                          removePerson(indexDuplicate);
                      } else {
                          allPersons.get(indexCurrentUnvisitedPerson).copyMainData(currentPerson);
                          isCurrentPersonDeleted = false;
                      }
                  }
                  

                  Чтобы корректно отработал метод indexOf(Object object) необходимо в классе Person переопределить методы equals(Object object) и hashCode():

                  @Override
                  public boolean equals(Object object) {
                      if ((object == null) || (!(object instanceof Person))) {
                          return false;
                      }
                  
                      Person person = (Person) object;
                      return this.url.equals(person.url);
                  }
                  
                  @Override
                  public int hashCode() {
                      return this.url.hashCode();
                  }
                  

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

                  1. Отцовство достоверно неизвестно. Как, например, в случае со Святополком Окаянным, отцом которого является либо Ярополк Святославич, либо Владимир Святославич
                  2. Оба родителя – потомки Рюрика от разных ветвей. Пример: Глеб Всеславич — потомок Рюрика в 8-м поколении был женат на Анастасии Ярополковне — тоже потомком Рюрика (они четвероюродные брат с сестрой).
                  3. Ошибки на странице: вызывает сомнение, что Всеволод Мстиславич имел сына Володаря Глебовича, родителями которого записаны другие люди, тоже принадлежащие династии Рюриковичей. Вероятнее всего, это просто опечатка в Wikipedia

                  Если эти дубликаты не устранить, то они породят новые повторения, т.к. по всем потомкам дубликатов обход будет производится два раза, а то и три (в случае с Володарем Глебовичем).

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

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

                  После удаления текущая персона помечается удаленной.

                  private void removePerson(int indexDuplicate) {
                      int idRemovedPerson = allPersons.get(indexCurrentUnvisitedPerson).getId();
                      int idDuplicate = allPersons.get(indexDuplicate).getId();
                      for (int i = 0; i < indexCurrentUnvisitedPerson; i++) {
                          Person person = allPersons.get(i);
                          person.replaceChild(idRemovedPerson, idDuplicate);
                      }
                      allPersons.remove(indexCurrentUnvisitedPerson);
                      isCurrentPersonDeleted = true;
                  }
                  

                  В классе Person добавляем метод замены «ребенка»:

                  public void replaceChild(int oldId, int newId) {
                      if (oldId == newId) {
                          return;
                      }
                      if (!children.contains(oldId)) {
                          return;
                      }
                      children.remove((Object) oldId);
                      setChild(newId);
                  }
                  

                  Рассмотрим добавление детей текущей персоне.

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

                  Если не удалена, то пробегаемся по списку, переданному в качестве параметра. Если ребенок уже встречается в родословном древе, то в список детей добавляется идентификатор найденного дубликата. Если ребенок не встречается в родословном древе, то в список детей добавляется его идентификатор, кроме того, сам ребенок добавляется в конец родословного древа, в список «непосещенных» персон.

                  Таким образом через метод setChildren() происходит «наполнение» списка.

                  public void setChildren(List children) {
                      if (isCurrentPersonDeleted) {
                          throw new IllegalArgumentException(
                              "Нельзя установить детей удаленной персоне. Текущая персона уже другая");
                      }
                  
                      for (Person person : children) {
                          int index = allPersons.indexOf(person);
                          int id;
                          if (index >= 0) {
                              id = allPersons.get(index).getId();
                          } else {
                              allPersons.add(person);
                              id = person.getId();
                          }
                          allPersons.get(indexCurrentUnvisitedPerson).setChild(id);
                      }
                  }
                  

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

                  public void updatingCurrentPerson() {
                      if (isCurrentPersonDeleted) {
                          isCurrentPersonDeleted = false;
                      } else {
                          indexCurrentUnvisitedPerson++;
                      }
                  }
                  

                  Обход осуществляется по поколениям: вначале основатель династии (0-е поколение), затем все его дети (1-е поколение) от старшего к младшему (подразумеваем, что именно в таком порядке располагаются урлы в Wikipedia), затем внуки (2-е поколение) (дети старшего сына по старшинству, затем — 2-го сына, и так до самого младшего), правнуки (3-е поколение) и так до самого последнего представителя династии.

                  Естественно, не забываем довести покрытие кода тестами до 100%, чтобы удостовериться, что все работает именно так, как и задумывалось. Описание тестов доступно в javadoc.

                  Отдельно стоит упомянуть вот о чём: класс GenealogicalTree является очень небезопасным и его легко заставить работать некорректно, если использовать вне алгоритма генерации родословного древа (вне GenerateGenealogicalTree). Единственно правильное решение в данной ситуации — перенос данного класса в качестве внутреннего приватного класса для GenerateGenealogicalTree. Но это пока не сделано для удобства тестирования алгоритма.
                  Запускаем программу.

                  Логирование результатов в БД


                  Первый запуск показывает, что мы имеем огромное количество данных, которые надо как-то анализировать, чтобы отсеять заведомо неверные результаты. Забегая вперед сообщу, что на 17 сентября 2017 в Wikipedia нашлось 3448 страниц прямых потомков Рюрика. Легче всего подобный объем информации обрабатывать в БД.

                  В первую очередь развернем локальную базу данных, которую назовем genealogicaltree. Со стандартным пользователем root без пароля. Для взаимодействия с БД будем использовать стандартную библиотеку MySQL JDBC Type 4 driver.

                  А дальше создаем новый класс для взаимодействия с БД и метод для сохранения родословного древа в таблице с заданным именем:

                  public class MySqlHelper {
                      private static final String url = "jdbc:mysql://localhost:3306/genealogicaltree" 
                          + "?serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8";
                      private static final String user = "root";
                      private static final String password = "";
                  
                      private static Connection connection;
                      private static Statement statement;
                      private static ResultSet resultSet;
                  
                      public static void saveTree(String tableName, List tree) throws MalformedURLException {
                          try {
                              connection = DriverManager.getConnection(url, user, password);
                              statement = connection.createStatement();
                  
                              String table = createTable(tableName);
                              statement.executeUpdate(table);
                  
                              for (Person person : tree) {
                                  String insert = insertPerson(tableName, person);
                                  statement.executeUpdate(insert);
                              }
                          } catch (SQLException sqlEx) {
                              sqlEx.printStackTrace();
                          } finally {
                              try {
                                  connection.close();
                              } catch (SQLException se) {
                              }
                              try {
                                  statement.close();
                              } catch (SQLException se) {
                              }
                          }
                      }
                  
                      private static String createTable(String tableName) {
                          StringBuilder sql = new StringBuilder();
                          sql.append("CREATE TABLE " + tableName + " (");
                          sql.append("id INTEGER not NULL, ");
                          sql.append("name VARCHAR(255), ");
                          sql.append("url VARCHAR(2048), ");
                          sql.append("children VARCHAR(255), ");
                          sql.append("PRIMARY KEY ( id ))");
                          return sql.toString();
                      }
                  
                      private static String insertPerson(String tableName, Person person) {
                          StringBuilder sql = new StringBuilder();
                          sql.append("INSERT INTO genealogicaltree." + tableName);
                          sql.append("(id, name, url, nameUrl, children, parents, numberGeneration) \n VALUES (");
                          sql.append(person.getId() + ",");
                          sql.append("'" + person.getName() + "',");
                          sql.append("'" + person.getUrl() + "',");
                          sql.append("'" + person.getChildren() + "',");
                          sql.append(");");
                          return sql.toString();
                      }
                  }
                  

                  Дорабатываем сохранение результатов генерации:

                  private static void saveResultAndQuit(GenealogicalTree tree) throws Exception {
                      Timestamp timestamp = new Timestamp(System.currentTimeMillis());
                      String tableName = "generate" + timestamp.getTime();
                      MySqlHelper.saveTree(tableName, tree.getGenealogicalTree());
                  }
                  

                  Разбор первых результатов


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

                  Разложим ошибки по категориям:

                  1. В список детей попал год (например, 1153 со страницы Ярослава Святославовича)
                  2. Нерусскоязычная статья: Аделаида Французская, дочь короля Франции Людовика VII
                  3. Страница «Внебрачный ребенок», появившаяся от того же Людовика VII
                  4. Внешние страницы наподобие этой, которые попали в список, например, от Галерана IV де Бомона
                  5. «Создание страницы». Например, Анна Юрьевна, дочь туровского князя Юрия Ярославича

                  Доработаем метод getChildrenUrl() определения страниц детей, чтобы исключить заведомо ошибочные. Чтобы не попадала 1 категория, нужно убрать те ссылки, текстовое содержимое которых начинается на цифру. Чтобы не попадала 2 категория, нужно убрать те ссылки, класс которых равен extiw. Чтобы не попадали 3-4 категории, необходимо исключить ссылки, родительский тег которых равен sup (уточняющие ссылки). Чтобы убрать из списка 5 категорию необходимо исключить ссылки, класс которых равен new (создание страницы).

                  Для начала доработаем тест testChildrenSize(), добавив в него проверку всех категории кривых ссылок:

                  driver.navigate().to("https://ru.wikipedia.org/wiki/Ярослав_Святославич");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 3);
                  
                  driver.navigate().to("https://ru.wikipedia.org/wiki/Людовик_VII");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 5);
                  
                  driver.navigate().to("https://ru.wikipedia.org/wiki/Галеран_IV_де_Бомон,_граф_де_Мёлан");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 0);
                  
                  driver.navigate().to("https://ru.wikipedia.org/wiki/Юрий_Ярославич_(князь_туровский)");
                  children = page.getChildrenUrl();
                  assertTrue(children.size() == 5);
                  

                  Тест предсказуемо красный.

                  Теперь доработаем метод getChildrenUrl():

                  public List getChildrenUrl() throws MalformedURLException {
                      waitLoadPage();
                      List childrenLinks = getChildrenLinks();
                      List children = new ArrayList();
                      for (WebElement link : childrenLinks) {
                          if (DriverHelper.isSup(link)) {
                              continue;
                          }
                          Person person = new Person(link.getAttribute("href"));
                          person.setNameUrl(link.getText());
                          if (person.isCorrectNameUrl()) {
                              children.add(person);
                          }
                      }
                      return children;
                  }
                  
                  private List getChildrenLinks() {
                      List childrenLinks = DriverHelper.getElements(driver,
                          By.xpath("//table[contains(@class, 'infobox')]//tr[th[.='Дети:']]" +
                                  "//a[not(@class='new' or @class='extiw')]"));
                      return childrenLinks;
                  }
                  
                  private void waitLoadPage() {
                      this.driver.findElement(By.cssSelector("#firstHeading"));
                  }
                  
                  public final class DriverHelper {
                      /**
                       * Возвращает список элементов без ожидания их появления.
                  * По умолчанию установлено неявное ожидание - это значит, что если на * странице нет заданных элементов, то пустой результат будет выведен не * сразу, а через таймаут, что приведет к потере времени. Чтобы не терять * время создан этот метод, где неявное ожидание обнуляется, а после поиска * восстанавливается. */ public static List getElements(WebDriver driver, By by) { driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS); List result = driver.findElements(by); driver.manage().timeouts().implicitlyWait(DriverHelper.TIMEOUT, TimeUnit.SECONDS); return result; } public static boolean isSup(WebElement element) { String parentTagName = element.findElement(By.xpath(".//..")).getTagName(); return parentTagName.equals("sup"); } } public class Person { private String nameUrl; public boolean isCorrectNameUrl() { Pattern p = Pattern.compile("^[\\D]+.+"); Matcher m = p.matcher(nameUrl); return m.matches(); } }

                  nameUrl — это наименование ссылки персоны, которое она имеет на странице родителя.
                  Перепрогоняем весь комплект тестов — позеленели.

                  У Рюрика очень много потомков, которым посвящены русскоязычные страницы в Wikipedia, поэтому вначале прогоним программу для Михаила Фёдоровича — первого царя из рода Романовых. Запускаем, ждём окончания и анализируем результаты.

                  Романовы


                  Прогон выдал 383 русскоязычные страницы потомков Михаила Федоровича (потомков с генетической точки зрения, а не с точки зрения принадлежности к роду Романовых, определяемой по мужской линии, которая прервалась ещё в 18 веке на Петре II), среди которых королева Дании, король Испании, король Нидерландов, король Швеции, и все потомки королевы Великобритании Елизаветы II, начиная с наследника британского престола принца Чарлза. Но эта информация, конечно, имеет второстепенное значение к исходной задаче.

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

                  1. Две персоны с именем Владимир Александрович и две персоны с именем Фризо Оранско-Нассауский
                  2. Три персоны с необычным именем Дети Алексея Михайловича, две с — Дети Ивана V, один с — Дети Петра I и ещё три- с Дети Михаила Фёдоровича

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

                  Первое, что бросается в глаза — это то, что данные представители династии потомков после себя не оставили, т.к. умерли маленькими детьми (за исключением, конечно же, дочерей Фризо Оранско-Нассауского, которые ещё не успели вырасти)
                  Поэтому можно смело дорабатывать метод getChildrenUrl(), возвращая пустой список, если текущий url имеет якорь. Это необходимо сделать, чтобы персоне с «якорем» в качестве детей не установились дети её родителя, т.е. собственные братья и сестры, как в первом случае ошибочных записей.

                  public List getChildrenUrl() {
                      waitLoadPage();
                      if (DriverHelper.hasAnchor(driver)) {
                          return new ArrayList();
                      }
                      ...
                  }
                  
                  public final class DriverHelper {
                      ...
                      public static boolean hasAnchor(WebDriver driver) throws MalformedURLException {
                          URL url = new URL(driver.getCurrentUrl());
                          return url.getRef() != null;
                      }
                      ...
                  }
                  

                  Добавляем новый тест, проверяющий, что персона с урлом, содержащим якорь, не имеет детей:

                  @Test
                  public void testEmptyChildrenInPersonWithAnchor() throws Exception {
                      driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Александрович");
                      PersonPage page = new PersonPage(driver);
                      List children = page.getChildrenUrl();
                      assertTrue(children.size() == 5);
                  
                      driver.navigate().to(
                          "https://ru.wikipedia.org/wiki/Владимир_Александрович#.D0.A1.D0.B5.D0.BC.D1.8C.D1.8F");
                      children = page.getChildrenUrl();
                      assertTrue(children.size() == 0);
                  }
                  

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

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

                  Вычисление имени по «якорю»


                  В большинстве подобных случаев имя персоны можно вычислить по «якорю»: имя — это текстовое содержимое тэга с идентификатором, равным значению «якоря».
                  Доработаем метод getName():

                  private String getName() throws MalformedURLException {
                      waitLoadPage();
                      String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();
                  
                      if (!DriverHelper.hasAnchor(driver)) {
                          return namePage;
                      }
                  
                      String anchor = DriverHelper.getAnchor(driver);
                      List list = DriverHelper.getElements(driver, By.id(anchor));
                  
                      if (list.size() == 0) {
                          return namePage;
                      }
                  
                      String name = list.get(0).getText().trim();
                      return name.isEmpty() ? namePage : name;
                  }
                  
                  public final class DriverHelper {
                      ...
                      public static String getAnchor(WebDriver driver) throws MalformedURLException {
                          URL url = new URL(driver.getCurrentUrl());
                          return url.getRef();
                      }
                      ...
                  }
                  

                  Если текущий url содержит «якорь», то необходимо проверить существование на странице элемента с идентификатором, равным «якорю». Его может не существовать, как в случае с Натальей — дочерью Петра I. На странице Петра ссылка содержит уже несуществующий «якорь», который не соответствует «якорю» Натальи.

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

                  Добавление наименования ссылки, родителей и номера поколения


                  Осталось проблема: имя Александра, старшего сына Владимира Александровича, определяется как «Семья». Что с этим делать?!

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

                  Конечно, не забываем покрыть тестами весь доработанный код.

                  Результатом доработки является то, что теперь у персоны есть два «имени», одно из которых уж точно «должно быть информативным». Исключением, по понятным причинам, является родоначальник династии, для которого nameUrl может быть любым (присвоим значение "" для определённости).

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

                  Вот так теперь выглядят урлы с якорями:
                  id name children url urlName
                  8 Пелагея [] ссылка Пелагея
                  9 Марфа [] ссылка Марфа
                  10 Софья [] ссылка Софья
                  15 Анна [] ссылка Анна
                  23 Евдокия (младшая) [] ссылка Евдокия
                  26 Феодора [] ссылка Феодора
                  28 Мария [] ссылка Мария
                  29 Феодосия [] ссылка Феодосия
                  36 Дети Петра I [] ссылка Наталья
                  133 Семья [] ссылка Александр
                  360 Брак и дети [] ссылка Луана Оранско-Нассауская

                  Добавление наименования ссылки не прошло бесследно. Прогон программы для Рюрика неожиданно вылетел с исключением о нарушении инструкции insert на Генрихе II (короле Наварры) из-за того, что nameUrl содержит значение с апострофом — «Генрих II д'Альбре». Доработаем методы setName и setNameUrl в классе Person, сохраняя заданное значение без апострофов.

                  Напомню, что в Wikipedia нашлось около трех с половиной тысяч потомков Рюрика. Если выводить эту информацию в виде таблицы, то получится очень большая страница, которую устанешь прокручивать. Было бы интересно не только видеть всю табличку, но иметь ещё возможность выделить связь заданного представителя с родоначальником династии (т.е. выделить всех предков заданного представителя вплоть до родоначальника). А также знать, каким по счету поколением он является.

                  Чтобы легче было реализовывать эту новую функциональность, добавим в класс Person поля для номера поколения и списка родителей (чтобы легче было построить возрастающую связь):

                  private List parents = new ArrayList();
                  private int numberGeneration = 0;
                  
                  public void setParent(int parent) {
                      parents.add(parent);
                  }
                  
                  public void setNumberGeneration(int numberGeneration) {
                      if (this.numberGeneration == 0) {
                          this.numberGeneration = numberGeneration;
                      }
                  }
                  

                  Родитель в большинстве случаев будет один, но возможны ситуации, когда оба родителя потомки родоначальника династии от разных ветвей и тогда их будет двое. Выше даже был приведен пример ошибки, когда сразу трое «являются» родителями одного и того же человека (первый, второй, третий, их «общий» ребенок — и все Рюриковичи). Конечно, физиологически это невозможно, получается, как в анектоде, но, к сожалению, автоматически определить, кто их них «лишний» невозможно, поэтому придется сохранять всех.

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

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

                  Устанавливать номер поколения и идентификатор родителя будем в методе setChildren(List children) класса GenerateGenealogicalTree:

                  public void setChildren(List children) {
                      if (isCurrentPersonDeleted) {
                          throw new IllegalArgumentException(
                              "Нельзя установить детей удаленной персоне. Текущая персона уже другая");
                      }
                  
                      Person currentPerson = allPersons.get(indexCurrentUnvisitedPerson);
                      int numberGeneration = currentPerson.getNumberGeneration();
                      numberGeneration++;
                      int idParent = currentPerson.getId();
                      for (Person person : children) {
                          int index = allPersons.indexOf(person);
                          int id;
                          if (index >= 0) { // Непервый родитель, номер поколения не трогаем
                              allPersons.get(index).setParent(idParent);
                              id = allPersons.get(index).getId();
                          } else { // Первый родитель
                              person.setNumberGeneration(numberGeneration);
                              person.setParent(idParent);
                              allPersons.add(person);
                              id = person.getId();
                          }
                          currentPerson.setChild(id);
                      }
                  }
                  

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

                  Итоговые результаты


                  Пришло время сформировать несколько родословных деревьев и посмотреть результаты:
                  Адам — первый человек на Земле
                  Чингисхан — величайший завоеватель в истории
                  Романовы
                  Рюриковичи (долго открывается — 3452 персоны).

                  Пояснение к страницам:

                  а) при нажатии на имя открывается страница персоны на Wikipedia
                  б) при нажатии на № открывается страница связи заданной персоны с родоначальником. Например, вот страница, доказывающая, что королева Великобритании Елизавета II является потомком Рюрика в 29 поколении.
                  в) при нажатии на идентификаторы в полях родителей и детей страница прокручивается на строку с этой персоной.

                  Результаты показывают, что, например, последний российский император Николай II был потомком Рюрика в 28 поколении. Более того, все российские императоры, начиная с Петра III и Екатерины II были потомками Рюрика от разных ветвей.

                  Исходный код проекта
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/338190/


                  Метки:  

                  Контроль опасных кассовых операций: интеграция видеонаблюдения с 1С

                  Понедельник, 18 Сентября 2017 г. 17:20 + в цитатник

                  Метки:  

                  Контроль опасных кассовых операций: интеграция видеонаблюдения с 1С

                  Понедельник, 18 Сентября 2017 г. 17:20 + в цитатник

                  Метки:  

                  Социнжиниринг в военной пропаганде

                  Понедельник, 18 Сентября 2017 г. 16:07 + в цитатник
                  Milfgard сегодня в 16:07 Разработка

                  Социнжиниринг в военной пропаганде



                    Во время Второй мировой англичане достали личные дела командиров немецких подлодок. Вроде бы не очень важная информация для военных целей – лодки-то уже вышли на задания, что им сделаешь. Но к делу подключились тёртые специалисты по пропаганде. У союзников были ежедневные радиопередачи, и вот пример:
                    — Мы обращаемся к вам, командир подводной лодки «U-507» капитан-лейтенант Блюм. С вашей стороны было очень опрометчиво оставить свою жену в Бремене, где в настоящее время проводит свой отпуск ваш друг капитан-лейтенант Гроссберг. Их уже, минимум, трижды видели вместе в ресторане, а ваша соседка фрау Моглер утверждает: ваши дети отправлены к матери в Мекленбург…
                    Цитата из «Операция «Гроза» — И. Бунич

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

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

                    Устные и радиоканалы


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

                    Смысл в том, чтобы передавать факты максимально объективно и без своих комментариев. Через несколько недель вещания противник привыкает им доверять и их слушать – благо очень легко можно проверить как местную, так и глобальную информацию. Собственно, немцы придали методу отточенность – они вещали то, что средний солдат противника ещё не знал, и потому с охотой доверял каналу. Дальше в канал при необходимости «замешивались» нужные факты, либо же он просто набирал популярность, и можно было использовать другие, более умные методы.

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

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

                    Обратите внимание – речь не идёт об искажении фактов. Просто правильная подборка и правильная подача. Чуть больше внимания тому, чуть меньше внимания этому – и вот уже противник осознал, что требовалось.

                    Но вернёмся к вещанию радио и звука. Третья стадия атаки (после фактов и аналитики) – ультиматум, когда противник находится в опасной ситуации. Может заставить врага сдаться в заведомо проигрышной ситуации — что позволит не трать боеприпас и личный состав на операцию. Главное – чтобы противник чётко понимал, что его ситуация действительно безвыходная, иначе могут возникнуть сомнения, и он начнёт сопротивляться. Информированность тут была уже оружием.

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

                    Что мне особенно импонирует, именно тогда были разработаны методики очень существенного уплотнения информации и соответствующие жанры журналистики. Дело в том, что вещание, например, машины с громкоговорителем могло длиться 3-6 минут максимум, после чего на её позицию прилетало из миномёта. В результате, например, наши комплексы аппаратуры рекомендовалось перемещать после каждых 3 минут вещания.

                    Очень важен был сигнал начала передачи. Он всегда одинаковый, всегда узнаваемый, и за прошлые недели обучения первым жанром объективной информации – ещё и привлекательный для противника. Враг успокаивается и готовится слушать. Союзные же войска знают, что после этого звука без крайней необходимости нельзя громко шуметь и мешать передаче. Потом идёт вступление: здесь оказалось наиболее важным лично обратиться к части, назвав её номер, например. Цель – установление контакта, привлечение внимания, по сути — «заголовок» передачи. Потом идёт payload – основной текст и резюме. Основной текст объективен и написан так, что с ним хочется согласиться логически. По крайней мере, поначалу он соответствует тому, что думают (по мнению пропагандиста) сами солдаты. В этом блоке не давят, не призывают ни к чему, просто ставят перед фактами. Дальше отбивка, чтобы разделить части, и в резюме, собственно, делается вывод. Для тупых. В конце – финальная часть протокола, рассказ о том, когда начнётся следующая передача. И завершающий сигнал.

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

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

                    Давайте посмотрим на ещё один классический вектор атаки, а потом перейдём к более интересным случаям.

                    Листовки – максимум эффективности на слово


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

                    Листовка (или сегодня — пост в блог) – быстрая, маленькая и очень концентрированная подача. Можно применять сразу по мере изменения ситуации. Должна быть максимально простой и понятной (у солдата нет времени читать – часто листовки очень оперативно отбирали). Один пост, простите, листовка – одна идея. Обязательно – жёсткая аргументированность (типы аргументов зависят от нации, например, для китайцев оказалось важнее читать «отзывы» своих пленных и подходящие цитаты мудрецов, а для немцев выигрывала логика на числах и фактах).

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

                    Ну и, наконец, дизайн – такой, чтобы привлечь. Единственный момент – если за подбирание листовок противник ввёл кару, то она, наоборот, должна быть незаметной.

                    Всё как у нас в сети.

                    Давайте разберём пару частых жанров. Первый – маскировочная листовка, вектор атаки road apple или фишинг по современной терминологии. Изучаем листовки противника, которые используются для раздачи по его же войска. Затем делаем точно такую же, только свою. Отличный пример – в американских военных частях часто и много раздавали материалов про опасность венерических заболеваний. И обоснованно. Немцы взяли пару таких, и написали свою с тем же примерно посылом. Только аргументация была другой:

                    «Военные власти проверили 20000 женщин. Свыше 80 процентов из них оказались больными венерическими заболеваниями. Среди проверенных женщин только 21 процент – проститутки. Остальные 79 процентов распределяются так: 61 процент – замужние женщины, вступившие в случайную связь, 18 процентов – девушки, знакомые военнослужащих (при этом 17 процентов в возрасте до 20 лет). Обе группы женщин оказались в большинстве своем членами быстрорастущего общества женщин „V“ (»Победа"), которые заявили о своем патриотическом стремлении утешать войска. А твоя девушка тоже среди них?".
                    Цитата из книги Крысько В. Г. — Секреты психологической войны (цели, задачи, методы, формы, опыт). Советую, там прямо весь опыт в методологии.


                    Чего нет в книге, так это факта про то, что как раз тогда шла очень успешная пропаганда BBC за движение «V», и после этой листовки солдат дёргался каждый раз, когда слышал рекламу по своему же радио. Безумно красивый подход.

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

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

                    «Ежедневное меню объединенных сил:
                    Завтрак: яйцо, хлеб с маслом, 2 фрукта, фруктовый сок, молоко, кофе, чай. Обед: мясо, фасоль или картошка, бутерброд с сыром, сладости, фруктовый сок. Ужин: мясо, хлеб с маслом, зелень (овощи), яйцо, молоко, фрукты и фруктовый сок, кофе, чай. Пленные питаются так же, как солдаты объединенных сил».




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

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



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

                    А ещё на листовках были социальные виджеты – например, «Прочитай и расскажи товарищам».

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

                    Особенности радиопротокола


                    Основные стандарты связи (зачатки современных протоколов с коррекцией) уже начали появляться на радиообмене Второй мировой. Это наложило довольно интересные особенности на распространение вашей информации по сетям противника:
                    • Просто вклинивание короткими сообщениями: вы ждёте, когда вражеская станция прекратит вызывать другие (ждёт ответов) и нагло передаёте своё сообщение на её рабочей частоте. Действенно, как серпом по молоту, но за неимением лучших вариантов поначалу – очень круто. Цель – пронести что-то интересное до радиста противника, чтобы он захотел послушать ещё. Payload’а пока нет.
                    • В сетях с архитектурой «звезда» — штабная станция и станции дивизий – вклинивание происходило в тот момент, когда центральная сообщала всем по очереди о важном сообщении. Когда эфир на 90% состоит из слушающих, а радисты центральной гонят в канал остальных по одному, врываетесь вы и приносите свою интересную штуку. 1 минута максимум.
                    • Следующая история, когда противнику уже интересно – адресные сообщения, в частности, по радиотелефонной связи. Здесь особенность в том, что для установления контакта не надо было ждать подтверждения – за такое подтверждение радиста могли и расстрелять. Вместо этого использовали обходные пути – либо сообщали заранее на жаргоне, что делать (сообщить о помехах конкретным образом, например), либо просили один раз нажать на тангенту без передачи сообщения, либо выдумывали что-то ещё.


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

                    Принцип правдивости информации


                    Примеры из пропаганды Англии 1940-х я нередко привожу, когда меня спрашивают на конференциях про необходимость очень чётко отвечать за каждое слово в рекламе. И не преувеличивать. Дело в том, что тогда BBC требовалось, чтобы сообщениям верили. Сложилась довольно интересная ситуация: чтобы бомбардировщики немцев не брали радиопеленги, вещание Великобритании сократили до одного канала – собственно, Би-Би-Си. А, значит, стоит один раз облажаться, как вся информационная волна Англии потеряет эффективность.

                    Был принят жёсткий стандарт – говорить правду и не преуменьшать потери. Каждая передача открывалась так называемым «счётом матча» — сводкой по сбитым самолётам Королевства и Люфтваффе. В 1940-м году потери Англии стали слишком большими, и тогда удалось трансформировать «кампанию правды» в «кампанию гнева», то есть мотивировать на агрессию против «бесчеловечности» противника.

                    Позже в результате правдивости кампании было получено ещё два крутых побочных эффекта:
                    1. Англичане померили эффективность немецкой контрпропаганды, и пришли к выводу, что её можно не глушить и даже не комментировать – здравомыслящий англичанин понимал, что это лапша на уши.
                    2. С другой стороны, немцы оказались вынуждены тратиться на довольно сложные контрмеры против английской пропаганды – многие, включая пилотов, привыкли слушать её как объективный источник информации.

                    Кампания «V»


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

                    «К лету 1940 г. германские передачи велись уже более чем на 30 языках. Один из руководителей нацистского иновещания сравнивал немецкие коротковолновые станции с дальнобойными орудиями, стреляющими через все границы. Чтобы пресечь влияние зарубежного иновещания на германское население, нацисты с 1 сентября 1939 г. запретили прослушивание иностранных радиопередач на территории Германии, была введена смертная казнь за распространение почерпнутых из них сведений.
                    После вступления Великобритании в войну в сентябре 1939 г. в структуре Би-Би-Си была создана Европейская служба, на которую возлагались задачи информационно-пропагандистской поддержки военных действий стран антигитлеровской коалиции на европейском театре военных действий. Передачи на европейскую аудиторию велись как на английском, так и на немецком, французском, португальском, испанском и других языках народов Европы. Стартовавшее еще в феврале 1938 г. немецкоязычное вещание Би-Би-Си быстро наращивало объем передач, совершенствовалось их содержание. Стремясь нейтрализовать воздействие британского вещания, государства нацистского блока организовали глушение радиопередач Би-Би-Си. В свою очередь, британские власти, убедившиеся в малой эффективности гитлеровской радиопропаганды, отказались от ответного глушения передач германского радио на Англию».
                    Беспалова А.Г., Корнилов Е.А., Короченский А.П.,
                    Лучинский Ю.В., Станько А.И.
                    ИСТОРИЯ МИРОВОЙ ЖУРНАЛИСТИКИ
                    .


                    Бельгийский режиссёр обнаружил, что буква V для оккупированных территорий имеет примерно общий смысл – либо «Победа» (Victory), либо Свобода (Vrijheid). Обозначение буквы V в азбуке Морзе (точка-точка-точка-тире) совпало с первыми четырьмя нотами Пятой симфонии Бетховена.

                    «Радиослушатели начали повторять эти звуки всеми возможными способами в знак поддержки движения сопротивления, — пишет Уэлч. — По всей оккупированной территории Европы люди чертили букву V и выстукивали ее „морзянкой“, демонстрируя свою солидарность… .19 июля 1941 года Уинстон Черчилль одобрительно отозвался о ней в своей речи и с тех пор стал изображать знак V пальцами».
                    Психологические приемы, которые помогли победить во Второй мировой войне
                    Фиона Макдоналд
                    BBC Culture


                    Результат можно наблюдать даже сейчас – жест «V» на двух пальцах довольно популярен в Англии. Только не перепутайте, если пальцы перевернуть и показывать как у нас «ещё два пива» — получится «идите в задницу».


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

                    Ещё в Рунете ходит популярный миф, что во время налётов бомбардировщиков был случай с несуществующим матчем. Два часа комментатор в жесточайший туман (не дающий работать ПВО) комментировал по BBC на весь Лондон меткие пасы и отбивания. И это удержало немцев от очередного налёта. Так вот, точных исторических свидетельств такому нет (точнее, приводятся новоделы), а миф, кажется, пошёл от довольно популярной данетки про матч, которого не существовало. Но кое-какие косвеные факты заставляют предположить, что нечто подобное было.

                    «One Saturday a big football match was to be played between Celtic and Rangers. At the same time we believed the Germans were to launch a bombing raid on Clydeside. The game was to be broadcast on the radio ,the commentator was R.E, Kingsley (REX). In the late morning however, a blanket of fog descended upon Clydeside so the match had to be abandoned. But, of course the Germans couldn't be allowed to find out about the fog as they would cancel their raid. So, REX Kingsley actually did a complete broadcast of a non existent game ,with all the goals and sound effects such as cheering and chanting. He even announced that it was a gloriously sunny day without a cloud in the sky. It was so life like it actually fooled the German Luftwaffe.»


                    Ещё из разработанных тогда технологий стоит отметить, опять же, не новую, но со времён Диогена доведённую до совершенства технологию повторяющегося тезиса. Если греческие и римские дядьки просто рекомендовали в речах на форуме повторять главную мысль в начале, середине и конце разными словами, то здесь пошли куда дальше. Как вам 11 вариантов листовки с одной и той же мыслью, но разными доказывающими фактами? Выглядит реально как разные мнения нескольких разных источников, а не одного.

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

                    Были отработаны механики будничного рассказа (в основном, на захваченных территориях) – это схема быстрого сдвига нравственных ориентиров так, чтобы реципиенты от «это недопустимо» переходили к состоянию «ну, это статистика». Наука убеждения шагала семимильными шагами.

                    Немцы пробовали использовать современный политических ход «забалтывание» в контрпропаганде. Это когда противник вещает об успехах какого-то конкретного деятеля, а затем к нему прицепляются подвиги Чака Норриса. В итоге слушатель не знает, чему верить. Но что-то не пошло. Как десятилетия спустя выяснилось, надо маскировать забалтывание под свой же канал – то есть, например, пускать маскировочные листовки с чрезмерными восхвалениями, а не делать это с немецкой волны.

                    Очень много исследовался эмоциональный резонанс (это когда что-то рядом с хорошим человеком тоже можно считать хорошим – когда политик стоит рядом с известным актёром, например). Но столь тонкие детали объективно стали эффективными много позже, в мирное время. На первый план выходили, скорее, жанрово-технические вещи. Например, немецкая пропаганда вместе с оперативностью информации ввела «эффект присутствия», который сегодня – просто техприём журналистики. Это, например, когда журналист рассказывает про столкновение через сутки, но всё ещё пригибается, а камера очень шатается, рядом кто-то бегает в страшном волнении и кричит.

                    Для интеллигенции и офицерского состава была разработана теория медиаторов. То, что сейчас используют для работы с блогерами в рекламных кампаниях. Задача СМИ – не дать информацию, а внести слухи, которые подхватят лидеры мнений. Уже результат переработки этих слухов будет значим для среднего человека, который советуется с друзьями. Поэтому цель – не прямое убеждение, а создание экосистемы, в которой лидеры мнений вынуждены обсуждать выгодные для вас вопросы.

                    Английский и наш блоки уделяли очень много внимания языку и символике. Захват терминов был крайне важен – например, не «вероломная атака», а «миротворческая операция», не «перебили всё живое», а «выполнили освобождение территории», не «высыпали на город 50 тысяч поражающих элементов», а «произвели ковровое бомбометание по стратегическим объектам» и так далее. Годами позже вводились стандарты на показ своих потерь по телевизору к чужим – если словами они были, например, равны, то пропорция в картинке времени своих разрушений к чужим – 1:10. И так далее. Довольно много примеров работы с изображениями есть у старой доброй Сонтаг.

                    В общем, следующий раз, когда будете смотреть профессионально сделанный ролик, по телевизору вы, возможно, распознаете технологии 1939-го года. Они точно там есть.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/338178/


                    Метки:  

                    Первый в России OpenHack от Microsoft (то есть от нас)

                    Понедельник, 18 Сентября 2017 г. 15:40 + в цитатник
                    Жизнь в движении. Поэтому мы не устаём экспериментировать с новыми форматами проведения мероприятий для достижения более высоких целей (Круто звучит, да?). Сначала мы отказались от проведения обычной конференции DevCon и перешли в формату DevCon School: эксперты индустрии и их реальный опыт в реальных проектах. За время эксперимента мы провели 6 школ и поняли, что этот формат позволяет «пощупать» новые технологии, получить о них общее представление, вернуться в рабочую рутину с обычной нехваткой времени и остановиться.



                    Сегодня я хочу рассказать вам о следующем шаге — OpenHack, который позволит провести эксперимент вместе с экспертами и проверить рабочую гипотезу в деле.
                    Читать дальше ->

                    https://habrahabr.ru/post/338146/


                    Kaggle: как наши сеточки считали морских львов на Алеутских островах

                    Понедельник, 18 Сентября 2017 г. 14:19 + в цитатник
                    temakone сегодня в 14:19 Разработка

                    Kaggle: как наши сеточки считали морских львов на Алеутских островах

                      header_im


                      Привет, Коллеги!
                      27 июня закончилось соревнование на Kaggle по подсчёту морских львов (сивучей) на аэрофотоснимках NOAA Fisheries Steller Sea Lions Population Count. В нем состязались 385 команд. Хочу поделиться с вами историей нашего участия в челлендже и (почти) победой в нём.


                      Небольшое лирическое отступление.


                      Как многие уже знают, Kaggle – это платформа для проведения онлайн соревнований по Data Science. И в последнее время там стало появляться всё больше и больше задач из области компьютерного зрения. Для меня этот тип задач наиболее увлекателен. И соревнование Steller Sea Lions Population Count — одно из них. Я буду повествовать с расчётом на читателя, который знает основы глубокого обучения применительно к картинкам, поэтому многие вещи я не буду детально объяснять.


                      Пару слов о себе. Я учусь в аспирантуре в университете города Хайдельберг в Германии. Занимаюсь исследованиями в области глубокого обучения и компьютерного зрения. Страничка нашей группы CompVis.


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


                      На тот момент у меня был некий опыт участия в соревнованиях на Kaggle, но только в нерейтинговых, за которые не дают ни медалей, ни очков опыта (Ranking Points). Но у меня был довольно обширный опыт работы с изображениями с помощью глубокого обучения. У Димы же был опыт на Kaggle в рейтинговых соревнованиях, и была 1 бронзовая медаль, но работать с компьютерным зрением он только начинал.


                      Перед нами встал нелёгкий выбор из 3 соревнований: предсказание рака матки на медицинских изображениях, классификация спутниковых изображений из лесов Амазонии и подсчёт сивучей на аэрофотоснимках. Первое было отброшено из-за визуально не очень приятных картинок, а между вторым и третьим было выбрано третье из-за более раннего дедлайна.


                      Постановка задачи


                      В связи со значительным уменьшением популяции сивучей на западных Алеутских островах (принадлежащих США) за последние 30 лет ученые из NOAA Fisheries Alaska Fisheries Science Center ведут постоянный учет количества особей с помощью аэрофотоснимков с дронов. До этого времени подсчет особей производился на фотоснимках вручную. Биологам требовалось до 4 месяцев, чтобы посчитать количество сивучей на тысячах фотографий, получаемых NOAA Fisheries каждый год. Задача этого соревнования — разработать алгоритм для автоматического подсчета сивучей на аэрофотоснимках.


                      Все сивучи разделены на 5 классов:


                      1. adult_males — взрослые самцы (),
                      2. subadult_males – молодые самцы (),
                      3. adult_females – взрослые самки (),
                      4. juveniles – подростки (),
                      5. pups – детёныши ().

                      Дано 948 тренировочных картинок, для каждой из которых известно Ground Truth число особей каждого класса. Требуется предсказать число особей по классам на каждой из 18641 тестовых картинок. Вот пример некоторых частей из датасета.


                      Картинки разных разрешений: 4608x3456 до 5760x3840. Качество и масштаб очень разнообразный, как видно из примера выше.
                      Положение на лидерборде определяется ошибкой RMSE, усредненной по всем тестовым изображениям и по классам.


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


                      image
                      (image credits to bestfitting)


                      Самые частые классы сивучей — это самки (), подростки () и детеныши ().


                      Проблемы


                      Здесь я кратко перечислю, какие были проблемы с данными, да и с задачей в целом.


                      • Зашумленность разметки. Не на всех тренировочных картинках были размечены все сивучи (часто биологи пропускали тех, что плавают в воде).
                      • Нет чёткого разделение между парами классов adult_males и subadult_males, adult_females и juveniles. Мы не всегда даже глазами можем понять где самка, а где подросток. Та же проблема со взрослыми и молодыми самцами. На вопрос “как вы их размечали?” биолог из NOAA ответил на форуме, что часто отличать классы приходилось только по поведенческим признакам. Например, взрослые самцы часто окружены множеством самок (сивучи живут гаремами), а молодые, еще не успешные, вынуждены одиноко ютиться в отдалении от всех.
                      • Детенышей бывает затруднительно отличить от мокрого камня. Они в разы меньше, чем другие особи.
                      • Нет segmentation масок — только грубая позиция животных. Лобовой подход на сегментацию объектов не применим.
                      • Разные масштабы изображений в целом. В том числе, на глаз, масштаб на тренировочных изображениях меньше чем в тестовых.
                      • Задача подсчёта случайных объектов (не людей) не очень освещена в научных статьях. Обычно все считают людей на фотографиях. Про животных, а тем более из нескольких классов, публикаций найдено не было.
                      • Огромное количество тестовых картинок (18641) большого разрешения. Предсказание занимало от 10 до 30 часов на одном Titan X.
                        Причем, большая часть из тестовых картинок добавлена исключительно в целях избежания того, что участники будут вручную аннотировать тест. То есть предсказания на некоторых из них никак не влияли на финальный счёт.

                      Как мы решали


                      В Германии, как и в России, в этом году выпали большие выходные на 1 Мая. Свободные дни с субботы по понедельник оказались как никогда кстати для того, чтобы начать погружаться в задачу. Соревнование длилось уже больше месяца. Всё началось с того, что мы с Димой Котовенко в субботу прочитали условие.


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


                      Первых 3 дня ушло на обработку данных и написание первой версии пайплайна. Один добрый человек, Radu Stoicescu, выложил кернел, который преобразовывал точки на тренировочных изображениях в координаты и класс сивуча. Здорово, что на это не пришлось тратить своё время. Первый сабмит я сделал только через неделю после начала.


                      Очевидно, решать эту задачу в лоб с помощью semantic segmentation нельзя, так как нет Ground Truth масок. Нужно либо генерить грубые маски самому либо обучать в духе weak supervision. Хотелось начать с чего-то попроще.
                      Задача подсчёта числа объектов/людей не является новой, и мы начали искать похожие статьи. Было найдено несколько релевантных работ, но все про подсчёт людей CrowdNet, Fully Convolutional Crowd Counting, Cross-scene Crowd Counting via Deep Convolutional Neural Networks. Все они имели одну общую идею, основанную на Fully Convolutional Networks и регрессии. Я начал с чего-то похожего.


                      Идея crowd counting на пальцах


                      Хотим научиться предсказывать хитмапы (2D матрицы) для каждого класса, да такие, что бы можно было просуммировать значения в каждом из них и получить число объектов класса.


                      Для этого генерируем Grount Truth хитмапы следующим образом: в центре каждого объекта рисуем гауссиану. Это удобно, потому что интеграл гауссианы равен 1. Получаем 5 хитмапов (по одному на каждый из 5 классов) для каждой картинки из тренировочной выборки. Выглядит это так.

                      Увеличить


                      Среднеквадратичное отклонение гауссиан для разных классов выставил на глазок. Для самцов – побольше, для детенышей – поменьше. Нейронная сеть (тут и далее по тексту я имею в виду сверточную нейронную сеть) принимает на вход изображения, нарезанные на куски (тайлы) по 256x256 пикселей, и выплёвывает 5 хитмапов для каждого тайла. Функция потерь – норма Фробениуса разности предсказанных хитмапов и Ground Truth хитмапов, что эквивалентно L2 норме вектора, полученного векторизацией разности хитмапов. Такой подход иногда называют Density Map Regression. Чтобы получить итоговое число особей в каждом классе, мы суммируем значения в каждом хитмапе на выходе.


                      Метод Public Leaderboard RMSE
                      Baseline 1: предсказать везде 0 29.08704
                      Baseline 2: предсказать везде среднее по train 26.83658
                      Мой Density Map Regression 25.51889

                      Моё решение, основанное на Density Map Regression, было немного лучше бейзлайна и давало 25.5. Вышло как-то не очень.




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


                      Оригинальная задача, которая решалась в статьях — это подсчет количества людей в толпе, т. е. был только один класс объектов. Вероятно, Density Map Regression не очень хороший выбор для задачи с несколькими классами. Да и всё усугубляет огромная вариация плотности и масштаба объектов. Пробовал менять L2 на L1 функцию потерь и взвешивать классы, всё это не сильно влияло на результат.


                      Было ощущение, что L2 и L1 функции потерь делают что-то не так в случае взаимоисключающих классов, и что попиксельная cross-entropy функция потерь может работать лучше. Это натолкнуло меня на идею натренировать сеть сегментировать особей с попиксельной cross-entropy функцией потерь. В качестве Ground Truth масок я нарисовал квадратики с центром в ранее полученных координатах объектов.


                      Но тут появилась новая проблема. Как получить количество особей из сегментации? В чатике ODS Константин Лопухин признался, что использует xgboost для регрессии числа сивучей по набору фич, посчитанных по маскам. Мы же хотели придумать как сделать всё end-to-end с помощью нейронных сетей.


                      Тем временем, пока я занимался crowd counting и сегментацией, у Димы заработал простой как апельсин подход. Он взял VGG-19, натренированную на классификации Imagenet, и зафайнтьюнил ее предсказывать количество сивучей по тайлу. Он использовал обычную L2 функцию потерь. Получилось как всегда — чем проще метод, тем лучше результат.


                      Итак, стало понятно, что обычная регрессия делает свое дело и делает хорошо. Идея с сегментацией была радостно отложена до лучших времен. Я решил обучить VGG-16 на регрессию. Присобачил в конце выходной слой для регрессии на 5 классов сивучей. Каждый выходной нейрон предсказывал количество особей соответствующего класса.
                      Я резко вышел в топ-20 c RMSE 20.5 на паблик лидерборде.


                      К этому моменту целеполагание претерпело небольшие изменения. Стало понятно, что целиться имеет смысл не в топ-100, а как минимум в топ-10. Это уже не казалось чем-то недостижимым.


                      Выяснилось, что на test выборке многие снимки были другого масштаба, сивучи на них выглядели крупнее, чем на train. Костя Лопухин (отдельное ему за это спасибо) написал в слаке ODS, что уменьшение тестовых картинок по каждой размерности в 2 раза давало существенный прирост на паблик лидерборде.


                      Но Дима тоже не лыком шит, он подкрутил что-то в своей VGG-19, уменьшил картинки и вышел на 2-e место со скором ~16.


                      Подбор архитектуры и гиперпараметров сети



                      (image credits to Konstantin Lopuhin)


                      С функцией потерь у нас всё понятно. Время начинать экспериментировать с более глубокими сетями. В ход пошли VGG-19, ResnetV2-101, ResnetV2-121, ResnetV2-152 и тяжелая артиллерия — Inception-Resnet-V2.


                      Inception-Resnet-V2 — это архитектура придуманная Google, которая представляет собой комбинацию трюков от Inception архитектур (inception блоки) и от ResNet архитектур (residual соединения). Эта сеть изрядно глубже предыдущих и выглядит этот монстр вот так.



                      (image from research.googleblog.com)


                      В статье "Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning" ребята из Google показывают, что эта архитектура давала на тот момент state of the art на Imagenet без использования ансамблей.


                      Кроме самих архитектур мне пришлось перебрать:


                      • различные размеры входного тайла: от 224x224 до 512x512 пикселей;
                      • тип пулинга после свёрточных слоёв: sum-pooling или average-pooling;
                      • количество дополнительных FC-слоёв перед финальным: 0, 1 или 2;
                      • количество нейронов в дополнительных FC-слоях: 128 или 256.

                      Лучшей комбинацией оказались: Inception-Resnet-V2-BASE + average-pooling + FC-слой на 256 нейронов + Dropout + финальный FC-слой на 5 нейронов. Inception-Resnet-V2-BASE обозначает часть оригинальной сети от первого до последнего сверточного слоя.
                      Лучшим размером входного тайла оказался 299x299 пикселей.


                      Аугментации изображений


                      Для тренировочных картинок мы делали типичный набор аугментаций для задач из CV.
                      К каждому тайлу применялись:


                      • Случайные флипы слева направо и сверху вниз;
                      • случайные вращения на углы, кратные 90 градусов;
                      • случайное масштабирование в 0.83 — 1.25 раза.
                        Цвет мы не аугментировали, так как это довольно скользкая дорожка. Зачастую сивучей отличить от ландшафта можно было только по цвету.

                      Test time augmentation мы не делали. Потому что предсказание на всех тестовых картинках и так занимало полдня.


                      Продолжение истории


                      В какой-то момент, пока я перебирал гиперпараметры и архитектуры сетей, мы объединились в команду с Димой Котовенко. Я в тот момент был 2-м месте, Дима на 3-м. Для отвода китайцев в ложном направлении команду назвали "DL Sucks".


                      Объединились, потому что было бы нечестно у кого-то забирать медальку, ведь с Димой мы активно обсуждали наши решения и обменивались идеями. Этому событию очень порадовался Костя, мы освободили ему призовое место. С 4-го он попал на 3-е.


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


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


                      За пару дней до дедлайна мы собрали все лучшие модели и сделали ансамбль из 24 нейронных сетей. Все модели имели лучшую архитектуру Inception-Resnet-V2, которую я описал ранее. Отличались модели только тем, насколько агрессивно мы аугментировали картинки, на каком масштабе тестовых изображений делались предсказания. Выходы с разных сетей усреднялись.


                      Команда "DL Sucks" закончила соревнование на 2-м месте на паблик лидерборде, что не могло не радовать, так как мы были "в деньгах". Мы понимали, что на прайвэт лидерборде всё может поменяться и нас вообще может выкинуть из первой десятки. У нас был приличный разрыв с 4-м и 5-м местом, и это добавляло нам уверенности. Вот так выглядело положение на лидерборде:


                      1-е место 10.98445 outrunner (Китаец 1)
                      2-е место 13.29065 Мы (DL Sucks)
                      3-е место 13.36938 Костя Лопухин
                      4-е место 14.03458 bestfitting (Китаец 2)
                      5-е место 14.47301 LeiLei-WeiWei (Команда из двух китайцев)

                      Оставалось дождаться финальных результатов…
                      И что бы вы думали? Китаец нас обошел! Мы были сдвинуты со 2-го на 4-ое место. Ну ничего, зато получили по золотой медали ;)


                      Первое место, как оказалось, занял другой китаец, альфа-гусь outrunner. И решение у него было почти как у нас. Обучил VGG-16 c дополнительным полносвязным слоем на 1024 нейрона предсказывать количество особей по классам. Что вывело его на первое место, так это ad-hoc увеличение количества подростков на 50% и уменьшение количества самок на такое же число, умножение количества детёнышей на 1.2. Такой трюк поднял бы нас на несколько позиций выше.


                      Финальное положение мест:


                      1-е место 10.85644 outrunner (Китаец 1)
                      2-е место 12.50888 Костя Лопухин
                      3-е место 13.03257 (Китаец 2)
                      4-е место 13.18968 Мы (DL Sucks)
                      5-е место 14.47301 Дмитро Поплавский (тоже в слаке ODS) в команде с 2 другими

                      Пару слов о других решениях


                      Резонный вопрос — а нельзя ли натренировать детектор и посчитать потом баундинг боксы каждого класса? Ответ — можно. Некоторые парни так и сделали. Александр Буслаев (13-ое место) натренировал SSD, а Владимир Игловиков (49-ое место) — Faster RCNN.
                      Пример предсказания Владимира:

                      (image credits to Vladimir Iglovikov)
                      Минус такого подхода состоит в том, что он сильно ошибается, когда сивучи на фотографии очень плотно лежат друг к другу. А наличие нескольких различных классов еще и усугубляет ситуацию.


                      Решение, основанное на сегментации с помощью UNet, тоже имеет место быть и вывело Константина на 2 место. Он предсказывал маленькие квадратики, которые он нарисовал внутри каждого сивуча. Далее — танцы с бубном. По предсказанным хитмапам Костя вычислял различные фичи (площади выше заданных порогов, количество и вероятности блобов) и скармливал их в xgboost для предсказания числа особей.

                      (image credits to Konstantin Lopuhin)
                      Подробнее про его решение можно посмотреть на youtube.


                      У 3-го места (bestfitting) тоже решение основано на UNet. Опишу его в двух словах. Парень разметил вручную сегментационные маски для 3 изображений, обучил UNet и предсказал маски на еще 100 изображениях. Поправил маски этих 100 изображений руками и заново обучил на них сеть. С его слов, это дало очень хороший результат. Вот пример его предсказания.

                      (image credits to bestfitting)
                      Для получения числа особей по маскам он использовал морфологические операции и детектор блобов.


                      Заключение


                      Итак, начальная цель была попасть хотя бы в топ-100. Цель мы выполнили и даже перевыполнили. Было перепробовано много всяких подходов, архитектур и аугментаций. Оказалось, проще метод – лучше. А для сетей, как ни странно, глубже — лучше. Inception-Resnet-V2 после допиливания, обученная предсказывать количество особей по классам, давала наилучший результат.
                      В любом случае это был полезный опыт создания хорошего решения новой задачи в сжатые сроки.


                      В аспирантуре я исследую в основном Unsupervised Learning и Similarity Learning. И мне, хоть я и занимаюсь компьютерным зрением каждый день, было интересно поработать с какой-то новой задачей, не связанной с моим основным направлением. Kaggle дает возможность получше изучить разные Deep Learning фреймворки и попробовать их на практике, а также поимплементировать известные алгоритмы, посмотреть как они работают на других задачах. Мешает ли Kaggle ресерчу? Вряд ли он мешает, скорее помогает, расширяет кругозор. Хотя времени он отнимает достаточно. Могу сказать, что проводил за этим соревнованием по 40 часов в неделю (прямо как вторая работа), занимаясь каждый день по вечерам и на выходных. Но оно того стоило.


                      Кто дочитал, тому спасибо за внимание и успехов в будущих соревнованиях!




                      Мой профиль на Kaggle: Artem.Sanakoev
                      Краткое техническое описание нашего решения на Kaggle: ссылка
                      Код решения на github: ссылка

                      Original source: habrahabr.ru (comments, light).

                      https://habrahabr.ru/post/337548/


                      [Перевод] Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними

                      Понедельник, 18 Сентября 2017 г. 13:56 + в цитатник

                      Метки:  

                      Компьютерное зрение. Задайте вопрос эксперту Intel

                      Понедельник, 18 Сентября 2017 г. 13:07 + в цитатник
                      saul сегодня в 13:07 Разработка

                      Компьютерное зрение. Задайте вопрос эксперту Intel

                        Далеко не все ответы можно найти в Интернет. Особенно если вопрос ваш относится к достаточно узкой или новой области — тут необходима консультация гуру, Владельца Тайного Знания. В традициях блога Intel — проведение блого-семинаров, построенных на вопросах читателей. На эти вопросы отвечают эксперты Intel, принимавшие непосредственное участие в создании технологий и продуктов — кому, как не им знать все детали?
                        В этом месяце место на трибуне предоставлено создателям библиотеки компьютерного зрения OpenCV (Open Source Computer Vision Library), бывшим сотрудникам компании Itseez, вошедшей в состав Intel — Вадиму Писаревскому и Анатолию Бакшееву. Итак, если у вас назрел вопрос об OpenCV, машинном зрении, распознавании образов и других смежных темах, но вы не знали, кому его задать — приглашаем вас в комментарии и личку. Вопросы принимаются до 24 сентября. Автор лучшего вопроса получит приз от Intel — набор фирменных принадлежностей для уютного отдыха.
                        Под катом — краткая информация о наших экспертах.

                        Анатолий Бакшеев, руководитель команды Computer Vision for Retail в Intel. Занимается компьютерным зрением и машинным обучением вот уже более 11 лет, в том числе последние несколько лет Deep Learning’ом. Начал свою карьеру в компании Itseez и проработал там более 10 лет до тех пор, пока компания не была приобретена Intel. За это время в его портфолио накопилось множество выполненных проектов, спроектированных и оптимизированных алгоритмов, большое количество кода, выложенного в библиотеку OpenCV, а также ее оптимизация под NVIDIA GPU.

                        Вадим Писаревский, бессменный руководитель команды OpenCV, начиная с 2000 года.
                        Закончил ВМК ННГУ, работал в компании Intel, затем в Itseez. Вернулся в Intel для продолжения работы над библиотекой и перевода ее на рельсы Deep Learning. Со-автор нескольких патентов и статей по компьютерному зрению. Периодически выступает с докладами и лекциями по OpenCV в различных университетах и на разных конференциях.

                        Ждем ваши вопросы экспертам в комментариях к этому посту или личных сообщениях saul до воскресенья, 24 сентября, включительно.
                        Original source: habrahabr.ru (comments, light).

                        https://habrahabr.ru/post/337810/


                        Метки:  

                        Поиск сообщений в rss_rss_hh_full
                        Страницы: 1824 ... 1537 1536 [1535] 1534 1533 ..
                        .. 1 Календарь