Следующий код объявляет объект Grid
(сетка) с основными методами:
function Grid(width, height) {
this.space = new Array(width * height);
this.width = width;
this.height = height;
}
Grid.prototype.isInside = function(vector) {
return vector.x >= 0 && vector.x < this.width &&
vector.y >= 0 && vector.y < this.height;
};
Grid.prototype.get = function(vector) {
return this.space[vector.x + this.width * vector.y];
};
Grid.prototype.set = function(vector, value) {
this.space[vector.x + this.width * vector.y] = value;
};
Элементарный тест:
var grid = new Grid(5, 5);
console.log(grid.get(new Vector(1, 1)));
// → undefined
grid.set(new Vector(1, 1), "X");
console.log(grid.get(new Vector(1, 1)));
// → X
Программный интерфейс существ
Перед тем, как заняться конструктором мира World
, нам надо определиться с объектами существ, населяющих его. Я упомянул, что мир будет спрашивать существ, какие они хотят произвести действия. Работать это будет так: у каждого объекта существа есть метод act
, который при вызове возвращает действие action
. Action
– объект типа property
, который называет тип действия, которое хочет совершить существо, к примеру “move”
. Action
может содержать дополнительную информацию – такую, как направление движения.
Существа ужасно близоруки и видят только непосредственно прилегающие к ним клетки. Но и это может пригодиться при выборе действий. При вызове метода act
ему даётся объект view
, который позволяет существу изучить прилегающую местность. Мы называем восемь соседних клеток их направлениями по компасу: “n”
на север, “ne”
на северо-восток, и т. п. Вот какой объект будет использоваться для преобразования из названий направлений в смещения по координатам:
var directions = {
"n": new Vector( 0, -1),
"ne": new Vector( 1, -1),
"e": new Vector( 1, 0),
"se": new Vector( 1, 1),
"s": new Vector( 0, 1),
"sw": new Vector(-1, 1),
"w": new Vector(-1, 0),
"nw": new Vector(-1, -1)
};
У объекта view
есть метод look
, который принимает направление и возвращает символ, к примеру "#"
, если там стена, или пробел, если там ничего нет. Объект также предоставляет удобные методы find
и findAll
. Оба принимают один из символов, представляющих вещи на карте, как аргумент. Первый возвращает направление, в котором этот предмет можно найти рядом с существом, или же null
, если такого предмета рядом нет. Второй возвращает массив со всеми возможными направлениями, где найден такой предмет. Например, существо слева от стены (на западе) получит [“ne”, “e”, “se”]
при вызове findAll
с аргументом “#”
.
Вот простое тупое существо, которое просто идёт, пока не врезается в препятствие, а затем отскакивает в случайном направлении.
function randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}
function BouncingCritter() {
this.direction = randomElement(Object.keys(directions));
};
BouncingCritter.prototype.act = function(view) {
if (view.look(this.direction) != " ")
this.direction = view.find(" ") || "s";
return {type: "move", direction: this.direction};
};
Вспомогательная функция randomElement
просто выбирает случайный элемент массива, используя Math.random
и немного арифметики, чтобы получить случайный индекс. Мы и дальше будем использовать случайность, так как она – полезная штука в симуляциях.
Конструктор BouncingCritter
вызывает Object.keys
. Мы видели эту функцию в предыдущей главе – она возвращает массив со всеми именами свойств объекта. Тут она получает все имена направлений из объекта directions
, заданного ранее.
Конструкция || "s"
в методе act
нужна, чтобы this.direction
не получил null
, в случае если существо забилось в угол без свободного пространства вокруг – например, окружено другими существами.
Мировой объект
Теперь можно приступать к мировому объекту World
. Конструктор принимает план (массив строк, представляющих сетку мира) и объект legend
. Это объект, сообщающий, что означает каждый из символов карты. В нём есть конструктор для каждого символа – кроме пробела, который ссылается на null
(представляющий пустое пространство).