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

function View(world, vector) {

  this.world = world;

  this.vector = vector;

}

View.prototype.look = function(dir) {

  var target = this.vector.plus(directions[dir]);

  if (this.world.grid.isInside(target))

    return charFromElement(this.world.grid.get(target));

  else

    return "#";

};

View.prototype.findAll = function(ch) {

  var found = [];

  for (var dir in directions)

    if (this.look(dir) == ch)

      found.push(dir);

  return found;

};

View.prototype.find = function(ch) {

  var found = this.findAll(ch);

  if (found.length == 0) return null;

  return randomElement(found);

};

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

Оно двигается

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

for (var i = 0; i < 5; i++) {

  world.turn();

  console.log(world.toString());

}

// → … пять ходов

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

animateWorld(world);

// → … заработало!

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

Больше форм жизни

Одна из интересных ситуаций, происходящих в мире, случается, когда два существа отскакивают друг от друга. Можете придумать другую интересную форму взаимодействий?

Я придумал существо, двигающееся по стенке. Оно держит свою левую руку (лапу, щупальце, что угодно) на стене и двигается вдоль неё. Это, как оказалось, не так-то просто запрограммировать.

Нам нужно будет вычислять, используя направления в пространстве. Так как направления заданы набором строк, нам надо задать свою операцию dirPlus для подсчёта относительных направлений. dirPlus("n", 1) означает поворот по часовой на 45 градусов на север, что приводит к “ne”. dirPlus("s", -2) означает поворот против часовой с юга, то есть на восток.

var directionNames = Object.keys(directions);

function dirPlus(dir, n) {

  var index = directionNames.indexOf(dir);

  return directionNames[(index + n + 8) % 8];

}

function WallFollower() {

  this.dir = "s";

}

WallFollower.prototype.act = function(view) {

  var start = this.dir;

  if (view.look(dirPlus(this.dir, -3)) != " ")

    start = this.dir = dirPlus(this.dir, -2);

  while (view.look(this.dir) != " ") {

    this.dir = dirPlus(this.dir, 1);

    if (this.dir == start) break;

  }

  return {type: "move", direction: this.dir};

};

Метод act только сканирует окружение существа, начиная с левой стороны и дальше по часовой, пока не находит пустую клетку. Затем он двигается в направлении этой клетки.

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

Так что есть ещё одна проверка через if, что сканирование нужно начинать, если существо только что прошло мимо какого-либо препятствия. То есть, если пространство сзади и слева не пустое. В противном случае сканировать начинаем впереди, поэтому в пустом пространстве он будет идти прямо.

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

Этот небольшой мир показывает существ, двигающихся по стенам:

animateWorld(new World(

  ["############",

   "#     #    #",

   "#   ~    ~ #",

   "#  ##      #",

   "#  ##  o####",

   "#          #",

   "############"],

  {"#": Wall,

   "~": WallFollower,

   "o": BouncingCritter}

));