-
Notifications
You must be signed in to change notification settings - Fork 50
/
modern-west.txt
339 lines (277 loc) · 22.6 KB
/
modern-west.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
Задача частично перевести код карточной игры на современные ECMAScript,
а затем добавить новые интересные карты использую возможности класов.
0. «Запуск»
В этот раз для того, чтобы запустить игру понадобится веб-сервер. Воспользуемся веб-сервером для 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 и понаблюдай как игра играет сама в себя.
- Загляни в Game.js, обрати внимания на стадии хода.
- Изучи Card.js, чтобы разобраться какие действия происходят с картой, какие возможности по расширению заложены.
Браузер имеет правильное свойство кэшировать все, что скачивает: скрипты, стили, картинки.
Правда в разработке это может мешать: только что внесенные изменения не будут появляться при обновлении страницы.
Для перезагрузки страницы без кэша можно использовать сочетание Control+F5.
А в Chrome можно полностью отключить кэширование в Developer Tools:
- Открой Developer Tools
- Перейди во вкладку Network
- Установи флажок Disable cache
Теперь можно приступать к решению задачи!
1. «Модули»
Изначально в скриптах на JavaScript нельзя было импортировать код из других файлов.
Если нужно было добавить на страницу несколько скриптов, они добавлялись отдельными тэгами script на страницу.
Именно так подключены скрипты игры в файле index.html.
Воспользуйся тем, что Chrome уже поддерживает использование модулей ECMAScript из коробки
и сделай так, чтобы все скрипты подключались с помощью конструкций import и export:
- Измени подключение index.js на такой вариант:
<script type="module" src="index.js"></script>
Это заставит Chrome подключить index.js в режиме с поддержкой модулей ECMAScript.
Каждый файл на js в этом режиме определяет независимый модуль,
модуль может экспортировать некоторые сущности с помощью конструкции export
и подключать нужные с помощью конструкции import.
- Убери подключение всех остальных скриптов из index.html.
- В каждом из файлов TaskQueue.js, Game.js, Card.js, CardView.js, Player.js, PlayerView.js, SpeedRate.js
допиши в конце export default с той сущностью, которую нужно экспортировать по-умолчанию.
Например, в конце файла TaskQueue.js добавь строчку:
export default TaskQueue;
Этого будет достаточно, потому что в каждом из файлов определен ровно один полезный тип.
- Остается импортировать все необходимое для каждого из модулей.
Тут поможет Developer Tools: смотри какие ошибки пишутся в консоль и импортируй в указанные модули недостающие типы.
Например, чтобы подключить TaskQueue добавь строчку:
import TaskQueue from './TaskQueue.js';
- В результате игра снова должна работать без ошибок, но в index.html при этом подключен только index.js.
2. «Классы»
Во всем коде типы определены по-старому, через прототипы.
Изоляция кода при этом обеспечивает за счет техники 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;
}
3. «Частичный импорт и псевдонимы»
Возможности экспорта/импорта не ограничиваются передачей одной сущности из модуля в модуль.
Есть много разных вариантов и подробнее можно почитать
тут про export:
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/export
тут про import:
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/import
Попробуй часть вариантов:
- Импортируй CardView в Card, без префикса Card:
import {default as View} from './CardView.js';
- Экспортируй функции set и get из SpeedRate под именами setSpeedRate и getSpeedRate:
export const setSpeedRate = SpeedRate.set;
export const getSpeedRate = SpeedRate.get;
- Импортируй функцию getSpeedRate в PlayerView.js как есть:
import {getSpeedRate} from './SpeedRate.js';
Поправь соответствующие вызовы.
- Импортируй функцию setSpeedRate под более конкретным именем setGameSpeedRate в index.js:
import {setSpeedRate as setGameSpeedRate} from './SpeedRate.js';
Поправь соответствующие вызовы.
4. «Утки против собак»
Создай в index.js две новые карты, используй class и унаследовав их от Card:
- Duck с именем «Мирная утка» и силой 2
- Dog с именем «Пес-бандит» и силой 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(),
];
5. «Утка или собака?»
Метод getDescriptions в Card создан для того, чтобы на картах появлялась дополнительная информация.
Его функционал хочется расширить. Причем так, чтобы это работало и для уток, и для собак,
и для всех остальных существ, которые будут добавляться.
- Создай новый тип Creature и унаследуй его от Card.
- Сделай так, чтобы Duck и Dog наследовались от Creature.
- Переопредели в классе Creature реализацию getDescriptions на новую.
Теперь в ней должны возвращаться две строки описания в виде массива.
Первая из них — из функции getCreatureDescription, которая определена в index.js.
Вторая — из getDescriptions в Card.
Для этого надо вызывать эту базовую версию функции getDescriptions из прототипа Card.
В классах это делается просто: super.getDescriptions();
Заметь, что getDescriptions должен возвращать массив строк, а не просто строку!
Верный признак, что ты напутал что-то с возвращаемым значением - вертикальная надпись на карте: У
т
к
а
Используй spread-оператор (так: [newValue, ...values]) или метод unshift массива,
чтобы дополнить возвращаемый базовой версией getDescriptions массив новыми значениями.
Используя те же колоды, убедись, что у уток появилась надпись «Утка» над строкой с цепочкой наследования,
а у собак надпись «Собака» над цепочкой наследования.
6. «Братки»
Чем их больше, тем они сильнее.
Добавь карту 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*. «Немо»
«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(),
];