Выбрать главу

В нашей собственной функции высшего порядка мы можем включить поддержку контекстного параметра, используя метод call для вызова функции, переданной в качестве аргумента. К примеру, вот вам метод forEach для нашего типа Grid, вызывающий заданную функцию для каждого элемента сетки, который не равен null или undefined:

Grid.prototype.forEach = function(f, context) {

  for (var y = 0; y < this.height; y++) {

    for (var x = 0; x < this.width; x++) {

      var value = this.space[x + y * this.width];

      if (value != null)

        f.call(context, value, new Vector(x, y));

    }

  }

};

Оживляем мир

Следующий шаг – создание метода turn (ход) для мирового объекта, дающего существам возможность действовать. Он будет обходить сетку методом forEach, и искать объекты, у которых есть метод act. Найдя объект, turn вызывает этот метод, получая объект action и производит это действие, если оно допустимо. Пока мы понимаем только действие “move”.

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

World.prototype.turn = function() {

  var acted = [];

  this.grid.forEach(function(critter, vector) {

    if (critter.act && acted.indexOf(critter) == -1) {

      acted.push(critter);

      this.letAct(critter, vector);

    }

  }, this);

};

Второй параметр метода forEach используется для доступа к правильной переменной this во внутренней функции. Метод letAct содержит логику, которая позволяет существам двигаться.

World.prototype.letAct = function(critter, vector) {

  var action = critter.act(new View(this, vector));

  if (action && action.type == "move") {

    var dest = this.checkDestination(action, vector);

    if (dest && this.grid.get(dest) == null) {

      this.grid.set(vector, null);

      this.grid.set(dest, critter);

    }

  }

};

World.prototype.checkDestination = function(action, vector) {

  if (directions.hasOwnProperty(action.direction)) {

    var dest = vector.plus(directions[action.direction]);

    if (this.grid.isInside(dest))

      return dest;

  }

};

Сначала мы просто просим существо действовать, передавая ему объект view, который знает про мир и текущее положение существа в мире (мы скоро зададим View). Метод act возвращает какое-либо действие.

Если тип действия не “move”, оно игнорируется. Если “move”, и если у него есть свойство direction, ссылающееся на допустимое направление, и если клетка в этом направлении пустует (null), мы назначаем клетке, где только что было существо, null, и сохраняем существо в клетке назначения.

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

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

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