Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 19906 сообщений
Cообщения с меткой

перевод - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

Scala vs Kotlin (перевод)

Пятница, 26 Августа 2016 г. 11:10 (ссылка)

Наша команда, аналогично с автором статьи, уже почти как год перешла со Scala на Kotlin в качестве основного языка. Мое мнение во многом совпадает с автором, поэтому предлагаю вам перевод его интересной статьи.



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



Ниже я хочу привести примеры, которые мне нравятся в Scala и Kotlin, а также их сравнение в том, как они реализованы в обоих языках.



Объявление и выведение типов



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



Пример кода будет одинаковый в обоих языках:



Объявление неизменяемой переменной с именем age и типом Int:



val age = 1 


Объявление изменяемой переменной с типом String:



var greeting = "Hello"


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



Scala



val double = (i: Int) => { i * 2 }


Kotlin



val double = {i: Int -> i * 2 }


Data / Case классы



Scala и Kotlin имеют схожий концепт data классов, которые являются представлением data model object.



Подход в Scala


В Scala это case классы, которые выглядят следующим образом:



case class Person(name: String, age: Int)



  • Есть apply метод (не нужно использовать ключевое слово new при создание инстанса)


  • Методы для доступа объявлены для каждого property (если property объявлено как var то setter метод также будет присутствовать)


  • toString, equal и hashCode разумно объявлены


  • Eсть copy функция


  • Есть unapply метод (который позволяет использовать данные классы в pattern matching)




Подход в Kotlin


Kotlin называет данные классы как data class



data class Person (val name: String, val age: Int)


Ключевые особенности:




  • Методы для доступа объявлены для каждого property (если property объявлено как var то setter метод также будет присутствовать). Это не исключительная особенность data классов, утверждение справедливо для любых классов в Kotlin.


  • Разумно объявлены toString, equal и hashCode


  • Сopy функция


  • component1..componentN функции. По аналогии используется в качестве unapply.


  • Реализует JavaBean getter и setter, необходимых для таких Java фреймворков как Hibernate, Jackson, без изменений.




В Kotlin нет необходимости в специальном apply методе, также как и не нужно ключевое слово new для инициализации класса. Так что это стандартное объявление конструктора как и для любых других классов.



Сравнение



В основном case и data классы похожи.



Пример ниже выглядит одинаково в обоих языках:



val jack = Person("jack", 1)
val olderJack = jack.copy(age = 2)


В целом я нашел data и case классы взаимозаменяемым в повседневном использовании. В Kotlin есть некоторые ограничения на наследование data классов, но это было сделано из благих намерений c учетом реализации equals и componentN функций, чтобы избежать подводных камней.



В Scala case классы более мощные в pattern matсhing по сравнению с тем как Kotlin работает с data классами в ‘when’ блоках, в которых этого не хватает.



Подход Kotlin работает лучше для существующих Java фреймворков, т.к. они выгдядят для них как обычные Java bean.



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



Null Safely / Optionality



Подход в Scala


В Scala null safely заключается в использовании монады option. Проще говоря, option может находится в одном из двух конкретных состояний: Some(x) или None



val anOptionInt: Option[Int] = Some(1)


или



val anOptionInt: Option[Int] = None


Можно оперировать с option при помощи функций isDefined и getOrElse (чтобы указать значение по умолчанию) но более часто используемая ситуация когда монады используется с операторами map, foreach или fold, для которых option представляет из себя коллекцию содержащую 0 или 1 элемент.



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



val n1Option: Option[Int] = Some(1)
val n2Option: Option[Int] = Some(2)
val sum = for (n1 <- n1Option; n2 <- n2Option) yield {n1 + n2 }


В Переменной sum будет значение Some(3). Наглядный пример того как for может быть использован как foreach или flatMap в зависимости от использования ключевого слова yield.



Другой пример:



case class Person(name: String, age: Option[Int])
val person: Option[Person] = Some(Person("Jack", Some(1)))
for (p <- person; age <- p.age)  {
  println(s"The person is age $age")
}


Будет напечатана строчка "The person is age 1"



Подход в Kotlin


Kotlin заимствует синтаксис groovy, достаточно практичный в повседневном использовании. В Kotlin все типы non-nullable и должны быть в явном виде объявлены nullable с помощью "?" если они могут содержать null.



Тот же пример может быть переписан следующим образом:



val n1: Int? = 1
val n2: Int? = 2
val sum = if (n1 != null && n2 != null) n1 + n2 else null


Это намного ближе к Java синтаксису за исключением того, что Kotlin принудительно выполняет проверки во время компиляции, запрещая использовать nullable переменные без проверки на null, так что можно не бояться NullPointerException. Также нельзя присвоить null переменной объявленной как non-nullable. Помимо всего компилятор достаточно умный, чтобы избавить от повторной проверки переменной на null, что позволяет избежать многократной проверки переменных как в Java.



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



data class Person(val name: String, val age: Int?)
val person: Person? = Person("Jack", 1)
if (person?.age != null) {
 printn("The person is age ${person?.age}")
}


Или альтернативный вариант с использованием "let", который заменает "if" блок на:



person?.age?.let {
 person("The person is age $it")
}


Сравнение



Я предпочитаю подход в Kotlin. Он гораздо более читабельный и понятный, и проще разобраться что происходит в многократных вложенных уровнях. Подход Scala отталкивается от поведения монад, который конечно нравится некоторым людям, но по собственному опыту могу сказать, что код становится излишне перегруженным уже для небольших вложений. Существует огромное количество подводных камней у подобного усложнения в использовании map или flatMap, причем вы даже не получите предупреждение при компиляции, если вы делаете что-то не так в мешанине из монад или используя pattern match без поиска альтернативных вариантов, что в результате выливается в runtime exception которые не очевидны.



Подход в Kotlin также уменьшает разрыв при интеграции с кодом Java благодаря тому что типы из нее по умолчанию nullable (тут автор не совсем корректен. Типы из Java попадают в промежуточное состояние между nullable и not-nullable, которое в будущем можно уточнить), тогда как Scala приходится поддерживать null как концепт без null-safely защиты.



Функциональные коллекции



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



В примере ниже нет особых различий в работе fold и map функций:



Scala



val numbers = 1 to 10
val doubles = numbers.map { _ * 2 }
val sumOfSquares = doubles.fold(0) { _ + _ }


Kotin



val numbers = 1..10
val doubles = numbers.map { it * 2 }
val sumOfSquares = doubles.fold(0) {x,y -> x+y }


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



Scala



val numbers = Stream.from(1)
val squares = numbers.map { x => x * x }
val evenSquares = squares.filter { _%2 == 0 }
println(evenSquares.take(10).toList)


Kotlin



val numbers = sequence(1) { it + 1 }
val squares = numbers.map { it * it }
val evenSquares = squares.filter { it%2 == 0 }
println(evenSquares.take(10).toList())


Implicit преобразования vs extension методы



Эта та область в которой Scala и Kotlin немного расходятся.



Подход в Scala


В Scala есть концепция implicit преобразований, которая позволяет добавлять расширенный функционал для класса благодаря автоматическому преобразованию к другому классу при необходимости. Пример объявления:



object Helpers {
 implicit class IntWithTimes(x: Int) {
   def times[A](f: => A): Unit = {
      for(i <- 1 to x) {
f
      }
   }
 }
}


Потом в коде можно будет использовать следующим образом :



import Helpers._
5.times(println("Hello"))


Это выведет "Hello" 5 раз. Работает это благодаря тому, что при вызове функции "times" (которая на самом деле не существует в Int) происходит автоматическая упаковка переменной в объект IntWithTimes, в котором и происходит вызов функции.



Подход в Kotlin


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



fun Int.times(f: ()-> Unit) {
 for (i in 1..this) {
   f()
 }
}


5.times { println("Hello")}


Сравнение



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



Особенности Scala которых нет в Kotlin и по которым я не буду скучать



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




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


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


  • Перегруженный for — Проблема с несколькими монадами, показанная выше.


  • Беспорядок с опциональным синтаксисом infix и postfix операторов — Kotlin чуть более формализованный. В результате код в нем менее двухсмысленный,  его проще читать и не так легко простой опечатке стать неочевидной ошибкой.


  • Переопределением операторов по максимуму — Kotlin разрешает переопределение только основных операторов (+, — и т.п.). Scala разрешает использовать любую последовательности символов. Действительно ли мне нужно знать разницу между "~%#>" и "~+#>"?


  • Медленное время компиляции.




Спасибо за внимание.

Оригинал Scala vs Kotlin

P.S. В некоторые местах в переводе специально оставил слова без перевода (null, null safely, infix, postfix и т.п.).


Original source: habrahabr.ru.

https://habrahabr.ru/post/308562/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Vlad53

Моя копилка . Как перевести видео на русский язык ?

Пятница, 26 Августа 2016 г. 10:27 (ссылка)

Это цитата сообщения Joker-6 Оригинальное сообщение

Как перевести видео на русский

Может кому пригодится...

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
NetFact

Фридрих Дюрренматт - Собрание сочинений в 5 томах (1997-1998) djvu » NetFact.Ru: Скачать бесплатно – Популярная Интернет Библиотека

Среда, 24 Августа 2016 г. 11:24 (ссылка)
netfact.ru/detective/3185-f...-djvu.html


Фридрих Дюрренматт - Собрание сочинений в 5 томах (1997-1998) djvu




Фридрих Дюрренматт (1921-1990) - всемирно известный швейцарский писатель и драматург.

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

Первые три тома отданы прозе, два оставшихся — драматургии.



Список книг:



Дюрренматт. Том 1. Рассказы и повесть

В. Седельник. Парадоксы и предостережения Фридриха Дюрренматта

Рассказы

• Рождество. Перевод С. Апта

• Палач. Перевод С. Апта

• Колбаса. Перевод С. Апта

• Сын. Перевод С. Апта

• Старик. Перевод С. Апта

• Образ Сизифа. Перевод С. Апта

• Директор театра. Перевод С. Апта

• Западня. Перевод С. Апта

• Пилат. Перевод С. Белокриницкой

• Город. Перевод В. Сеферьянца

• Сведения о состоянии печати в каменном веке. Перевод С. Фридлянд

• Собака. Перевод В. Сеферъянца

• Туннель. Перевод Е. Вильмонт

• Из записок охранника. Перевод В. Седельника

• Остановка в небольшом городке. Фрагмент. Перевод С. Фридлянд

• Мистер Ч. в отпуске. Фрагмент. Перевод Н. Федоровой

• Абу Ханифа и Анан бен Давид. Перевод С. Апта

• Смити. Перевод Г. Косарик

• Смерть пифии. Перевод Г. Косарик

• Минотавр. Перевод С. Белокриницкой

Грек ищет гречанку. Комедия в прозе. Перевод Л. Черной

В. Седельник. Комментарии



Дюрренматт. Том 2. Романы и повести

Судья и его палач. Перевод Е. Кацевой

Подозрение. Перевод Е. Факторовича

Авария. Перевод Н. Бунина

Обещание. Перевод Н. Касаткиной

Переворот. Перевод Б. Хлебникова

В. Седельник. Комментарии



Дюрренматт. Том 3. Романы и повести

Зимняя война в Тибете. Перевод И. Кивель

Лунное затмение. Перевод Г. Косарик

Бунтовщик. Перевод Т. Стеженской

Правосудие. Перевод С. Фридлянд

Поручение, или О наблюдении наблюдателя за наблюдателями. Перевод Н. Федоровой

Ущелье Вверхтормашки. Перевод Е. Михелевич

В. Седельник. Комментарии



Дюрренматт. Том 4. Пьесы и радиопьесы

Ромул Великий. Перевод Н. Карп

Брак господина Миссисипи. Перевод В. Седельника

Ангел приходит в Вавилон. Перевод В. Седельника

Визит старой дамы. Перевод Н. Оттен и Б. Черной

Ночной разговор с палачом. Перевод В. Седельника

Процесс из-за тени осла. Перевод Н. Надеждиной

Экспедиция "Вега". Перевод В. Седельника

Страницкий и Национальный герой. Перевод Н. Павловой

Вечер поздней осенью. Перевод В. Кораллова

Двойник. Перевод В. Седельника

В. Седельник. Комментарии



Дюрренматт. Том 5. Пьесы и радиопьесы

Франк Пятый. Перевод В. Колязина и В. Санчука

Физики. Перевод Е. Михелевич

Геркулес и Авгиевы конюшни. Перевод К. Богатырева

Метеор. Перевод Н. Бунина

Анабаптисты. Перевод И. Розанова и В. Санчука

Играем Стриндберга. Перевод Е. Шукшиной

Портрет планеты. Перевод Н. Федоровой

Подельник. Перевод Э. Венгеровой и Н. Крыгиной

В. Седельник. Комментарии



Название: Фридрих Дюрренматт - Собрание сочинений в 5 томах

Автор: Фридрих Дюрренматт

Серия: Вершины

Издательство: Фолио, Прогресс

Год издания: 1997-1998

Язык: русский

Формат: djvu

Размер: 24,19 Мб



Скачать: Фридрих Дюрренматт - Собрание сочинений в 5 томах (1997-1998) djvu



Скачать | Download | TurboBit.net

http://turbobit.net/3vsn73j1uwhi/Durren-5.rar.html



Скачать | Download | HitFile.net

http://www.hitfile.net/JL9opjv/Durren-5.rar.html



Скачать | Download | Файлообменник.рф

http://файлообменник.рф/f7p9tqgtrxqd/Durren-5.rar.html



Скачать | Download | BornCash.org

http://borncash.org/load/1734001207&amp;name=Durren-5.rar



Скачать | Download | StartFiles.org

http://startfiles.org/load/1734001207&amp;name=Durren-5.rar



Скачать | Download | GoldFiles.org

http://goldfiles.org/load/1734001207&amp;name=Durren-5.rar



Скачать | Download | File-Space.org

http://file-space.org/files/get/TAknFLeer_/durren-5.rar.html

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
bayro

Перевод картинки на майку

Понедельник, 24 Августа 2015 г. 08:32 (ссылка)

Перевод картинки на майку.


Читать далее
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
r5gor

Вышел в свет перевод трактата Макот

Вторник, 23 Августа 2016 г. 13:02 (ссылка)


4679592_talmudbrachot296 (241x322, 7Kb)Вавилонского Талмуда.



Уже четвертую неделю он в продаже. Московское издательство "Книжники".



До сих пор переводов этого трактата на русском языке не было. Ни Мишны, ни Гмары. Мы первые.



Осуществлен он теми же силами, что и перевод трактата Брахот (вышел весной этого года). И по той же схеме. Текст представляет собой пшат Гмары, то есть самое основное. Сделано так специально – ибо наш перевод предназначен для тех, кто лишь начинает изучать Талмуд. Не учебник-самоучитель, а книга-помощница. По ней человек входит в тематику Гмары. Или закрепляет то, что выслушал на еврейском уроке. Или готовится к новому уроку. А также повторяет пройденное давно или недавно.Читать далее

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Романтические отношения в (неромантических) играх: линейные сюжеты

Вторник, 23 Августа 2016 г. 08:55 (ссылка)

Перед вами перевод статьи Александра Фрида «Writing Romance in (non-Romance) Games: Linear Romances» (первой из двух). Автор — геймдизайнер, пишет романы и комиксы. К слову, сейчас он работает над новеллизацией «Изгоя-один», а в его послужном списке — должность ведущего сценариста в BioWare (проекты Star Wars: The Old Republic и Shadow Realms).



Немного примечаний:


  • Отсутствие единой терминологии в геймдизайне — в некотором роде факт, потому там, где мне показалось необходимым, указаны ссылки на источники слов. Отдельно в плане терминов нарративного дизайна есть неплохой перевод статьи Томаса Грипа.

  • Ссылки на другие статьи из блога Фрида сохранены.

  • Выбор картинок — на совести автора статьи; я лишь расставила на них ссылки на сами игры для любопытствующих.

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






О, эта романтика!



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



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



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



Слона-то я и не приметил



Всё это — огромный пласт материала, и мы лишь коснёмся его части.



Leisure Suit Larry 5 А именно — нам интересны игры, основное внимание в которых уделяется не романтике. Многие советы ниже не применимы, если вы пишете адвенчуру с сюжетом о развитии конкретных романтических отношений, симулятор организатора вечеринок, где создание пар — основная механика, симулятор свиданий или любую другую игру, где удаление любовной линии и лишит вас существенной части прочего сюжета, и оставит почти без геймплея. Правда, многие советы всё равно могут остаться полезными и в таких случаях — только убедитесь, что рассматриваете всё в нужном контексте.



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



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



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



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



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



Игры с неветвящимся сюжетом



Реализовать романтические отношения в играх с неветвящимся сюжетом проще, чем в играх с ветвящимся сюжетом. Но «просто» — не значит «легко», и проблем может встретиться много.



Sid Meier's Railroad Tycoon Обратите внимание, что я использую слово «неветвящийся» вместо «линейный» для того, чтобы включить в этот термин, например, игры с открытым миром, у которых нет ветвящихся сюжетных линий. Можно спорить, являются ли Assassin’s Creed или Grand Theft Auto играми с линейным сюжетом, но если в них нет множества разных сюжетных веток, они относятся к играм с неветвящимся сюжетом.



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



Но перейдём к делу.



Убедитесь, что это точно хорошая идея



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



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



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



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



Создайте сильную личность для персонажа игрока



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



И убедитесь, что личность персонажа игрока хорошо выглядит не только на бумаге, постарайтесь сделать детали как можно более однозначными! То есть, игроки будут меньше сопротивляться любовной линии для персонажа игрока вроде Натана Дрейка (личность с чётко обрисованным характером), чем для персонажа вроде Гордона Фримена (молчаливого и целиком спроецированного игроком на себя). И тем не менее, о по-настоящему неопределённых или пассивных протагонистах мы поговорим в разделе «Следуйте принципу дороги с односторонним движением» ниже.



Uncharted 4 Чем больше власти над своим персонажем чувствует игрок (и, если расширить понятия, над его романтическими отношениями), тем больше вероятность, что он будет недоволен в те моменты, когда романтические отношения развиваются без его контроля («Он мне даже не нравится — почему мы целуемся?»). Конечно, есть и обратная сторона уменьшения этой власти. Власть — это мощный и ценный инструмент усиления вовлечения игрока. Меняя что-то, убедитесь, что игра стоит свеч.





Создайте сильную личность для объекта романтического интереса



Этот пункт неразрывно связан с предыдущим. Чтобы достигнуть этой цели, вам нужно убедить игрока, что его персонаж и объект романтического интереса должны привлекать друг друга — из-за особенностей их характеров. В таком случае ситуация имеет смысл, даже если игроку она не нравится. Если у игрока хоть раз возник вопрос «Что мой персонаж в нём/ней нашёл?», то у вас серьёзные проблемы.



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



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



Сделайте любовную линию частью фона истории



Shadow of Mordor Утверждение, что персонаж игрока уже был вовлечён в какие-то отношения с объектом своего романтического интереса до начала игры, может помочь подкупить игрока. Ему может не нравиться происходящее, но он не будет задавать вопросов о выборе персонажа — в отличие от того случая, когда отношения зарождаются уже в течение игры.



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



Сделайте любовную линию привлекательной и приятной



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



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



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



Сделайте объект романтического интереса таким, которого нельзя не любить



Candy Crush Saga Этот приём я лично не люблю, но он может сработать: вычистите все шероховатости объекта романтического интереса, убедитесь, что он или она постоянно поддерживает персонажа игрока (но при этом не страдает излишним раболепием и отсутствием чувства юмора), наделите этого персонажа искромётным, но безобидным чувством юмора, и никогда не позволяйте серьёзно спорить, врать или ставить свои интересы выше интересов меня, как игрока. Сделайте этого персонажа привлекательным и не давайте игроку ни малейшего шанса не любить его.



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



Не буду врать: люди любят сладкое. Но вы же выше этого, не так ли?





Не делайте любовную линию препятствием



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



The Rescue of Princess Blobette Беспокойство, независимо от того, насколько оно значимо вне игры, является ещё одним препятствием. Кроме того, его трудно подать должным образом — и крайне сложно заставить игрока и его персонажа сохнуть по NPC или вместе страдать от потери возлюбленных. И непросто дать игроку возможность сделать многое в таких случаях. Игры — это активный опыт, и пока эмоционально страдающие персонажи игрока в целом в порядке, вы с лёгкостью можете найти, чем занять игрока, показать, в каком направлении ему двигаться.



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



Интегрируйте любовную линию в геймплей



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



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



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



Следуйте принципу дороги с односторонним движением



Или… делайте вещи по-разному. Это хорошо работает с относительно неопределёнными персонажами игрока — молчаливым протагонистом и ему подобными — и становится невероятно неловким тем больше, чем больше развивается характер и личность персонажа. Объект романтического интереса, который обожает персонажа игрока, может быть сильной деталью — игроки, как правило, хорошо реагируют на лесть и преклонение, и на ощущение себя желанным, даже если они на самом деле не хотят этого. Я, вероятно, буду симпатизировать хорошо прописанному и интересному персонажу, который однозначно (без элементов жути и без излишней скрытности) влюблён в меня.



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



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



Заставьте чувствовать отсутствие объекта романтического интереса



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



Max Payne Если предположить, что романтические отношения подаются в позитивном ключе, то подчеркните положительные моменты, когда объект романтического интереса присутствует, и отрицательные — когда отсутствует. Например, если у меня есть спутник, являющийся объектом романтического интереса, и отношения строятся на азарте и соперничестве, убедитесь, что миссии, которые я прохожу вместе с этим спутником, по ощущениям невероятно захватывающие и вообще лучшие, а те, что без него — в чём-то унылые, персонаж чувствует себя одиноко или вынужден действовать осторожно. Убедитесь, что многие лучшие моменты (как бы вы ни определяли слово «лучшие») прошли именно вместе с объектом романтического интереса, так что я всегда буду хотеть возвращаться к нему или к ней. Вам не нужно, чтобы персонаж игрока хандрил в одиночестве — если во всё остальное время радость от активных отношений достаточно сильна, то игрок это додумает сам.



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



Отношения — это то, что вы показываете



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



Помните, что правдоподобие — не панацея



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



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



Помните, о чём на самом деле ваша игра (не об отношениях)



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



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



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



Используйте расплывчатость и архетипы



Passage Ищете способ проигнорировать почти всё вышесказанное и всё равно успешно встроить любовную линию в игру? Я уверен, что способы есть. Возможно, вам стоит сыграть на силе архетипов — игра вроде Passage всё равно вызывает эмоции, невзирая на отсутствие костылей вроде «фактически существующих» персонажей. Или же вы можете положиться на символизм. Быть может, ваши «романтические отношения» целиком скрыты в подтексте, а сам текст позволяет дать совершенно платоническое определение взаимоотношениям персонажа игрока и NPC. А может, процедурная генерация и умные механики могут создать убедительную любовную линию в контексте roguelike-игры!



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



В конце концов, добавьте возможность выбора



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



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






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

https://habrahabr.ru/post/308302/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Клон Trello на Phoenix и React. Части 6-7

Понедельник, 22 Августа 2016 г. 15:03 (ссылка)







Оглавление (текущий материал выделен)

  1. Введение и выбор стека технологий

  2. Начальная настройка проекта Phoenix Framework

  3. Модель User и JWT-аутентификация

  4. Front-end для регистрации на React и Redux

  5. Начальное заполнение базы данных и контроллер для входа в приложение

  6. Аутентификация на front-end на React и Redux

  7. Настраиваем сокеты и каналы

  8. Выводим список и создаём новые доски

  9. Добавляем новых пользователей досок

  10. Отслеживаем подключённых пользователей досок

  11. Добавляем списки и карточки

  12. Выкладываем проект на Heroku



Теперь, когда back-end готов обслуживать запросы на аутентификацию, давайте перейдём к front-end и посмотрим, как создать и отправить эти запросы и как использовать возвращённые данные для того, чтобы разрешить пользователю доступ к личным разделам.



Файлы маршрутов



Прежде, чем продолжить, посмотрим снова на файл маршрутов React:



// web/static/js/routes/index.js

import { IndexRoute, Route } from 'react-router';
import React from 'react';
import MainLayout from '../layouts/main';
import AuthenticatedContainer from '../containers/authenticated';
import HomeIndexView from '../views/home';
import RegistrationsNew from '../views/registrations/new';
import SessionsNew from '../views/sessions/new';
import BoardsShowView from '../views/boards/show';
import CardsShowView from '../views/cards/show';

export default (












);


Как мы видели в четвертой части, AuthenticatedContainer запретит пользователям доступ к экранам досок, кроме случаев, когда jwt-токен, полученный в результате процесса аутентификации, присутствует и корректен.



Компонент представления (view component)



Сейчас необходимо создать компонент SessionNew, который будет отрисовывать форму входа в приложение:



import React, {PropTypes}   from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';

import { setDocumentTitle } from '../../utils';
import Actions from '../../actions/sessions';

class SessionsNew extends React.Component {
componentDidMount() {
setDocumentTitle('Sign in');
}

_handleSubmit(e) {
e.preventDefault();

const { email, password } = this.refs;
const { dispatch } = this.props;

dispatch(Actions.signIn(email.value, password.value));
}

_renderError() {
const { error } = this.props;

if (!error) return false;

return (

{error}

);
}

render() {
return (






{::this._renderError()}








Create new account


);
}
}

const mapStateToProps = (state) => (
state.session
);

export default connect(mapStateToProps)(SessionsNew);


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



Конструктор действия (action creator)



Следуя по направлению действий пользователя, создадим конструктор действия сессий:



// web/static/js/actions/sessions.js

import { routeActions } from 'redux-simple-router';
import Constants from '../constants';
import { Socket } from 'phoenix';
import { httpGet, httpPost, httpDelete } from '../utils';

function setCurrentUser(dispatch, user) {
dispatch({
type: Constants.CURRENT_USER,
currentUser: user,
});

// ...
};

const Actions = {
signIn: (email, password) => {
return dispatch => {
const data = {
session: {
email: email,
password: password,
},
};

httpPost('/api/v1/sessions', data)
.then((data) => {
localStorage.setItem('phoenixAuthToken', data.jwt);
setCurrentUser(dispatch, data.user);
dispatch(routeActions.push('/'));
})
.catch((error) => {
error.response.json()
.then((errorJSON) => {
dispatch({
type: Constants.SESSIONS_ERROR,
error: errorJSON.error,
});
});
});
};
},

// ...
};

export default Actions;


Функция signIn создаст POST-запрос, передающий email и пароль, указанные пользователем. Если аутентификация на back-end прошла успешно, функция сохранит полученный jwt-токен в localStorage и направит JSON-структуру currentUser в хранилище. Если по какой-то причине результатом аутентификации будут ошибки, вместо этого функция перенаправит именно их, а мы сможем показать их в форме входа в приложение.



Преобразователь (reducer)



Создадим преобразователь session:



// web/static/js/reducers/session.js

import Constants from '../constants';

const initialState = {
currentUser: null,
error: null,
};

export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.CURRENT_USER:
return { ...state, currentUser: action.currentUser, error: null };

case Constants.SESSIONS_ERROR:
return { ...state, error: action.error };

default:
return state;
}
}


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



Контейнер authenticated



// web/static/js/containers/authenticated.js

import React from 'react';
import { connect } from 'react-redux';
import Actions from '../actions/sessions';
import { routeActions } from 'redux-simple-router';
import Header from '../layouts/header';

class AuthenticatedContainer extends React.Component {
componentDidMount() {
const { dispatch, currentUser } = this.props;
const phoenixAuthToken = localStorage.getItem('phoenixAuthToken');

if (phoenixAuthToken && !currentUser) {
dispatch(Actions.currentUser());
} else if (!phoenixAuthToken) {
dispatch(routeActions.push('/sign_in'));
}
}

render() {
const { currentUser, dispatch } = this.props;

if (!currentUser) return false;

return (




{this.props.children}


);
}
}

const mapStateToProps = (state) => ({
currentUser: state.session.currentUser,
});

export default connect(mapStateToProps)(AuthenticatedContainer);


Если при подключении этого компонента токен аутентификации уже существует, но в хранилище отсутствует currentUser, компонент вызовет конструктор действия currentUser, чтобы получить от back-end данные пользователя. Добавим его:



// web/static/js/actions/sessions.js
// ...

const Actions = {
// ...

currentUser: () => {
return dispatch => {
httpGet('/api/v1/current_user')
.then(function(data) {
setCurrentUser(dispatch, data);
})
.catch(function(error) {
console.log(error);
dispatch(routeActions.push('/sign_in'));
});
};
},

// ...
}

// ...


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



Компонент заголовка



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



// web/static/js/layouts/header.js

import React from 'react';
import { Link } from 'react-router';
import Actions from '../actions/sessions';
import ReactGravatar from 'react-gravatar';

export default class Header extends React.Component {
constructor() {
super();
}

_renderCurrentUser() {
const { currentUser } = this.props;

if (!currentUser) {
return false;
}

const fullName = [currentUser.first_name, currentUser.last_name].join(' ');

return (

{fullName}

);
}

_renderSignOutLink() {
if (!this.props.currentUser) {
return false;
}

return (
Sign out
);
}

_handleSignOutClick(e) {
e.preventDefault();

this.props.dispatch(Actions.signOut());
}

render() {
return (







);
}
}


При нажатии пользователем кнопки выхода происходит вызов метода singOut конструктора действия session. Добавим этот метод:



// web/static/js/actions/sessions.js
// ...

const Actions = {
// ...

signOut: () => {
return dispatch => {
httpDelete('/api/v1/sessions')
.then((data) => {
localStorage.removeItem('phoenixAuthToken');

dispatch({
type: Constants.USER_SIGNED_OUT,
});

dispatch(routeActions.push('/sign_in'));
})
.catch(function(error) {
console.log(error);
});
};
},

// ...
}

// ...


Он отправит на back-end запрос DELETE и, в случае успеха, удалит phoenixAuthToken из localStorage, а так же отправит действие USER_SIGNED_OUT, обнуляющее currentUser в состоянии (state), используя ранее описанный преобразователь сессии:



// web/static/js/reducers/session.js

import Constants from '../constants';

const initialState = {
currentUser: null,
error: null,
};

export default function reducer(state = initialState, action = {}) {
switch (action.type) {
// ...

case Constants.USER_SIGNED_OUT:
return initialState;

// ...
}
}


Ещё кое-что



Хотя мы закончили с процессом аутентификации и входа пользователя в приложение, мы ещё не реализовали ключевую функциональность, которая станет основой всех будущих возможностей, которые мы запрограммируем: пользовательские сокеты и каналы (the user sockets and channels). Этот момент настолько важен, что я скорее предпочёл бы оставить его для следующей части, где мы увидим, как выглядит userSocket, и как к нему подключиться, чтобы у нас появились двунаправленные каналы между front-end и back-end, показывающие изменения в реальном времени.





Сокеты и каналы



Оригинал



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



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



Пользовательский сокет (user socket)



При создании нового приложения Phoenix оно автоматически создаёт для нас начальную конфигурацию сокета:



# lib/phoenix_trello/endpoint.ex

defmodule PhoenixTrello.Endpoint do
use Phoenix.Endpoint, otp_app: :phoenix_trello

socket "/socket", PhoenixTrello.UserSocket

# ...
end


Создаётся и UserSocket, но нам понадобится внести некоторые изменения в нём, чтобы обрабатывать нужные сообщения:



# web/channels/user_socket.ex

defmodule PhoenixTrello.UserSocket do
use Phoenix.Socket

alias PhoenixTrello.{Repo, User}

# Channels
channel "users:*", PhoenixTrello.UserChannel
channel "boards:*", PhoenixTrello.BoardChannel

# Transports
transport :websocket, Phoenix.Transports.WebSocket
transport :longpoll, Phoenix.Transports.LongPoll

# ...
end


По сути, у нас будет два разных канала:




  • UserChannel будет обрабатывать сообщения на любую тему, начинающуюся с `"users:", и мы воспользуемся им, чтобы информировать пользователей о событиях, относящихся к ним самим, например, если они были приглашены присоединиться к доске.

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



Нам так же нужно реализовать функции connect и id, которые будут выглядеть так:



# web/channels/user_socket.ex

defmodule PhoenixTrello.UserSocket do
# ...

def connect(%{"token" => token}, socket) do
case Guardian.decode_and_verify(token) do
{:ok, claims} ->
case GuardianSerializer.from_token(claims["sub"]) do
{:ok, user} ->
{:ok, assign(socket, :current_user, user)}
{:error, _reason} ->
:error
end
{:error, _reason} ->
:error
end
end

def connect(_params, _socket), do: :error

def id(socket), do: "users_socket:#{socket.assigns.current_user.id}"
end


При вызове функции connect (что происходит автоматически при подключении к сокету — прим. переводчика) с token в качестве параметра, она проверит токен, получит из токена данные пользователя с помощью GuardianSerializer, созданного нами в части 3, и сохранит эти данные в сокете, так, что они в случае необходимости будут доступны в канале. Более того, она так же запретит подключение к сокету неаутентифицированных пользователей.



Прим. переводчика

Обратите внимание, приведено два описания функции connect: def connect(%{"token" => token}, socket) do ... end и def connect(_params, _socket), do: :error. Благодаря механизму сопоставления с шаблоном (pattern matching) первый вариант будет вызван при наличии в ассоциативном массиве, передаваемом первым параметром, ключа "token" (а значение, связанное с этим ключом, попадёт в переменную, названную token), а второй — в любых других случаях. Функция connect вызывается фреймворком автоматически при соединении с сокетом.



Функция id используется для идентификации текущего подключения к сокету и может использоваться, к примеру, для завершения всех активных каналов и сокетов для данного пользователя. При желании это можно сделать из любой части приложения, отправив сообщение "disconnect" вызовом PhoenixTrello.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})



Кстати, с помощью .Endpoint.broadcast(topic, message, payload) можно отправить сообщение не только об отключении пользователя, но и вообще любое сообщение всем пользователям, подписанным на соответствующую тему. При этом topic — это строка с темой, (например, "boards:877"), message — это строка с сообщением (например, "boards:update"), а payload — ассоциативный массив с данными, который перед отправкой будет преобразован в json. Например, вы можете отправить пользователям, которые находятся online, какие-то изменения, произведённые с помощью REST api, прямо из контроллера или из любого другого процесса.



Канал user



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



# web/channels/user_channel.ex

defmodule PhoenixTrello.UserChannel do
use PhoenixTrello.Web, :channel

def join("users:" <> user_id, _params, socket) do
{:ok, socket}
end
end


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



Подключение к сокету и каналу



Прежде, чем продолжить, вспомним, что мы сделали в предыдущей части… после аутентификации пользователя вне зависимости от того, использовалась ли форма для входа или ранее сохранённый phoenixAuthToken, нам необходимо получить данные currentUser, чтобы переправить их в хранилище (store) Redux и иметь возможность показать в заголовке аватар и имя пользователя. Это выглядит неплохим местом, чтобы подключиться также к сокету и каналу, поэтому давайте проведём некоторый рефакторинг:



// web/static/js/actions/sessions.js

import Constants from '../constants';
import { Socket } from 'phoenix';

// ...

export function setCurrentUser(dispatch, user) {
dispatch({
type: Constants.CURRENT_USER,
currentUser: user,
});

const socket = new Socket('/socket', {
params: { token: localStorage.getItem('phoenixAuthToken') },
});

socket.connect();

const channel = socket.channel(`users:${user.id}`);

channel.join().receive('ok', () => {
dispatch({
type: Constants.SOCKET_CONNECTED,
socket: socket,
channel: channel,
});
});
};

// ...


После переадресации данных пользователя мы создаём новый объект Socket из JavaScript-библиотеки Phoenix, передав параметром phoenixAuthToken, требуемый для установки соединения, а затем вызываем функцию connect. Мы продолжаем созданием нового канала пользователя (user channel) и присоединяемся к нему. Получив сообщение ok в ответ на join, мы направляем действие SOCKET_CONNECTED, чтобы сохранить и сокет, и канал в хранилище:



// web/static/js/reducers/session.js

import Constants from '../constants';

const initialState = {
currentUser: null,
socket: null,
channel: null,
error: null,
};

export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.CURRENT_USER:
return { ...state, currentUser: action.currentUser, error: null };

case Constants.USER_SIGNED_OUT:
return initialState;

case Constants.SOCKET_CONNECTED:
return { ...state, socket: action.socket, channel: action.channel };

case Constants.SESSIONS_ERROR:
return { ...state, error: action.error };

default:
return state;
}
}


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



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



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


Original source: habrahabr.ru.

https://habrahabr.ru/post/308248/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
Мила_Влада

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

Воскресенье, 22 Августа 2016 г. 01:14 (ссылка)
nitkoj.ru/pulover-zhaket-sv...avami.html


Кофточка спицами Вязаная кофточка спицами с очень красивыми рукавамиКофточка спицами Вязаная кофточка спицами с очень красивыми рукавами Автор: Amanda Jones Перевод: Mirelle Нажимайте на фо

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Клон Trello на Phoenix и React. Части 4-5

Пятница, 19 Августа 2016 г. 11:27 (ссылка)





Оглавление (текущий материал выделен)

  1. Введение и выбор стека технологий

  2. Начальная настройка проекта Phoenix Framework

  3. Модель User и JWT-аутентификация

  4. Front-end для регистрации на React и Redux

  5. Начальное заполнение базы данных и контроллер для входа в приложение

  6. Аутентификация на front-end на React и Redux

  7. Настраиваем сокеты и каналы

  8. Выводим список и создаём новые доски

  9. Добавляем новых пользователей досок

  10. Отслеживаем подключённых пользователей досок

  11. Добавляем списки и карточки

  12. Выкладываем проект на Heroku





Front-end для регистрации на React и Redux



Оригинал



Предыдущую публикацию мы закончили созданием модели User с проверкой корректности и необходимыми для генерации зашифрованного пароля трансформациями набора изменений (changeset); так же мы обновили файл маршрутизатора и создали контроллер RegistrationController, который обрабатывает запрос на создание нового пользователя и возвращает данные пользователя и его jwt-токен для аутентификации будущих запросов в формате JSON. Теперь двинемся дальше — к front-end.



Подготовка маршрутизатора React



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



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



Обновим файл routes для react-router, чтобы отразить то, что мы хотим сделать:



// web/static/js/routes/index.js

import { IndexRoute, Route } from 'react-router';
import React from 'react';
import MainLayout from '../layouts/main';
import AuthenticatedContainer from '../containers/authenticated';
import HomeIndexView from '../views/home';
import RegistrationsNew from '../views/registrations/new';
import SessionsNew from '../views/sessions/new';
import BoardsShowView from '../views/boards/show';

export default (










);


Хитрый момент — AuthenticatedContainer, давайте взглянем на него:



// web/static/js/containers/authenticated.js

import React from 'react';
import { connect } from 'react-redux';
import { routeActions } from 'redux-simple-router';

class AuthenticatedContainer extends React.Component {
componentDidMount() {
const { dispatch, currentUser } = this.props;

if (localStorage.getItem('phoenixAuthToken')) {
dispatch(Actions.currentUser());
} else {
dispatch(routeActions.push('/sign_up'));
}
}

render() {
// ...
}
}

const mapStateToProps = (state) => ({
currentUser: state.session.currentUser,
});

export default connect(mapStateToProps)(AuthenticatedContainer);


Вкратце, что мы тут делаем: проверяем при подключении компонента, присутствует ли jwt-токен в локальном хранилище браузера. Позже мы разберёмся, как этот токен сохранить, но пока давайте представим, что токен не существует; в результате благодаря библиотеке redux-simple-route перенаправим пользователя на страницу регистрации.



Компонент представления (view component) для регистрации



Это то, что мы будем показывать пользователю, если обнаружим, что он не аутентифицирован:



// web/static/js/views/registrations/new.js

import React, {PropTypes} from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';

import { setDocumentTitle, renderErrorsFor } from '../../utils';
import Actions from '../../actions/registrations';

class RegistrationsNew extends React.Component {
componentDidMount() {
setDocumentTitle('Sign up');
}

_handleSubmit(e) {
e.preventDefault();

const { dispatch } = this.props;

const data = {
first_name: this.refs.firstName.value,
last_name: this.refs.lastName.value,
email: this.refs.email.value,
password: this.refs.password.value,
password_confirmation: this.refs.passwordConfirmation.value,
};

dispatch(Actions.signUp(data));
}

render() {
const { errors } = this.props;

return (








{renderErrorsFor(errors, 'first_name')}



{renderErrorsFor(errors, 'last_name')}



{renderErrorsFor(errors, 'email')}



{renderErrorsFor(errors, 'password')}



{renderErrorsFor(errors, 'password_confirmation')}



Sign in


);
}
}

const mapStateToProps = (state) => ({
errors: state.registration.errors,
});

export default connect(mapStateToProps)(RegistrationsNew);


Не особо много можно рассказать об этом компоненте… он изменяет заголовок документа при подключении, выводит форму регистрации и перенаправляет результат конструктора действия (action creator) регистрации singUp.



Конструктор действия (action creator)



Когда предыдущая форма отправлена, нам нужно переслать данные на сервер, где они будут обработаны:



// web/static/js/actions/registrations.js

import { pushPath } from 'redux-simple-router';
import Constants from '../constants';
import { httpPost } from '../utils';

const Actions = {};

Actions.signUp = (data) => {
return dispatch => {
httpPost('/api/v1/registrations', {user: data})
.then((data) => {
localStorage.setItem('phoenixAuthToken', data.jwt);

dispatch({
type: Constants.CURRENT_USER,
currentUser: data.user,
});

dispatch(pushPath('/'));
})
.catch((error) => {
error.response.json()
.then((errorJSON) => {
dispatch({
type: Constants.REGISTRATIONS_ERROR,
errors: errorJSON.errors,
});
});
});
};
};

export default Actions;


Когда компонент RegistrationsNew вызывает конструктор действия, передавая ему данные формы, на сервер отправляется новый POST-запрос. Запрос фильтруется маршрутизатором Phoenix и обрабатывается контроллером RegistrationController, который мы создали в предыдущей публикации. В случае успеха полученный с сервера jwt-токен сохраняется в localStorage, данные созданного пользователя передаются действию CURRENT_USER и, наконец, пользователь переадресуется на корневой путь. Наоборот, если присутствуют любые ошибки, связанные с регистрационными данными, будет вызвано действие REGISTRATIONS_ERROR с ошибками в параметрах, так что мы сможем показать их пользователю в форме.



Для работы с http-запросами мы собираемся положиться на пакет isomorphic-fetch, вызываемый из вспомогательного файла, который для этих целей включает несколько методов:



// web/static/js/utils/index.js

import React from 'react';
import fetch from 'isomorphic-fetch';
import { polyfill } from 'es6-promise';

export function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
var error = new Error(response.statusText);
error.response = response;
throw error;
}
}

export function parseJSON(response) {
return response.json();
}

export function httpPost(url, data) {
const headers = {
Authorization: localStorage.getItem('phoenixAuthToken'),
Accept: 'application/json',
'Content-Type': 'application/json',
}

const body = JSON.stringify(data);

return fetch(url, {
method: 'post',
headers: headers,
body: body,
})
.then(checkStatus)
.then(parseJSON);
}

// ...


Преобразователи (reducers)



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



// web/static/js/reducers/session.js

import Constants from '../constants';

const initialState = {
currentUser: null,
};

export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.CURRENT_USER:
return { ...state, currentUser: action.currentUser };

default:
return state;
}
}


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



// web/static/js/reducers/registration.js

import Constants from '../constants';

const initialState = {
errors: null,
};

export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.REGISTRATIONS_ERROR:
return {...state, errors: action.errors};

default:
return state;
}
}


Обратите внимание, что для вывода ошибок мы обращаемся к фунцкии renderErrorsFor из этого вспомогательного файла:



// web/static/js/utils/index.js

// ...

export function renderErrorsFor(errors, ref) {
if (!errors) return false;

return errors.map((error, i) => {
if (error[ref]) {
return (

{error[ref]}

);
}
});
}


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





Начальное заполнение базы данных и контроллер для входа в приложение



Оригинал



Вход пользователя в приложение



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



Начальное заполнение базы данных



Если у вас есть опыт работы с Rails, вы увидите, что первоначальное заполнение базы данных в Phoenix выглядит очень похоже. Всё, что нам нужно для этого — наличие файла seeds.exs:



# priv/repo/seeds.exs

alias PhoenixTrello.{Repo, User}

[
%{
first_name: "John",
last_name: "Doe",
email: "john@phoenix-trello.com",
password: "12345678"
},
]
|> Enum.map(&User.changeset(%User{}, &1))
|> Enum.each(&Repo.insert!(&1))


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



$ mix run priv/repo/seeds.exs


Контроллер для входа в приложение



До того, как создать контроллер, необходимо внести некоторые изменения в файл router.ex:



# web/router.ex

defmodule PhoenixTrello.Router do
use PhoenixTrello.Web, :router

#...

pipeline :api do
# ...

plug Guardian.Plug.VerifyHeader
plug Guardian.Plug.LoadResource
end

scope "/api", PhoenixTrello do
pipe_through :api

scope "/v1" do
# ...

post "/sessions", SessionController, :create
delete "/sessions", SessionController, :delete

# ...
end
end

#...
end


Первая добавка, которую нужно произвести — добавить в цепочку :api две вставки (plugs, далее будет оригинальный термин использоваться — plug, — поскольку слово "вставка" хоть и отражает букву сути, но не передаёт, как мне кажется, полного смысла; но если я не прав, буду рад нормальному русскому термину. Также имеет смысл для понимания почитать переводной материал о plug и plug pipeline — прим. переводчика):




  • VerifyHeader: этот plug просто проверяет наличие токена в заголовке Authorization (на самом деле, он помимо этого пытается расшифровать его, попутно проверяя на корректность, и создаёт структуру с содержимым токена — прим. переводчика)

  • LoadResource: если токен присутствует, то делает текущий ресурс (в данном случае — конретную запись из модели User — прим. переводчика) доступным как результат вызова Guardian.Plug.current_resource(conn)



Также нужно добавить в область /api/v1 ещё два маршрута для создания и удаления сессии пользователя, оба обрабатываемые контроллером SessionController. Начнём с обработчика :create:



# web/controllers/api/v1/session_controller.ex

defmodule PhoenixTrello.SessionController do
use PhoenixTrello.Web, :controller

plug :scrub_params, "session" when action in [:create]

def create(conn, %{"session" => session_params}) do
case PhoenixTrello.Session.authenticate(session_params) do
{:ok, user} ->
{:ok, jwt, _full_claims} = user |> Guardian.encode_and_sign(:token)

conn
|> put_status(:created)
|> render("show.json", jwt: jwt, user: user)

:error ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json")
end
end

# ...
end


Чтобы аутентифицировать пользователя с полученными параметрами, мы воспользуемся вспомогательным модулем PhoenixTrello.Session. Если всё :ok, то мы зашифруем идентификатор пользователя и впустим его (encode and sign in — несколько вольный, но более понятный перевод — прим. переводчика). Это даст нам jwt-токен, который мы сможем вернуть вместе с записью user в виде JSON. Прежде, чем продолжить, давайте взглянем на вспомогательный модуль Session:



# web/helpers/session.ex

defmodule PhoenixTrello.Session do
alias PhoenixTrello.{Repo, User}

def authenticate(%{"email" => email, "password" => password}) do
user = Repo.get_by(User, email: String.downcase(email))

case check_password(user, password) do
true -> {:ok, user}
_ -> :error
end
end

defp check_password(user, password) do
case user do
nil -> false
_ -> Comeonin.Bcrypt.checkpw(password, user.encrypted_password)
end
end
end


Он пытается найти пользователя по e-mail и проверяет, соответствует ли пришедший пароль зашифрованному паролю пользователя. Если пользователь существует и пароль правильный, возвращается кортеж, содержащий {:ok, user}. В противном случае, если пользователь не найден или пароль неверен, возвращается атом :error.



Возвращаясь к контроллеру SessionController обратите внимание, что он интерпретирует шаблон error.json, если результат аутентификации пользователя — упомянутый ранее атом :error. Наконец, необходимо создать модуль SessionView для отображения обоих результатов:



# web/views/session_view.ex

defmodule PhoenixTrello.SessionView do
use PhoenixTrello.Web, :view

def render("show.json", %{jwt: jwt, user: user}) do
%{
jwt: jwt,
user: user
}
end

def render("error.json", _) do
%{error: "Invalid email or password"}
end
end


Пользователи, уже авторизовавшиеся в приложении



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



Добавим в файл router.ex новый маршрут:



# web/router.ex

defmodule PhoenixTrello.Router do
use PhoenixTrello.Web, :router

#...

scope "/api", PhoenixTrello do
pipe_through :api

scope "/v1" do
# ...

get "/current_user", CurrentUserController, :show

# ...
end
end

#...
end


Теперь нам нужен контроллер CurrentUserController, который выглядит так:



# web/controllers/api/v1/current_user_controller.ex

defmodule PhoenixTrello.CurrentUserController do
use PhoenixTrello.Web, :controller

plug Guardian.Plug.EnsureAuthenticated, handler: PhoenixTrello.SessionController

def show(conn, _) do
user = Guardian.Plug.current_resource(conn)

conn
|> put_status(:ok)
|> render("show.json", user: user)
end
end


Guardian.Plug.EnsureAuthenticated проверяет наличие ранее проверенного токена, и при его отсутствии перенаправляет запрос на функцию :unauthenticated контроллера SessionController. Таким способом мы защитим приватные контроллеры, так что если появится желание определённые маршруты сделать доступными только аутентифицированным пользователям, всё, что понадобится — добавить этот plug в соответствующие контроллеры. Прочая функциональность довольно проста: после подтверждения наличия аутентифицированного токена будет транслирован current_resource, которым в нашем случае являются данные пользователя.



Наконец, нужно в контроллер SessionController добавить обработчик unauthenticated:



# web/controllers/api/v1/session_controller.ex

defmodule PhoenixTrello.SessionController do
use PhoenixTrello.Web, :controller

# ...

def unauthenticated(conn, _params) do
conn
|> put_status(:forbidden)
|> render(PhoenixTrello.SessionView, "forbidden.json", error: "Not Authenticated")
end
end


Он вернёт код 403 — Forbidden вместе с простым текстовым описанием ошибки в JSON. На этом мы закончили с функциональность back-end, относящейся ко входу в приложение и последующей аутентификации. В следующей публикации мы раскроем, как справиться с этим во front-end и как подключиться к UserSocket, сердцу всех вкусняшек режима реального времени. А пока не забудьте взглянуть на живое демо и исходный код конечного результата.


Original source: habrahabr.ru.

https://habrahabr.ru/post/308100/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
Tatiana_Nikkolaevna

Запреты. Интимные пристрастия / Taboo. Private Passions (2012) SATRip » SoftLabirint.Ru: Скачать бесплатно и без регистрации - Самые Популярные Новости Интернета

Пятница, 19 Августа 2016 г. 19:39 (ссылка)
softlabirint.ru/video/docum...atrip.html

В США у некоторых страсти разгораются своеобразно... Информация Название: Запреты. Интимные пристрастия Оригинальное название: Taboo. Private Passions Год выпуска: 2012 Жанр: документаль
Комментарии (0)КомментироватьВ цитатник или сообщество
Tatiana_Nikkolaevna

Генрих Гиммлер. Второй после Гитлера (2014) IPTVRip » SoftLabirint.Ru: Скачать бесплатно и без регистрации - Самые Популярные Новости Интернета

Пятница, 19 Августа 2016 г. 18:26 (ссылка)
softlabirint.ru/video/docum...tvrip.html

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

Нагой мир: Тайная Америка / Naked World: America Undercover (2003) DVDRip » SoftLabirint.Ru: Скачать бесплатно и без регистрации - Самые Популярные Новости Интернета

Пятница, 19 Августа 2016 г. 13:53 (ссылка)
softlabirint.ru/video/docum...vdrip.html

Один год. Семь континетов. Более 6000 людей, желающих обнажиться перед фотографом Спенсером Тьюником во имя искусства. Это — продолжение фильма Нагие штаты, но на этот раз Тьюник работает во
Комментарии (0)КомментироватьВ цитатник или сообщество
Tatiana_Nikkolaevna

BBC: Чудеса Вселенной / Wonders of the Universe (2011) BDRip » SoftLabirint.Ru: Скачать бесплатно и без регистрации - Самые Популярные Новости Интернета

Пятница, 19 Августа 2016 г. 13:38 (ссылка)
softlabirint.ru/video/docum...bdrip.html

Кто мы? Почему мы здесь? Откуда мы пришли? Это самые фундаментальные и извечные вопросы которые мы можем задать. И это неотъемлемая часть человеческой природы, которая стремиться найти на ни
Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Дружественное введение в Dagger 2. Часть 2

Пятница, 19 Августа 2016 г. 07:45 (ссылка)

Используем модули, чтобы указать, как должны создаваться объекты



В предыдущей статье из этой серии мы рассмотрели, как Dagger 2 избавляет нас от рутины написания инициализирующего кода путем внедрения зависимостей.



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



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



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



Пример



Дабы проиллюстрировать описанную выше ситуацию, давайте вернемся к первому примеру из предыдущей статьи, где у нас было всего 3 класса: WeatherReporter и 2 его зависимости, LocationManager и WeatherService. Обе зависимости были конкретными классами. На практике этого может и не случиться.



Давайте предположим, что WeatherService — это интерфейс, и у нас есть еще один класс, скажем, YahooWeather, который имплементирует этот интерфейс:

package com.example;

public interface WeatherService {
}


package com.example;
import javax.inject.Inject;

public class YahooWeather implements WeatherService {
@Inject
public YahooWeather() {
}
}


Если мы снова попытаемся скомпилировать проект, Dagger выдаст ошибку и скажет, что он не может найти provider для WeatherService.



Когда класс конкретный и у него есть аннотированный конструктор, Dagger может автоматически сгенерировать provider для этого класса. Однако поскольку WeatherService — интерфейс, мы должны предоставить Dagger'у больше информации.



Что такое модули?



Модули — это классы, способные создавать экземпляры определенных классов. Например, следующий модуль способен создавать по запросу объекты WeatherService, создавая экземпляр класса YahooWeather.

@Module
public class YahooWeatherModule {
@Provides
WeatherService provideWeatherService() {
return new YahooWeather();
}
}


Модули должны быть помечены аннотацией @Module. Некоторые из их методов, известные также как provider-методы, помечены аннотацией Provides для указания, что они могут предоставлять по запросу экземпляры определенного класса. Имена методов значения не имеют: Dagger смотрит лишь на сигнатуры.



Используя модули, мы совершенствуем Dagger'овские возможности создания объектов и разрешения зависимостей. Раньше в качестве зависимостей могли использоваться только конкретные классы с аннотированными конструкторами, а теперь, с модулем, любой класс может зависеть от интерфейса WeatherService. Нам осталось только подсоединить этот модуль к компоненту, который используется в точке входа нашего приложения:

@Component(modules = {YahooWeatherModule.class})
interface AppComponent {
WeatherReporter getWeatherReporter();
}


Проект снова компилируется. Каждый экземпляр WeatherReporter, созданный методом getWeatherReporter, создает экземпляр YahooWeather.



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



Как подменять модули



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



Положим, у нас есть еще один класс, WeatherChannel, также реализующий WeatherService. Если мы захотим использовать этот класс в WeatherReporter взамен YahooWeather, мы можем написать новый модуль WeatherChannelModule и подставить в компонент именно его.

@Module
public class WeatherChannelModule {
@Provides
WeatherService provideWeatherService() {
return new WeatherChannel();
}
}


@Component(modules = {WeatherChannelModule.class})
public interface AppComponent {
WeatherReporter getWeatherReporter();
}


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



Если мы попробуем подсоединить к компоненту два различных модуля, возвращающих один и тот же тип, Dagger выдаст ошибку компиляции, сообщив, что тип привязан множество раз (type is bound multiple times). Например, так сделать не выйдет:

@Component(modules = {WeatherChannelModule.class, YahooWeatherModule.class})
public interface AppComponent {
WeatherReporter getWeatherReporter();
}


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



Создание более сложных объектов



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



Создание экземпляров сторонних (third-party) классов


Иногда изменить класс и добавить в него аннотацию невозможно. Например, класс является частью фреймворка или сторонней библиотеки. Также достаточно часто сторонние классы имеют сомнительный дизайн и не облегчают применение DI-фреймворков.



Предположим, наш LocationManager зависит от GpsSensor. А этот класс предоставлен компанией «Рога и копыта» и не может быть изменен. Усложним ситуацию еще больше: конструктор класса инициализирует его не полностью. После создания экземпляра класса, прежде чем его использовать, мы обязаны вызвать еще методы, такие как calibrate. Ниже исходники LocationManager и GpsSensor.

public class GpsSensor {

public GpsSensor() {
}

public void calibrate() {
}
}


public class LocationManager {

private final GpsSensor gps;

@Inject
public LocationManager(GpsSensor gps) {
this.gps = gps;
}
}


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



Нам хотелось бы избежать вызова метода calibrate в конструкторе LocationManager, поскольку настройка GpsSensor'а — это не его обязанность. В идеале все принимаемые зависимости уже должны быть готовы к использованию. Кроме того, этот экземпляр GpsSensor может быть использован во многих местах, а «Рога и копыта» предупреждают, что множественный вызов calibrate приводит к крэшу.



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

@Module
public class GpsSensorModule {
@Provides
GpsSensor provideGpsSensor() {
GpsSensor gps = new GpsSensor();
gps.calibrate();
return gps;
}
}




Создание объектов с зависимостями


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



Предположим, например, что классу YahooWeather для работы требуется WebSocket. Взглянем на код.

public class WebSocket {
@Inject
public WebSocket() {
}
}


public class YahooWeather implements WeatherService {

private final WebSocket socket;

@Inject
public YahooWeather(WebSocket socket) {
this.socket = socket;
}
}


Поскольку конструктор YahooWeather требует теперь передачи праметра, нам требуется изменить YahooWeatherModule. Необходимо так или иначе получить экземпляр WebSocket для вызова конструктора.



Вместо создания зависимости прямо внутри модуля, что свело бы на нет все преимущества DI, мы можем просто изменить сигнатуру provider'а. А Dagger уже сам позаботится о том, чтобы создать экземпляр WebSocket.

@Module
public class YahooWeatherModule {
@Provides
WeatherService provideWeatherService(WebSocket socket) {
return new YahooWeather(socket);
}
}




Создание объектов, требующих настройки конфигурации


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



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

public class YahooWeather implements WeatherService {

private final WebSocket socket;

private final String key;

public YahooWeather(String key, WebSocket socket) {
this.key = key;
this.socket = socket;
}
}


Как видите, мы удалили аннотацию Inject. Поскольку за создание YahooWeather теперь отвечает наш модуль, Dagger'у о конструкторе знать не требуется. Точно также он не знает, как автоматически внедрять параметр String (хотя это несложно сделать, как — увидим в будущей статье).



Если ключ API — константа, доступная после компиляции, например, в классе BuildConfig, возможно такое решение:

@Module
public class YahooWeatherModule {
@Provides
WeatherService provideWeatherService(WebSocket socket) {
return new YahooWeather(BuildConfig.YAHOO_API_KEY, socket);
}
}


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

@Module
public class YahooWeatherModule {

private final String key;

public YahooWeatherModule(String key) {
this.key = key;
}

@Provides
WeatherService provideWeatherService(WebSocket socket) {
return new YahooWeather(key, socket);
}
}


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

public class Application {
public static void main(String args[]) {
String apiKey = args[0];
YahooWeatherModule yahoo = new YahooWeatherModule(apiKey);
AppComponent component = DaggerAppComponent.builder()
.yahooWeatherModule(yahoo)
.build();
WeatherReporter reporter = component.getWeatherReporter();
reporter.report();
}
}


В строках 3-4 мы получаем ключ API из аргументов командной строки и сами создаем экземпляр модуля. В строках 5-7 мы просим Dagger создать новый компонент, используя свежесозданный экземпляр модуля. Вместо вызова метода create, мы обращаемся к builder'у, передаем наш экземпляр модуля и наконец вызываем build.



Будь подобных модулей больше, нам пришлось бы подобным образом инициализировать и их до вызова build.



Заключение



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



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



Прим. от переводчика. Пока автор не написал продолжение, всем заинтересованным предлагаю продолжить знакомство с Dagger'ом по замечательной серии постов от xoxol_89 (ч.1, ч.2)
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/308040/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Основы геймдизайна: 20 настольных игр. Часть седьмая и последняя: головоломки Nikoli, кроссворды

Четверг, 18 Августа 2016 г. 16:54 (ссылка)

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







Предыдущие статьи из серии:



Го, шахматы, парчиси.

Нарды, монополия, скрэббл.

Пенте, Колонизаторы, Пуэрто-Рико.

Билет на поезд, Каркассон, Колонизаторы.

Манчкин, Контрактный бридж, Ужас Аркхэма.

Ядерная война, Паранойя, Зов Ктулху.



Головоломки Nikoli





Издатель: Nikoli

Тип: логические головоломки для решения в одиночку

Уровень сложности: от умеренного до высокого

Создатели: разные авторы

Фактор везения: отсутствует

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








Обзор



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



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



Всем хорошо известны головоломки судоку. Для примера задач такого типа я остановился на менее популярной головоломке под названием «Китайская стена», которая в журнале Nikoli называется Slitherlink («скользящие линии»). Мне кажется, что ей также подходит название Loopy («петляющая»), которое упоминается в собрании головоломок Саймона Татэма (Tatham's Puzzle Collection). Иллюстрации, которые я привожу в этой статье, взяты из адаптированной компьютерной версии, но логика у них точно такая же, как и в «бумажном» первоисточнике. Каждая из головоломок, которые публикуют в Nikoli, хороша по-своему, но «Китайская стена» видится мне наиболее подходящей для знакомства с ними.

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



Рассмотрим одну легкую задачу из собрания головоломок Саймона Татэма. При должной практике её можно решить менее чем за 3 минуты. Что о ней можно сказать?







Во-первых, квадраты с цифрой 0 не должны иметь видимых границ. Квадраты с цифрой 3, напротив, должны быть обведены с трех сторон. Поскольку «троечка» предполагает только одну пустую границу, квадрат с цифрой 3 в верхнем правом углу следует обвести по краю – здесь мы может провести либо 2 угловые линии, либо ни одной.



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







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



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







Что можно почерпнуть из этих головоломок?



Особый интерес для разработчиков игр представляют головоломки, которые были успешно адаптированы до компьютерных версий. Помимо того, что судоку играет ключевую роль в сайд-стори серии игр Brain Age от Nintendo, приложение с открытым кодом Simon Tatham Puzzle Collection объединяет игровые модули множества головоломок и весьма достойные генераторы случайных задач в одном пакете.



На страницах Nikoli можно найти множество идей для головоломок, которые сейчас очень популярны. Этим, а также превосходной коллекцией судоку и интерфейсом для их решения в значительной мере объясняется популярность серии игр Brain Age. Кроме того, элементы головоломок можно использовать в качестве мини-игр в составе игр других жанров.



Дополнительные материалы:



Сейчас Nikoli поддерживает англоязычный сайт с головоломками и инструкциями по их решению.

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



Кроссворды





Традиционная головоломка

Тип: словесная головоломка для одного игрока

Уровень сложности: от низкого до высокого

Создатель: Артур Уинн (авторство более ранней версии приписывается Джузеппе Айрольди)

Фактор везения: отсутствует

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








Обзор



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

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



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



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



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



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



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







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

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



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

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



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







Что можно почерпнуть из этих головоломок?



На консоли Nintendo DS есть как минимум 3 сборника кроссвордов: от изданий New York Times (рекомендуется) и USA Today (достойно внимания), а также коллекция оригинальных кроссвордов (последний сборник был выпущен Nintendo и, как ни странно, выглядит весьма удручающе на фоне остальных игр этой компании). К тому же, совсем недавно стал доступен для скачивания еще один сборник кроссвордов для консоли DSi.



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



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

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



Примечание



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



Написав эту статью, я нашел отличную серию видеороликов под названием Board Games With Scott («Настольные игры со Скоттом»). Если вы хотите больше узнать о настольных играх, в частности о «Билете на поезд» и «Пуэрто-Рико», видеообзоры Скотта Николсона – это именно то, что вам нужно.



И напоследок напомню, что лучшее место для знакомства с настольными играми – сайт BoardGameGeek. Это крупнейший интернет-портал, посвященный настольным играм, на котором вы можете найти ответы на все интересующие вас вопросы.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/307926/

Комментарии (0)КомментироватьВ цитатник или сообщество
Tatiana_Nikkolaevna

Джеймс Хэдли Чейз - Собрание сочинений - 204 книги (1938-2016) FB2 » SoftLabirint.Ru: Скачать бесплатно и без регистрации - Самые Популярные Новости Интернета

Четверг, 18 Августа 2016 г. 13:16 (ссылка)
softlabirint.ru/book/24213-...6-fb2.html

James Hadley Chase (Великобритания, 24.12.1906 - 06.02.1985) Автор детективных романов. Настоящее имя - Рене Брабазон Реймонд. Также писал под псевдонимом Реймонд Маршалл. Один из самых знам
Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<перевод - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda