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

Затем создайте тип объекта ArraySeq, оборачивающий массив, и позволяющий проход по массиву с использованием разработанного вами интерфейса. Создайте другой тип объекта, RangeSeq, который проходит по диапазону чисел (его конструктор должен принимать аргументы from и to).

// Ваш код.

logFive(new ArraySeq([1, 2]));

// → 1

// → 2

logFive(new RangeSeq(100, 1000));

// → 100

// → 101

// → 102

// → 103

// → 104

7. Проект: электронная жизнь

Вопрос о том, могут ли машины думать, так же уместен, как вопрос о том, могут ли подводные лодки плавать.

Эдсгер Дейкстра, «Угрозы вычислительной науке»

В главах-проектах я перестану закидывать вас теорией, и буду работать вместе с вами над программами. Теория незаменима при обучении программированию, но она должна сопровождаться чтением и пониманием нетривиальных программ.

Наш проект – постройка виртуальной экосистемы, небольшого мира, населённого существами, которые двигаются и борются за выживание.

Определение

Чтобы задача стала выполнимой, мы кардинально упростим концепцию мира. А именно – мир будет двумерной сеткой, где каждая сущность занимает одну клетку. На каждом ходу существа получат возможность выполнить какое-либо действие.

Таким образом, мы порубим время и пространство на единицы фиксированного размера: клетки для пространства и ходы для времени. Конечно, это грубое и неаккуратное приближение. Но наша симуляция должна быть развлекательной, а не аккуратной, поэтому мы свободно «срезаем углы».

Определить мир мы можем при помощи плана – массива строк, который раскладывает мировую сетку, используя один символ на клетку.

var plan = ["############################",

            "#      #    #      o      ##",

            "#                          #",

            "#          #####           #",

            "##         #   #    ##     #",

            "###           ##     #     #",

            "#           ###      #     #",

            "#   ####                   #",

            "#   ##       o             #",

            "# o  #         o       ### #",

            "#    #                     #",

            "############################"];

Символ “#” обозначает стены и камни, “o” – существо. Пробелы – пустое пространство.

План можно использовать для создания объекта мира. Он следит за размером и содержимым мира. У него есть метод toString, который преобразовывает мир в выводимую строчку (такую, как план, на котором он основан), чтобы мы могли наблюдать за происходящим внутри него. У объекта мир есть метод turn (ход), позволяющий всем существам сделать один ход и обновляющий состояние мира в соответствии с их действиями.

Изображаем пространство

У сетки, моделирующей мир, заданы ширина и высота. Клетки определяются координатами x и y. Мы используем простой тип Vector (из упражнений к предыдущей главе) для представления этих пар координат.

function Vector(x, y) {

  this.x = x;

  this.y = y;

}

Vector.prototype.plus = function(other) {

  return new Vector(this.x + other.x, this.y + other.y);

};

Потом нам нужен тип объекта, моделирующий саму сетку. Сетка – часть мира, но мы делаем из неё отдельный объект (который будет свойством мирового объекта), чтобы не усложнять мировой объект. Мир должен загружать себя вещами, относящимися к миру, а сетка – вещами, относящимися к сетке.

Для хранения сетки значений у нас есть несколько вариантов. Можно использовать массив из массивов-строк, и использовать двухступенчатый доступ к свойствам:

var grid = [["top left",    "top middle",    "top right"],

            ["bottom left", "bottom middle", "bottom right"]];

console.log(grid[1][2]);

// → bottom right

Или мы можем взять один массив, размера width × height, и решить, что элемент (x, y) находится в позиции x + (y × width).

var grid = ["top left",    "top middle",    "top right",

            "bottom left", "bottom middle", "bottom right"];

console.log(grid[2 + (1 * 3)]);

// → bottom right

Поскольку доступ будет завёрнут в методах объекта сетки, внешнему коду всё равно, какой подход будет выбран. Я выбрал второй, потому что с ним проще создавать массив. При вызове конструктора Array с одним числом в качестве аргумента он создаёт новый пустой массив заданной длины.