Задача добавить новые интересные карты в карточную игру.
- «Осмотр реквизита»
Для того, чтобы запустить игру понадобится веб-сервер. Воспользуемся веб-сервером для Node.js.
Необходимые настройки уже сделаны в
package.json, поэтому все, что нужно сделать:
- Иметь установленную Node.js.
- Перейти в консоли в папку с
package.jsonи выполнить командуnpm install. Для этого можно воспользоваться встроенным в IDE терминалом (верно для WebStorm и Visual Studio Code). Произойдет скачивание веб-сервера и его зависимостей в папкуnode_modules. - Выполнить команду
npm startдля запуска веб-сервера. После запуска веб-сервер выведет в консоль свой адрес. Один из адресов должен быть http://localhost:8080, по нему и доступна игра.localhost- стандартное доменное имя для хоста, на котором расположена программа-клиент, в нашем случае браузер.8080- порт веб-сервера, который часто используется в разработке (в продакшене обычно используется порт80).
Для начала осмотрись:
- Запусти
index.htmlи понаблюдай как игра играет сама в себя. - Посмотри
index.js. В этом файле создаются колоды карты и запускается игра. - Загляни в
Game.js, обрати внимания на стадии хода. - Изучи
Card.js, чтобы разобраться какие действия происходят с картой, какие возможности по расширению заложены.
Браузер имеет правильное свойство кэшировать все, что скачивает: скрипты, стили, картинки.
Правда в разработке это может мешать: только что внесенные изменения не будут появляться при обновлении страницы.
Для перезагрузки страницы без кэша можно использовать сочетание Control+F5.
А в Chrome можно полностью отключить кэширование в Developer Tools:
- Открой
Developer Tools - Перейди во вкладку
Network - Установи флажок
Disable cache
Изначально в скриптах на JavaScript нельзя было импортировать код из других файлов.
Если нужно было добавить на страницу несколько скриптов, они добавлялись отдельными тэгами script на страницу.
Chrome уже поддерживает использование модулей ECMAScript из коробки.
Чтобы это работало в файле index.html, основной скрипт подключается так:
<script type="module" src="index.js"></script>
Кроме того, в каждом из файлов TaskQueue.js, Game.js, Card.js, CardView.js, Player.js, PlayerView.js, SpeedRate.js
используются директивы export или export default, чтобы экспортировать определенные в файле сущности.
И в этих же файлах, а еще в index.js используются директивы import, чтобы подключать сущности из других файлов.
В результате каждый скрипт подключает только то, что нужно ему, дерево зависимостей скриптов строится динамически
и не надо прописывать пути до всех скриптов в index.html.
Инструктаж окончен. Твоя задача - создавать новые типы карт, наследующиеся от Card. Можешь приступать к решению задачи!
- «Классы» Во всем коде типы определены по-старому, через прототипы. Изоляция кода при этом обеспечивает за счет техники IIFE: https://developer.mozilla.org/ru/docs/%D0%A1%D0%BB%D0%BE%D0%B2%D0%B0%D1%80%D1%8C/IIFE
Благодаря использованию модулей ECMAScript каждый файл изолирован и IIFE больше не нужны, ведь снаружи будет видно только то, что явным образом экспортируется.
Сами типы можно определять с помощью новой инструкции class.
Результат будет тот же, но запись будет более лаконичной.
Чтобы лучше понять разницу между старым и новым синтаксисом перепиши TaskQueue с использованием class и без IIFE:
- Убери обрамляющую определение
TaskQueueсамовызывающуюся функцию. - Перенеси вверх функцию
runNextTask, потому что это не методTaskQueue. - Объяви тип
TaskQueueс помощью инструкцииclass, определи constructor и все методы. export defaultпоставь сразу перед инструкцииclass.
Пример перехода от старого синтаксиса к новому:
// до переработки, старый синтаксис
const Bar = function () {
function BarInternal(a, b) {
this.a = a;
this.b = b;
}
BarInternal.prototype.do = function (c) {
this.a += secret(c);
}
function secret(value) {
return 2*value;
}
return BarInternal;
}();
// после переработки, новый синтаксис
class Bar {
constructor(a, b) {
this.a = a;
this.b = b;
}
do(c) {
this.a += secret(c);
}
};
// эта функция доступна только внутри модуля
function secret(value) {
return 2*value;
}- «Утки против собак»
Создай в
index.jsдве новые карты, используйclassи унаследовав их отCard:
Duckс именем «Мирная утка» и силой 2Dogс именем «Пес-бандит» и силой 3
Посмотреть, как унаследовать один тип от другого с помощью классов можно тут: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Classes#%D0%9D%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%B2_%D1%81_%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E_extends
Card при этом не нужно переводить на современный синтаксис с class,
можно просто создавать новые типы в новом синтаксисе.
Не забудь вызвать базовый конструктор Card с помощью super!
- Новые карты должны создаваться, даже если им не передать параметров:
new Duck()иnew Dog(). - Методы
quacksиswimsутки должны создаваться на уровне класса, а не добавляться в конструкторе. - После добавления новых типов замени карты в колоде шерифа на уток, а в колоде бандита - на собак.
- Функция
isDuckдолжна возвращатьtrueдля утки, а функцияisDog— для собаки. - Если все сделано правильно, то внизу карты утки должен быть текст Duck➔ Card, а у собаки Dog➔ Card.
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Duck(),
new Duck(),
];
const banditStartDeck = [
new Dog(),
];- «Утка или собака?»
Метод
getDescriptionsвCardсоздан для того, чтобы на картах появлялась дополнительная информация. Его функционал хочется расширить. Причем так, чтобы это работало и для уток, и для собак, и для всех остальных существ, которые будут добавляться.
-
Создай новый тип
Creatureи унаследуй его отCard. -
Сделай так, чтобы
DuckиDogнаследовались отCreature. -
Переопредели в классе
CreatureреализациюgetDescriptionsна новую. Теперь в ней должны возвращаться две строки описания в виде массива. Первая из них — из функцииgetCreatureDescription, которая определена вindex.js. Вторая — изgetDescriptionsвCard. Для этого надо вызывать эту базовую версию функцииgetDescriptionsиз прототипаCard. В классах это делается просто:super.getDescriptions();Заметь, что
getDescriptionsдолжен возвращать массив строк, а не просто строку! Верный признак, что ты напутал что-то с возвращаемым значением - вертикальная надпись на карте: У т к а Используйspread-оператор(так:[newValue, ...values]) или методunshiftмассива, чтобы дополнить возвращаемый базовой версиейgetDescriptionsмассив новыми значениями.
Используя те же колоды, убедись, что у уток появилась надпись «Утка» над строкой с цепочкой наследования, а у собак надпись «Собака» над цепочкой наследования.
- «Громила» Для уток все становится плохо, когда в рядах бандитов появляется Громила.
Добавь карту Trasher:
- называется Громила, сила 5, наследуется от
Dog. - если Громилу атакуют, то он получает на 1 меньше урона.
Подсказки:
- переопредели метод
modifyTakenDamage, чтобы уменшать урон this.view.signalAbility— используй, чтобы при применении способности карта мигала Работает это так:this.view.signalAbility(() => { // то, что надо сделать сразу после мигания. }
Обрати внимание, что если Громиле нанести 2 урона,
то он сначала должен мигнуть «белым», потому что применилась способность и урон уменьшился на 1,
а затем мигнуть «красным» так как он все же получит единицу урона.
За красное мигание отвечает signalDamage, и он будет вызван сам в методе takeDamage в Card.js.
Переопредели getDescriptions, чтобы на лицевой стороне карты выводилось краткое описание способности Громилы.
Не забудь вызвать реализацию из базового типа, чтобы информация «Утка или Собака» никуда не делась!
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Duck(),
new Duck(),
new Duck(),
];
const banditStartDeck = [
new Trasher(),
];- «Гатлинг» Нехорошо нападать на мирных жителей. Это еще может быть опасно, если в сарае припрятан Гатлинг.
Добавь карту Gatling:
- называется Гатлинг, сила 6, наследуется от
Creature. - при атаке наносит 2 урона по очереди всем картам противника на столе, но не атакует игрока-противника. Таким образом урон сначала получает самая левая карта противника, затем вторая слева и так далее. Урон не должен наноситься одновременно.
Подсказки:
- переопредели метод
attackтак, чтобы урон наносился всем картам противника - список карт противника можно получить через
gameContext.oppositePlayer.table - в качестве примера выполнения действий над несколькими картами можешь использовать
applyCardsизPlayer.js
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Duck(),
new Duck(),
new Gatling(),
];
const banditStartDeck = [
new Trasher(),
new Dog(),
new Dog(),
];- «Братки» Чем их больше, тем они сильнее.
Добавь карту Lad:
- называется Браток, сила 2, наследуется от
Dog. - чем больше братков находится в игре, тем больше урона без потерь поглощается и больше урона по картам наносится каждым из них.
Защита от урона = количество * (количество + 1) / 2 Дополнительный урон = количество * (количество + 1) / 2
Подсказки:
- текущее количество братков в игре надо где-то хранить, свойство в функции-конструкторе
Lad— подходящее место. Заведи для этого пару методов:static getInGameCount() { return this.inGameCount || 0; }static setInGameCount(value) { this.inGameCount = value; }Хоть свойствоinGameCountв функцииLad, другими словами статическое свойство классаLad, явно не объявляется, при первом вызовеsetInGameCount, оно будет создано со значениемvalue. - чтобы обновлять количество братков в игре переопредели методы
doAfterComingIntoPlay,doBeforeRemoving - чтобы рассчитывать бонус к урону и защите стоит завести статический метод в классе
Lad. Выглядеть будет как-то так:static getBonus() { ... }Чтобы вgetBonusобращаться к другим статическим методам используйthis, а не имя классаLad. Вспомни, почему в статических методах в качествеthisпередаетсяLad. - переопредели методы
modifyDealedDamageToCreatureиmodifyTakenDamage, чтобы они использовали бонус.
Добавь в описание карты «Чем их больше, тем они сильнее».
Этот текст должен появляться только если непосредственно у братков (т.е. в Lad.prototype)
переопределены методы modifyDealedDamageToCreature или modifyTakenDamage.
Проверка на наличие свойства непосредственно у объекта выполняется с помщью метода hasOwnProperty.
Проверка наличия метода modifyDealedDamageToCreature у братков выглядит так:
Lad.prototype.hasOwnProperty('modifyDealedDamageToCreature')
Эта особенность понадобится на следующем шаге.
Как видишь, даже при использовании class весь функционал прототипов доступен и работает.
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Duck(),
new Duck(),
];
const banditStartDeck = [
new Lad(),
new Lad(),
];7*. «Изгой» От него все бегут, потому что он приходит и отнимает силы...
Добавь карту Rogue:
- называется Изгой, сила 2, наследуется от
Creature. - перед атакой на карту забирает у нее все способности к увеличению наносимого урона или уменьшению получаемого урона. Одновременно эти способности забираются у всех карт того же типа, но не у других типов карт. Изгой получает эти способности, но не передает их другим Изгоям.
Подсказки:
- Изгой похищает эти способности:
modifyDealedDamageToCreature,modifyDealedDamageToPlayer,modifyTakenDamage - Чтобы похитить способности у всех карт некоторого типа, надо взять их из прототипа
- Получить доступ к прототипу некоторой карты можно так:
Object.getPrototypeOf(card) - Чтобы не похищать способности у других типов, нельзя задевать прототип прототипа
Object.getOwnPropertyNamesиobj.hasOwnPropertyпозволяют получать только собственные свойства объекта- Удалить свойство из объекта можно с помощью оператора
deleteтак:delete obj[propName]Это не то же самое, чтоobj[propName] = undefined - После похищения стоит обновить вид всех объектов игры.
updateViewизgameContextпоможет это сделать.
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Duck(),
new Duck(),
new Rogue(),
];
const banditStartDeck = [
new Lad(),
new Lad(),
new Lad(),
];8*. «Пивовар» Живительное пиво помогает уткам творить невозможное!
Добавь карту Brewer:
- называется Пивовар, сила 2, наследуется от
Duck. - перед атакой на карту Пивовар раздает пиво, которое изменяет максимальную силу карты на +1, а затем текущую силу на +2.
- Пивовар угощает пивом все карты на столе: и текущего игрока и игрока-противника,
но только с утками, проверяя их с помощью
isDuck. - Пивовар само собой утка, поэтому его сила тоже возврастает.
Подсказки:
- Все карты на столе можно получить из
gameContextтак:currentPlayer.table.concat(oppositePlayer.table). this.view.signalHeal— используй, чтобы подсветить карту, у которой увеличилась сила.card.updateView()— используй, чтобы обновлять вид карт, у которых увеличилась сила.
Важно, чтобы при увеличении текущей силы она не превышала максимальную.
Добиться этого можно по разному, но решить этот вопрос раз и навсегда иначе определив свойство currentPower в Card.
Сейчас оно определяется в конструкторе Card довольно просто:
this.currentPower = maxPower
Пусть так и определяется.
А вот в Creature определи заново свойство currentPower через get и set.
Геттер должен просто возвращать текущее значение, а сеттер не давать устанавливать значение выше, чем this.maxPower.
Подробнее про get и set тут:
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/get
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/set
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Brewer(),
];
const banditStartDeck = [
new Dog(),
new Dog(),
new Dog(),
new Dog(),
];9*. «Псевдоутка» Чтобы получить доступ к живительному напитку надо всего лишь выглядеть как утка, плавать как утка и крякать как утка!
Добавь карту PseudoDuck:
- называется Псевдоутка, сила 3, наследуется от
Dog. - Псевдоутка — это обычный пес, но еще умеет крякать и плавать, поэтому легко проходит проверку на утиность
isDuck.
Подсказки:
- чтобы быть похожим на утку надо всего лишь реализовывать пару методов:
quacksиswims - убедись, что в описании Псевдоутки присутстует «Утка-Собака».
Колоды для проверки:
const seriffStartDeck = [
new Duck(),
new Brewer(),
];
const banditStartDeck = [
new Dog(),
new PseudoDuck(),
new Dog(),
];10*. «Немо» «The one without a name without an honest heart as compass»
Добавь карту Nemo:
- называется Немо, сила 4, наследуется от
Creature. - перед атакой на карту крадет ее прототип и назначает себе, получая все ее способности, но вместе со своим старым прототипом теряет способность красть.
- если карта, у которой был украден прототип, обладает способностями, выполняющимися перед атакой, то они должны быть выполнены сразу после кражи прототипа.
Подсказки:
updateViewизgameContextпозволяет обновить вид всех объектов игры.Object.getPrototypeOf(obj)позволяет получить прототип объекта.Object.setPrototypeOf(obj, proto)позволяет задать протип объекту.- функция
doBeforeAttackиз прототипа должна быть вызвана сразу после кражи прототипа.
Колоды для проверки:
const seriffStartDeck = [
new Nemo(),
];
const banditStartDeck = [
new Brewer(),
new Brewer(),
];