`
Читать книги » Книги » Компьютеры и Интернет » Программирование » Марейн Хавербеке - Выразительный JavaScript

Марейн Хавербеке - Выразительный JavaScript

1 ... 22 23 24 25 26 ... 79 ВПЕРЕД
Перейти на страницу:

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 (представляющий пустое пространство).

function elementFromChar(legend, ch) {

  if (ch == " ")

    return null;

  var element = new legend[ch]();

  element.originChar = ch;

  return element;

}

function World(map, legend) {

  var grid = new Grid(map[0].length, map.length);

  this.grid = grid;

  this.legend = legend;

  map.forEach(function(line, y) {

    for (var x = 0; x < line.length; x++)

      grid.set(new Vector(x, y),

               elementFromChar(legend, line[x]));

  });

}

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

Нам понадобится это свойство originChar при изготовлении мирового метода toString. Метод строит карту в виде строки из текущего состояния мира, проходя двумерным циклом по клеткам сетки.

function charFromElement(element) {

  if (element == null)

    return " ";

  else

    return element.originChar;

}

World.prototype.toString = function() {

  var output = "";

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

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

      var element = this.grid.get(new Vector(x, y));

      output += charFromElement(element);

    }

    output += "n";

  }

  return output;

};

Стена wall – простой объект. Используется для занятия места и не имеет метода act.

function Wall() {}

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

var world = new World(plan, {"#": Wall, "o": BouncingCritter});

console.log(world.toString());

// → ############################

//   #      #    #      o      ##

//   #                          #

//   #          #####           #

//   ##         #   #    ##     #

//   ###           ##     #     #

//   #           ###      #     #

//   #   ####                   #

//   #   ##       o             #

//   # o  #         o       ### #

//   #    #                     #

//   ############################

this и его область видимости

В конструкторе World есть вызов forEach. Хочу отметить, что внутри функции, передаваемой в forEach, мы уже не находимся непосредственно в области видимости конструктора. Каждый вызов функции получает своё пространство имён, поэтому this внутри неё уже не ссылается на создаваемый объект, на который ссылается this снаружи функции. И вообще, если функция вызывается не как метод, this будет относиться к глобальному объекту.

Значит, мы не можем писать this.grid для доступа к сетке изнутри цикла. Вместо этого внешняя функция создаёт локальную переменную grid, через которую внутренняя функция получает доступ к сетке.

Это промах в дизайне JavaScript. К счастью, в следующей версии есть решение этой проблемы. А пока есть пути обхода. Обычно пишут var self = this и после этого работают с переменной self.

Другое решение – использовать метод bind, который позволяет привязаться к конкретному объекту this.

var test = {

  prop: 10,

  addPropTo: function(array) {

    return array.map(function(elt) {

      return this.prop + elt;

    }.bind(this));

  }

};

console.log(test.addPropTo([5]));

// → [15]

Функция, передаваемая в map – результат привязки вызова, и посему её this привязан к первому аргументу, переданному в bind, то есть переменной this внешней функции (в которой содержится объект test).

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

var test = {

  prop: 10,

  addPropTo: function(array) {

    return array.map(function(elt) {

      return this.prop + elt;

    }, this); // ← без bind

  }

};

console.log(test.addPropTo([5]));

// → [15]

Это работает только с теми функциями высшего порядка, у которых есть такой контекстный параметр. Если нет – приходится использовать другие упомянутые подходы.

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

1 ... 22 23 24 25 26 ... 79 ВПЕРЕД
Перейти на страницу:

Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение Марейн Хавербеке - Выразительный JavaScript, относящееся к жанру Программирование. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.

Комментарии (0)