...

суббота, 21 февраля 2015 г.

Самые нужные плагины для Grunt

NAS + SoftetherVPN = универсальный VPN сервер

Думаю не ошибусь, если предположу, что у любого пользователя периодически возникает необходимость получить доступ к какому-либо сетевому ресурсу (IP-камере, сетевому накопителю, компьютеру, холодильнику и т.п.) внутри домашней или офисной сети через Интернет. Ну и само собой этот доступ должен быть:

А) универсальный, т. е. с любого компьютера или гаджета;

Б) защищенный от несанкционированного доступа нежелательных лиц.

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


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






Недавно меня познакомили с поистине волшебным ПО под названием Softether VPN. Чем же он так волшебен? Вот часть того, что он умеет:

• Множество виртуальных хабов. Т.е. внутри одного VPN сервера может быть более одного виртуального VPN сервера со своими правилами и правами доступа;

• Remote-Access (клиент-к-LAN) и Site-to-Site (объединение двух и более LAN в одну) туннелей;

• Поддержка L2TP over IPsec, IPSec, OpenVPN, MS-SSTP, L2TPv3, EtherIP (бридж) и своего собственного SSL-VPN протокола;

• VPN через ICMP или через DNS (только через свой протокол), что позволяет обойти защиту даже самых «стойких» фаерволов;

• Подробное логгирование;

• Встроенный firewall для каждого виртуального хаба в отдельности;

• Поддержка IPv6 в L3-режиме (ну и в L2, конечно, тоже);

• Шейпинг трафика по группам пользователей либо по конкретным пользователям;

• SecureNAT (user-space NAT и DHCP-сервер). Удобно на не-серверных Windows;

• Поддержка VLAN;

• Поддержка QoS с автоматической приоритезацией;

• Ну и то, что умеют единицы VPN серверов — Dynamic DNS и NAT Traversal через бесплатный релей.



Последний пункт будет особенно интересен многим пользователям домашних сетей. К примеру вам повезло с провайдером, который на соединение бесплатно выдает «белый» (маршрутизируемый IP адрес, т.е. доступный из любой точки Интернета), но не фиксированный, а динамический. В этом случае вы можете использовать любой VPN сервер, но нужно знать какой IP адрес вам выдал на данный момент провайдер. Это не проблема, решается через использование служб Dynamic DNS. Но большинство таких служб на сегодня платные. А как приятно слово «шара»! Softether VPN имеет собственную службу Dynamic DNS и при этом совершенно бесплатно. Вариант второй – вам не повезло с провайдером и он по какой-то причине не может выдать вам «белый» IP адрес, даже динамический, или вас просто жаба давит, платить за «белый» IP адрес. В этом случае доступ к своей домашней сети вы не сможете получить никакими простыми штатными средствами. Softether VPN решает и эту проблему, используя функцию «NAT Traversal через бесплатный релей». Т.е. вы получаете доступ к своему VPN серверу через сервер-посредник. Это конечно в какой-то мере нарушает требования безопасности, но с таким же успехом провайдер может перехватить ваш трафик и попытаться вскрыть содержимое. Тут уже все зависит от уровня вашей паранойи ;) Можно использовать многокилобайтные ключи, что снизит вероятность «вскрытия» фактически до нуля, но при этом в разы увеличит нагрузку на процессоры сервера и клиента и, как следствие, возможное снижение скорости в туннеле. Тут уже решать самому пользователю.


Ну теперь вернемся к тому, о чем я собственно хотел написать. Есть такие замечательные NAS компании D-Link – DNS-320L, DNS-325, DNS-327L, DNS-345 и DNS-340L. Конечно найдется много «доброжелателей», которые скажут «D-Link» и «замечательные» не согласуется. Я не буду вступать в подобные обсуждения, т.к. они всегда заканчиваются одинаково – каждый остается при своем мнении. Скажу только, что идеала просто не существует, у каждого есть свои плюсы и свои минусы. Главное из моего личного опыта – NAS D-Link полностью отвечают своей стоимости, в отличии от NAS некоторых других производителей, цена которых неоправданно завышена.


И так, имеем NAS D-Link, одной из выше перечисленных моделей. Идем на сайт с Адд-Онами и скачиваем Softether VPN под свою модель http://ift.tt/1DGxTtZ.


Далее установим Add-On на свой NAS и запустим его.




Веб интерфейс у SoftetherVPN есть, но он мало функционален, может использоваться разве что для получения статистической информации. Настройку сервера лучше производить с помощью “SoftetherVPN Server Manager for Windows”



Брать тут http://ift.tt/1fFIIzP



Возможна настройка с помощью утилиты vpncmd, входящей в пакет Адд-Она, но для этого требуются хотя бы базовые знания линукса и установка Адд-Она utelnetd или sshd. Про настройку через vpncmd рекомендую прочитать тут http://ift.tt/LrpaUK



Интерфейс “SoftetherVPN Server Manager for Windows” неплохо описан тут http://ift.tt/1ahDs0x



Если вы предполагаете использовать VPN протокол L2TP over IPSec, нужно добавить пользователя и ввести парольную фразу для IPSec. Нажмите кнопку “IPSec / L2TP Settings”, включите «Enable L2TP…» и введите Pre-Shared Key. После этого зайдите в раздел «Manage Virtual Hub» и нажмите «Manage Users».




Создаем пользователя, вводим пароль, «Auth Type» выбираем «Password Authentication» и нажимаем ОК. После этого перейдите в «Virtual NAT and Virtual DHCP Server (Secure NAT)», нажмите «SecureNAT Configuration», включите галки «Use Virtual NAT Function» и «Use Virtual NAT DHCP Server Function» и ОК. После этого нужно активировать эти функции кнопкой «Enable SecureNAT».



Если у вас «белый» IP адрес, нужно на маршрутизаторе «настроить проброс» портов для L2TP и IPSec. Как это делается – читаете руководство пользователя для вашего маршрутизатора. Например так «Часто задаваемые вопросы и ответы». Порты 1701, 500 и 4500, протокол UDP. DDNS имя можно использовать то, которое сгенерировал Softether VPN или задать свое – раздел «Dynamic DNS Setting».



Если же у вас «серый» IP адрес, перейдите в раздел «VPN Azure Setting» и активируйте эту службу.



Соответственно из Интернета в первом случае вы будете подключаться к «ваше_имя.softether.net», во втором случае «ваше_имя.vpnazure.net».



Примеры настройки клиента под Android, Linux, МАС, Windows.


Не менее интересен для более продвинутых пользователей будет протокол OpenVPN. Но об этом в следующей статье. Хотя настройку OpenVPN клиента я уже рассматривал в статье «Один из простых вариантов защиты VoIP», но там как клиенты рассмотрены только маршрутизатор D-Link серии DSR и смартфон с Android.



Интерфейс о-о-очень подробно описан на сайте http://ift.tt/13IqARD. Правда на английском, но при наличии разнообразных онлайн-переводчиков, думаю любой сможет разобраться.


Если, все же, возникнут вопросы по работе, обращайтесь.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Пишем бот для MMORPG с ассемблером и дренейками. Часть 0

CxxMock — Mock-объекты в C++

пятница, 20 февраля 2015 г.

Обзор железок для занятий робототехникой с детьми

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


Lego


Самый распространенный детский конструктор роботов и самый распространенный конструктор вообще, в России >80% детей занимаются на Лего. Имеет следующие достоинства:


  1. Большая методическая база на русском языке.

  2. От преподавателя требуется не очень высокая квалификация.

  3. Конструктор очень прочный, детям редко удается что-то сломать.




Недостатки:


  1. Изначально это все-таки детский конструктор, для серьезных задач не предназначенный.

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

  3. Программировать можно либо в визуальных средах программирования, либо на С++.




По нашему опыту Лего хорош для детей до 7 класса, тем, кто постарше, лучше что-то другое.

Fishertechnic


http://ift.tt/1qk6GZY

В целом повторяет идеологию Lego. По сравнению с ним имеет ряд интересных дополнительных элементов: пневмоприводы, хемотроника, ионисторы, электрохимические суперконденсаторы и др. Есть специальные комплекты, моделирующие разного рода производство. Если Лего — это общеразвивающая игрушка, то fischertechnik больше инженерно развивающая, однако по прежнему игрушка.

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

Стоимость Fishertechnic примерно соответствует стоимости Лего.

Arduino


http://ift.tt/yiZFzg

Самая распространенная платформа для взрослой робототехники и электроники, вторая по распространённости среди детей. Разработка полностью открытая, у нее есть множество ответвлений.

Можно выделить три направления работы детей с Ардуино:


  1. Сборка электрических схем. Соответствующие комплекты поставляет Амперка: wiki.amperka.ru/

  2. Сборка и программирование простейших машинок, типа такой: http://ift.tt/1I7FhCQ. В основном эти машинки занимаются тем, что ездят по нарисованной линии (лайнфоловеры).

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




Достоинства Ардуино:


  1. Открытость и совместимость со всем на свете.

  2. Универсальность: и шестиклассники на нем могут заниматься, и взрослые выполнять серьезные проекты.

  3. Сравнительно низкая цена.




Недостатки:


  1. Сравнительная ломкость (это открытая электронная плата с небольшим уровнем защиты от неправильных подключений).

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




Raspberry Pi и аналоги


Raspberry Pi — это компьютер под управлением операционной системы Linux, имеющий размеры баковской карты. На нем можно запускать те же программы, делать те же вычисления, что и на настольном компьютере (есть видеовыходы, аудиовыходы, USB). Малые размеры вкупе с низким энергопотреблением позволяют устанавливать его на подвижных роботов.

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

Преимущества Raspberry перед Ардуино описаны выше, недостатки следующие:


  1. По сравнению с Ардуино Raspberry имеет примерно в два раза более высокую стоимость.

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




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

Наиболее перспективным направлением сейчас является совмещение Raspberry и ардуино. Существуют следующие варианты:



  1. Интегрирование разъемов арудино в плату, аналогичную Raspberry (например). Таким образом Raspberry лишается своего недостатка неудобства подключения внешних устройств.

  2. Подключение к оригинальной Raspberry специального переходника с разъемами ардуино (например). Это дает те же преимущества, что и в предыдущем пункте, плюс к этому появляется дополнительная гибкость: можно отцепить этот переходник и использовать оригинальные разъемы Raspberry (редко, но с ними тоже бывает удобнее). Кроме того, если одна плата сгорит, это не помешает продолжить использовать другую.

  3. Интегрирование в одну плату процессора Raspberry, процессора и разъемов ардуино (http://www.udoo.org/). Помимо преимуществ из предыдущих пунктов такая схема дает преимущества двухпроцессорной схемы, некоторые вещи на ней делать гораздо удобнее.

  4. Специальная плата ардуино, имеющая разъем для подключения к Raspberry (поскольку оба проекта открыты, они прекрасно взаимодействуют друг с другом). Помимо преимуществ предыдущего пункта это дает гибкость.




Стоимость первого варианта за западе — около 50$, второй вариант — 75$; третий — 100$; четвертый — 60$. (Чтобы получить стоимость в России эти цены нужно умножить примерно на 2.) При этом последний вариант предоставляет наибольшие возможности и гибкость.

Например, может быть реализован следующий сценарий использования: сначала, когда ребенок только начинает заниматься робототехникой, он работает с ардуино в графической среде программирования; далее, когда он вырастает из графической среды, подключаем к ардуине Raspberry, и ребенок начинает программировать на Python, использовать различные дополнительные возможности. Если дорогая Raspberry недоступна, ребенок может программировать имеющуюся ардуину на С++.

Механические конструкторы для Arduino и Raspberri


Наиболее известный — это Huna:

По сути это старый советский металлический конструктор на винтах.

Похожий на него конструктор предлагает Трик: http://ift.tt/17z61v9

На наш взгляд наиболее перспективным является недавно появившийся конструктор Multiplo. В отличии от других конструкторов у него основные детали не металлические, а вырезаны из трехмиллиметрового пластика. Благодаря этому их можно вырезать самостоятельно на специальном станке (тем более, что 3Д-модели деталей выложены в открытый доступ, проект опенсорсный). Если специальный станок недоступен, детали можно вырезать руками с помощью лобзика и дрели.

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


Стоимость Huna с Ардуино на борту примерно соответствует стоимости Лего. Стоимость Multiplo с ардуино на борту примерно в полтора раза ниже.


Преимущества конструкторов на винтах перед Лего:



  1. У детей развивается мелкая моторика.

  2. Нигде на производстве не используются крепления как в Лего, везде крепеж на винтах.




Недостатком является то, что на винтах сборка конструктора идет несколько медленнее и менее интересно.

Заключение


Мы остановились на варианте arduino + Raspberry Pi + конструктор Multiplo.

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


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


Если стоит задача научить детей самих нарезать и печатать детали, то за основу лучше брать Multiplo.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Приём радиофаксов и прочих цифровых передач с помощью обычного приёмника и компьютера



Один из неудачных радиофаксов

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



Оборудование


Чтобы хоть что-то принимать, нужны:



  • КВ-приёмник

  • Антенна

  • Компьютер

  • Софт для декодирования




Но обо всем по порядку.

Тут всё относительно просто. Я использую всем известный Degen DE1103, но можно попробовать и RTLSDR, используя софт вроде Virtual Audio Cable, чтобы загнать звук в декодер. Думаю, не надо объяснять, что приёмник должен иметь хотя бы разъём для наушников(а лучше — линейный выход, как на DE1103) — иначе его просто будет не подключить.



Degen DE1103




Самая важная часть — без хорошей антенны поймать слабый сигнал далёкой радиостанции нереально. Самый простой вариант — длинный провод длиной 1/4 от длины волны. Я использую стандартную антенну, входившую в комплект моего приёмника, и в городе получаю далеко не самый лучший приём, и КДПВ — тому подтверждение: из-за шумов разобрать многие символы просто нереально. Вот здесь есть хороший материал по изготовлению нормальной антенны.



Плохое фото хорошей антенны




Требования просты: наличие звуковой карты. Встроенная подходит в большинстве случаев, если есть внешняя — вообще отлично.

Есть много программ для декодирования WEFAX-сигнала: SeaTTY, MultiPSK, Fldigi и некоторые другие. Я рекомендую пользоваться Fldigi из-за его бесплатности и универсальности: кроме факсов, он может декодировать почти все виды цифровой связи, а также работать на передачу(что, впрочем, и MultiPSK умеет).

Сначала надо подготовить компьютер. Для этого скачиваем и устанавливаем Fldigi. При первом запуске он спросит позывной и прочее — можно это смело пропускать. После этого надо выбрать режим факса — для этого надо выбрать сверху Op Mode>WEFAX>WEFAX-IOC576 или WEFAX-IOC288 в зависимости от режима станции. После этого соединяем микрофонный вход с выходом радио, предварительно убавив громкость на приёмнике до минимума. Снизу должен появиться «водопад».



Теперь надо найти частоту передачи. Актуальный список публикуется здесь (pdf), вот тутесть более понятный список. Обратите внимание на цифры вроде 120/576 — они обозначают режим WEFAX-IOC576, 120 LPM (строк в минуту). Для начала рекомендую частоту 13883КГц — там идет круглосуточная передача из Гамбурга (в режиме USB настраиваться надо чуть ниже — у меня ~13881КГц).

После этого настраиваем приёмник на нужную частоту, и в большинстве случаев fldigi всё сделает за вас — будет слышен ритмичный звук передачи, а в верхнем окне начнёт появляться декодированная картинка. Если по «водопаду» видно, что программа настроилась куда-то не туда, внизу окна есть кнопка AFC(Automatic Frequency Correction) — отключите её, после чего «натрвите»программу на частоту вручную, щёлкнув по сигналу на графике. Если картинка выглядит перекошенной — с помощью поля Slant выставьте значение, при котором картинка выглядит прямой.



Если на частоте тишина — то попробуйте позже, когда будет хорошее прохождение. Лучше всего принимать факс ночью.

На всякий случай — видео процесса приёма:



Под спойлером — некоторые другие факсы, например, Kyodo News.


Осторожно, простыня


Kyodo News




Погода






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

Полезные ссылки:

Мануал по fldigi

Про upconverter'ы для RTLSDR


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


iOS Developers RU – сообщество для русскоязычных iOS-разработчиков

Всем привет.

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


В текущий момент для группового общения появились новые инструменты, такие как HipChat или Slack (подробнее про Slack здесь: http://ift.tt/1ng4O2H). Кроме того, что они используются в работе конкретной команды, их также можно использовать для объединения в сообщества по интересам.


Одно из таких сообществ я и хочу представить: iOS Developers RU — Slack-сообщество для русскоязычных iOS-разработчиков. Создано с целью объединения, обмена опытом, а также просто для живого общения среди iOS-разрабочтиков (OS X и смежные темы также обсуждаются там ;)).


Вот так мы выглядим:


На сайте http://ift.tt/17z63D6 есть форма для ввода адреса email (в Slack можно попасть по приглашению, отправленному на email). Присоединяйтесь к нам, приглашайте друзей и коллег!


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


To Git, or not to Git


сегодня в 13:36


Здравствуйте!

Издательство «Питер» рассматривает возможность выпустить книгу по системе управления версиями Git.

Хотим посоветоваться с будущими читателями – какое издание вам будет наиболее интересно в русском переводе?

Мы сразу исключили книги ознакомительного уровня и выбрали три кандидатуры:

1. Новое издание (декабря 2014) книги Скотта Шакона

image

(Хотим сразу предупредить, что данная книга получится самой дорогой — розничная цена может достигать 1500 руб. Необходимо учесть это при принятии решения)


2. Классику от O'Reilly

image


3. Более краткое, но не сильно уступающее в информативности издание от Manning

image


Просьба голосовать:



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






Похожие публикации



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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Подборка интересных CSS-рецептов «Голые пятницы #4»

голые пятницы

Здравствуй, дорогой читатель хабра!

Сегодня мы поговорим о «липких» блоках, новом свойстве для изображений object-fit, продвинутом использовании CSS-счетчиков, ключевом слове currentColor, и о том, есть ли анимация в z-index.



position: sticky




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

position: sticky animation


Пока что поддерживают это свойство только Firefox и Safari последних версий, но для других браузеров можно просто задать любое другое позиционирование:



.sticky {
position: static; /* указываем перед sticky */
position: sticky;
top: 0px; /* обязательно указываем позицию элемента */
}




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

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


background-size для или волшебное свойство object-fit




Помните, как часто вам хотелось задать изображению свойство background-size? Ведь это так удобно: не нужно высчитывать ширину, высоту, следить за пропорциями. Так вот, теперь для этого есть замечательное свойство object-fit, которая очень неплохо поддерживается webkit-браузерами, и будет поддерживаться firefox-ом со следующей версии. Для всего остального есть полифил.

Принцип работы object-fit такой же, как у background-size для фоновых изображений, с той лишь разницей, что оно применяется для изображений, видео и других медиа-элементов:



.image__contain {
object-fit: contain; // изображение ужимается или растягивается, чтобы полностью поместиться в область с сохранением пропорций
}
.image__fill {
object-fit: fill; // растягивается на всю область блока
}
.image__cover {
object-fit: cover; // растягивается на всю область блока с сохранением пропорций и центрируется
}
.image__scale-down {
object-fit: scale-down; // изображение ужимается (но не растягивается!), чтобы полностью поместиться в область с сохранением пропорций
}




css object-fit

Пример

Продвинутые CSS счетчики




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

Начнем с чего-нибудь простого. Например, с пэйджинации:








.pages {
counter-reset: pages;
}
.pages a {
counter-increment: pages;
}
.pages a:before {
content: counter(pages);
}





Как видите, номера прописываются автоматически с помощью CSS. В реальных проектах это бесполезно, но, согласитесь, выглядит довольно забавно:)



Пример.


Также можно считать отмеченные пользователем элементы:







Total selected:




.languages {
counter-reset: characters;
}
input:checked {
counter-increment: characters;
}
.total:after {
content: counter(characters);
}





Здесь мы увеличиваем значения счетчика на каждый отмеченный чекбокс с помощью селектора input:checked и выводим его значение.



Пример


Также можно написать небольшой калькулятор:







Sum




.numbers {
counter-reset: sum;
}

#one:checked { counter-increment: sum 1; }
#two:checked { counter-increment: sum 2; }
#three:checked { counter-increment: sum 3; }
#four:checked { counter-increment: sum 4; }
#five:checked { counter-increment: sum 5; }
#one-hundred:checked { counter-increment: sum 100; }

.sum::after {
content: '= ' counter(sum);
}





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



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


currentColor




Уже достаточно давно (с момента выхода IE9) мы можем пользоваться замечательным, но малоизвестным ключевым словом currentColor. В чем его суть? Оно содержит в себе текущее значение цвета элемента, как наследованное от родителя, так и не наследуемое по умолчанию. Это позволяет не задавать по многу раз один и тот же цвет и иногда не вводить переменные (если вы работаете с препроцессорами).

Например, это может быть очень полезно при работе с SVG-иконками, которые меняют свой цвет в зависимости от состояния родителя. Обычно, нам пришлось бы записать это примерно так:



.button {
color: black;
}
.button:hover {
color: red;
}
.button:active {
color: green;
}

.button svg {
fill: black;
}
.button:hover svg {
fill: red;
}
.button:active svg {
fill: green;
}




но currentColor делает код куда лаконичнее:

svg {
fill: currentColor;
}

.button {
color: black;
border: 1px solid currentColor;
}
.button:hover {
color: red;
}
.button:active {
color: green;
}




Еще один пример полезной области применения — псевдо-элементы:

a {
color: #000;
}
a:hover {
color: #333;
}
a:active {
color: #666;
}

a:after,
a:hover:after,
a:active:after {
background: currentColor;
...
}


Transition для z-index




Возможно, вы не догадывались, но свойство z-index также поддерживает анимированные переходы. Однако, оно меняет значения по шагам, поэтому кажется, что никакого перехода нет. Но он есть! Не дайте себя обмануть!

z-index transition


Отличный пример того, как это работает.


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


P.S. Отдельное спасибо пользователю DonSinDRom, который буквально засыпает меня ссылками на новые интересные css-фишки после каждой статьи :)



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



This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


API в реальной жизни: Как облегчить задачу создания сайтов для поиска и покупки автозапчастей

image

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


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


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


Что требуется от API в отрасли поиска автозапчастей




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



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

Отсутствие такой информации — серьезный минус магазина, который приводит к снижению продаж.


image


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


Ниже представлен пример JSON-выдачи заменителей для тормозного диска VOLKSWAGEN модели 6R0615301:



{
"brand": "VAG",
"number": "6R0615301",
"crosses": [
{
"brand": "Brembo",
"number": "09.7011.11",
"numberFix": "09701111"
},
{
"brand": "Febi",
"number": "14404",
"numberFix": "14404"
},
{
"brand": "Jurid",
"number": "562040J",
"numberFix": "562040J"
},

.........................

{
"brand": "Zimmermann",
"number": "100 1233 20",
"numberFix": "100123320"
}
]
}




Поиск



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

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


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


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


Интерфейс API ABCP позволяет осуществлять операции поиска разной степени сложности.


Например так выглядит поисковый запрос для поиска детали по номеру:


http://ift.tt/1w4eTEX


Ответ на подобный запрос может выглядеть как-то так:



[
{
"brand": "Hepu",
"articleCode": "P999",
"articleCodeFix": "P999",
"articleId": "2854918",
"description": "Антифриз [синий] 1,5л.",
"availability": "-1",
"deliveryPeriod": 3,
"price": 231,
"weight": "0"
},
{
"brand": "Febi",
"articleCode": "01089",
"articleCodeFix": "01089",
"articleId": "37367",
"description": "Антифриз 1.5 л синий",
"availability": "1943",
"deliveryPeriod": 3,
"price": 233,
"weight": "1.76"
},
{
"brand": "Vaico Vemo",
"articleCode": "V600020",
"articleCodeFix": "V600020",
"articleId": "4144961",
"description": "Антифриз g-11 1.5л. концентрат",
"availability": "-2",
"deliveryPeriod": 3,
"price": 235,
"weight": "0"
}
]




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

Более подробное описание API и, в том числе, возможностей по поиску, представлено на специальной вики-странице.


Добавление каталога TecDoc на свой сайт



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

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


image


Обычно TecDoc в Рунете используют контрафактно, для чего разработчикам приходится не только приобретать базу данных (она занимает несколько DVD-дисков), но и писать большое количество «костылей» для конвертации информации из DVD в MySQL (или платить умельцам-конвертаторам $50-$200 за базу). Размещение этого гигантского объёма данных влечет дополнительные затраты.


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


Чтобы облегчить жизнь разработчикам, мы создали сайт tecdoc.abcp.ru — на нем можно взять исходные коды для установки лицензионной версии каталога TecDoc на внешние сайты (база постоянно обновляется). Для нестандартного использования данных каталога разработчикам доступны API-функции, набор которых мы постепенно увеличиваем.


Мы — не единственные официальные реселлеры каталога TecDoc на рынке стран СНГ, но единственные, кто сумел снизить себестоимость использования официальной версии всего до 2000 рублей в месяц.


Подробнее про работу с каталогом TecDoc мы расскажем в одном из следующих топиков.


Как еще улучшить сайт по поиску автозапчастей




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



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

На сайте поисковика автозапчастей 4mycar.ru мы создали специальную кнопку и «скрывающиеся» за ней автоэксперты подберут нужную запчасть по VIN-коду.


image


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


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


На сегодня все, спасибо за внимание! В следующих топиках мы расскажем об инфраструктуре нашего поисковика автозапчастей 4mycar.ru, создании сервиса «Биржа VIN-запросов» и том, как наша служба техподдержки справилась с 60 тысячами тикетов за 5 лет работы.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Часть команды, часть корабля

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





Руководитель отдела разработки веб-интервейсов Тинькофф Банка ekubyshin решил поделиться, как строилась и проходила работа по этому масштабному проекту.

Подготовка

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


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


Очевидно, что часть работ совершенно не зависит от постановки задачи или от сроков подготовки дизайнов.


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


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


1. подбор технического лида, который будет прорабатывать архитектуру и вести весь проект;

2. подбор команды и ее доукомплектование;

3. проработка архитектуры проекта;

4. выбор технологий, фреймворков и инструментов;

5. построение рабочего прототипа без дизайнов.


Команда — залог успеха

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


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


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


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

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


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


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


Архитектура и не только

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


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


Это удалось сделать поэтапно, поначалу вытащив лишь на 50% каждого разработчика, а потом уже сказав альфа-самцовое «ТАК НАДО» и вытащив нужных разработчиков на 100% — и вот тогда уже дело пошло.


И чем же мы занимались? А примерно следующим.


Потратили с неделю или две на анализ различных фреймворков, их особенностей и возможностей, на всякие холивары и игры в to do-листы. Проанализировали свой опыт использования BackboneJS, EmberJS, прикинули примерно на новый проект и поняли, что по многим причинам они не подходят для него. Поиграли с разными сборщиками, порассуждали, что лучше — BEM или SMACSS, LESS или Stylus и так далее. В общем, не отказывали себе практически ни в чем, но при этом старались не углубляться, так как времени было не так уж и много.


После всех поисков остановились на AngularJS, так как он давал ряд преимуществ:


1. two-way data-bindings;

2. куча готового в «коробочном» решении: сервисы, кэширование, dependency injection, routing, довольно удобный синтаксис директив и фильтров;

3. новый и интересный фреймворк, под который можно набирать новых разработчиков довольно неплохо и быстро;

4. интерес разработчиков к проекту на новом фреймворке;

5. довольно быстрый и простой вход, что нам было очень важно.


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

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


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

Эта часть работ состояла из:


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

2. роутинга и отрисовки страниц;

3. сборки и деплоя проекта на тестовые стенды;

4. Request service для работы с нашим API с возможностью кэширования и валидации.


Параллельно начали пробовать NodeJS + Express + MongoDB, чтобы подготовить платформу секретного проекта.


В итоге получился следующий стек технологий:


1. AngularJS

2. RequireJS

3. BEM (но только нейминг, без bemtools)

4. LESS

5. Gulp


Для нового ИБ достаточно, чтобы nginx отдавал index.html со сборкой и всей статикой, а все остальное оставалось на плечах браузера.


На выходе получилась платформа а-ля AngularJS Bootstrap, на которой можно строить SPA приложения. Кто знает, возможно, мы опубликуем код на GitHub.


А вот и первые дизайны

Когда прототип уже был в принципе готов, подоспели и дизайны.


Что дальше? Браться сразу верстать и натягивать на готовый код? Нет! Теперь предстояло составить планы запуска и разработки, диаграмму ганта (для Сами-знаете-кого) и доказать, что если делать новый интернет-банк, то полностью новым, не только в дизайне и функционале, но и технически новым.


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


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


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

2. готовый прототип, который уже работает и на котором можно наращивать бизнес-логику;

3. доверие к команде, которое мы заслужили кропотливой работой.


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


Процесс

К черту SCRUM, даешь KANBAN! Невозможно представить, как вообще можно разработать столь большой и сложный проект, требования к которому постоянно меняются в ходе работы, используя SCRUM, тратя время на планирование и спринты. Тут SCRUM не будет работать — он хорош только тогда, когда проект уже запущен и есть какой-то road map.


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


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


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


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

2. добавили задачи по архитектуре, общие модули, UI-компоненты;

3. еще раз пробежались по всем задачам и декомпозировали их на более мелкие, выделяя ресурсы по HTML-кодингу и JS-кодингу.


В итоге получился огромный пул задач и дата запуска проекта. Работа закипела.


О запуске

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


1. тестирование нашим отделом QA;

2. внутреннее тестирование, в котором участвуют все сотрудники банка;

3. запуск приватной бета-версии на ограниченный круг лиц;

4. публичный запуск.


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


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


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


Кризис проекта

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


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


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


Front-end-команда Тинькофф Банка — профессионалы своего дела и ответственные разработчики. Как руководитель, я очень горжусь ими, без них проект не получился бы таким успешным.


Один из разработчиков сказал фразу из известного фильма «Пираты Карибского моря», которая запала всем в душу: «Часть команды, часть корабля». Это действительно так, команда работала, повторяя эту фразу, и не унывала. Даже тогда, когда было совсем не до смеха, работа не прекращалась.


Именно командный дух позволил довести проект до завершения в столь короткие сроки.


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


На скорую руку набросали табличку в Google Docs, собрали пул распределенных по важности задач и приступили к разгребанию. Каждый день разработчика, который исправлял больше всех багов, награждали чем-то вкусным или полезным: это были тортики, пирожные, хорошие термокружки или просто бодрящий кофе. В первый день, конечно, это не особо работало, но благодаря тому, что у одного разработчика команды еще оставались силы, он на своем примере показал, что можно разгрести кучу багов за день, и получил за это кое-что вкусное в качестве приза. На следующий день уже подтянулись другие разработчики, у команды появилось второе дыхание. Да, немного пришлось слукавить — все разработчики получили призы, и каждый день их было не по одному, а даже по несколько, дабы поддержать боевой настрой. Это сработало, а отсюда вывод — не бойтесь жертвовать своими финансами и личным временем ради проекта, ведь команда и ее настрой намного важнее всех этих мелочей.


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


P.S.

1. Команда и ее настрой решают всё.

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

3. Не надо бояться экспериментировать.

4. Стоять на своей точке зрения и доказывать ее перед начальством и бизнесом.

5. Уважать свою команду, верить в нее и в результат.

6. Быть оптимистом: вся команда следит за своим лидом и реагирует так, как он.


Приходите в Тинькофф банк — у нас очень круто и интересно, мы не боимся экспериментировать и создавать крутые сервисы.


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Бильярдный бот: история создания

Привет, хабрахабр!




О чём эта статья?




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

Предисловие




У всех нас есть любимые игры и виды спорта. Здорово, когда первое совпадает со вторым. Помимо своих увлечений спортом и спортивными проектами, я люблю также и некоторые компьютерные игры. Одна из моих любимейших игр, и вживую, и виртуально — это, конечно же, бильярд. Бильярд, пул, снукер… как угодно, — я люблю их все! Я разделяю мнение многих о том, что, например, снукер — это «недискретные» шахматы. Мало просто забивать последовательность определённых шаров в лузы, там ведётся ещё и невероятная стратегическая борьба. Борьба за снукеры, за позиции… а какой фантастической техникой обладают профессиональные бильярдисты — просто молчу в тряпочку.

Достоинства этой несомненно аристократической игры можно перичислять очень долго. Но перейдём к сути статьи. Моя самая любимая игра в бильярд вот уже пять лет и по сегодняшний день — это «Pool Billiard» на Facebook. Она классно сделана не только эстетически, но и технически. Невооруженным глазом видны классно написаный физический движок, продуманный геймплей, клиент-серверная валидация действий, обработка ошибок, дизайн, система статистики, магазин, чат в конце концов. Игру явно делали профи, да и она в топах. В неё очень приятно играть… и выигрывать!

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

В общем, да, я решился! Добро пожаловать под кат! :)



Создание




Перед началом создания я, имея на тот момент уже довольно большой опыт игры в Pool Billiard, всё тщательно продумал с карандашом и листочком в руках. Конечно, всё предугадать невозможно, но «скелет» был создан. Внесите скелет! Для создания бота я использовал C++. Написание и отладка кода в факультативном режиме по вечерам заняли у меня почти четыре месяца. Согласитесь, сложно отлаживать что-то на закрытой системе, особенно когда необходимо оттестировать некоторый редкий или исключительный случай, который случается в одной из ста партий. Я ведь не могу фривольно расставить шары и смоделировать ту или иную ситуацию на столе.


Как робот взаимодействует с игрой?




Я понял, что бот будет взаимодейтсовать с браузером посредством полной имитации человека: распознавания образов и имитации человеческого ввода: мышь, клавиатура. Слава богу, перед каждым ударом не надо вводить капчу ;))) Так я и сделал: люблю системы имитации поведения человека, от которого браузеры так беззащитны!

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

Какова общая логика работы?




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


Какова детальная логика работы?




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

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

Далее сервер случайно определяет, кто начнёт игру: я или оппонент. Бот ожидает начала своего хода. На весь ход даётся только 20 секунд. За это время надо успеть определить, что ход уже мой, считать со стола шары, очки, правильно их распознать, найти нужный удар и успеть его осуществить.

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

После этого бот рассчитывает и наносит удар. (самый короткий, но самый важный абзац)

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

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

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

State-машина в общем примерно такая.

Как игра находится на экране?




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

Маркер игры — это неизменяемая часть изображения игрового окна.

Как определяется количество денег?




Тут мне просто повезло. Мне не пришлось ставить снифер на браузер и слушать трансляцию игры для определения количества денег или распознавать символы с экрана. Всё прозаично: дело в том, что в игре не всё сделано на флеше. Сам элемент с суммой моих монет — простое текстовое поле, легко выделяемое мышью. Я просто имитирую следующее: подвожу мышь к началу поля, зажимаю ЛКМ, провожу вправо до конца поля, отпускаю ЛКМ. В результате бот попросту выделяет поле с деньгами. Потом я копирую его в буфер обмена, удаляю спецсимволы типа пробелов, запятых и точек… и всё! Сумма монет у меня в кармане ;)

Игровые монеты — это просто выделяемое поле.

Как выбирается комната для игры?




Есть несколько комнат для игры, каждая из которых содержит определённую ставку (плату за вход). Два оппонента заходят в комнату, ставя каждый, например, по 100 монет. Победитель получает выигрыш в 140 монет (не 200, как это можно было ожидать (это сделано гейм-дизайнерами для того, чтобы постоянно из игры утекала масса денег, и люди были вынуждены пользоваться магазином и докупать себе монет за реальные деньги и играть, либо ждать пока ежедневное колесо фортуны не принесёт им монет для игры)). Проигравший уходит ни с чем. Логика проста.

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

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

В дорогих комнатах тусуется элита: либо суперпрофи, либо платники с купленной системой навигации, подсказок и так далее. Скорей всего, там светит проигрыш.

У бота есть несколько режимов выбора ставки: принудительные режимы (когда бот всегда ставит ту сумму, которую я ему указал) и авторежим. Авторежим сделан очень интересным образом. Я собрал статистику выигрыша ботом в комнатах разных ставок и на её основе написал весьма интересную программу. Она всего лишь выдаёт на выход несколько чисел, но очень важных чисел. Это так называемые пороговые количества монет. Например, если у нас 300 монет, бот имеет право играть только в первой комнате, а вот после преодоления порогового количества в 740 монет бот уже имеет право играть в первой и во второй комнате. Разумеется, бот стремится играть в самой дорогой комнате.

Так вот, программа интересна тем, что она автоматически подбирает эти самые пороговые значения по некоторому алгоритму, который она же сама дальше и использует для предсказания рисков последующего проигрыша. Она имитирует миллионы партий и принятий решения об игре в той или иной комнате (обстрел Монте-Карло) и выдаёт правильные пограничные количества монет. Причём, я заметил, алгоритм — сходящийся!

Непосредственно вход в комнату осуществляется нажатием на определённый фрагмент экрана, где расположена кнопка входа и осуществления определённой ставки.

После выбора комнаты жмём на соответствующую кнопку.

Как определяется возможность хода?




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


Как распознаются шары?




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

Как только я не пытался по-другому искать шары! И с помощью определения теней, и с помощью метода границ Канни, и с помощью хитроумных классификаторов: все эти и многие другие методы были очень низкорезультативны и неточны по сравнению с методом, написанным лично мной. Я даже натравливал на статистику Wolfram Mathematica, но особо чёткого ответа это мне не дало. Может, плохо натравливал.

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

Я перебрал целое море решений, отбрасывая их из-за недостаточной точности, и, наконец, сам написал самый точный из них. У меня получилось решающее дерево, в каждом узле которого — самописный классификатор. Для каждого найденного шара я определяю цветовые характеристики: средний {R, G, B}, средний {H, S, B}, самый яркий и самый тёмный цвет без учёта цветовых пятен. Потом я однозначно нахожу биток как самый белый шар. Далее пытаюсь определить восьмёрку как самый тёмный шар. После всего, необходимо разделить цветные от полосатых. Но непросто ларчик открывался: я составлял трёхмерные таблицы выборок, фотографировал по тысяче раз шары в попытках обучить сеть, но всё же мне так и не удалось построить однозначно разделяющую кластеры сплошных и полосатых шаров гиперплоскость. Всё равно ошибки встречаются. Для этого случая я сделал небольшую коррекцию-ошибок-на-лету. Об этом — чуть ниже.

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

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

Как определяется возможность разбития пирамиды?




Тут я было расслабился: создаём простой классификатор позиции пирамиды (каждый шар стоит в точке пирамиды) и если наш ход и наш ход — первый, то разбивам. Но не тут то было! Что-то не срабатывало! Оказывается, хитрые программисты добавили некоторый шум в начальные позиции шаров в пирамиде. Умно! А иначе можно было бы записать «идеальную партию» — безошибочную серию ударов, ведущую к победе, и, постоянно воспроизводя её, выигрывать. Но они предусмотрели такую вот защиту. Чуть подкорректировав классификатор, я достиг успеха в распознавании момента разбития пирамиды.

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


Как определяется количество очков на столе?




Возле моей аватарки и аватарки оппонента есть стек из забитых шаров. Он представляет из себя полосу с помещёнными туда забитыми шарами чуть меньшего радиуса, чем на столе. Однако, как быть? Одного распознавания шаров на столе мало, чтобы понять, какую серию я играю — полосатых или сплошных. Значит, надо распознать не только количество шаров в стеках, но ещё и сами шары! Пфффф… алгоритм «вычитания стола» тут не подходит, да и размеры не те.



невероятным усилием воли я заставил себя написать ещё один распознаватель



и вторым невероятным усилием — объединить оба этих распознавателя в одну универсальную функцию распознавания. Фактически, пришлось всё писать с нуля. Вот она — проблема недостаточного планирования. Но зато теперь я указываю регион и размер шаров, а на выходе получаю координаты и характеристики каждого шара! Победа разума!

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


Как определяется какие шары бить?




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


Как определяются фолы противника?




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

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

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


Как вычисляется угол и сила удара?




Итак, передо мной возникла задача за заданное время при заданной конфигурации шаров на столе и заданном правиле нанесения ударов рассчитать все возможные результативные удары и выбрать среди них лучший. Возможные значения заданного времени: {20 секунд}. Возможные конфигурации шаров: их?.. Возможные правила нанесения ударов: {любые кроме чёрного, только сплошные, только полосатые, только чёрный}.
Забивание шара



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




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




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




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




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



Лузы разные. И попадание в них подчинено разным законам. Например, в угловую лузу можно попасть разными способами:




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




Я запрограммировал бот как раз целиться в эти точки. Единственное ограничение, которое налагают на себя лузы длинных бортов: залетание в них шаров возможно только в створке 70 градусов. Это также заложено в код.
Моделируемое пространство



Я решил отказаться от рикошетов. Нет нет, не пугайтесь, не в том смысле, конечно же ;) Построить такое моделируемое пространство, чтобы вообще забыть о рикошетах. Они в игре есть и остаются, а вот в пространстве их не будет. Над решением этой задачи я работал лишь вечер. Я понял: соударение шара радиуса R с бортом толщиной 0 — это то же, что и соударение материальной точки радиуса 0 с бортом толщиной R:




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




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

В моделируемом пространстве все удары — прямые линии, рикошетов нет. А вот на столе они уже есть.

Максимальные сила удара и путь битка



Отлично, масса работы проделана! Но есть ещё чрезвычайно важный вопрос, которого мы вообще не коснулись: какой максимальный путь может пройти биток? Какая в нём энергия? Я не хочу каждый раз лупасить шары со всей силы; это будет подвергать биток риску случайного скатывания в лузу. Я хочу очень аккуратно и точно рассчитывать силу удара. Для этого мне нужно знать при какой оттяжке кия от битка в пикселях происходит минимальный удар и какой он длины, и при какой оттяжке кия от битка в пикселях происходит максимальный удар и какой он длины. Также мне надо знать какая энергия отбирается у шара при ударе о борт. Теперь осталось всё это выяснить. С минимальными ударами всё прозаично: это меряется в любой момент. С максимальными гораздо, гораздо сложнее. Диагонали стола не хватит даже для среднего удара, поэтому я явно померять длину его пути не могу. Я придумал разложить это на два случая: горизонтальный и вертикальный. Произведя потом соответствующие замеры и приравняв уравнения вместе я бы получил полную длину максимальной траектории шара и коэффициент гашения энергии о борт. Так я и сделал. Я выждал моменты, когда противник получил фол, чтобы аккуратно поставить биток прямо к краешкам бортов, да так, чтобы горизонтальному и вертикальному движению битка ничего не мешало. И дважды ударил со всей силы: один раз горизонтально, другой — вертикально. Снял показатели: при вертикальном ударе биток отскочил от длинных бортов 8 раз и, докатившись на остатке энергии ещё какой-то путь, остановился. При горизонтальном ударе число отскоков составило 5 с хвостиком. Отсюда, зная размеры стола, нетрудно получить уравнения, численно решив которые мы получим и полную длину максимального пути, и коэффициент гашения.

Пусть x — максимальная длина пути качения шара без ударов о борты в пикселях, а p


  • x —— в начальный момент времени самого сильного удара шар обладает полной энергией качения на максимальную длину пути x;

  • x – w —— такой энергией и, соответственно, запасом пути обладает шар, прокатившийся по горизонтали от борта до борта на ширину стола w;

  • p · (x – w) —— ударившись о борт, шар потерял энергию и, следовательно, запас пути согласно коэффициенту p;

  • p · (x – w) – w —— прокатившись снова от борта до борта шар ещё раз потерял запас пути, равный ширине стола;

  • p · (p · (x – w) – w) —— шар снова ударился о борт и потерял энергию согласно p;

  • p · (p · (x – w) – w) – w —— опять качение от борта до борта, уже третье;

  • p · (p · (p · (x – w) – w) – w) —— очередной удар о борт и потеря энергии;

  • p · (p · (p · (x – w) – w) – w) – w —— четвёртая потеря энергии и запаса пути при качении от борта до борта;

  • p · (p · (p · (p · (x – w) – w) – w) – w) —— удар о борт;

  • p · (p · (p · (p · (x – w) – w) – w) – w) – 388 —— пятое, последнее качение от борта: шар полностью потерял всю энергию и запас пути и останавился на расстоянии 388 пикселей от края борта.




Приравниваем последнее уравнение к нулю. Аналонично получаем уравнение для вертикального случая. Решая эту нелинейную систему из двух уравнений с двумя неизвестными численно, я получил искомые x и p.
Цепные правила



Разумеется, глубина удара может быть любой, лишь бы энергии хватило. Но из-за накопления погрешностей я решил остановиться на числе 4, то есть максимальное количество задействованых объектов (включая лузы) равно 4. Мой биток может ударить шар А, который ударит шар Б, который упадёт в лузу. И то (!) точность теряется настолько сильно, что я ввёл целый ряд ограничений на подобные удары. Они должны быть без рикошетов о борты, расстояния между шарами и лузой должны быть примерно равны и углы атаки близки к упругим ударам. А вот такая фантастика мне пока не светит:


Откат битка



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

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



Вот это западлянка. Оказывается, в игре учитывается ищё и инерция кручения шара и его направление. Пришлось почти четыре вечера писать и тестировать нелинейную функцию рассчёта траектории отката. Такие мелочи бесят, но в конце оказывается, что это вовсе и не мелочи. Более менее точную функцию мне определить удалось только методом проб и ошибок. Но я сделал это! Теперь я могу смело выбрасывать удары, проводящие биток через лузы в результате отката.
Функция поиска лучшего удара



В результате анализа конфигурации стола я получал коллекцию большого количества возможных ударов. Но как выбрать лучший из них? Надо включить мозги и оценить каждый параметр удара. Подумаем логически, чего бы нам хотелось? Число рикошетов пути от битка до целевого шара желательно бы свести к минимуму: каждый рикошет вносит маленькую, но погрешность. Расстояние от битка до целевого шара должно быть как можно меньше, но не совсем маленьким, чтобы была возможность качественно прицелиться. Как ни странно, это два разных условия. Подумайте над этим. Далее. Расстояние от целевого шара до лузы, в которую он должен упасть, должно быть как можно меньше. Безусловно. И этот путь должен содержать минимум рикошетов, желательно — ноль. Удар желателен средней силы, не слишком сильный (погрешности кручения и физического движка игры) и не слишком слабый (погрешности уже моего бота). Удар битка о целевой шар должен быть максимально «прямым» и упругим: касательные — зло! Отсюда автоматически вытекает минимальная длина отката. Из моего продолжительного подводного плавания и анализа подводных камней вытекли ещё дополнительные условия (не столь важные, но всё же значимые): надо, чтобы целевой шар в угловые лузы летел под углом, максимально близким к 45 градусам, а в лузы длинных бортов — под углом не более 35 градусов. Есть ещё некоторые дополнительные условия, но о них умолчу.

























Но по самой сути все эти вещи далеко не идеально сравнимы и друг с другом, и между собой. Например, если угол атаки (упругость удара) ещё как-то сравним с другим углом атаки (скажем, 0 градусов лучше, чем 50), то такая характеристика, как сила удара…? Для разных длин траекторий нужны разные силы, ведь там и расстояние разное, и резка. Если же перевести силу удара в некоторый относительный показатель, то что делать с резкой? Ведь для разных ударов она — своя! А как сравнивать несравнимые категории, такие, например, как число рикошетов (число от 0 до, скажем, 10) и угол влёта шара в лузу длинного борта? Что важнее?

Пффф… Передо мной возникла ещё одна существенная проблема. Необходимо было привести все эти характеристики к некоторой консистентности. Скажем, к числовому диапазону от 0 до 1, где 0 — чудовищно плохо или невозможно, а 1 — абсолютно идеально хорошо. В этом и заключался следующий огромный шаг работы; поиск и выбор таких функций, которые бы отражали (и адекватно отражали) все представленные характеристики в коридор [0, 1]. А это огромная работа. Взять число рикошетов. Да, оно может быть от 0 до 10, но в среднем нормой считается 1 или 2 рикошета. 8 — это уже исключительная ситуация, встречающаяся в игре крайне редко. Посему линейно переводить диапазон [0, 10] в отрезок [0, 1] глупо и неоправдано. Пришлось искать «обратные» функции распределения для всех характеристик, подгонять, выкапывать их, высасывать из пальца. В результате всё получилось довольно сносно, но с меня сошло 77 потов.

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




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



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


Адаптивный поиск за 20 секунд



На всё про всё у нас есть всего 20 секунд, и бот может не успеть найти удар. Поэтому высокоприоритетным потоком я ищу так называемые аварийные удары. Это удары, имеющие целью не забитие шара, а хотя бы прикосновение к своему шару. Если в течение 15 секунд поиска бот так и не нашёл ни одного удара на забивание, он немедленно реализует аварийный удар на прикосновение для избежания фола. А сам поиск удара я сделал адаптивным. Сначала в качестве моделируемого (того самого зеркального) пространства берётся сам стол как он есть. Если ни одного удара найдено не было, то моделируемое пространство увеличивается в три раза по горизонтали и вертикали: к столу слева и справа «приставляется» его вертикальное отражение вместе с шарами и лузами, а сверху и снизу — горизонтальное отражение. Если удар не найден и тут — происходит ещё одно достраивание моделируемого пространства. И так до тех пор, пока либо не пройдёт 15 секунд, либо число «блоков» стола не превысит максимально допустимого числа рикошетов; дальше прокатиться у шара попросту не хватит энергии.

Надо забить зелёный шар:
Прицеливание и удар



Казалось бы, достиг всего: есть и прицельная точка, и сила удара. Осталось только его осуществить. Но и тут я наткнулся на трудности, а, точнее, либо на неточность программирования, либо на скрытую защиту от товарищей программистов игры Pool Billiard. Но обо всём по порядку. Между прицельной точкой и битком находится угол удара. Допустим, он равен 27 градусов:




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




Ну вот, казалось бы, и всё! Ставим мышку в нужный пиксель, делаем оттяжку на заданное количество пикселей к битку и отпускаем. Но нет. Не туда шар летит! Что за проклятье!? В части случаев шар бьёт идеально, в части как-то сомнительно, а иногда совсем не туда! Я потратил 4 дня, чтоб найти это плавающую ошибку. Перелопатил весь код. Оказалось, что дело совсем не на моей стороне: мышка физическая (экранная) не совпадала с мышкой игровой. На флеше ведь сделано. То ли это баг, то ли фича, но их координаты отличаются на 4 пикселя горизонтально и на 1 вертикально. Каким-то чудом я это случайно заметил; введя соответствующие поправки, получил отличный результат.
Коррекция ошибочного распознавания шаров



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

Предположим, бот неверно распознал шар на столе. Пусть для определённости бот думает, что шар полосатый, когда на самом деле он сплошной. Бот забивает полосатые. Волей случая именно этот шар был выбран под удар. Что же происходит дальше? Если бот нанесёт удар не по своему шару, он получит Fault и пропуск хода. А это нехорошо. Я сделал так: если в момент прицеливания по шару (если нет рикошетов) в интерфейсе игры возле шара загорается зелёная стрелочка, то можно бить.




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

… логика куда хитрей. Ведь возможна и такая ситуация: начало партии; бот забивает первый и единственный шар. На самом деле он сплошной, тогда как бот думает, что забит полосатый. Это как раз вторая ситуация из описанных в начале раздела. Что же будет происходить дальше? Бот будет абсолютно уверен, что ему необходимо забивать полосатые, когда на самом деле — сплошные. Бот будет целиться в первый полосатый шар — красная стрелка, во второй полосатый шар — опять красная стрелка, в третий — то же самое. На самом деле этот процесс не будет длиться до тайм-аута. Я запрограммировал так: если более половины шаров на столе дают красную стрелку, значит попросту неправильно распознан тип шара в стеке забитых шаров. Тогда бот немедленно переклассифицирует тип забиваемых шаров с неправильных полосатых на правильные сплошные и ищёт уже удары по сплошным, выводя себя из крутого пике. И это всё — если останется время.

Как происходит чат?




На войне любые средства хороши. А, значит, можно и нужно искать такие средства. Например, средства воздействия на противника. Игра по сути полностью закрывает оппонентов друг от друга. Они взаимодействуют только посредством игрового стола. Игрового стола и… чата! Да, в игре есть чат. И это максимальный из доступных канал вербальной коммуникации. А, значит, воздействия. Что можно сделать через чат с противником? Призвать его сдаться, конечно же, не получится, но можно здорово поотвлекать его внимание и даже потрепать нервы! Ай да метода!

Я решил не заморачиваться со связностью транслируемого текста. Ведь чат — вторичная задача. Пусть это будут просто некие короткие сообщения.

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

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

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

Вот и всё :)




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

Немного видео




Размещаю для вас презентационное видео очень старой версии бота:



И видео новой версии многочасовой игры в автоматическом режиме:


Послесловие




Дорогие друзья! Если вам понравилась эта статья, смею предположить, что вам понравятся и другие. С уважением, Андрей Миронов.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.