Марейн Хавербеке - Выразительный JavaScript
"# ## o####",
"# #",
"############"],
{"#": Wall,
"~": WallFollower,
"o": BouncingCritter}
));
Более жизненная ситуация
Чтобы сделать жизнь в нашем мирке более интересной, добавим понятия еды и размножения. У каждого живого существа появляется новое свойство, energy (энергия), которая уменьшается при совершении действий, и увеличивается при поедании еды. Когда у существа достаточно энергии, он может размножаться, создавая новое существо того же типа. Для упрощения расчётов наши существа размножаются сами по себе.
Если существа только двигаются и едят друг друга, мир вскоре поддастся возрастающей энтропии, в нём закончится энергия и он превратится в пустыню. Для предотвращения этого финала (или оттягивания), мы добавляем в него растения. Они не двигаются. Они просто занимаются фотосинтезом и растут (нарабатывают энергию), и размножаются.
Чтобы это заработало, нам нужен мир с другим методом letAct. Мы могли бы просто заменить метод прототипа World, но я привык к нашей симуляции ходящих по стенам существ и не хотел бы её разрушать.
Одно из решений – использовать наследование. Мы создаём новый конструктор, LifelikeWorld, чей прототип основан на прототипе World, но переопределяет метод letAct. Новый letAct передаёт работу по совершению действий в разные функции, хранящиеся в объекте actionTypes.
function LifelikeWorld(map, legend) {
World.call(this, map, legend);
}
LifelikeWorld.prototype = Object.create(World.prototype);
var actionTypes = Object.create(null);
LifelikeWorld.prototype.letAct = function(critter, vector) {
var action = critter.act(new View(this, vector));
var handled = action &&
action.type in actionTypes &&
actionTypes[action.type].call(this, critter,
vector, action);
if (!handled) {
critter.energy -= 0.2;
if (critter.energy <= 0)
this.grid.set(vector, null);
}
};
Новый метод letAct проверяет, было ли передано хоть какое-то действие, затем – есть ли функция, обрабатывающая его, и в конце – возвращает ли эта функция true, показывая, что действие выполнено успешно. Обратите внимание на использование call, чтобы дать функции доступ к мировому объекту через this.
Если действие по какой-либо причине не сработало, действием по умолчанию для существа будет ожидание. Он теряет 0,2 единицы энергии, а когда его уровень энергии падает ниже нуля, он умирает и исчезает с сетки.
Обработчики действий
Самое простое действие – рост, его используют растения. Когда возвращается объект action типа {type: "grow"}, будет вызван следующий метод-обработчик:
actionTypes.grow = function(critter) {
critter.energy += 0.5;
return true;
};
Рост всегда успешен и добавляет половину единицы к энергетическому уровню растения.
Движение получается более сложным.
actionTypes.move = function(critter, vector, action) {
var dest = this.checkDestination(action, vector);
if (dest == null ||
critter.energy <= 1 ||
this.grid.get(dest) != null)
return false;
critter.energy -= 1;
this.grid.set(vector, null);
this.grid.set(dest, critter);
return true;
};
Это действие вначале проверяет, используя метод checkDestination, объявленный ранее, предоставляет ли действие допустимое направление. Если нет, или же в том направлении не пустой участок, или же у существа недостаёт энергии – move возвращает false, показывая, что действие не состоялось. В ином случае он двигает существо и вычитает энергию.
Кроме движения, существа могут есть.
actionTypes.eat = function(critter, vector, action) {
var dest = this.checkDestination(action, vector);
var atDest = dest != null && this.grid.get(dest);
if (!atDest || atDest.energy == null)
return false;
critter.energy += atDest.energy;
this.grid.set(dest, null);
return true;
};
Поедание другого существа также требует предоставления допустимой клетки направления. В этом случае клетка должна содержать что-либо с энергией, например существо (но не стену, их есть нельзя). Если это подтверждается, энергия съеденного переходит к едоку, а жертва удаляется с сетки.
И наконец, мы позволяем существам размножаться.
actionTypes.reproduce = function(critter, vector, action) {
var baby = elementFromChar(this.legend,
critter.originChar);
var dest = this.checkDestination(action, vector);
if (dest == null ||
critter.energy <= 2 * baby.energy ||
this.grid.get(dest) != null)
return false;
critter.energy -= 2 * baby.energy;
this.grid.set(dest, baby);
return true;
};
Размножение отнимает в два раза больше энергии, чем есть у новорожденного. Поэтому мы создаём гипотетического отпрыска, используя elementFromChar на оригинальном существе. Как только у нас есть отпрыск, мы можем выяснить его энергетический уровень и проверить, есть ли у родителя достаточно энергии, чтобы родить его. Также нам потребуется допустимая клетка направления.
Если всё в порядке, отпрыск помещается на сетку (и перестаёт быть гипотетическим), а энергия тратится.
Населяем мир
Теперь у нас есть основа для симуляции существ, больше похожих на настоящие. Мы могли бы поместить в новый мир существ из старого, но они бы просто умерли, так как у них нет свойства energy. Давайте сделаем новых. Сначала напишем растение, которое, по сути, довольно простая форма жизни.
function Plant() {
this.energy = 3 + Math.random() * 4;
}
Plant.prototype.act = function(context) {
if (this.energy > 15) {
var space = context.find(" ");
if (space)
return {type: "reproduce", direction: space};
}
if (this.energy < 20)
return {type: "grow"};
};
Растения начинают со случайного уровня энергии от 3 до 7, чтобы они не размножались все в один ход. Когда растение достигает энергии 15, а рядом есть пустая клетка – оно размножается в неё. Если оно не может размножиться, то просто растёт, пока не достигнет энергии 20.
Теперь определим поедателя растений.
function PlantEater() {
this.energy = 20;
}
PlantEater.prototype.act = function(context) {
var space = context.find(" ");
if (this.energy > 60 && space)
return {type: "reproduce", direction: space};
var plant = context.find("*");
if (plant)
return {type: "eat", direction: plant};
if (space)
return {type: "move", direction: space};
};
Для растений будем использовать символ * — то, что будет искать существо в поисках еды.
Вдохнём жизнь
И теперь у нас есть достаточно элементов для нового мира. Представьте следующую карту как травянистую долину, где пасётся стадо травоядных, лежат несколько валунов и цветёт буйная растительность.
var valley = new LifelikeWorld(
["############################",
"##### ######",
"## *** **##",
"# *##** ** O *##",
"# *** O ##** *#",
"# O ##*** #",
"# ##** #",
"# O #* #",
"#* #** O #",
"#*** ##** O **#",
"##**** ###*** *###",
"############################"],
{"#": Wall,
"O": PlantEater,
"*": Plant}
);
Большую часть времени растения размножаются и разрастаются, но затем изобилие еды приводит к взрывному росту популяции травоядных, которые съедают почти всю растительность, что приводит к массовому вымиранию от голода. Иногда экосистема восстанавливается и начинается новый цикл. В других случаях какой-то из видов вымирает. Если травоядные, тогда всё пространство заполняется растениями. Если растения – оставшиеся существа умирают от голода, и долина превращается в необитаемую пустошь. О, жестокость природы…
Упражнения
Искусственный идиот
Грустно, когда жители нашего мира вымирают за несколько минут. Чтобы справиться с этим, мы можем попробовать создать более умного поедателя растений.
У наших травоядных есть несколько очевидных проблем. Во-первых, они жадные – поедают каждое растение, которое находят, пока полностью не уничтожат всю растительность. Во-вторых, их случайное движение (вспомните, что метод view.find возвращает случайное направление) заставляет их болтаться неэффективно и помирать с голоду, если рядом не окажется растений. И наконец, они слишком быстро размножаются, что делает циклы от изобилия к голоду слишком быстрыми.
Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение Марейн Хавербеке - Выразительный JavaScript, относящееся к жанру Программирование. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.


