В нашей собственной функции высшего порядка мы можем включить поддержку контекстного параметра, используя метод 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
, выглядит следующим образом: