Конспект урока
Наша игра работает, но обладает ограничением — мы не можем её полноценно тестировать.
const cards = l(
cons('Алчный натиск скорости', () => 4),
cons('Демонов маршрут воздаяния', health => Math.round(health * 0.3)),
);
const game = make(cards);
const log = game('John', 'Ada');
Если колода содержит две и более карты, то ход игры и её результат заранее определить и протестировать НЕ получится.
assert.equal(length(log), ?); // неизвестно
Ведь в процессе игры карта выбирается случайным образом:
const card = random(cards); // эта строчка делает игру недетерминированной
const cardName = car(card);
const damage = cdr(card)(health2);
const newHealth = health2 - damage;
Вызов random(cards)
возвращает случайную карту. Этот код располжен внутри функции с игрой, поэтому делает недетерминированной всю игру.
Сейчас выбор карты осуществляется внутри игры, и мы не можем на это никак повлиять. Но ситуация изменится, если сделать так, чтобы алгоритм выбора карты игра получала "снаружи". Это легко реализовать с помощью передачи параметра.
Рассмотрим простой пример: функция принимает на вход колоду карт cards
, внутри происходит случайный выбор карты и какие-то другие дальнейшие манипуляции. Это принципиальная схема, если отвлечься от несущественных деталей.
(cards) => {
const card = random(cards);
// to do something with card
};
Эта функция НЕ является чистой, она недетерминирована. И это нормально для игры, но не для тестов.
Применим к функции технику инвертирования, реализовав передачу процесса выбора карты снаружи, через параметры:
(cards, customRandom) => {
const card = customRandom(cards);
// to do something with card
}
Теперь мы можем управлять процессом выбора карты, передавая (в зависимости от ситуации и наших целей) ту или иную функцию в параметр customRandom
.
Для тестирования нам не подойдёт обычный random. Поэтому определим и передадим в функцию игры специальную функцию выбора карты, обеспечивающую предсказуемое поведение:
const cards = l(
cons('Тусклый маниту диспута', () => 7),
cons('Мыслительный рубитель ограды', health => Math.round(health * 0.8))
);
let cardIndex = 1;
const game = make(cards, (pack) => {
cardIndex = cardIndex === 0 ? 1 : 0;
return get(cardIndex, pack);
});
const log = game('John', 'Ada');
Мы передали в игру (вторым параметром) анонимную функцию:
(pack) => {
cardIndex = cardIndex === 0 ? 1 : 0;
return get(cardIndex, pack);
};
Её ядро заключается в строчке кода, определяющей текущий индекс:
cardIndex = cardIndex === 0 ? 1 : 0;
Значение переменной cardIndex
функция берёт из переменной, определённой во внешнем окружении:
let cardIndex = 1;
Это важно, так мы можем смоделировать нужное нам предсказуемое поведение. При каждом новом вызове значение cardIndex
циклически меняется с нуля на единицу и наоборот (индексирование в списке карт начинается с нуля!). Это как раз то, что нужно для ситуации колоды, состоящей из двух карт.
Обязательно проанализируйте процесс выбора карт в модуле с тестами в практике к этому уроку!
На примере простой функции продемонстрируем принцип определения циклического (а значит предсказуемого!) изменения величины:
let cardIndex = 1;
const getIndex = () => {
cardIndex = cardIndex === 0 ? 1 : 0;
return cardIndex;
};
for (let i = 0; i < 10; i += 1) {
console.log(getIndex());
}
// => 0
// => 1
// => 0
// => 1
// => 0
// => 1
// => 0
// => 1
// => 0
// => 1
https://repl.it/@hexlet/js-ddp-invert-index
Для колоды из трёх или какого-нибудь другого количества карт надо будет модифицировать функцию. Можно сразу написать более универсальный вариант:
const customRandom = (cardIndex, minIndex, maxIndex) => {
return () => {
if (cardIndex > maxIndex) {
cardIndex = minIndex;
}
const currentIndex = cardIndex;
cardIndex += 1;
return currentIndex;
};
};
console.log('Выводим индексы с 0 до 2. Начинаем с 0');
const getIndex = customRandom(0, 0, 2);
for (let i = 0; i < 6; i += 1) {
console.log(getIndex());
}
console.log('Выводим индексы с 1 до 5. Начинаем с 2');
const getIndex2 = customRandom(2, 1, 5);
for (let i = 0; i < 10; i += 1) {
console.log(getIndex2());
}
// => Выводим индексы с 0 до 2. Начинаем с 0
// => 0
// => 1
// => 2
// => 0
// => 1
// => 2
// => Выводим индексы с 1 до 5. Начинаем с 2
// => 2
// => 3
// => 4
// => 5
// => 1
// => 2
// => 3
// => 4
// => 5
// => 1
https://repl.it/@hexlet/js-ddp-invert-generator
С помощью техники инвертирования мы добились следующих преимуществ:
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт