...

суббота, 2 января 2016 г.

Обзор возможностей современного JavaScript

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

В ушедшем году, вышел стандарт ECMAScript 2015 (неформально ES6), который сильно изменил, то к чему мы привыкли. Появилась масса новых возможностей, которые по сути представляют собой современное надмножество языка, пытающегося решить существующие проблемы. Class, let, const, стрелочные функции… разработчик, который ранее не видел код, написанный на ES6 не сразу догадается, что перед ним, по сути, старый добрый JS.

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

Императивный подход


Самый простое решение, сделать все императивно. Да, действительно, если следовать принципу «меньше больше — больше меньше», то это будет самое прагматичное решение:
function sayHappyNewYear(year){
    console.log('Happy New ' + year + ' Year!');
}
    
sayHappyNewYear(2016);

Решение простое, и оно будет работать во всех средах, где есть реализация объекта console, но в нем есть изъян: у нас есть не только Новый Год, но и множество других праздников. Создавать для каждого праздника свою отдельную функцию не очень разумное занятие. Если нам необходимо отформатировать сообщения однообразно, то при изменении формата сообщения необходимо будет изменить соответствующий код во всех функциях (да, примеры в статье получились немного искусственными). Можно, конечно, вынести все, что связано с форматированием, в отдельную функцию formatMessage() и пропустить через нее поздравление во всех функциях. Но давайте для начала попробуем отразить предметную область с помощью ООП и посмотреть, чем нам может помочь JavaScript в этой ситуации.

Объектно-ориентированный подход


Как всем вам хорошо известно в JS можно писать в объектно-ориентированном стиле с наследованием на базе прототипов:
function Greeter() {}

Greeter.prototype.doGreeting = function(msg) {
    console.log(msg);
}

function NewYearGreeter(currentYear) {
    this.currentYear = currentYear;
}

NewYearGreeter.prototype = Object.create(Greeter.prototype);
NewYearGreeter.prototype.constructor = NewYearGreeter;

NewYearGreeter.prototype.doGreeting = function() {
    var year = this.currentYear + 1;
    var newYearMsg = 'Happy New ' + year + ' Year!';
    Greeter.prototype.doGreeting.call(this, newYearMsg);
}

var newYearGreeter = new NewYearGreeter(2015);
newYearGreeter.doGreeting();

Получилось довольно многословное решение, плюс которого в том, что этот код будет работать во всех современных runtime-средах (браузеры, Node.js). Именно из-за многословности реализации объектно-ориентированного подхода в ES6 появились классы, которые знакомы любому программисту, владеющему C++, Java, C#, Python etc. Вот таким образом будет выглядеть пример выше, если использовать классы ES6:

'use strict';

class Greeter {
    doGreeting(msg) {
        console.log(msg);
    }
}

class NewYearGreeter extends Greeter {
    constructor(currentYear) {
        super();
        this.currentYear = currentYear;
    }

    doGreeting() {
        let year = this.currentYear + 1;
        let newYearMsg = 'Happy New ' + year + ' Year!';
        super.doGreeting(newYearMsg);
    }
}

let newYearGreeter = new NewYearGreeter(2015);
newYearGreeter.doGreeting();

Выглядит уже симпатичнее. Но как вы наверное знаете было много споров вокруг классов в JS. Были ярые противники этого новшества. В целом их позиция была ясна — ООП в перспективе порождает проблему «гориллы и банана»:

Проблема объектно-ориентированных языков в том, что они тащат с собой всё своё неявное окружение. Вам нужен был банан – а вы получаете гориллу с бананом, и целые джунгли впридачу.
Джо Армстронг «Coders at Work»


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

Хорошо, если наследование порождает проблему связности, какой есть альтернативный вариант? Композиция.

Предпочитайте объектную композицию наследованию классов
Гамма, Хелм, Джонсон, Влиссидс «Приёмы объектно-ориентированного проектирования. Паттерны проектирования.»


В JS, композицию можно реализовать разными способами. Вот одно из решений, которое будет работать во всех современных runtime-средах:
function extend(destination, source) {
    for (var key in source) {
        if (source.hasOwnProperty(key)) {
            destination[key] = source[key];
        }
    }
}

function Greeter() {
    this.doGreeting = function(msg) {
        console.log(msg);
    }
}

function NewYearGreeter(year) {
    this.year = year;

    this.doNewYearGreeting = function() {
        var newYearMsg = 'Happy New ' + this.year + ' Year!';
        this.doGreeting(newYearMsg);
    }
}

var greeter = new Greeter;
var newYearGreeter = new NewYearGreeter(2016);
extend(newYearGreeter, greeter);

newYearGreeter.doNewYearGreeting();

В этом примере с помощью конструкторов создаются объекты, свойства которых (методы) компонуются в одной сущности (объект newYearGreeter) с помощью служебной функции extend. Современный стандарт позволяет упростить реализацию композиции с помощью Object.assign():

'use strict';
let greeter = {
    doGreeting(msg) {
        console.log(msg);
    }
};

let newYearGreeter = {
    setYear(year) {
        this.year = year;
    },

    doNewYearGreeting() {
        let newYearMsg = 'Happy New ' + this.year + ' Year!';
        this.doGreeting(newYearMsg);
    }
};

Object.assign(newYearGreeter, greeter);
newYearGreeter.setYear(2016);

newYearGreeter.doNewYearGreeting();

Object.assign() по сути делает тоже самое, что и extend, за тем исключением, что его можно использовать «из коробки» при этом компоновать можно любое количество объектов.

Это объектная сторона вопроса. Но JS также предоставляет средства для программирования в функциональном стиле.

Функциональный стиль


Особенность такого подхода заключается в том, что мы больше не оперируем объектами, а оперируем чистыми функциями, композиция которых позволяет решить поставленную задачу. Наш пример слишком маленький, поэтому для примера возьмем другое понятие из функционального программирования — каррирование:
function createGreeter(msg) {
    return function(param) {
        return msg.replace(/%.*%/, param);
    }
}

var greetWithNewYear = createGreeter('Happy New %year% Year!');
console.log(greetWithNewYear(2016));

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

function createGreeter(msg) {
    return param => msg.replace(/%.*%/, param);
}

var greetWithNewYear = createGreeter('Happy New %year% Year!');
console.log(greetWithNewYear(2016));

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

Что почитать / посмотреть?


Блог Акселя Раушмайера:
Серия статей ES6 in Depth на mozilla.org:
Youtube-канал Маттиаса Йохонсона:

Итоги


JavaScript развивается очень быстро — будущий стандарт готовит другие замечательные нововведения в языке, но то, что мы можем использовать для создания web-приложений уже сегодня по большому счету новая инкарнация языка, цель которой упростить жизнь всем разработчикам.
(_ => ++_)(2015);

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

Php Inspections (EA Extended): выбираем стратегию на 2016 год

С наступившим новым годом, хабровчане!

2015 год был очень насыщенным для мира PHP: это и долгожданный релиз 7-ой версии, и мажорное обновление многих фреймворков, и множество многообещающих RFC.

Для Php Inspections (EA Extended) прошедший год был тоже очень насыщенным: проект увидел свет, очень быстро повзрослел и смог сделать мир PHP чуточку лучше (symfony2, symfony 1.5, PHP CS Fixer).

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

С самого начала проект решал одну-единственную задачу: сделать рецензирование кода и обучение людей не столь затратным по времени (и не столь болезненым). Получилось очень удачно и удалось помочь многим командам.

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

Как результат, на данный момент сформировались следующие сценарии:

  • фокус на создание сообщества (редкие релизы);
  • фокус на коммерческую версию (возможность автоматической замены кода);
  • специализация на high-load проектах.

Как вы думаете, на чём следует сосредоточиться?

PS: Для того чтобы быть в курсе жизни проекта, присоединяйтесь: твиттер. Или LinkedIn для профессионального нетворкинга.

UPD: поддержать проект можно здесь (PayPal).

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

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

пятница, 1 января 2016 г.

Задача про 2016

31 декабря 2015 в 20:48

Предлагаю порешать в кругу прекрасных дам-программистов традиционную новогоднюю задачу про 2016 год. Надо расставить знаки и скобки, чтобы получилось любое число от 1 до 100.
Например

20*(-1+6)=100


Или

2+0-1^6=1


Факториалы и степени милостиво допускаются.

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

Работа с AngularJS Protractor из C # и Java


Как лозунг на Angular.org гордостью объясняет:
Angular is what HTML would have been, had it been designed for applications , что в вольном переводе звучит так: Angular является тем, чем был бы HTML — если бы он с самого начала был предназначен для создания (веб -) приложений. AngularJS был разработан с нуля, чтобы быть тестируемым. Но многие разработчиков Selenium хотят продолжать использовать свои существующие Java или C # кодовую базу и навыки но обнаруживают при переключении на тестирование AngularJS SPA и MVVM веб-приложений, что Protractor, лидирующий инструмент тестирования приложегий AngularJS, написан на JavaScript тоже.

К счастью, Protractor довольно легко портируется на другие языки — он использует небольшое подмножество протокола JsonWire на котором основан Selenium WebDriver, а именно всего один интерфейс.

За короткое время был дополнен и развит проект protractor-net представляющий порт существующих методов Protractor http://ift.tt/1xUbe7X из Javascript на C# и затем другой проект, выполняющий ту же задачу из Java.
Для тестирования был выбран сайт http://ift.tt/1JiKCIT на котором среди прочего есть и проект для AngularJS,
http://ift.tt/1YV2H1w.

тесты представляют собой «стандарные» действия «клиента» и «менеджера» банка «XYZ Bank» по проверке баланса, созданию учетных записей, проведения платежей и т.п. — это позволило проиллюстрировать все имеющиеся методы. Вызов тестов осуществлен из проекта на C# и из Java


C#


«Клиент» заходит, выбирает счет, заносит сумму, и когда транзакция прошла, проверяет баланс (есть и тест на съем средств — тут он не показан, смотрите архив).

image


  [TestFixture]
  public class Way2AutomationTests
  {
      private StringBuilder verificationErrors = new StringBuilder();
      private IWebDriver driver;
      private NgWebDriver ngDriver;
      private WebDriverWait wait;
      private IAlert alert;
      private string alert_text;
      private Regex theReg;
      private MatchCollection theMatches;
      private Match theMatch;
      private Capture theCapture;
      private int wait_seconds = 3;
      private int highlight_timeout = 100;
      private Actions actions;
      private String base_url = "http://ift.tt/1YV2H1w";

      [TestFixtureSetUp]
      public void SetUp()
      {
          driver = new FirefoxDriver();
          driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromSeconds(60));
          ngDriver = new NgWebDriver(driver);
          wait = new WebDriverWait(driver, TimeSpan.FromSeconds(wait_seconds));
          actions = new Actions(driver);
      }

      [SetUp]
      public void NavigateToBankingExamplePage()
      {
          driver.Navigate().GoToUrl(base_url);
          ngDriver.Url = driver.Url;
      }

      [TestFixtureTearDown]
      public void TearDown()
      {
          try
          {
              driver.Close();
              driver.Quit();
          }
          catch (Exception) { } 
          Assert.IsEmpty(verificationErrors.ToString());
      }

      [Test]
      public void ShouldDeposit()
      {
          ngDriver.FindElement(NgBy.ButtonText("Customer Login")).Click();
          ReadOnlyCollection<NgWebElement> ng_customers = ngDriver.FindElement(NgBy.Model("custId")).FindElements(NgBy.Repeater("cust in Customers"));
          // select customer to log in
          ng_customers.First(cust => Regex.IsMatch(cust.Text, "Harry Potter")).Click();

          ngDriver.FindElement(NgBy.ButtonText("Login")).Click();
          ngDriver.FindElement(NgBy.Options("account for account in Accounts")).Click();


          NgWebElement ng_account_number_element = ngDriver.FindElement(NgBy.Binding("accountNo"));
          int account_id  = 0;
          int.TryParse(ng_account_number_element.Text.FindMatch(@"(?<result>\d+)$"), out account_id);
          Assert.AreNotEqual(0, account_id);

          int account_amount = -1;
          int.TryParse(ngDriver.FindElement(NgBy.Binding("amount")).Text.FindMatch(@"(?<result>\d+)$"), out account_amount);
          Assert.AreNotEqual(-1, account_amount);

          ngDriver.FindElement(NgBy.PartialButtonText("Deposit")).Click();

          // core Selenium
          wait.Until(ExpectedConditions.ElementExists(By.CssSelector("form[name='myForm']")));
          NgWebElement ng_form_element = new NgWebElement(ngDriver, driver.FindElement(By.CssSelector("form[name='myForm']")));


          NgWebElement ng_deposit_amount_element = ng_form_element.FindElement(NgBy.Model("amount"));
          ng_deposit_amount_element.SendKeys("100");

          NgWebElement ng_deposit_button_element = ng_form_element.FindElement(NgBy.ButtonText("Deposit"));
          ngDriver.Highlight(ng_deposit_button_element);
          ng_deposit_button_element.Click();
          
          // inspect status message
          var ng_message_element = ngDriver.FindElement(NgBy.Binding("message"));
          StringAssert.Contains("Deposit Successful", ng_message_element.Text);
          ngDriver.Highlight(ng_message_element);

          // re-read the amount
          int updated_account_amount = -1;            
          int.TryParse(ngDriver.FindElement(NgBy.Binding("amount")).Text.FindMatch(@"(?<result>\d+)$"), out updated_account_amount);
          Assert.AreEqual(updated_account_amount, account_amount + 100);
      }

Java


«Клиент» заходит, выбирает счет, смотрит транзакции, умеет найти записи «Credit».
@Test
    public void testListTransactions() throws Exception {
      // customer login
      ngDriver.findElement(NgBy.buttonText("Customer Login")).click();
      // select customer/account with transactions
      assertThat(ngDriver.findElement(NgBy.input("custId")).getAttribute("id"), equalTo("userSelect"));

      Enumeration<WebElement> customers = Collections.enumeration(ngDriver.findElement(NgBy.model("custId")).findElements(NgBy.repeater("cust in Customers")));
      
      while (customers.hasMoreElements()){
        WebElement next_customer = customers.nextElement();
        if (next_customer.getText().indexOf("Hermoine Granger") >= 0 ){
          System.err.println(next_customer.getText());
          next_customer.click();
        }
      }
      NgWebElement login_element = ngDriver.findElement(NgBy.buttonText("Login"));
      assertTrue(login_element.isEnabled());
      login_element.click();

      Enumeration<WebElement> accounts = Collections.enumeration(ngDriver.findElements(NgBy.options("account for account in Accounts")));
      
      while (accounts.hasMoreElements()){
        WebElement next_account = accounts.nextElement();
        if (Integer.parseInt(next_account.getText()) == 1001){
          System.err.println(next_account.getText());
          next_account.click();
        }
      }
      // inspect transactions
      NgWebElement ng_transactions_element = ngDriver.findElement(NgBy.partialButtonText("Transactions"));
      assertThat(ng_transactions_element.getText(), equalTo("Transactions"));
      highlight(ng_transactions_element);
      ng_transactions_element.click();
      wait.until(ExpectedConditions.visibilityOf(ngDriver.findElement(NgBy.repeater("tx in transactions")).getWrappedElement()));
      Iterator<WebElement> ng_transaction_type_columns = ngDriver.findElements(NgBy.repeaterColumn("tx in transactions", "tx.type")).iterator();
      while (ng_transaction_type_columns.hasNext() ) {
        WebElement column = (WebElement) ng_transaction_type_columns.next();
        if (column.getText().isEmpty()){
          break;
        }
        if (column.getText().equalsIgnoreCase("Credit") ){
          highlight(column);
        }
      }
    }

Для интерактивного тестирования, стоит запустить Selenium-ноду и хаб локально на порт 4444
@BeforeClass
public static void setup() throws IOException {
  DesiredCapabilities capabilities =   new DesiredCapabilities("firefox", "", Platform.ANY);
  FirefoxProfile profile = new ProfilesIni().getProfile("default");
  capabilities.setCapability("firefox_profile", profile);
  seleniumDriver = new RemoteWebDriver(new URL("http://ift.tt/MlyWIA"), capabilities);
  try{
    seleniumDriver.manage().window().setSize(new Dimension(600, 800));
    seleniumDriver.manage().timeouts()
      .pageLoadTimeout(50, TimeUnit.SECONDS)
      .implicitlyWait(20, TimeUnit.SECONDS)
      .setScriptTimeout(10, TimeUnit.SECONDS);
  }  catch(Exception ex) {
    System.out.println(ex.toString());
  }
  ngDriver = new NgWebDriver(seleniumDriver);
}

Для билда используем
@BeforeClass
public static void setup() throws IOException {
  seleniumDriver = new PhantomJSDriver();
  wait = new WebDriverWait(seleniumDriver, flexible_wait_interval );
  wait.pollingEvery(wait_polling_interval,TimeUnit.MILLISECONDS);
  actions = new Actions(seleniumDriver);
  ngDriver = new NgWebDriver(seleniumDriver);
}

Полный список тестов на 01.01.2016:

  • ShouldAddCustomer
  • ShouldDeleteCustomer
  • ShouldDeposit
  • ShouldListTransactions
  • ShouldLoginCustomer
  • ShouldOpenAccount
  • ShouldSortCustomersAccounts
  • ShouldWithdraw

Это на C# — на Jave тестов пока меньше, но остаток — вопрос времени

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

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

Objective-C вопросы на уровень middle/senior

Что должен знать objc разработчик на уровень middle/senior?
К сожалению, четкой черты на вертикальное развитие нет. Парадокс, но чтобы знать что изучить, нужно знать что ты не знаешь.
Я постарался вспомнить самые интересные вопросы, которые мне задавали самому на различных собеседованиях, а так же расширил их множеством вопросов(тем же уровнем) от себя.
Здесь нет общих вопросов вроде: IoC, design patterns, S.O.L.I.D. и т.п.

ВНИМАНИЕ!!!
Помимо вертикального развития немаловажно и горизонтальное

ВНИМАНИЕ!!! (2)
Не пишите в комментарии ответы на вопросы, это дает возможность людям самостоятельно разобраться.
Однако, если у вас имеются интересные вопросы по теме, я с радостью добавлю их в список.

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


что такое void *?
а что означает просто void?

в чем разница между void * и id?

что такое id?
как определен id?
можно ли создать структуру и привести к id?

id vs instancetype

что значит root класс?
можно ли создать свой root класс?
какие есть еще root классы кроме NSObject и NSProxy?

можно ли использовать NSObject вместо NSProxy?
если ответ «да», то зачем тогда нужен NSProxy?

как происходит выравнивание указателей на объекты в objc?
с чем это связано?
какие оптимизации с этим связаны при хранении данных?
почему нельзя делать свою реализацию?

что такое bridge и зачем это нужно?

все эти методы допустимы? если нет то какие и почему?
- (instancetype)initMyObj {
    self = nil;
    return self;
}

- (instancetype)initmyObj {
    self = nil;
    return self;
}

- (instancetype)myObj {
    self = nil;
    return self;
}



что такое мета-класс?

objc_msgSend, что это за функция?
как это связано с [obj foo]? какие еще есть связанные функции?

что такое dispatch table?

как происходит отправка сообщения?
всегда ли сообщения отправляются с одинаковой скоростью?

что будет если метод не найден в dispatch table?

есть ли в objc приватные методы?
как протестировать метод, не объявленный в публичном интерфейсе

exception отправки сообщения, кто выбрасывает?
что такое NSInvocation. Привести пример использования

что такое swizzling?

можно ли сделать private ivar как property, через категорию?
какие проблемы могут быть?
как в категории сделать свою property?

target-action, передается селектор, как сделать так, чтобы по селектору был вызван не метод, а блок?

что такое селектор?

что такое блок?
какие типы блоков бывают?
от какого класса наследуются?

всегда ли использование self внутри блока означает retain cycle?

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

как работает __block?

как происходит вызов блока?

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

что такое assert и когда использовать?

что такое __autoreleasing?

что такое autorelease pool?

что такое run loop?

как связаны NSTimer и run loop?
как запустить таймер на отдельном потоке?
что будет если запустить таймер и держать UIScrollView?
как это исправить?

как связаны run loop и autorelease pool?

почему в этом коде утечка памяти и как устранить?
        NSString *str;
        while (YES) {
            str = [NSString stringWithFormat:@"hello world"];
        }


какие типы управления памятью есть в objc?

garbage collection vs reference counting?

отличия ARC от MRC?
как работает ARC? магически определяет когда удалять объект?
напишите реализацию сеттера на MRC

чем отличается weak от strong и почему так много runtime функций для этого?

почему weak невозможно использовать на некоторых классах?
на каких именно?
как быть в этом случае?

ARC, как работает dealloc?
когда расставляются release сообщения для ivar?

причина выполнения true ветки? причина выполнения false ветки? от чего зависит результат?
        BOOL b = 1024;
        if (b) {
            NSLog(@"true");
        } else {
            NSLog(@"false");
        }


одинаковую ли память занимают эти структуры и почему так?
struct StructA {
    int32_t a;
    char b;
    char c;
};

struct StructB {
    char b;
    int32_t a;
    char c;
};


что такое union?
а сколько памяти занимают эти структура и что это вообще такое?
struct Value1 {
    int32_t foo:12;
    int32_t foo1:4;
    int32_t foo2:6;
    int32_t foo3:10;
};

struct Value2 {
    int32_t foo;
    int32_t foo1;
    int32_t foo2;
    int32_t foo3;
};



что такое volatile?

что такое inline функции?

в чем отличие потока от процесса?

NSThread vs pthread

сокет, как это связано с вопросами выше?
tcp, udp когда что применять?

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

как передавать данные между NSOperation?
когда операция отправленная в NSOperationQueue начинает выполняться, можно ли отложить выполнение?

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

что такое стек вызовов и как это работает?
в чем отличие стека от кучи?
int8_t matrix[2048][2024], допустимо ли?

рекурсивные функции и хвостовая рекурсия, любая ли рекурсия может быть хвостовой?

что происходит со счетчиком ссылок когда объект добавляется в array, dictionary, set?
если нужно другое поведение, что использовать?
можно ли узнать, какой объект сколько раз был добавлен в set? (bag)

как использовать свой класс как ключ?

в чем плюсы использования immutable?
в чем минусы?
зачем нужен атрибут copy для property и какую проблему решает?

почему и как работает KVC?
key-path, хаки для коллекций

зачем нужен NSMapTable, какие отличия от NSHashTable

проблемы при работе с KVO и как это все работает

frame, bounds, center, как все это между собой связано?
как изменятся свойства, если применить поворот на 45º?
как передвинуть все subviews на 3 pt вверх и влево?

отличие view от layer?

responder принцип работы
как отправить сообщение по responder chain?

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

зачем нужен NSCache? В чем от реализации кэша на NSDictionary?
поддерживает ли NSURLConnection протокол HTTP/2?

какая типизация у objc? Сильная/слабая, явная/не явная, статическая/динамическая?
в чем отличие QoS User-interactive и user-initiated?
в чем отличие nil/Nil/NULL/NSNull?
________

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

Статья написана совместно с complexityclass

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

[Из песочницы] Как за 3 дня создать игровой движок для новелл

Одним дождливым питерским днем мой проект в UE4 перестал загружаться, и из-за этого я захотел сделать свой собственный движок. И одна новелла подтолкнула меня сделать движок именно для новелл. Если вам хочется узнать побольше и вы не боитесь goto, gosub и других ужасов, добро пожаловать под кат.
image



image
Все началось с того, как после прохождения N-ой новеллы друг порекомендовал мне Katawa Shoujo. Как оказалось официальной версии в App Store нет и не предвидится, только .ipa на 4pda.

Этот же друг зная, что я пытаюсь создать игру уже третий месяц предложил мне портировать Катаву на iPad без Jailbreak. Выбор пал на smart BASIC, так как я пользуюсь этой программой уже полтора года и знаю синтаксис её внутреннего языка программирования (также у smart BASIC есть SDK для XCode).

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


image

1. Текст


Для начала я создал папки (спойлер).
Папки

/Sprites
/Event
/BGs
/Music
/Scenario
/Scripts
/Scripts/Ren_sB
/Scripts/Ren_sB/Functions
/UI
/UI/main
/VFX


Файл Katawa Shoujo Port.txt будет подгружать все скрипты.
scenario$ = “Habr”
{Scripts/colorcodes.txt}
{Scripts/Ren_sB/render.txt}


Файл colorcodes.txt будет содержать RGB цвета имен для рендера, для примера возьму Сидзунэ и ее синее выделение:
data "Сидзунэ","107","174","239"
dim colors$(1,2)
read colors$(color,0)


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

За это будет отвечать скрипт speak.txt.

Для начала в файл load.txt я прописал загрузку спрайта bg-say.png (поле для реплик) и шрифта playtime_cyrillic:

font "Font/playtime_cyrillic2.ttf" load a$
sprite "bg-say" load "UI/bg-say.png"
sprite "bg-say" resize 1024,205
sprite "bg-say" at 0,screen_height()-205
sprite "bg-doublespeak" load "UI/bg-doublespeak.png"
sprite "bg-doublespeak" resize 1024,205
sprite "bg-doublespeak" at 0,screen_height()-205
sprite "ctc-strip" load "UI/ctc-strip.png"
get sprite "ctc_strip" size w,h
sprite "ctc_strip" resize w*1.28,h*1.28
sprite "ctc_strip" at screen_width()-w*1.28-15,screen_height()-52
sprite "ctc_strip" delay 0.05

После я решил использовать в скрипте сценария знак “|” как разделитель команды, цели команды, вторичной цели, действия и аттрибута.

Показ текста: “1|перс|текст”

1|Сейдзунэ|[Привет, Хабр]

Файл render.txt считывает эту команду и перенаправляет на метку alone

render.txt
graphics
set toolbar off
set orientation landscape

file "Scenario/"&scenario$&".txt" readline hmm$
count = count+1
end = file_end("Scenario/"&scenario$&".txt")
if end = 0 then goto count

file "Scenario/"&scenario$&".txt" setpos 0

dim info$(count,5)

for l=0 to count-1
file "Scenario/"&scenario$&".txt" readline line$

splite line$ to temp$,n with "|"
for m = 0 to n-1
info$(l,m) = temp$(m)
next m
next l

'Разбор массива текста, перенаправления на подпрограммы
'
show:
for y = 0 to count-1
if info$(y,0) = "1" then gosub alone
...
alone:
{Scripts/Ren_sB/Functions/Speak.txt}
return



speak.txt
sprite "bg-say" show
name$ = info$(y,1)
text$ = info$(y,2)

'Поиск кода цвета данного персонажа
'
for color = 0 to 6
if name$ = colors$(color,0) then
r = colors$(color,1)
g = colors$(color,2)
b = colors$(color,3)
endif
next color

'Создание двух полей
'
field 1 text name$ at 20,575 size 600,40 ro
field 1 back alpha 0
field 1 font color r/255,g/255,b/255
field 1 font name a$
field 1 font size 28

field 2 text render$ at 20,615 size screen_width()-20,130 ro ml
field 2 back alpha 0
field 2 font color 1,1,1
field 2 font size 28
field 2 font name a$

'Анимация текста
'
for k = 0 to len(text$)-1
'Использование касания для пропуска анимации
'
gosub 3
render$ = substr$(text$,0,k)
field 2 text render$
pause 0.025
next k

'Ожидание касания для перехода
'
sprite "ctc_strip" show ! sprite "ctc_strip" loop
gosub 1
sprite "ctc_strip" hide



1
1
x1 = touch_x(0)
y1 = touch_y(0)
if x1 > -1 or y1 > -1 then goto 2
slowdown
goto 1

2
x1 = touch_x(0)
y1 = touch_y(0)
if x1 = -1 or y1 = -1 then
return
endif
slowdown
goto 2



3
3
x1 = touch_x(0)
y1 = touch_y(0)
if x1 > -1 or y1 > -1 then
tapped = 1
endif

if tapped = 1 then
if x1 = -1 or y1 = -1 then
tapped = 0
if len(text$) = 0 then
k = lengthmax - 1
else
k = len(text$) - 1
endif
return
else
return
endif
endif
return



Результат:

image

“Но ведь в Катаве может говорить два персонажа одновременно!” Для этого есть отдельная команда: “2|перс1&перс2|текст1&текст2”:
2|Хисао&Лилли|«Привет!»&”Здравствуйте!"

Добавим в render.txt строки:

render.txt
...
show:
...
if info$(y,0) = 2 then gosub together
...
together:
{Scripts/Ren_sB/Functions/Double_Speak.txt}
return



Double_Speak.txt выполняет данные команды:
Double_Speak.txt
sprite "bg-doublespeak" show
names$ = info$(y,1)
'Имена и тексты
'
nd = instr(names$,"&")
name1$ = substr$(names$,0,nd-1)
name2$ = substr$(names$,nd+1,len(names$)-1)
texts$ = info$(y,2)
td = instr(texts$,"&")
text1$ = substr$(texts$,0,td-1)
text2$ = substr$(texts$,td+1,len(texts$)-1)
'Определение цвета для каждого персонажа
'
for color = 0 to 6
if name1$ = colors$(color,0) then
r1 = colors$(color,1)
g1 = colors$(color,2)
b1 = colors$(color,3)
endif
next color

for color = 0 to 6
if name2$ = colors$(color,0) then
r2 = colors$(color,1)
g2 = colors$(color,2)
b2 = colors$(color,3)
endif
next color
'Создание полей
'
field 11 text name1$ at 20,575 size 300,40 ro
field 11 back alpha 0
field 11 font color r1/255,g1/255,b1/255
field 11 font name a$
field 11 font size 28

field 12 text name2$ at 535,575 size 300,40 ro
field 12 back alpha 0
field 12 font color r2/255,g2/255,b2/255
field 12 font name a$
field 12 font size 28

field 21 text render1$ at 20,615 size screen_width()/2-20,130 ro ml
field 21 back alpha 0
field 21 font color 1,1,1
field 21 font size 28
field 21 font name a$

field 22 text render2$ at 535,615 size screen_width()/2-20,130 ro ml
field 22 back alpha 0
field 22 font color 1,1,1
field 22 font size 28
field 22 font name a$

'Определение максимальной и минимальной длинны
'
lengthmax = max(len(text1$),len(text2$))
lengthmin = min(len(text1$),len(text2$))

'Анимация текста
' 
if len(text1$) = len(text2$) then
for k = 0 to lengthmax-1
gosub 3
render1$ = substr$(text1$,0,k)
field 21 text render1$
render2$ = substr$(text2$,0,k)
field 22 text render2$
pause 0.025
next k
endif

if len(text1$) > len(text2$) then
for k = 0 to lengthmax-1
gosub 3
render1$ = substr$(text1$,0,k)
field 21 text render1$
if k < lengthmin then
render2$ = substr$(text2$,0,k)
field 22 text render2$
endif
pause 0.025
next k
endif

if len(text2$) > len(text1$) then
for k = 0 to lengthmax-1
gosub 3
render2$ = substr$(text2$,0,k)
field 22 text render2$
if k < lengthmin then
render1$ = substr$(text1$,0,k)
field 21 text render1$
endif
pause 0.025
next k
endif

'Ожидание касания
'
sprite "ctc_strip" show ! sprite "ctc_strip" loop
gosub 1
sprite "ctc_strip" hide


Результат:

image

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

Спасибо всем тем, кто дочитал до конца статьи. До встречи в следующей части!

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

Ajenti 2 и прочие новости


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


image

Так уж вышло, что некоторое время назад я создал инструмент, который позволял людям быстро и легко настраивать систему и сайты на LEMP, Node и [g]Unicorn. В то время я был студентом, и свободного времени было больше, поэтому я решил что больше — значит лучше, и пошел по следам Webmin в вопросе количества плагинов. В результате этого я не только повысил свое ЧСВ, но и стал получать по десятку support-запросов в день, причем первая половина из них была уровня how do I PHP?, а вторая — не имеющие отношения к самой панели вопросы по настройке линукса.

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

Что я решил сделать по этому поводу? Я решил сделать меньше, но лучше: бета Ajenti 2. Я оставил самые необходимые инструменты администратора — файловый менеджер, редактор, терминал, сервисы, пакеты и дашборд, обернув все это в быстрый интерфейс с поддержкой мобильных устройств. Насколько хорошо это у меня вышло — судить вам.

В виду всего этого, сейчас я стою перед дилеммой: стоит мне объявить это новым релизом Ajenti, и существующая аудитория проекта заживо меня распнет ввиду уменьшившегося функционала. А вновь наращивать проект до размера webmin/cpanel у меня нет ни желания, ни возможности. Этот пост — по сути крик отчаяния, так как никакого решения этой проблемы я не вижу.

Однако…

Жить стало лучше, жить стало веселее


Новый фронтенд переписан на AngularJS, работает быстрее, надежнее и обладает модной в этом сезоне адаптивной версткой.

RAM

Занимает меньше памяти (~50-60 МБ + ~10 МБ на сессию). Заткнуты имеющиеся утечки. Можно ограничить размер пула сессий.
Упрощенное API

UI теперь не передается туда-сюда целиком после каждого клика.
Изоляция сессий на уровне ОС

Неавторизованные сессии теперь работают в «песочнице», а авторизованные — под соответствующими аккаунтами.Возможность элевации через sudo.
Аутентификация по сертификату

Augeas

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

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

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

Мониторим S.M.A.R.T. в Zabbix

test.local smartctl.info[sg1,model_family] "Western Digital RE4 (SATA 6Gb/s)"
test.local smartctl.info[sg1,device_model] "WDC WD2000FYYZ-01UL1B1"
test.local smartctl.info[sg1,serial_number] "WD-WCC1P1175320"
test.local smartctl.info[sg1,firmware_version] "01.01K02"
test.local smartctl.info[sg1,user_capacity] "2 000 398 934 016 bytes [2,00 TB]"
test.local smartctl.info[sg1,sector_size] "512 bytes logical/physical"
test.local smartctl.info[sg1,rotation_rate] "7200 rpm"
test.local smartctl.smart[sg1,test_result] "PASSED"
test.local smartctl.smart[sg1,1,attribute_name] "Raw_Read_Error_Rate"
test.local smartctl.smart[sg1,1,flag] "0x002f"
test.local smartctl.smart[sg1,1,value] 200
test.local smartctl.smart[sg1,1,worst] 200
test.local smartctl.smart[sg1,1,thresh] 51
test.local smartctl.smart[sg1,1,type] "Pre-fail"
test.local smartctl.smart[sg1,1,updated] "Always"
test.local smartctl.smart[sg1,1,when_failed] "-"
test.local smartctl.smart[sg1,1,raw_value] 0
test.local smartctl.smart[sg1,3,attribute_name] "Spin_Up_Time"
test.local smartctl.smart[sg1,3,flag] "0x0027"
test.local smartctl.smart[sg1,3,value] 169
test.local smartctl.smart[sg1,3,worst] 169
test.local smartctl.smart[sg1,3,thresh] 21
test.local smartctl.smart[sg1,3,type] "Pre-fail"
test.local smartctl.smart[sg1,3,updated] "Always"
test.local smartctl.smart[sg1,3,when_failed] "-"
test.local smartctl.smart[sg1,3,raw_value] 6508
test.local smartctl.smart[sg1,4,attribute_name] "Start_Stop_Count"
test.local smartctl.smart[sg1,4,flag] "0x0032"
test.local smartctl.smart[sg1,4,value] 100
test.local smartctl.smart[sg1,4,worst] 100
test.local smartctl.smart[sg1,4,thresh] 0
test.local smartctl.smart[sg1,4,type] "Old_age"
test.local smartctl.smart[sg1,4,updated] "Always"
test.local smartctl.smart[sg1,4,when_failed] "-"
test.local smartctl.smart[sg1,4,raw_value] 36
test.local smartctl.smart[sg1,5,attribute_name] "Reallocated_Sector_Ct"
test.local smartctl.smart[sg1,5,flag] "0x0033"
test.local smartctl.smart[sg1,5,value] 200
test.local smartctl.smart[sg1,5,worst] 200
test.local smartctl.smart[sg1,5,thresh] 140
test.local smartctl.smart[sg1,5,type] "Pre-fail"
test.local smartctl.smart[sg1,5,updated] "Always"
test.local smartctl.smart[sg1,5,when_failed] "-"
test.local smartctl.smart[sg1,5,raw_value] 0
test.local smartctl.smart[sg1,7,attribute_name] "Seek_Error_Rate"
test.local smartctl.smart[sg1,7,flag] "0x002e"
test.local smartctl.smart[sg1,7,value] 200
test.local smartctl.smart[sg1,7,worst] 200
test.local smartctl.smart[sg1,7,thresh] 0
test.local smartctl.smart[sg1,7,type] "Old_age"
test.local smartctl.smart[sg1,7,updated] "Always"
test.local smartctl.smart[sg1,7,when_failed] "-"
test.local smartctl.smart[sg1,7,raw_value] 0
test.local smartctl.smart[sg1,9,attribute_name] "Power_On_Hours"
test.local smartctl.smart[sg1,9,flag] "0x0032"
test.local smartctl.smart[sg1,9,value] 79
test.local smartctl.smart[sg1,9,worst] 79
test.local smartctl.smart[sg1,9,thresh] 0
test.local smartctl.smart[sg1,9,type] "Old_age"
test.local smartctl.smart[sg1,9,updated] "Always"
test.local smartctl.smart[sg1,9,when_failed] "-"
test.local smartctl.smart[sg1,9,raw_value] 15927
test.local smartctl.smart[sg1,10,attribute_name] "Spin_Retry_Count"
test.local smartctl.smart[sg1,10,flag] "0x0032"
test.local smartctl.smart[sg1,10,value] 100
test.local smartctl.smart[sg1,10,worst] 253
test.local smartctl.smart[sg1,10,thresh] 0
test.local smartctl.smart[sg1,10,type] "Old_age"
test.local smartctl.smart[sg1,10,updated] "Always"
test.local smartctl.smart[sg1,10,when_failed] "-"
test.local smartctl.smart[sg1,10,raw_value] 0
test.local smartctl.smart[sg1,11,attribute_name] "Calibration_Retry_Count"
test.local smartctl.smart[sg1,11,flag] "0x0032"
test.local smartctl.smart[sg1,11,value] 100
test.local smartctl.smart[sg1,11,worst] 253
test.local smartctl.smart[sg1,11,thresh] 0
test.local smartctl.smart[sg1,11,type] "Old_age"
test.local smartctl.smart[sg1,11,updated] "Always"
test.local smartctl.smart[sg1,11,when_failed] "-"
test.local smartctl.smart[sg1,11,raw_value] 0
test.local smartctl.smart[sg1,12,attribute_name] "Power_Cycle_Count"
test.local smartctl.smart[sg1,12,flag] "0x0032"
test.local smartctl.smart[sg1,12,value] 100
test.local smartctl.smart[sg1,12,worst] 100
test.local smartctl.smart[sg1,12,thresh] 0
test.local smartctl.smart[sg1,12,type] "Old_age"
test.local smartctl.smart[sg1,12,updated] "Always"
test.local smartctl.smart[sg1,12,when_failed] "-"
test.local smartctl.smart[sg1,12,raw_value] 30
test.local smartctl.smart[sg1,183,attribute_name] "Runtime_Bad_Block"
test.local smartctl.smart[sg1,183,flag] "0x0032"
test.local smartctl.smart[sg1,183,value] 100
test.local smartctl.smart[sg1,183,worst] 100
test.local smartctl.smart[sg1,183,thresh] 0
test.local smartctl.smart[sg1,183,type] "Old_age"
test.local smartctl.smart[sg1,183,updated] "Always"
test.local smartctl.smart[sg1,183,when_failed] "-"
test.local smartctl.smart[sg1,183,raw_value] 0
test.local smartctl.smart[sg1,192,attribute_name] "Power-Off_Retract_Count"
test.local smartctl.smart[sg1,192,flag] "0x0032"
test.local smartctl.smart[sg1,192,value] 200
test.local smartctl.smart[sg1,192,worst] 200
test.local smartctl.smart[sg1,192,thresh] 0
test.local smartctl.smart[sg1,192,type] "Old_age"
test.local smartctl.smart[sg1,192,updated] "Always"
test.local smartctl.smart[sg1,192,when_failed] "-"
test.local smartctl.smart[sg1,192,raw_value] 29
test.local smartctl.smart[sg1,193,attribute_name] "Load_Cycle_Count"
test.local smartctl.smart[sg1,193,flag] "0x0032"
test.local smartctl.smart[sg1,193,value] 200
test.local smartctl.smart[sg1,193,worst] 200
test.local smartctl.smart[sg1,193,thresh] 0
test.local smartctl.smart[sg1,193,type] "Old_age"
test.local smartctl.smart[sg1,193,updated] "Always"
test.local smartctl.smart[sg1,193,when_failed] "-"
test.local smartctl.smart[sg1,193,raw_value] 6
test.local smartctl.smart[sg1,194,attribute_name] "Temperature_Celsius"
test.local smartctl.smart[sg1,194,flag] "0x0022"
test.local smartctl.smart[sg1,194,value] 125
test.local smartctl.smart[sg1,194,worst] 96
test.local smartctl.smart[sg1,194,thresh] 0
test.local smartctl.smart[sg1,194,type] "Old_age"
test.local smartctl.smart[sg1,194,updated] "Always"
test.local smartctl.smart[sg1,194,when_failed] "-"
test.local smartctl.smart[sg1,194,raw_value] 25
test.local smartctl.smart[sg1,196,attribute_name] "Reallocated_Event_Count"
test.local smartctl.smart[sg1,196,flag] "0x0032"
test.local smartctl.smart[sg1,196,value] 200
test.local smartctl.smart[sg1,196,worst] 200
test.local smartctl.smart[sg1,196,thresh] 0
test.local smartctl.smart[sg1,196,type] "Old_age"
test.local smartctl.smart[sg1,196,updated] "Always"
test.local smartctl.smart[sg1,196,when_failed] "-"
test.local smartctl.smart[sg1,196,raw_value] 0
test.local smartctl.smart[sg1,197,attribute_name] "Current_Pending_Sector"
test.local smartctl.smart[sg1,197,flag] "0x0032"
test.local smartctl.smart[sg1,197,value] 200
test.local smartctl.smart[sg1,197,worst] 200
test.local smartctl.smart[sg1,197,thresh] 0
test.local smartctl.smart[sg1,197,type] "Old_age"
test.local smartctl.smart[sg1,197,updated] "Always"
test.local smartctl.smart[sg1,197,when_failed] "-"
test.local smartctl.smart[sg1,197,raw_value] 0
test.local smartctl.smart[sg1,198,attribute_name] "Offline_Uncorrectable"
test.local smartctl.smart[sg1,198,flag] "0x0030"
test.local smartctl.smart[sg1,198,value] 200
test.local smartctl.smart[sg1,198,worst] 200
test.local smartctl.smart[sg1,198,thresh] 0
test.local smartctl.smart[sg1,198,type] "Old_age"
test.local smartctl.smart[sg1,198,updated] "Offline"
test.local smartctl.smart[sg1,198,when_failed] "-"
test.local smartctl.smart[sg1,198,raw_value] 0
test.local smartctl.smart[sg1,199,attribute_name] "UDMA_CRC_Error_Count"
test.local smartctl.smart[sg1,199,flag] "0x0032"
test.local smartctl.smart[sg1,199,value] 200
test.local smartctl.smart[sg1,199,worst] 200
test.local smartctl.smart[sg1,199,thresh] 0
test.local smartctl.smart[sg1,199,type] "Old_age"
test.local smartctl.smart[sg1,199,updated] "Always"
test.local smartctl.smart[sg1,199,when_failed] "-"
test.local smartctl.smart[sg1,199,raw_value] 0
test.local smartctl.smart[sg1,200,attribute_name] "Multi_Zone_Error_Rate"
test.local smartctl.smart[sg1,200,flag] "0x0008"
test.local smartctl.smart[sg1,200,value] 200
test.local smartctl.smart[sg1,200,worst] 200
test.local smartctl.smart[sg1,200,thresh] 0
test.local smartctl.smart[sg1,200,type] "Old_age"
test.local smartctl.smart[sg1,200,updated] "Offline"
test.local smartctl.smart[sg1,200,when_failed] "-"
test.local smartctl.smart[sg1,200,raw_value] 0


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

четверг, 31 декабря 2015 г.

happy(new Year(2016));

сегодня в 17:20


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

Все желают хорошего и безбажного кода, а я же хочу отдельно поздравить всех инженеров хабра!
С Новым годом, товарищи, без которых не было бы столь большого количества таких клевых вещей)
Желаю, чтобы у Вас просто удавались все расчеты, без проблем находились оптимальные программы управления, сходились все ряды и все системы были устойчивые! Чтобы у навигационных устройств СКО не выходило за требования, чтобы электромагнитные волны распространялись так, как вам нужно, а не как вздумается природе, чтобы фильтр Калмана не «схлопывался», а радиоэлектроника не горела!
Чтобы Вы шли по намеченному пути и не отклонялись от него больше, чем на три сигмы, а все случайные шумы были Гауссовскими!
Ну и общее — чтобы Вам всегда дул попутный ветер во всех ваших начинаниях и Вы всегда достигали поставленные цели!
С Новым годом, тов. инженеры, с Новым годом, жители Хабра, с Новым годом, IT сообщество)

P.S. А тем, кому скучно встречать новый год одному или довелось провести его на смене — добро пожаловать в Новогодний хаброчатик v.2016

Системный администратор, управляющий

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

[Из песочницы] Webpack ProvidePlugin: как не писать простыню import/require в начале javascript модуля

Если вы разрабатываете на современном javascript, то почти любой ваш модуль содержит простыню таких строк:
import React from 'react'
import $ from 'jquery'
...


Как оказалось, большинство этих строк можно не писать, доверив их генерацию автоматике. И помогает в этом новомодный webpack, в котором, как оказывается, полно приятных сюрпризов. Кроме всем известных require и import для любых файлов и уже описанного на хабре «hot module replacement», webpack может проанализировать ваш исходный код и автоматически включить нужные модули на основании используемых литералов. Под катом — краткое описание как работает эта магия.
За анализ ваших исходников и автоматическое создание import директив отвечает специальный плагин ProviderPlugin, который встроен в webpack и не требует установки. Чтобы магия сработала, необходимо указать плагин в конфигурационном файле wbpack и снабдить его списком идентификаторов и модулей. Как известно, webpack использует парсер esprima, и поэтому имеет весьмы точное представление о структуре вашего кода. Встретив в исходнике указанный индентификатор, webpack сгенерирует код загрузки указанного модуля так же, как это он это делает для import или require. Фрагмент конфигурационного файла:
module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      'React':     'react',
...


При использовании плагина с конфигурацией из примера, webpack будет искать использование индентификатора React. Он проигнорирует такую строку:
const foo = "React";


и даже такую:
bar.React = true;


Зато встретив вот такую, сразу поймет что в этом модуле используется ReactJS и снабдит фрагмент bundle кодом загрузки соответствующего модуля:
React.createClass(…)


И конечно же никто не ограничивает вас только ReactJS. Используйте этот способ для популярных библиотек и часто использумых модулей вашего собственного проекта, чтобы сделать код лакониченее и проще:
new webpack.ProvidePlugin({
    '$':          'jquery',
    '_':          'lodash',
    'ReactDOM':   'react-dom',
    'cssModule':  'react-css-modules',
    'Promise':    'bluebird'
  })


P.S.


Если вы, как и я, хотите использовать ES6 import вместо старенького require, то делается это путем указания babel как loader'а для webpack. И не забывайте про .babelrc и presets — в последней версии babel разработчики подготовили сюрприз для новичков, без указания presets babel теперь не делает ничего:
module.exports = {
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
...

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

Графовые базы данных: святой Грааль для разработчиков?

На Хабре не утихают споры о том, какие базы данных лучше и круче, дискуссии о перспективах SQL и NoSQL. Я не удержался и решил порассуждать о том, где могут быть полезны именно графовые БД.

Прежде чем начать, давайте задумаемся, какая информация имеется у нас сегодня на повестке дня? Это уже не просто данные – это весьма непредсказуемая структура, которая со временем может превратиться либо в BigData, либо в сложную семантическую сеть, и часто разработчик не может заранее сказать, какой она будет. Так как же выбрать базу данных – или хотя бы ее архитектуру, чтобы создать действительно быстрое и эффективно работающее приложение?
Чтобы ответить на этот вопрос, попробуем немножко систематизировать ту информацию о базах данных, которая есть у нас. Первый и самый известный кандидат на эксплуатацию – это реляционные базы данных с их единым языком SQL. Просто, удобно и стандартно. Именно благодаря стандартизации реляционные базы данных заработали себе популярность и доминируют на рынке. Но по факту реляционные базы данных – это же просто таблицы, где в каждой строке выстраивается однозначное соответствие между ключом и его многочисленными (или малочисленными) параметрами. Пока приложения обходились отдельными таблицами и не рассматривали особенных взаимодействий между собой и разными типами данных, этого было вполне достаточно.
Город
Год основания
Население (чел)
Площадь (кв. км)
Санкт-Петербург
1703
5 131 942
1 439
Москва
1147
12 108 257
2 511
Екатеринбург
1723
1 412 346
495
Владивосток
1860
603 244
3 3116
Структура реляционной БД

Как альтернатива базам данных SQL где-то с начала 2000-х развивается направление NoSQL. В эту категорию объединяют все подряд – от иерархических и сетевых БД (где помимо иерархии предусмотрены дополнительные связи) до упрощенных БД ключ-значение и документарных баз данных без определенных параметров значений каждого элемента. Причина эволюции этой категории баз данных заключается в следующем: если у вас примитивные и однотипные наборы данных, а запросы касаются одной таблицы – то все ОК, и можно работать с SQL. Но если нет? Если нужно обратиться к 10, 100, 1000 таблиц, чтобы обработать запрос? Тогда реляционная база данных начинает работать медленно, а для написания запроса требуется немало строчек кода.

Пожалуй, наибольшей популярностью баз данных из категории NoSQL пользуются документарные БД, в частности, MongoDB. Они позволяют хранить объекты с произвольными наборами значений, что очень удобно – скажем, у платежного поручения будут одни поля, у приказа – другие. И все это хранится в одном и том же сегменте БД, без подразделения на примитивные таблицы. Однако и у этого подхода есть ограничения, о которых я расскажу парой абзацев ниже.

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

Плюсы и минусы


Итак, про реляционную архитектуру мы уже поговорили – это прекрасное решение для тех случаев, когда все просто и однозначно, но совершенно неповоротливая архитектура для создания сложных и гибких запросов, обработки разнообразных и многократных связей между объектами. Однако, нельзя забывать о таких преимуществах SQL-баз данных, как возможность создания сложных (JOIN) запросов. Такой подход делает стандартизированные реляционные БД более универсальными, ведь пусть даже большим количеством кода, но каждый запрос может быть в них реализован. Например, найти всех людей моложе 20 лет, у которых есть автомобили красного цвета, будет достаточно легко сделать в SQL, в то время как БД из категории NoSQL потребуют массы усилий для решения этой задачи.


Иллюстрация сложного запроса в SQL

Прямая альтернатива SQL – документарные базы данных. Их главное преимущество – это отсутствие единой схемы всех элементов (schemaless). В отличие от SQL эти базы могут сохранять любой сложный объект, например, документ с большим количеством полей за одну операцию, а также за одну операцию выдавать его. Это очень удобно, например, для добавления новых категорий товаров в каталог интернет-магазина, ведь для телевизора, микроволновой печи и утюга будут применяться совершенно разные свойства. В той же MongoDB можно работать с ними через короткие запросы, в то время как в SQL для получения и обновления такой сложной записи придется создавать специальные процедуры, выполняющие множество запросов.


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

Минусы документарных баз также вытекают из их архитектурных особенностей. Например, документарная модель не подразумевает таких простых функций объединения (JOIN), а также возможности работать с двунаправленными связями. Кроме этого документарная база рассчитана на хранение отдельных элементов, не имеющих дополнительных связей между собой. Хороший пример того, с какими сложностями столкнулись создатели социальной сети Diaspora приведен тут (http://ift.tt/1q7IiFP). Ребята сначала начали активно эксплуатировать преимущества документарной модели, но потом просто столкнулись с тем, что социальные данные имеют множество связей друг с другом и ох очень сложно представить в виде отдельных «документов». И им все равно пришлось вернуться к SQL.


Структура социальных данных, для которых не подошла документарная модель хранения

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

Основным преимуществом графовых баз данных в этом свете является универсальность, ведь в них можно хранить и реляционные, и документарные и сложные семантические данные. А сама модель построения БД может меняться и модифицироваться в процессе развития приложения без изменения архитектуры и исходных запросов. А значит – не нужно будет ничего переписывать!

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

Графы все же перспективны?


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

Графовые данные: множество объектов, множество типов связей между ними

Более того, сегодня активно идет доработка RDF – основного стандарта, согласно которому работают графовые базы данных. И, если вспомнить, именно стандартизация SQL сделала такими популярными именно реляционные БД. При этом ряд проектов демонстрирует поддержку OData для создания стандартных веб-запросов через HTTP, а также язык SPARQL, обладающий обширными возможностями для работы с различными видами запросов и данных (тут можно провести аналогию с SQL для реляционных БД). Но и, наконец, за счет развития архитектуры производительность графовых БД растет, и, возможно, скоро окажется выше реляционной даже при небольшом количестве связей. Так что, быть может, в скором времени графовые БД станут чем-то вроде Святого Грааля для разработчиков?

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

Yota: достижения уходящего года

Декабрь — самое время подводить итоги. Yota растет и развивается. В 2015 году мы реализовали много интересных проектов. Так, например, дистрибуция наших SIM-карт для смартфонов и планшетов действует уже в 71 регионе, связь Yota в сетях 2G/3G/4G работает по всей стране. К концу 3-его квартала 2015 года наших пользователей стало полтора миллиона. Только с августа по конец сентября количество клиентов увеличилось на 50%! Под катом — больше цифр, дат и событий, значимых для нас и наших пользователей.

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


SIM-карты Yota для смартфонов и планшетов сегодня можно приобрести в 71 регионе, а модемы и роутеры — в 66.

Мы стали самым динамично развивающимся оператором на телеком-рынке России. С января 2015 Yota начала дистрибуцию SIM-карт для смартфонов и планшетов в 45 новых регионах. География запусков — самая обширная, от европейской части России до Дальнего Востока. Мы подключили и весь Урал, сделали новые запуски на Северном Кавказе, в Сибирском федеральном округе, затронули Поволжье, европейский север России.

Мы расширили собственную розницу по всей России, а в декабре подписали контракт с цифровой ритейл-сетью DNS. Наши продукты можно будет приобрести в более чем 800-х магазинах DNS, расположенных в 68 регионах. И к концу 2015 года, с учетом партнёрских сетей, количество точек, где клиенты могут приобрести SIM-карты Yota, достигло 12 000.

Настоящий безлимитный 4G


В 2015 году мы начали предоставлять безлимитный 4G-интернет в 32 новых регионах. А с запуском в Кабардино-Балкарии наш безлимитный 4G-интернет теперь доступен в 66 регионах России.

Награды 2015


В 2015 году мы получили «Премию Рунета» в номинации «Мобильный Рунет» за лучшее мобильное приложение. Кстати, годом ранее мы получили Премию в номинации «Технологии и инновации».

Скачать приложение можно под Android или iOS.

Кроме того, сайт www.yota.ru получил награду на всероссийском конкурсе сайтов и мобильных приложений «Рейтинг Рунета-2015» в номинации информационные технологии.

Но и это не всё. В этом году мы завоевали ещё целый ряд наград в разных сферах:

  • «Лучший мобильный оператор» по версии Hi-Tech.Mail.Ru, февраль 2015.
  • «Лучшая практика онлайн-обслуживания клиентов», премия «Хрустальная гарнитура», март 2015.
  • «Тренер года», премия «Хрустальная гарнитура», март 2015.
  • «Лучшая команда бэк-офиса» (высокое одобрение жюри), премия «Хрустальная гарнитура», март 2015.
  • «Сцена года» по версии Geometria.ru, Focus Awards 2015, март 2015.
  • «Пресс-служба года», конкурс «Пресс-служба года-2014», апрель 2015.
  • Вошли в рейтинг «Топ-100 лучших директоров по маркетингу» по версии «Коммерсанта», Миша Чернышев, сентябрь 2015
  • Вошли в рейтинг «Топ-100 лучших директоров по IT» по версии «Коммерсанта», Сергей Волков, сентябрь 2015.
  • III место в номинации «Сайт телекоммуникационной компании», премия «Золотой сайт-2015», октябрь 2015.
  • Лауреат в номинации «Мобильный Рунет», «Премия Рунета 2015», ноябрь 2015.
  • Лауреат в номинации «Лучший проект по популяризации инновационной деятельности», премия «Время Инноваций – 2015», декабрь 2015.

Yota на будущее: платформа Yota FutureProof


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

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

Строительство сети заправок для электромобилей и SIM-карты для автомобилей Tesla


Фактически наша серия инновационных экспериментов началась с совместного проекта, организованного с компанией Revolta Motors. В феврале 2015 года мы приняла участие в строительстве сети заправок для электрокаров ЭМИ — название расшифровывается как «ЭлектроМобильная Инфраструктура».

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

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

Первая в мире доставка SIM-карт дронами


В сентябре этого года доставка дронами стала первым массовым экспериментальным проектом в рамках Yota FutureProof. Эксперимент по доставке SIM-карт дронами стартовал 2 сентября в четырёх точках Москвы: на территории института «Стрелка», в парках «Музеон» и «Сокольники», а также в павильоне Политехнического музея на ВДНХ. Целью эксперимента #yotaдоставляет было честно и открыто протестировать в реальных условиях необычный способ доставки и поделиться полученными результатами со стартапами, молодыми учёными и создателями проектов в сфере IT.

  • Всего за месяц работы было доставлено 964 SIM-карты.
  • Общее расстояние, которое пролетели дроны, — более 578 км. Это приблизительно равно расстоянию от Москвы до Воронежа.
  • Цена одной воздушной доставки в Москве составила 488,5 рублей, что несколько ниже средней стоимости доставки курьером (от 500 рублей в пределах МКАД).
  • Общее время подзарядки батарей для квадрокоптеров превысило 96 часов, на это ушло 6,5 кВт электроэнергии.
  • Каждый полёт дрона обошёлся в 4 копейки.
  • Среднее время доставки квадрокоптером составило 90 секунд.

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

Эксперимент с дронами оценили не только клиенты, но и эксперты. Yota вошла в число лауреатов премии «Время инноваций – 2015».

Обновлённый роутер Yota Many


Мы начали продажи обновлённого роутера Yota Many, обладающего более высокой скоростью приёма и передачи данных, а также более стабильно удерживающего связь. В режиме интенсивного использования роутер работает без подзарядки более 12 часов, а время работы в режиме ожидания составляет 60 часов. Обновленная модель Yota Many всего за час заряжается с 0 до 70% — вдвое быстрее, чем предыдущая модель.

Мобильный роутер Yota Many по-прежнему позволяет подключать до восьми устройств, работает в двух режимах (по USB или по WiFi), подходит для смартфонов, планшетов, ридеров, ноутбуков, персональных компьютеров, телевизоров, игровых приставках. В роутерах уже установлены micro-SIM карты Yota. Yota Many совместимы с Windows, Mac OS, Linux и другими операционными системами.

Полный контроль расходов в роуминге


В канун новогодних каникул мы представили обновлённое предложение для международного роуминга. Теперь роуминг в Yota доступен в 229 странах мира.
Основные преимущества обновленного предложения:

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

В 55 странах Европы и СНГ клиенты, уже использующие пакетные предложения, будут оплачивать помегабайтно до 20 Мб интернет-трафика, а при достижении этого объема, получат ещё 20 Мб в подарок. Полчаса входящих звонков в роуминге по всему миру будут стоить 39 рублей в день. Стоимость исходящих звонков — от 19 рублей за минуту.

Трафик установленного мобильного приложения Yota и мобильной версии сайта, а также клиентское обслуживание, включая SMS-поддержку по номеру 0999, чат в мобильном приложении и на сайте yota.ru, остаются бесплатными за границей. Сервис работает даже при нулевом балансе.

С 23 декабря 2015 года по 2 января 2016 года Yota дарит путешественникам новогодний подарок — все наши пользователи, выезжающие в 48 популярных стран, получат первые 40 Мб трафика бесплатно. Подробнее с условиями акции и списком стран можно ознакомиться на сайте yota.ru.

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

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

Новогодний хабрачатик v.2016

Приёмы работы в Blender. Часть 3

Как применить один атрибут на много объектов.

Как покрасить много объектов в один материал?
Создайте новую сцену и удалите куб который дан на старте. На этот куб уже применён материал и Вы можете запутаться. Создайте новый куб. Сделайте несколько его копий. Выберите их.

Среди выбранных объектов есть главный. Главный объект стандартно выделен жёлтым цветом.


Не снимая выделения назначаем материал. Он назначится только на главный объект.

Красим в яркий цвет для понимания происходящего.

Давим Ctrl+L (Link). В появившемся окне выбираем Materials.

На все объекты применился один и тот же материал.

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

Выберем один из кубов.

Изменим его цвет.

Изменился цвет всех кубов. Потому что материал один и тот же. Цифра в панели материалов указывает на количество объектов на которые применён этот материал.

Кликнем левой кнопкой мыши на этой цифре.

Материал изменил имя и связь с материалами других объектов разорвалась. Меняем цвет этого объекта. Изменится только он.

Буква F в строке названия материала.

Присвоение материалу Fake User'а защитит материал от удаления.

Ещё раз кратко методика:

  1. Выбор объектов, которые надо покрасить в один материал
  2. Указание главного объекта, на котором уже назначен тот самый материал
  3. Ctrl+L (Link) > Materials

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

Меш
Связывается сетка полигонов (меш) и материал. Измени один объект и изменятся все. Вообще настройка здесь.

Но можно и с помощью Ctrl+L (Link) > Object Data.

Анимация
Связывается анимация. Можно через меню Ctrl+L (Link) > Animation Data. А редактирование вручную здесь:

Группы
Например: колесо автомобиля с диском, покрышкой, тормозом, болтами, логотипами. Там у Вас и меш и кривые и пустые объекты. Разнородные объекты которые нельзя объединить не применив модификаторы. Не хочется их применять. А колёс 4 штуки. Решение. Назначаем всем объектам одну и ту же группу. Теперь в меню добавления новых объектов есть возможность добавить в сцену эту группу.

Можно Ctrl+L (Link) > Group. Ручные настройки здесь:

Может возникнуть необходимость убрать группы с большого количества объектов. Есть команда Remove From All Groups. Я выбираю все объекты с которых надо убрать группы. Давлю клавишу «Пробел». Выскакивает поиск команд и я набираю там название команды Remove From All Groups и применяю.

Модификаторы
Ctrl+L (Link) > Modifiers. Это копирование модификаторов. Удаление тех что были и назначение новых с другого объекта.

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

[Из песочницы] Феномен инди-игры для Steam на миллион долларов

В данной статье хотел поделиться своими наблюдениями по волнующем каждого инди-разработчика вопросу — «В чем секрет однопользовательских игр-миллионников?». В качестве объекта наблюдения мною выбрана относительно свежая игра – Undertale. Данная игра примечательна тем, что нарушает каноны общепринятой игровой индустрии и вызывает адскую зубную боль у «профессионалов», показывая такие финансовые результаты.
N.B. Всё нижеизложенное верно для разработчиков, ориентированных на «платформу» Steam. Для мобильных, социальных и «портальных» игр подходы несколько иные.

Хроника


Мало кому известный композитор Тоби Фокс заявил об игре своей мечты в мае 2013 г., представив общественности демо-версию Undertale. Как и подобает началу хорошей сказки, программировать и рисовать он не умел, но очень хотел создать свою игру. Графику рисовал в Paint, игру собирал в Game Maker.

Через месяц после появления демо-версии игры, проект выходит на кикстартер, где собирает 51000$ (планировал собрать от 5000-45000$). После успешного сбора средств, говоря о планах, Тоби Фокс озвучил, что постарается сделать игру за год, но сделал её за два (Фил Фишу привет!).

15 сентября 2015 г релиз в steam, чуть более чем за месяц продано почти 200 тыс. копий игры с базовой ценой в десять (9,99) долларов. Даже с учетом региональных цен, выручка уже перевалила за миллион. При этом, взглянув на график продаж, нет ощущения достижения пика.

Undertale становится игрой месяца на многих игровых порталах.

Про качество проекта


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

Оптимизация оставляет желать лучшего. Игра мало чем отличающаяся от игр эпохи «386-х» (20 MHz) нагружает ЦП (2.9 GHz) под 50%. Рекомендуемая разработчиком частота CPU 2 GHz+ (у DOOM 3 были запросы поменьше).

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

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

Отступление №1

Я из того поколения, которое застало рассвет игровой индустрии 80-х и «золотую эру» 90-х. В то время игры были ближе к книгам, чем к кинематографу. Скудная визуализация приводила к тому, что игроку приходилось достраивать в фантазии виртуальный мир. Текстовые квесты, вообще, мало чем отличались от книг. Секрет хорошей книги, как и секрет хорошей игры, был в способности авторов создать мир/событие, которое оставляет в памяти неизгладимый отпечаток, а это невозможно без ярких «переживаний». Например, спустя почти 30 лет, у меня в памяти хорошо отпечатались такие игры для ZX Spectrum, как Elite, Nether Earth, Sim Sity, Dictator, Zombi, Lode Runner (аркада, которой посвятил больше всего времени).

В остатке


И как так получилось, что плохо нарисованная игра с весьма внушительными системными требованиями да ещё и с ошибками стала миллиоником?

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

Отступление №2

Помнит ли кто-то из игроков солдата Биттермана? А ведь он всего на один год старше Гордона Фримана. Биттерман – это главный герой Quake II. Спустя 18 лет о сюжете Quake II лично я ничего не могу сказать, а вот первую «вагонетку» Half-Life помню, как будто это было вчера, про сюжет и говорить не приходится.

Half-Life, своего рода, ознаменовал конец эры шутеров с номинальным сюжетом: Wolfenstein 3D, Doom, Heretic, Quake, Duke Nukem 3D и т.д. Стоит отметить, что подход Valve к созданию шутера от первого лица для однопользовательского прохождения стал эталонным. Невооруженным взглядом видно, что все последующие успешные игры данного жанра Medal of Honor (2002+), Call of Duty (2003+) и т.д. использовали те же приёмы, что и Valve: множество второстепенных героев, диалоги во время игры, задачи по спасению других персонажей и т.п. Одним словом, игрока подводят к мысли, что он часть большого «живого мира», а не одинокий коридорный бегун.

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

Отступление №3

Седовласые игроки ностальгируют по игре Elite I (80-х) отнюдь не из-за «прекрасной графики», музыки и т.п., а тем чувствам, которые они испытали в те времена, бороздя просторы вселенной. Играя на этих чувствах, Elite: Dangerous в своё время на кикстартере собрала 1.58 млн. фунтов стерлингов.

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

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

1. Если, рассказывая о своей игре, вы выделяете в ней: 2bit графику, 100 тыс. часов работы художника над фоном, 1 млн. полигонов на модель и т.д., то вы явно не очень понимаете принцип успеха инди-игры.

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

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

Отступление №4

Игры жанра «top-down shooter» существовали, сколько себя помню. Они были всегда проходными незапоминающимися аркадами с условным сюжетом. Все flash-порталы забиты играми данного жанра. Успех игры Hotline Miami (продано боле 1.7 млн. копий) отнюдь не в способах умерщвления или пиксельной графике (не всегда хорошей), а в сюжет, способном «принудить» игрока к полному прохождению игры. Музыка и графика здесь являются лишь дополнительным инструментом.

Итог


Лично мне не понравилась даже демо-версия Undertale, но, как говорится, это дело вкуса. Мне она интересна больше, как отличный объект исследования. На мой взгляд, Undertale является игрой из далекого прошлого, где качество игр определялось не количеством полигонов на модель.

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

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