Соглашение с ФАС обязывает Google возобновить работу своих сервисов в Крыму

image

Корпорация Google 17 апреля подписала соглашение с ФАС. Помимо прочих условий, этот документ обязывает компанию дать возможность выбора поисковой системы пользователям на всей территории России, пишут «Известия».

«Для устройств, которые находятся в обращении на территории РФ, Google разработает окно выбора, которое при очередном обновлении предоставит пользователям возможность выбора поисковой системы по умолчанию», — объяснил замруководителя ФАС Алексей Доценко. Еще более интересным является нюанс относительно работы Google в Крыму. По мнению российских чиновников, сейчас американская компания обязана сделать свои сервисы доступными для всех жителей полуострова.

В ФАС говорят, что если компания не исполнит часть соглашения, касающуюся Крыма, то в этом случае последует обращение регулятора в суд: «Мировое соглашение действует на всей территории Российской Федерации для пользователей, которые пользуются Android с предустановленным магазином приложений и поиском Google. В случае неисполнения условий соглашения последует прямое понуждение через суд».

На данный момент жители Крыма не могут использовать магазин приложений Google Play напрямую. Если попытаться включить его, равно, как и некоторые другие сервисы, то пользователь из Крыма получит следующее сообщение: «Покупки не поддерживаются. Покупки не доступны в вашем регионе согласно регламенту (ЕС) № 1351/2014 Европейского совета и исполнительному приказу № 13685 президента США». У пользователей региона на данный момент нет показа платных приложений, а часть бесплатных — «не поддерживаются устройством». Решить проблему обходными путями можно, но напрямую работать с рядом сервисов Google в Крыму не получится.

Что касается соглашения, то к нему Google и ФАС шли два года. Еще в сентябре 2015 года ФАС признала Google нарушителем закона «О защите конкуренции» после заявления компании «Яндекс». По мнению регулятора, американская корпорация злоупотребляла своим доминирующим положением на рынке предустановленных приложений, включая поиск в ОС Android. В итоге ФАС предписала корпорации устранить нарушения до 18 декабря 2015 года. Google оспаривала это решение в судах, но без особого успеха. В результате ФАС и Google заключили мировое соглашение, предусматривающее выплату штрафа в размере 438 млн рублей и обязательство следовать российским законам.

«Чтобы соблюсти требования соглашения ФАС, Google обязана признать Крым российским и дать возможность жителям полуострова выбрать поисковую систему. В противном случае могут начаться новые разбирательства. IT-сервисы становятся ключевым общественным благом, а соблюдение западными платформами нашего законодательства влияет на экономику и IT-суверенитет страны. Уверен, это далеко не последнее антимонопольное дело. Российским разработчикам стоит чаще обращаться в суд», — заявил директор по проектной деятельности Института развития интернета Арсений Щельцин.
ссылка на оригинал статьи https://geektimes.ru/post/288676/

Dive into Ethereum

Сегодня платформа Ethereum стала одним из самых узнаваемых брендов блокчейн сферы, вплотную приблизившись по популярности (и капитализации) к Bitcoin. Но из-за отсутствия "полноценного" рускоязычного гайда, отечественные разработчики все еще не очень понимают, что это за зверь и как с ним работать. Поэтому в данной статье я попытался максимально подробно охватить все аспекты разработки умных контрактов под Ethereum.

Я расскажу про инструменты разработки, сам ЯП, процесс добавления UI и еще много интересного. В конечном итоге мы получим обычный сайт-визитку, но "под капотом" он будет работать на умных контрактах Ethereum. Кого заинтересовало — прошу под кат.

preview

Содержание

Введение в Ethereum

Эта статья не расчитана на тех, кто совсем не знаком с Ethereum (или технологией блокчейн вообще), поэтому объяснений базовых вещей вроде блоков, транзакций или контрактов здесь не будет. Я подразумеваю, что вы хотя бы чуть-чуть в курсе происходящего. В противном случае полистайте статьи из списка ниже, а потом возвращайтесь 🙂

Больше ссылок на интересные статьи вы найдете в конце.

P.S. Я работаю под Ubuntu 16.04, так что весь процесс установки, разработки и деплоя будет описан под эту ОС. Тем не менее все используемые инструменты кроссплатформенны (скорее всего, не проверял), так что при желании можете поэкспериментировать на других ОС.

Инструменты

Geth

Работа с Ethereum возможна через огромное число клиентов, часть из которых terminal-based, часть GUI и есть несколько гибридных решений. Своего рода стандартом является [Geth](), который разрабатывается командой Ethereum. Про него я уже писал в предыдущих статьях, но на всякий случай повторюсь.

Клиент написан на Go, устанавливается стандартным способом:

sudo apt-get install software-properties-common sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt-get update sudo apt-get install ethereum

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

Вот команда, которую я чаще всего использую в работе: $ geth --dev --rpc --rpcaddr "0.0.0.0" --rpcapi "admin,debug,miner,shh,txpool,personal,eth,net,web3" console

  • --dev запускает geth в режиме приватного блокчейна, то есть не синхронизирет основную / тестовую ветку. Вместо этого вы получаете стерильную цепочку без единого блока. Это самый удобный вариант в плане разработки, так как, например, майнинг блока занимает несколько секунд и нет никакой нагрузки на сеть или диск.

  • --rpc включает RPC-HTTP сервер. По сути это API к вашей ноде — через него сторонние приложения, вроде кошельков или IDE, смогут работать с блокчейном: загружать контракты, отправлять транзакции и так далее. По дефолту запускается на localhost:8545, можете изменить эти параметры с помощью --rpcaddr и --rpcport соответственно.
  • --rpcapi устанавливает что-то вроде прав доступа для приложений, подключенных к RPC серверу. Например, если вы не укажете "miner", то, подключив к ноде кошелек и запустив майнер, вы получите ошибку. В примере я указал все возможные права, подробнее можете почитать здесь.
  • console — как можно догадаться, эта опция запускает консоль разработчика. Она поддерживает самый обычный JS и ряд встроенных функций для работы с Ethereum, вот простой пример (пункт — Поднимаем ноду).

Parity

Geth довольно хорош, но в последнее время все чаще можно встретить другой клиент — Parity, написанный на Rust. Главным его отличием от Geth является встроенный web интерфейс, на мой взгляд, самый удобный среди всех ныне существующих. Установка:

sudo <(curl https://get.parity.io -Lk)

По окончании загрузки запустите в консоли parity и по адресу localhost:8180 можете найти сам кошелек.

parity

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

Единственный нюанс — своей консоли в parity нет. Но можно без проблем использовать для этих целей Geth:

$ parity --geth # Run parity in Geth mode $ geth attach console # Attach Geth to the PArity node (Do it in another window)

TestRPC

Этот инструмент, в отличие от предыдущих, будет полезен только разработчикам. Он позволяет одной командой testrpc поднять приватный блокчейн с включенным RPC протоколом, десятком заранее созданных аккаунтов с этерами на счету, работающим майнером и так далее. Весь список здесь. По сути, testrpc — это тот же geth --dev --rpc ..., только на этот раз не надо тратить время на создание аккаунтов, включение / выключение майнера и прочие рутинные действия.

Установка — npm install -g ethereumjs-testrpc.

testrpc

Mist

Самый популярный кошелек для Ethereum, хотя на самом деле он умеет намного больше. Вот отличная статья, где step-by-step объясняется весь процесс работы с Mist. Скачать самую свежую версию можно со страницы релизов. Помимо работы с кошельком, есть возможность работы с контрактами.

mist

Remix

Самая популярная IDE для разработки контрактов. Работает в браузере по адресу ethereum.github.io/browser-solidity/, поддерживает огромное число функций:

  • Подключение к указанному RPC провайдеру
  • Компиляция кода в байткод / опкоды
  • Публикация в Github gist
  • Пошаговый дебагер
  • Подсчет стоимости исполнения функций в газе
  • Сохранение вашего кода в localstorage
  • И многое другое

При этом нет автокомплита, что очень печально.

remix

Cosmo

Еще одна IDE для разработки умных контрактов, написана на Meteor, работает из коробки. Для начала откройте новый терминал и поднимите ноду с включенным RPC интерфесом geth --rpc --rpcapi="db,eth,net,web3,personal" --rpcport "8545" --rpcaddr "127.0.0.1" --rpccorsdomain "localhost" console. После этого можете запускать саму IDE:

$ git clone http://github.com/SilentCicero/meteor-dapp-cosmo.git $ cd meteor-dapp-cosmo/app $ meteor

Далее открываете localhost:3000 и можете начинать работать:

cosmo_screenshot

Etheratom

Последний на сегодня инструмент для ускорения разработки умных контрактов. Это плагин для редактора Atom, устанавливается с помощью apm install atom-ethereum-interface. Штука удобная, сам пользуюсь. Позволяет работать c JS EVM или подключиться к ноде через RPC. Компилирует контракт на CTRL + ALT + C, деплоит в сеть на CTRL + ALT + S. Ну и предоставляет неплохой интерфейс для работы с самим контрактом.

atom_ethereum

Если вам не нужен такой навороченный функционал внутри редактора, то для Atom есть отдельный плагин с подсветкой синтаксиса Solidity — language-ethereum. Последний по сути является плагином под Sublime text, только конвертированный для работы в Atom.

Solidity

Возможно, вы слышали про то, что можно писать контракты не только на Solidity, но и на других языках, например Serpent (внешне напоминает Python). Но последний комит в develop ветке ethereum/serpent был примерно полгода назад, так что, по-видимому, язык, увы, deprecated.

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

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

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

Создаем контракт-визитку

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

  • Имя, почта, контакты и так далее
  • Список проектов
  • Образование: вузы, курсы и тд
  • Навыки
  • Публикации

Первый шаг

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

pragma solidity ^0.4.0;  contract EthereumCV is Structures {     address owner;      // =====================     // ==== CONSTRUCTOR ====     // =====================     function EthereumCV() {         owner = msg.sender;     } }

Базовая информация

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

address owner; mapping (string => string) basic_data;

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

function getBasicData (string arg) constant returns (string) {     return basic_data[arg]; }

Здесь все просто, стоит только отметить модификатор constant — его можно (и нужно) использовать для тех функций, которые не изменяют state приложения. Главный плюс таких функций (sic!), в том что их можно использовать как обычные функции.

Администрирование

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

function setBasicData (string key, string value) {     basic_data[key] = value; }

Но в этом случае любой при желании смог бы изменить, например, наше имя, вызвав setBasicData("name", "New Name"). К счастью, есть способ всего в одну строку пресечь любые такие попытки:

function setBasicData (string key, string value) {     if (msg.sender != owner) { throw; }     basic_data[key] = value; }

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

modifier onlyOwner() {     if (msg.sender != owner) { throw; }     _; // Will be replaced with function body }  // Now you can use it with any function function setBasicData (string key, string value) onlyOwner() {     basic_data[key] = value; }

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

Модульность

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

Для этого в той же директории создадим новый файл structures.sol и библиотеку Structures. А уже внутри нее опишем каждую из структур:

pragma solidity ^0.4.0;  library Structures {     struct Project {         string name;         string link;         string description;     }      struct Education {         string name;         string speciality;         int32 year_start;         int32 year_finish;     }      struct Publication {         string name;         string link;         string language;     }      struct Skill {         string name;         int32 level;     } }

Теперь осталось только импортировать полученный файл

pragma solidity ^0.4.0;  import "./structures.sol";  contract EthereumCV {     mapping (string => string) basic_data;     address owner;      Structures.Project[] public projects;     Structures.Education[] public educations;     Structures.Skill[] public skills;     Structures.Publication[] public publications;      // ... }

Самые сообразительные уже догадались, что нотация Structures.Project[] projects означает создание динамического массива с элеметнами типа Project. А вот с модификатором public уже сложнее. По сути, он заменяет нам написание функции вроде get_project(int position) { return projects[position]; } — компилятор сам создаст такую функцию. Называться она будет так же как и переменная, в нашем случае — projects.

Вы можете спросить — почему мы в самом начале не написали mapping (string => string) public basic_data, а вместо этого сами создавали такую функцию? Причина банальна — public пока что не умеет работать c переменными, для которых ключом является динамический тип данных (string именно такой тип).

Unimplemented feature (/src/libsolidity/codegen/ExpressionCompiler.cpp:105): Accessors for mapping with dynamically-sized keys not yet implemented.

Для этого нужно объявлять basic_data как например mapping (bytes32 => string).

BTW На всякий случай отмечу, что кроме локального файла, Remix умеет импортировать .sol файлы по ссылке на Github и даже с помощью протокола Swarm (это что-то вроде распределенного хранилища для Ethereum, подробнее здесь)

Загружаем и удаляем данные

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

function editPublication (bool operation, string name, string link, string language) onlyOwner() {     if (operation) {         publications.push(Structures.Publication(name, link, language));     } else {         delete publications[publications.length - 1];     } }

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

Отдаем данные

Как я уже сказал, модификатор public в строке Project[] public projects обеспечил нас функцией которая по индексу i вернет проект projects[i]. Но мы не знаем, сколько у нас всего проектов, и здесь есть два пути. Первый — итерироваться по i до того момента, пока мы не получим ошибку о несуществующем элементе. Второй — написать отдельную функцию, которая вернет нам размер projects. Я пойду вторым путем, чуть позже скажу почему:

function getSize(string arg) constant returns (uint) {     if (sha3(arg) == sha3("projects")) { return projects.length; }     if (sha3(arg) == sha3("educations")) { return educations.length; }     if (sha3(arg) == sha3("publications")) { return quotes.length; }     if (sha3(arg) == sha3("skills")) { return skills.length; }     throw; }

Заметьте, что мы не можем сравнить две строки привычным способом 'aaa' == 'bbb'. Причина все та же, string — это динамический тип данных, работа с ними довольно болезненна. Так что остается либо сравнивать хэши, либо использовать функцию для посимвольного сравнения. В этом случае можете использовать популярную библиотеку stringUtils.sol, в ней есть такая функция.

Деплой

В разных средах разработки процесс компиляции и деплоя разумеется отличается, поэтому я ограничусь Remix, как самым популярным.

Сначала, само собой, заливаем весь код (финальную версию можете найти в репозитории проекта). Далее в выпадающем списке Select execution environment выберите Javascript VM — пока что протестируем контракт на JS эмуляторе блокчейна, чуть позже научимся работать и с настоящим. Если с контрактом все в порядке, то вам будет доступна кнопка Create — нажимаем и видим:

remix_create

Теперь, когда контракт залит в блокчейн (его эмуляцию, но не суть), можем попробовать вызвать какую-нибудь функцию и посмотреть, что из этого выйдет. Например можно сохранить в контракте email — для этого найдите функцию setBasicData, заполните поле и нажмите кнопку с именем функции:

remix_set_basic_data

Функция ничего не возвращает, поэтому result: 0x. Теперь можно запросить у контракта email: ищем функцию getBasicData и пробуем:

remix_get_basic_data

С остальными функциями предлагаю вам поэксперементировать самим.

Добавляем UI

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

Web3.js

This is the Ethereum compatible JavaScript API which implements the Generic JSON RPC spec. It’s available on npm as a node module, for bower and component as an embeddable js and as a meteor.js package.

Это JS библиотека, позовляющая использовать API Ethereum с помощью обычного JS. По сути с ее помощью вы просто подключаетесь ноде и у вас появляется что-то вроде консоли geth в браузере. Устанавливается через npm или bower:

$ sudo npm install web3 $ bower install web3

Вот пример работы с web3 через node.js (предварительно запустите testrpcили любую другую ноду с RPC интерфейсом):

$ node > var Web3 = require('web3'); > var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); > web3.eth.accounts [ '0x5f7aaf2199f95e1b991cb7961c49be5df1050d86',   '0x1c0131b72fa0f67ac9c46c5f4bd8fa483d7553c3',   '0x10de59faaea051b7ea889011a2d8a560a75805a7',   '0x56e71613ff0fb6a9486555325dc6bec8e6a88c78',   '0x40155a39d232a0bdb98ee9f721340197af3170c5',   '0x4b9f184b2527a3605ec8d62dca22edb4b240bbda',   '0x117a6be09f6e5fbbd373f7f460c8a74a0800c92c',   '0x111f9a2920cbf81e4236225fcbe17c8b329bacd7',   '0x01b4bfbca90cbfad6d6d2a80ee9540645c7bd55a',   '0x71be5d7d2a53597ef73d90fd558df23c37f3aac1' ] >

Тоже самое, только из JS консоли браузера (не забудьте про <script src="path_to/web3.js"></script>)

browser_js_web3

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

Второй нюанс — RPC не имеет никакого встроенного механизма авторизации, поэтому любой желающий может узнать адрес вашей ноды из исходников JS и пользоваться ей в свое удовольствие. Тут конечно можно писать какую-нибудь обертку на Nginx с простейшей HTTP basic auth, но это как-нибудь в другой раз.

Metamask

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

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

metamask_ready

Deploy with Metamask

С Metamask задеплоить контракт в сеть так же просто, как и в случаем с JS EVM. Для этого снова открываем Remix и в списке Select execution environment выбираем пункт Injected Web3 (скорее всего он выбран автоматически). После этого нажимаем Create и видим всплывающее окно:

metamask_popup

Чуть позже надпись Waiting for transaction to be mined... сменится на информацию об опубликованном контракте — это значит что он попал в блокчейн. Адрес контракта можете узнать, открыв Metamask и нажав на запись вида:

metamask_info

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

Пример

Теперь дело за малым — надо научиться получать данные от контракта через Web3. Для этого, во-первых, надо научиться определять наличие web3 на странице:

window.addEventListener('load', function() {   // Checking if Web3 has been injected by the browser (Mist/MetaMask)   if (typeof web3 !== 'undefined') {     // Use Mist/MetaMask's provider     console.log("Web3 detected!");     window.web3 = new Web3(web3.currentProvider);     // Now you can start your app & access web3 freely:     startApp()   } else {     alert('Please use Chrome, install Metamask and then try again!')   } })

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

function startApp() {   var address = {     "3" : "0xf11398265f766b8941549c865d948ae0ac734561" // Ropsten   }    var current_network = web3.version.network;   // abi initialized ealier, in abi.js   var contract = web3.eth.contract(abi).at(address[current_network]);    console.log("Contract initialized successfully")    contract.getBasicData("name", function(error, data) {     console.log(data);   });    contract.getBasicData("email", function(error, data) {     console.log(data);   });    contract.getSize("skills", function(error, data) {     var skills_size = data["c"][0];     for (var i = 0; i < skills_size; ++i) {       contract.skills(i, function(error, data) {         // Don't forget to check blank elements!         if (data[0]) { console.log(data[0], data[1]["c"][0]); }       })     }   }) }

js_logs

Итог

Теперь, когда вы со всем разобрались, можно браться за верстку и JS. Я использовал Vue.js и Spectre.css, для визуализации навыков добавил Google Charts. Результат можете увидеть на pavlovdog.github.io:

cv

Вместо заключения

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

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

Другая, не такая критичная проблема, — мы забыли про стоимость деплоя контрактов и транзакций к ним. На практике, стоит десять раз подумать, прежде чем использовать string вместо bytes, потому как такие вещи прежде всего влияют на затраты при работе с контрактом. Опять же, в примере я использовал Testnet, так что никаких денег мы не потратили, но при работе с Main net не стоит быть такими расточительными.

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

Ссылки

ссылка на оригинал статьи https://habrahabr.ru/post/327236/

Учимся у мастеров: дизайн уровней Legend Of Zelda

image

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

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

Я думал, что NES была Диким Западом разработки игр, не принимавшим никаких законов. Поэтому когда на 25-летнюю годовщину Линка я решил поиграть в первую Zelda и, возможно, написать о ней статью, то немного опасался последствий.

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

В одном из интервью создатель игры Сигэру Миямото рассказал, что в The Legend of Zelda он хотел вызвать в игроке ощущения, появляющиеся при исследовании:

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

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


Я до сих пор слышу эту музыку в своих снах…

Чем мы здесь займёмся?

Проходя The Legend of Zelda, я играл в каждый из уровней, а затем выполнял подробный анализ уровня на бумаге. Такой анализ — довольно стандартный подход, я постоянно делаю его в дизайне уровней своих коллег. Вот что я всегда стараюсь выделить:

  • Прохождение уровня. Хорошо ли сочетаются друг с другом пространства уровня? Куда должен идти игрок, и поймёт ли он, как туда добраться?
  • Изменение интенсивности. Повышается ли интенсивность игры правильным образом? Становятся ли монстры сложнее по ходу уровня? Имеет ли игрок возможность освоить поведение врагов и позже показать своё мастерство?
  • Вариативность. Достаточно ли вариативен геймплей? Часто ли повторяются встречи с одинаковыми врагами? Интересно ли варьируются пространства?
  • Обучение. Если дизайн требует от игрока новых навыков, учит ли игра этим навыкам и проверяет ли их правильно?

В этой статье я применю ту же методику к первому уровню оригинальной Legend of Zelda. К счастью, это легко сделать, ведь карты уровней с видом сверху широко распространены и доступны. Я рассмотрю в статье только первое подземелье, но те же принципы относятся ко всем уровням.

Если вы хотите проверить их, то можно воспользоваться картами, которые нашёл я: Mike’s RPG Center. (Кстати, Майк любезно дал мне разрешение на использование его карт в этой статье. Спасибо, Майк.)

Прохождение уровня

Разбивка

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

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

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

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

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


Игрок начинает с комнаты 1 и может выбрать переход в комнату 2 или 3. Комнаты, не находящиеся на критическом пути, на карте обозначены как более бледные (нажмите на изображение, чтобы посмотреть его в полном размере).

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

Если Миямото действительно хотел передать ощущения исследователя, то этот дизайн является мастерской реализацией его намерений.

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

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

  1. Как я уже упомянул, критический путь почти полностью линеен. Это значит, что игроку гораздо проще найти путь сквозь подземелье, не потерявшись в нём.
  2. Благодаря комнатам, ответвляющимся от критического пути, уровень ощущается менее линейным.
  3. Повторно посещаемые комнаты в начале уровня тоже усиливают ощущение нелинейности, а из-за малого количества таких комнат игрок скорее всего не сможет заблудиться.
  4. Маленькие скрытые сокращения пути на уровне позволяют игроку ощущать себя умным, а дизайнер может таким образом замаскировать линейность уровня.

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

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

Изменение интенсивности

Разбивка

При изучении изменения интенсивности я обычно обращаю внимание на два аспекта:

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


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

Комната 2: 3 Bats
Комната 3: 5 Stalfos (с двумя большими блоками)
Комната 4: 3 Stalfos (с одним большим блоком)
Комната 5: 5 Stalfos (с четырьмя отдельными блоками)
Комната 6: 6 Bats
Комната 7: 3 gels
Комната 8: 5 gels
Комната 9: 3 Moblins
Комната 10: 2 wall masters
Комната 11: босс

Анализ

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

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

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

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

Вариативность

Разбивка

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

Анализ

Однако тут можно добавить одно критическое замечание — вариативность монстров может быть слишком большой. В десяти комнатах с монстрами разработчики использовали шесть типов монстров и босса. В большинстве современных игр было бы меньше типов врагов, а сложность комнат повышалась сочетанием типов монстров. Например, если бы в подземелье были только Stalfos, Bats и Moblins (и босс, конечно же) в части дальних комнат сочетались бы все три типа, и их прохождение было бы сложнее из-за таких комбинаций.

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

Обучение

Разбивка

Обучение — очевидная примета большинства современных игр. Однако во времена The Legend of Zelda приходилось читать руководство, чтобы понять, как играть в игру. Когда настала эра SNES, в дизайн многих AAA-игр было встроено обучение, но на NES оно встречалось редко.

Интересно, что в оригинальной Legend of Zelda есть элементы обучения, однако они отличаются от обучения в современных играх. Обучение The Legend of Zelda в основном выполняется в «чёрных комнатах», где NPC даёт игроку подсказки.


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

Например, на первом уровне подсказка выглядит как «Eastmost peninsula has the secret» («На восточном полуострове есть секрет») и даёт понять, что нужно добраться до конца подземелья. Довольно бесполезная подсказка.

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

Анализ

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

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

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

Открытый вопрос

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

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

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

К моменту, когда мы добрались до A Link to the Past на Super Nintendo (шесть лет спустя), они устранили эту проблему, поэтому очевидно, что она им тоже не нравилась. Но я по прежнему не понимаю, в чём заключался замысел.

Чему мы научились?

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

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

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

Приложение: уровень 4, уровень 9

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


Уровень 4: Змея (нажмите, чтобы просмотреть в полном размере).

Комната 2: 3 Vires (с четырьмя отдельными блоками)
Комната 3: 8 Bats (с четырьмя отдельными блоками)
Комната 4: 5 Vires (с диагональными блоками)
Комната 5: 5 Zols (с участками воды)
Комната 6: 3 Vires, 2 Bubbles (с участком воды)
Примечание — в этой комнате нужна лестница
Комната 7: 5 Vires (с узкой дорожкой)
Примечание — эта комната гораздо проще проходится во второй раз, когда у игрока есть лестница
Комната 8: 2 Zols, 2 Like Likes, 2 Bubbles
Комната 9: 5 Vires (с участками воды из комнаты 5)
Комната 10: Минибосс (Manhandla)
Комната 12: 6 Bats (с участками воды)
Комната 13: 6 Blade Traps
Комната 14: 5 Vires (с двумя отдельными блоками)
Комната 15: босс

Пояснения

Этот уровень очень интересен. Его прохождение в целом линейно, а сложность повышается плавно, как и на уровне 1, но дизайнеры останавливают игрока в комнате 6 и не позволяют продвинуться дальше, пока он не возьмёт лестницу в комнате 8.

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

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


Уровень 9: Череп (нажмите, чтобы просмотреть в полном размере). Красными стрелками показан путь к серебряной стреле. Она нужна для победы над боссом Гэноном, но не требуется, чтобы попасть к нему. Вокруг комнаты 4 создан круг, позволяющий игроку зайти в неё с любой стороны. Я выбрал вход из комнаты 3, потому что так короче.

Пояснения

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

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

Я выделил путь к серебряной стреле красными линиями, чтобы вы видели, куда нужно идти. При этом нужно повторно пересечь 5 комнат, что сильно отклоняется от нормы.
ссылка на оригинал статьи https://habrahabr.ru/post/327448/

Семь раз ALTER один DROP

image

Все началось с того, что я начал писать стандарт оформления T-SQL для своей компании. В этой теме я остановлюсь на конструкции удаления объекта перед его созданием.

В нашей команде порядка двадцати SQL Ninja разработчиков и все описывают данную конструкцию по разному, например вот так:

IF OBJECT_ID('dbo.Function', 'TF') IS NOT NULL 	DROP FUNCTION dbo.Function; GO CREATE FUNCTION dbo.Function .. 


Или так:

IF EXISTS (     SELECT *      FROM sys.objects      WHERE name = 'Procedure'         AND type = 'P'  )     DROP PROCEDURE dbo.Procedure; GO CREATE PROCEDURE dbo.Procedure .. 

И даже так:

IF EXISTS (     SELECT 1     FROM sys.objects      WHERE object_id = OBJECT_ID(N'dbo.Function')         AND type IN (N'FN', N'IF', N'TF', N'FS', N'FT') )     DROP FUNCTION dbo.Function; GO CREATE FUNCTION dbo.Function .. 

А на StackOverflow больше всего лайков собрал вот такой вариант:

IF EXISTS (     SELECT * FROM sysobjects WHERE id = object_id(N'function_name')      AND xtype IN (N'FN', N'IF', N'TF') )     DROP FUNCTION function_name GO 

Звезды пошли ко мне на встречу и наткнулся я на одном из SQL-сайтов на реализацию, которая по началу возмутила меня, но потом мне подсказали что с ней «так»:

IF OBJECT_ID('dbo.Function', 'TF') IS NULL     EXEC('CREATE FUNCTION dbo.Function() RETURNS @t TABLE(i INT) BEGIN RETURN END'); GO ALTER FUNCTION dbo.Function .. 

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

Вобщем мне понравился этот лямбда декоратор способ и я решил его инкапсулировать
в процедуру под названием dbo.antidrop.

У процедуры всего два аргумента, это имя объекта и его тип. Посмотреть тип своего объекта можно вот так:

SELECT type  FROM sys.objects  WHERE name = 'Name' 

Вот как это будет выглядеть по итогу:

EXEC dbo.antidrop('dbo.Name', 'FN'); ALTER FUNCTION dbo.Name .. 

Ну и конечно же код самой процедуры:

IF OBJECT_ID('dbo.antidrop', 'P') IS NOT NULL     DROP PROC dbo.antidrop; GO CREATE PROC dbo.antidrop @name SYSNAME, @type SYSNAME AS BEGIN      DECLARE @if_tf NVARCHAR(512) = '         IF OBJECT_ID(' + @name + ', ' + @type + ') IS NULL             EXEC(''CREATE FUNCTION ' + @name + '() RETURNS @t TABLE(i INT) BEGIN RETURN END'');         GO     ';     DECLARE @fn NVARCHAR(512) = '         IF OBJECT_ID(' + @name + ', ' + @type + ') IS NULL             EXEC(''CREATE FUNCTION ' + @name + '(@i INT) RETURNS INT AS BEGIN RETURN @i + 1 END'');         GO     ';     DECLARE @p NVARCHAR(512) = '         IF OBJECT_ID(' + @name + ', ' + @type + ') IS NULL             EXEC(''CREATE PROC ' + @name + 'AS BEGIN SELECT 1 END'');         GO     ';     DECLARE @v NVARCHAR(512) = '         IF OBJECT_ID(' + @name + ', ' + @type + ') IS NULL             EXEC(''CREATE VIEW ' + @name + ' AS SELECT 1 AS i'');         GO     ';      IF @type in ('IF', 'TF')     BEGIN         EXEC(@if_tf);     END      ELSE IF @type = 'FN'     BEGIN         EXEC(@fn);     END          ELSE IF @type = 'P'     BEGIN         EXEC(@p);     END      ELSE IF @type = 'V'     BEGIN         EXEC(@v);     END  END GO 

Спасибо за внимание!
ссылка на оригинал статьи https://habrahabr.ru/post/327566/

Простой индикатор загрузки для React

В сети встретил индикатор загрузки на CSS.
Перенес в React. Крутится…

import React from 'react';  class Loading extends React.Component { 	constructor(props) { 		super(props); 		this.style = { 		    margin: '10% auto', 		    borderBottom: '1px solid #8af', 		    borderLeft: '1px solid #8af', 		    borderRight: '5px solid #0af', 		    borderTop: '5px solid #0af', 		    borderRadius: '100%', 		    background: 'linear-gradient(rgba(199, 216, 234, 0.6) 30%, rgba(166, 195, 224, 0.9) 90%)', 		    height: '80px', 		    width: '100px', 			animationName: 'spin', 			animationTimingFunction: 'linear', 			animationDuration: '2s', 			animationDelay: '0.0s', 			animationIterationCount: 'infinite', 			animationDirection: 'normal', 			animationFillMode: 'forwards' 		}; 		this.keyframes = ` @-webkit-keyframes "spin"{     from{     -webkit-transform:rotate(0deg);     transform:rotate(0deg);     }     to{     -webkit-transform:rotate(359deg);     transform:rotate(359deg);     } }`; 	}  	render() { 		let styleSheet = document.styleSheets[0]; 		styleSheet.insertRule(this.keyframes, styleSheet.cssRules.length); 		return <div style={this.style}/> 	} }  export default Loading  

Оригинал из сети:

<html> <head> <META http-equiv="Content-Type" content="text/html;charset=UTF-8"> <style> div {     margin:30px;     height:100px;     width:100px;     border-radius:100%;     background:fff;     animation:spin 1.9s infinite linear;  } .loading1{     border-bottom:12px solid #8af;     border-left:12px solid #8af;     border-right:0px solid #fff;     border-top:0px solid #fff;     } .loading2{     border-bottom:5px solid #8af;     border-left:5px solid #8af;     border-right:0px solid #fff;     border-top:0px solid #fff;     background: linear-gradient(rgba(199, 216, 234, 0.6) 30%, rgba(166, 195, 224, 0.9) 90%);     } .loading3{     border-bottom:5px solid #8af;     border-left:5px solid #8af;     border-right:1px solid #fff;     border-top:1px solid #fff;     background: linear-gradient(rgba(199, 216, 234, 0.6) 30%, rgba(166, 195, 224, 0.9) 90%);     height:50px;     }   .loading4{     border-bottom:6px solid #8af;     border-left:6px solid #8af;     border-right:6px solid #fff;     border-top:6px solid #fff;     }       @keyframes "spin"{     from{-webkit-transform:rotate(0deg);     -moz-transform:rotate(0deg);     -o-transform:rotate(0deg);     -ms-transform:rotate(0deg);     transform:rotate(0deg);     }     to{     -webkit-transform:rotate(359deg);     -moz-transform:rotate(359deg);     -o-transform:rotate(359deg);     -ms-transform:rotate(359deg);     transform:rotate(359deg);     } } @-moz-keyframes spin{     from{     -moz-transform:rotate(0deg);     transform:rotate(0deg);     }     to{     -moz-transform:rotate(359deg);     transform:rotate(359deg);     } } @-webkit-keyframes "spin"{     from{     -webkit-transform:rotate(0deg);     transform:rotate(0deg);     }     to{     -webkit-transform:rotate(359deg);     transform:rotate(359deg);     } } @-ms-keyframes "spin"{     from{     -ms-transform:rotate(0deg);     transform:rotate(0deg);}     to{-ms-transform:rotate(359deg);     transform:rotate(359deg);     } } @-o-keyframes "spin"{     from{     -o-transform:rotate(0deg);     transform:rotate(0deg);     }     to{     -o-transform:rotate(359deg);     transform:rotate(359deg);     } } </style> </head> <body> <div class="loading1"></div> <div class="loading2"></div> <div class="loading3"></div> <div class="loading4"></div> </body> </html> 

ссылка на оригинал статьи https://habrahabr.ru/post/327558/