├── .gitignore
├── README.md
├── css
└── main.css
├── game.js
├── index.html
├── js
└── app.js
├── levels.json
├── package.json
├── res
└── preview.png
└── test
├── actor.spec.js
├── coin.spec.js
├── fireball.spec.js
├── firerain.spec.js
├── horizontalfireball.spec.js
├── index.html
├── level.spec.js
├── parser.spec.js
├── player.spec.js
├── vector.spec.js
└── verticalfireball.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Дипломный проект курса JavaScript
2 | ===
3 |
4 | В рамках дипломного проекта вам необходимо реализовать ключевые компоненты игры. Игра будет запускаться и работать в браузере.
5 |
6 | Игра после реализации имеет следующий интерфейс:
7 | 
8 |
9 | На иллюстрации вы можете видеть:
10 | - Белые стены препятствий.
11 | - Красные огненные шары и лаву.
12 | - Желтые монетки.
13 | - Игрока бордового цвета, потому что в данный момент он умер от столкновения с огненным шаром.
14 |
15 | Игрок управляется стрелками с клавиатуры. Основная цель каждого уровня — собрать все монетки.
16 |
17 | ## Реализация
18 |
19 | Реализовать проект и предоставить его на проверку можно двумя способами:
20 | + Локально и публиковать код в ваш репозиторий [GitHub] или [BitBucket]
21 | + В онлайн-песочнице [CodePen] или [Repl.it]
22 |
23 | Сама игра будет функционировать, когда вы окончательно реализуете все компоненты. Но чтобы понять, правильно ли реализован каждый из них, для каждого компонента дан пример кода и результат его работы, по которому вы можете проверить, правильно ли вы его реализовали. Сам код примеров в итоговом решении оставлять не рекомендуется.
24 |
25 | Также есть возможность запустить тесты, которые покажут, верно ли реализован каждый компонент. Об этом будет подробно описано в разделе «Тестирование».
26 |
27 | ### Реализация в репозитории
28 |
29 | #### Подготовка репозитория
30 |
31 | 1. Установить git.
32 | 2. Создайте аккаунт в сервисе [GitHub] или [BitBucket].
33 | 3. Создайте публичный репозиторий.
34 | 4. Скопируйте ссылку на репозиторий (рекомендуем использовать HTTPS, если ранее вы не сталкивались с SSH).
35 | 5. Клонируйте ваш репозиторий локально, используя команду `git clone`.
36 |
37 | Итогом будет наличие папки на локальном компьютере, в которой инициализирован git-репозиторий и настроена связь с репозиторием на [GitHub] или [BitBucket].
38 |
39 | #### Подготовка проекта
40 |
41 | 1. Скачайте свежую версию проекта по ссылке
42 |
43 | https://github.com/netology-code/js-game/releases
44 |
45 | 2. Разверните архив проекта в папку, созданную при подготовке репозитория.
46 | 3. Ваш код пишите в файле `./game.js`.
47 | 4. Для запуска игры откройте в браузере файл `./index.html`.
48 | 5. Для запуска тестов откройте в браузере файл `./test/index.html`.
49 |
50 | Менять остальные файлы не рекомендуется.
51 |
52 | #### Публикация промежуточных версий
53 |
54 | 1. Добавьте к коммиту файл `game.js` командой `git add game.js`.
55 | 2. Сделайте коммит `git commit`.
56 | 3. Опубликуйте изменения с помощью команды `git push`.
57 |
58 | #### Создание локального сервера (необязательно)
59 |
60 | Все компоненты игры будут работать локально, кроме функции `loadLevels`, действия которой будут заблокированы политикой безопасности бразуера.
61 |
62 | Один из вариантов обойти это — запустить локальный веб-сервер.
63 |
64 | ##### Локальный сервер на php
65 |
66 | 1. Установить php на компьютер.
67 | 2. Для запуска сервера в папке проекта запустить команду `php -S localhost:3000`.
68 | 3. Для запуска игры откройте в браузере адрес `http://localhost:3000/index.html`.
69 | 4. Для запуска тестов откройте в браузере адрес `http://localhost:3000/test/index.html`.
70 |
71 | ##### Локальный сервер на NodeJS
72 |
73 | 1. Установить NodeJS.
74 | 2. В папке проекта выполнить команду `npm install`.
75 | 3. Для запуска сервера в папке проекта запустить команду `npm start`.
76 | 4. Для запуска игры откройте в браузере адрес `http://localhost:3000/index.html`.
77 | 5. Для запуска тестов откройте в браузере адрес `http://localhost:3000/test/index.html`.
78 |
79 | При использовании NodeJS тесты и игра будут обновляться автоматически при изменении файлов.
80 |
81 |
82 | ### Реализация в песочнице
83 |
84 | #### CodePen
85 |
86 | Для реализации в онлайн-песочнице вам нужно:
87 |
88 | 1. Зарегистрироваться на сервисе [CodePen].
89 | 2. Открыть заготовку проекта по ссылке:
90 |
91 | https://codepen.io/Netology/pen/QmPgWe?editors=0010
92 |
93 | 3. Нажать кнопку «Fork», тем самым создав свою копию заготовки.
94 | 4. Реализовать код игры, последовательно следуя инструкции, в окне «JS».
95 | 5. Периодически сохраняйте результат, чтобы не потерять изменения.
96 | 6. Отправляйте наставнику на проверку ссылку на ваш пен.
97 |
98 | Инструкция по использованию сервиса CodePen:
99 | https://github.com/netology-code/guides/tree/master/codepen
100 |
101 | #### Repl.it
102 |
103 | 1. Зарегистрироваться на сервисе [Repl.it].
104 | 2. Создать новую песочницу «HTML, CSS, JS»
105 |
106 | https://repl.it/languages/web_project
107 |
108 | 3. Во вкладке `index.html` поместите следующий код:
109 |
110 | ```html
111 |
112 |
113 |
114 |
115 |
116 |
117 | Document
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | ```
126 |
127 | 4. Нажмите кнопку «Save».
128 | 5. Реализовать код игры, последовательно следуя инструкции, во вкладке «index.js».
129 | 6. Периодически сохраняйте результат, чтобы не потерять изменения.
130 | 7. Отправляйте наставнику на проверку ссылку на вашу песочницу, которую можно получить по кнопке «Share».
131 |
132 | ## Тестирование
133 |
134 | В файле `./test/index.html` настроена среда автоматизированного тестирования вашего кода. Она проверяет созданные компоненты на соответствие требованиям. И если находит расхождения, сообщает об ошибке. Тем самым, тесты — ваш навигатор, показывающий, какую часть требований в вашем коде вы выполнили, а какую нет.
135 |
136 | По тестам можно осуществлять навигацию. Можно выбрать конкретный компонент или конкретный метод и следить за выполнением только выбранных тестов, не отвлекаясь на другие.
137 |
138 | Также можно отобразить только проваленные тесты или наоборот, только успешные.
139 |
140 | Просто кликайте на соответствующий пункт, чтобы сосредоточиться на нем.
141 |
142 | Процесс реализации можно построить таким образом:
143 | 1. Выбрать компонент или даже метод компонента.
144 | 2. Отфильтровать тесты, оставив только выбранный компонент или его метод.
145 | 3. Реализовать код, который удовлетворит первому проваленному тесту.
146 | 4. Убедиться, что тест помечен как успешный.
147 | 5. Если еще остались проваленные тесты, вернуться к пункту 3.
148 |
149 | Такой подход называется «разработка через тестирование» или TDD. За тем лишь исключением, что тесты уже написаны.
150 |
151 | ## Процесс и порядок реализации
152 |
153 | Для того, чтобы максимально просто и быстро получить базовый рабочий вариант проекта, рекомендуем придерживаться следующего плана разработки:
154 |
155 | 1. Реализовать базовые классы игры: `Vector`, `Actor` и `Level`.
156 | 2. После этого вы уже сможете запустить игру.
157 | ```javascript
158 | const grid = [
159 | new Array(3),
160 | ['wall', 'wall', 'lava']
161 | ];
162 | const level = new Level(grid);
163 | runLevel(level, DOMDisplay);
164 | ```
165 |
166 | На экране отобразится схема уровня. Узнайте подробнее про функцию `runLevel` и класс `DOMDisplay` ниже.
167 |
168 | 3. Реализуйте `LevelParser`, что позволит вам описывать уровни с помощью текстовой схемы:
169 | ```javascript
170 | const schema = [
171 | ' ',
172 | ' ',
173 | ' ',
174 | ' ',
175 | ' !xxx',
176 | ' ',
177 | 'xxx! ',
178 | ' '
179 | ];
180 | const parser = new LevelParser();
181 | const level = parser.parse(schema);
182 | runLevel(level, DOMDisplay);
183 | ```
184 | 4. Реализуйте `Player`, поместите его символ на схему и добавьте словарь при создании парсера:
185 | ```javascript
186 | const schema = [
187 | ' ',
188 | ' ',
189 | ' ',
190 | ' ',
191 | ' !xxx',
192 | ' @ ',
193 | 'xxx! ',
194 | ' '
195 | ];
196 | const actorDict = {
197 | '@': Player
198 | }
199 | const parser = new LevelParser(actorDict);
200 | const level = parser.parse(schema);
201 | runLevel(level, DOMDisplay);
202 | ```
203 | 5. Реализуйте другие движущиеся объекты игрового поля и помещайте их символы на схему и в словарь парсера.
204 | 6. Реализуйте загрузку уровней с помощью функции `loadLevels` и запуск игры с помощью `runGame`.
205 | 7. Когда игрок пройдет все уровни, используйте функцию `alert`, чтобы сообщить о победе.
206 |
207 | ## Реализованные компоненты, которые необходимо использовать
208 |
209 | ### Класс `DOMDisplay`
210 |
211 | Отвечает за отрисовку в браузере сетки игрового поля и движущихся объектов. Конструктор принимает два аргумента: первый — узел DOM, в котором необходимо отрисовать игровое поле, и уровень `Level`, описывающий игровое поле.
212 |
213 | Непосредственно создавать этот объект не потребуется. Его необходимо передавать вторым аргументом в функцию `runLevel` и третьим аргументом в функцию `runGame`.
214 |
215 | Пример использования:
216 | ```javascript
217 | const schema = [
218 | ' ',
219 | ' ',
220 | ' = ',
221 | ' ',
222 | ' !xxx',
223 | ' @ ',
224 | 'xxx! ',
225 | ' '
226 | ];
227 | const actorDict = {
228 | '@': Player,
229 | '=': HorizontalFireball
230 | }
231 | const parser = new LevelParser(actorDict);
232 | const level = parser.parse(schema);
233 | DOMDisplay(document.body, level);
234 | ```
235 |
236 | После такого вызова будет отрисовано исходное состояние сетки игрового поля, все движущиеся объекты. Но эта схема будет статичной.
237 |
238 | ### Функция `runLevel`
239 |
240 | Инициализирует процесс регулярной отрисовки текущего состояния игрового поля и обработку событий клавиатуры.
241 |
242 | Принимает два аргумента: уровень (объект класса `Level`) и конструктор объекта, отвечающего за отрисовку. В случае реализации игры в браузере вторым аргументом необходимо использовать класс `DOMDisplay`.
243 |
244 | Функция возвращает промис, который разрешится статусом завершения игры, _строка_. С учетом реализации класса `Level` он может принимать значения `won` или `lost`.
245 |
246 | Пример использования:
247 | ```javascript
248 | const schema = [
249 | ' ',
250 | ' ',
251 | ' = ',
252 | ' o ',
253 | ' !xxx',
254 | ' @ ',
255 | 'xxx! ',
256 | ' '
257 | ];
258 | const actorDict = {
259 | '@': Player,
260 | '=': HorizontalFireball
261 | }
262 | const parser = new LevelParser(actorDict);
263 | const level = parser.parse(schema);
264 | runLevel(level, DOMDisplay)
265 | .then(status => console.log(`Игрок ${status}`));
266 | ```
267 |
268 | После вызова такого кода в браузере будет отрисована схема уровня, движущиеся объекты будут перемещаться, и вы сможете управлять игроком с клавиатуры.
269 |
270 | ### Функция `runGame`
271 |
272 | Инициализирует процесс прохождения игры, состоящей из последовательного прохождения нескольких уровней.
273 |
274 | Принимает три аргумента: список схем уровней, _массив_, каждый элемент которого — схема (массив строк); парсер схем, _объект_ `LevelParser`, и конструктор объекта, отвечающего за отрисовку. В случае реализации игры в браузере третьим аргументом необходимо использовать класс `DOMDisplay`.
275 |
276 | Возвращает промис, который разрешится, когда пользователь пройдет все уровни.
277 |
278 | Пример использования:
279 | ```javascript
280 | const schemas = [
281 | [
282 | ' ',
283 | ' ',
284 | ' = ',
285 | ' o ',
286 | ' !xxx',
287 | ' @ ',
288 | 'xxx! ',
289 | ' '
290 | ],
291 | [
292 | ' v ',
293 | ' v ',
294 | ' v ',
295 | ' o',
296 | ' x',
297 | '@ x ',
298 | 'x ',
299 | ' '
300 | ]
301 | ];
302 | const actorDict = {
303 | '@': Player,
304 | 'v': FireRain
305 | }
306 | const parser = new LevelParser(actorDict);
307 | runGame(schemas, parser, DOMDisplay)
308 | .then(() => console.log('Вы выиграли приз!'));
309 | ```
310 |
311 | Запустит игру из двух уровней, которые необходимо будет пройти последовательно.
312 |
313 | ### Функция `loadLevels`
314 |
315 | Загружает коллекцию уровней. Не принимает аргументов.
316 |
317 | Возвращает промис, который разрешится JSON-строкой, в которой закодирован массив схем уровней.
318 |
319 | ## Компоненты, которые необходимо реализовать
320 |
321 | ### Вектор
322 |
323 | Необходимо реализовать класс `Vector`, который позволит контролировать расположение объектов в двумерном пространстве и управлять их размером и перемещением.
324 |
325 | #### Конструктор
326 |
327 | Принимает два аргумента — координаты по оси X и по оси Y, _числа_, по умолчанию `0`.
328 |
329 | Создает объект со свойствами `x` и `y`, равными переданным в конструктор координатам.
330 |
331 | #### Метод `plus`
332 |
333 | Принимает один аргумент — вектор, _объект_ `Vector`.
334 |
335 | Если передать аргумент другого типа, то бросает исключение `Можно прибавлять к вектору только вектор типа Vector`.
336 |
337 | Создает и возвращает новый _объект_ типа `Vector`, координаты которого будут суммой соответствующих координат суммируемых векторов.
338 |
339 | #### Метод `times`
340 |
341 | Принимает один аргумент — множитель, _число_.
342 |
343 | Создает и возвращает новый _объект_ типа `Vector`, координаты которого будут равны соответствующим координатам исходного вектора, умноженным на множитель.
344 |
345 | #### Пример кода
346 | ```javascript
347 | const start = new Vector(30, 50);
348 | const moveTo = new Vector(5, 10);
349 | const finish = start.plus(moveTo.times(2));
350 |
351 | console.log(`Исходное расположение: ${start.x}:${start.y}`);
352 | console.log(`Текущее расположение: ${finish.x}:${finish.y}`);
353 | ```
354 |
355 | Результат выполнения кода:
356 | ```
357 | Исходное расположение: 30:50
358 | Текущее расположение: 40:70
359 | ```
360 |
361 | ### Движущийся объект
362 |
363 | Необходимо реализовать класс `Actor`, который позволит контролировать все движущиеся объекты на игровом поле и контролировать их пересечение.
364 |
365 | #### Конструктор
366 |
367 | Принимает три аргумента: расположение, _объект_ типа `Vector`, размер, тоже _объект_ типа `Vector` и скорость, тоже _объект_ типа `Vector`. Все аргументы необязательные. По умолчанию создается объект с координатами 0:0, размером 1x1 и скоростью 0:0.
368 |
369 | Если в качестве первого, второго или третьего аргумента передать не объект типа `Vector`, то конструктор должен бросить исключение.
370 |
371 | #### Свойства
372 |
373 | Должно быть определено свойство `pos`, в котором размещен `Vector`.
374 |
375 | Должно быть определено свойство `size`, в котором размещен `Vector`.
376 |
377 | Должно быть определено свойство `speed`, в котором размещен `Vector`.
378 |
379 | Должен быть определен метод `act`, который ничего не делает.
380 |
381 | Должны быть определены свойства только для чтения `left`, `top`, `right`, `bottom`, в которых установлены границы объекта по осям X и Y с учетом его расположения и размера.
382 |
383 | Должен иметь свойство `type` — строку со значением `actor`, только для чтения.
384 |
385 | #### Метод `isIntersect`
386 |
387 | Метод проверяет, пересекается ли текущий объект с переданным объектом, и если да, возвращает `true`, иначе – `false`.
388 |
389 | Принимает один аргумент — движущийся объект типа `Actor`. Если передать аргумент другого типа или вызвать без аргументов, то метод бросает исключение.
390 |
391 | Если передать в качестве аргумента этот же объект, то всегда возвращает `false`. Объект не пересекается сам с собой.
392 |
393 | Объекты, имеющие смежные границы, не пересекаются.
394 |
395 | #### Пример кода
396 | ```javascript
397 | const items = new Map();
398 | const player = new Actor();
399 | items.set('Игрок', player);
400 | items.set('Первая монета', new Actor(new Vector(10, 10)));
401 | items.set('Вторая монета', new Actor(new Vector(15, 5)));
402 |
403 | function position(item) {
404 | return ['left', 'top', 'right', 'bottom']
405 | .map(side => `${side}: ${item[side]}`)
406 | .join(', ');
407 | }
408 |
409 | function movePlayer(x, y) {
410 | player.pos = player.pos.plus(new Vector(x, y));
411 | }
412 |
413 | function status(item, title) {
414 | console.log(`${title}: ${position(item)}`);
415 | if (player.isIntersect(item)) {
416 | console.log(`Игрок подобрал ${title}`);
417 | }
418 | }
419 |
420 | items.forEach(status);
421 | movePlayer(10, 10);
422 | items.forEach(status);
423 | movePlayer(5, -5);
424 | items.forEach(status);
425 | ```
426 |
427 | Результат работы примера:
428 | ```
429 | Игрок: left: 0, top: 0, right: 1, bottom: 1
430 | Первая монета: left: 10, top: 10, right: 11, bottom: 11
431 | Вторая монета: left: 15, top: 5, right: 16, bottom: 6
432 | Игрок: left: 10, top: 10, right: 11, bottom: 11
433 | Первая монета: left: 10, top: 10, right: 11, bottom: 11
434 | Игрок подобрал Первая монета
435 | Вторая монета: left: 15, top: 5, right: 16, bottom: 6
436 | Игрок: left: 15, top: 5, right: 16, bottom: 6
437 | Первая монета: left: 10, top: 10, right: 11, bottom: 11
438 | Вторая монета: left: 15, top: 5, right: 16, bottom: 6
439 | Игрок подобрал Вторая монета
440 | ```
441 |
442 | ### Игровое поле
443 |
444 | Объекты класса `Level` реализуют схему игрового поля конкретного уровня, контролируют все движущиеся объекты на нём и реализуют логику игры. Уровень представляет собой координатное поле, имеющее фиксированную ширину и высоту.
445 |
446 | Сетка уровня представляет собой координатное двумерное поле, представленное двумерным массивом. Первый массив — строки игрового поля; индекс этого массива соответствует координате Y на игровом поле. Элемент с индексом `5` соответствует строке с координатой Y, равной `5`. Вложенные массивы, расположенные в элементах массива строк, представляют ячейки поля. Индекс этих массивов соответствует координате X. Например, элемент с индексом `10`, соответствует ячейке с координатой X, равной `10`.
447 |
448 | Так как `grid` — это двумерный массив, представляющий сетку игрового поля, то, чтобы узнать, что находится в ячейке с координатами X=10 и Y=5 (10:5), необходимо получить значение `grid[5][10]`. Если значение этого элемента равно `undefined`, то эта ячейка пуста. Иначе там будет строка, описывающая препятствие. Например, `wall` — для стены и `lava` — для лавы. Отсюда вытекает следующий факт: все препятствия имеют целочисленные размеры и координаты.
449 |
450 | #### Конструктор
451 |
452 | Принимает два аргумента: сетку игрового поля с препятствиями, _массив массивов строк_, и список движущихся объектов, _массив объектов_ `Actor`. Оба аргумента необязательные.
453 |
454 | #### Свойства
455 |
456 | Имеет свойство `grid` — сетку игрового поля. Двумерный массив строк.
457 |
458 | Имеет свойство `actors` — список движущихся объектов игрового поля, массив объектов `Actor`.
459 |
460 | Имеет свойство `player` — движущийся объект, тип которого — свойство `type` — равно `player`. Игорок передаётся с остальными движущимися объектами.
461 |
462 | Имеет свойство `height` — высоту игрового поля, равное числу строк в сетке из первого аргумента.
463 |
464 | Имеет свойство `width` — ширину игрового поля, равное числу ячеек в строке сетки из первого аргумента. При этом, если в разных строках разное число ячеек, то `width` будет равно максимальному количеству ячеек в строке.
465 |
466 | Имеет свойство `status` — состояние прохождения уровня, равное `null` после создания.
467 |
468 | Имеет свойство `finishDelay` — таймаут после окончания игры, равен `1` после создания. Необходим, чтобы после выигрыша или проигрыша игра не завершалась мгновенно.
469 |
470 | #### Метод `isFinished`
471 |
472 | Определяет, завершен ли уровень. Не принимает аргументов.
473 |
474 | Возвращает `true`, если свойство `status` не равно `null` и `finishDelay` меньше нуля.
475 |
476 | #### Метод `actorAt`
477 |
478 | Определяет, расположен ли какой-то другой движущийся объект в переданной позиции, и если да, вернёт этот объект.
479 |
480 | Принимает один аргумент — движущийся объект, `Actor`. Если не передать аргумент или передать не объект `Actor`, метод должен бросить исключение.
481 |
482 | Возвращает `undefined`, если переданный движущийся объект не пересекается ни с одним объектом на игровом поле.
483 |
484 | Возвращает объект `Actor`, если переданный объект пересекается с ним на игровом поле. Если пересекается с несколькими объектами, вернет первый.
485 |
486 | #### Метод `obstacleAt`
487 |
488 | Аналогично методу `actorAt` определяет, нет ли препятствия в указанном месте. Также этот метод контролирует выход объекта за границы игрового поля.
489 |
490 | Так как движущиеся объекты не могут двигаться сквозь стены, то метод принимает два аргумента: положение, куда собираемся передвинуть объект, _вектор_ `Vector`, и размер этого объекта, тоже _вектор_ `Vector`. Если первым и вторым аргументом передать не `Vector`, то метод бросает исключение.
491 |
492 | Вернет строку, соответствующую препятствию из сетки игрового поля, пересекающему область, описанную двумя переданными векторами, либо `undefined`, если в этой области препятствий нет.
493 |
494 | Если описанная двумя векторами область выходит за пределы игрового поля, то метод вернет строку `lava`, если область выступает снизу. И вернет `wall` в остальных случаях. Будем считать, что игровое поле слева, сверху и справа огорожено стеной и снизу у него смертельная лава.
495 |
496 | #### Метод `removeActor`
497 |
498 | Метод удаляет переданный объект с игрового поля. Если такого объекта на игровом поле нет, не делает ничего.
499 |
500 | Принимает один аргумент, объект `Actor`. Находит и удаляет его.
501 |
502 | #### Метод `noMoreActors`
503 |
504 | Определяет, остались ли еще объекты переданного типа на игровом поле.
505 |
506 | Принимает один аргумент — тип движущегося объекта, _строка_.
507 |
508 | Возвращает `true`, если на игровом поле нет объектов этого типа (свойство `type`). Иначе возвращает `false`.
509 |
510 | #### Метод `playerTouched`
511 |
512 | Один из ключевых методов, определяющий логику игры. Меняет состояние игрового поля при касании игроком каких-либо объектов или препятствий.
513 |
514 | Если состояние игры уже отлично от `null`, то не делаем ничего, игра уже и так завершилась.
515 |
516 | Принимает два аргумента. Тип препятствия или объекта, _строка_. Движущийся объект, которого коснулся игрок, — объект типа `Actor`, необязательный аргумент.
517 |
518 | Если первым аргументом передать строку `lava` или `fireball`, то меняем статус игры на `lost` (свойство `status`). Игрок проигрывает при касании лавы или шаровой молнии.
519 |
520 | Если первым аргументом передать строку `coin`, а вторым — объект монеты, то необходимо удалить эту монету с игрового поля. Если при этом на игровом поле не осталось больше монет, то меняем статус игры на `won`. Игрок побеждает, когда собирает все монеты на уровне. Отсюда вытекает факт, что уровень без монет пройти невозможно.
521 |
522 | #### Пример кода
523 |
524 | ```javascript
525 | const grid = [
526 | [undefined, undefined],
527 | ['wall', 'wall']
528 | ];
529 |
530 | function MyCoin(title) {
531 | this.type = 'coin';
532 | this.title = title;
533 | }
534 | MyCoin.prototype = Object.create(Actor);
535 | MyCoin.constructor = MyCoin;
536 |
537 | const goldCoin = new MyCoin('Золото');
538 | const bronzeCoin = new MyCoin('Бронза');
539 | const player = new Actor();
540 | const fireball = new Actor();
541 |
542 | const level = new Level(grid, [ goldCoin, bronzeCoin, player, fireball ]);
543 |
544 | level.playerTouched('coin', goldCoin);
545 | level.playerTouched('coin', bronzeCoin);
546 |
547 | if (level.noMoreActors('coin')) {
548 | console.log('Все монеты собраны');
549 | console.log(`Статус игры: ${level.status}`);
550 | }
551 |
552 | const obstacle = level.obstacleAt(new Vector(1, 1), player.size);
553 | if (obstacle) {
554 | console.log(`На пути препятствие: ${obstacle}`);
555 | }
556 |
557 | const otherActor = level.actorAt(player);
558 | if (otherActor === fireball) {
559 | console.log('Пользователь столкнулся с шаровой молнией');
560 | }
561 | ```
562 |
563 | Результат выполнения:
564 | ```
565 | Все монеты собраны
566 | Статус игры: won
567 | На пути препятствие: wall
568 | Пользователь столкнулся с шаровой молнией
569 | ```
570 |
571 | ### Парсер уровня
572 |
573 | Объект класса `LevelParser` позволяет создать игровое поле `Level` из массива строк по следующему принципу:
574 | * Каждый элемент массива соответствует строке в сетке уровня.
575 | * Каждый символ строки соответствует ячейке в сетке уровня.
576 | * Символ определяет тип объекта или препятствия.
577 | * Индекс строки и индекс символа определяют исходные координаты объекта или координаты препятствия.
578 |
579 | Символы и соответствующие им препятствия и объекты игрового поля:
580 | * **x** — стена, препятствие
581 | * **!** — лава, препятствие
582 | * **@** — игрок, объект
583 | * **o** — монетка, объект
584 | * **=** — движущаяся горизонтально шаровая молния, объект
585 | * **|** — движущаяся вертикально шаровая молния, объект
586 | * **v** — огненный дождь, объект
587 |
588 | > Обратите внимание, что тут слово «символ» означает букву, цифру или знак, которые используются в строках, а не тип данных `Symbol`.
589 |
590 | #### Конструктор
591 |
592 | Принимает один аргумент — словарь движущихся объектов игрового поля, _объект_, ключами которого являются символы из текстового представления уровня, а значениями — конструкторы, с помощью которых можно создать новый объект.
593 |
594 | #### Метод `actorFromSymbol`
595 |
596 | Принимает символ, _строка_. Возвращает конструктор объекта по его символу, используя словарь. Если в словаре не нашлось ключа с таким символом, вернет `undefined`.
597 |
598 | #### Метод `obstacleFromSymbol`
599 |
600 | Аналогично принимает символ, _строка_. Возвращает строку, соответствующую символу препятствия. Если символу нет соответствующего препятствия, то вернет `undefined`.
601 |
602 | Вернет `wall`, если передать `x`.
603 |
604 | Вернет `lava`, если передать `!`.
605 |
606 | Вернет `undefined`, если передать любой другой символ.
607 |
608 | #### Метод `createGrid`
609 |
610 | Принимает массив строк и преобразует его в массив массивов, в ячейках которого хранится либо строка, соответствующая препятствию, либо `undefined`.
611 |
612 | Движущиеся объекты не должны присутствовать на сетке.
613 |
614 | #### Метод `createActors`
615 |
616 | Принимает массив строк и преобразует его в массив движущихся объектов, используя для их создания классы из словаря.
617 |
618 | Количество движущихся объектов в результирующем массиве должно быть равно количеству символов объектов в массиве строк.
619 |
620 | Каждый объект должен быть создан с использованием вектора, определяющего его положение с учетом координат, полученных на основе индекса строки в массиве (Y) и индекса символа в строке (X).
621 |
622 | Для создания объекта должен быть использован класс из словаря, соответствующий символу. При этом, если этот класс не является наследником `Actor`, то такой символ игнорируется, и объект не создается.
623 |
624 | #### Метод `parse`
625 |
626 | Принимает массив строк, создает и возвращает игровое поле, заполненное препятствиями и движущимися объектами, полученными на основе символов и словаря.
627 |
628 | #### Пример использования
629 |
630 | ```javascript
631 | const plan = [
632 | ' @ ',
633 | 'x!x'
634 | ];
635 |
636 | const actorsDict = Object.create(null);
637 | actorsDict['@'] = Actor;
638 |
639 | const parser = new LevelParser(actorsDict);
640 | const level = parser.parse(plan);
641 |
642 | level.grid.forEach((line, y) => {
643 | line.forEach((cell, x) => console.log(`(${x}:${y}) ${cell}`));
644 | });
645 |
646 | level.actors.forEach(actor => console.log(`(${actor.pos.x}:${actor.pos.y}) ${actor.type}`));
647 | ```
648 |
649 | Результат выполнения кода:
650 | ```
651 | (0:0) undefined
652 | (1:0) undefined
653 | (2:0) undefined
654 | (0:1) wall
655 | (1:1) lava
656 | (2:1) wall
657 | (1:0) actor
658 | ```
659 |
660 | ### Шаровая молния
661 |
662 | Класс `Fireball` станет прототипом для движущихся опасностей на игровом поле. Он должен наследовать весь функционал движущегося объекта `Actor`.
663 |
664 | #### Конструктор
665 |
666 | Принимает два аргумента: координаты, _объект_ `Vector` и скорость, тоже _объект_ `Vector`. Оба аргумента необязательные. По умолчанию создается объект с координатами `0:0` и скоростью `0:0`.
667 |
668 | #### Свойства
669 |
670 | Созданный объект должен иметь свойство `type` со значением `fireball`. Это свойство только для чтения.
671 |
672 | Также должен иметь размер `1:1` в свойстве `size`, _объект_ `Vector`.
673 |
674 | #### Метод `getNextPosition`
675 |
676 | Создает и возвращает вектор `Vector` следующей позиции шаровой молнии. Это функция времени. И как в школьной задаче, новая позиция — это текущая позиция плюс скорость, умноженная на время. И так по каждой из осей.
677 |
678 | Принимает один аргумент, время, _число_. Аргумент необязательный, по умолчанию равен `1`.
679 |
680 | #### Метод `handleObstacle`
681 |
682 | Обрабатывает столкновение молнии с препятствием. Не принимает аргументов. Ничего не возвращает.
683 |
684 | Меняет вектор скорости на противоположный. Если он был `5:5`, то после должен стать `-5:-5`.
685 |
686 | #### Метод `act`
687 |
688 | Обновляет состояние движущегося объекта.
689 |
690 | Принимает два аргумента. Первый — время, _число_, второй — игровое поле, _объект_ `Level`.
691 |
692 | Метод ничего не возвращает. Но должен выполнить следующие действия:
693 | 1. Получить следующую позицию, используя время.
694 | 2. Выяснить, не пересечется ли в следующей позиции объект с каким-либо препятствием. Пересечения с другими движущимися объектами учитывать не нужно.
695 | 3. Если нет, обновить текущую позицию объекта.
696 | 4. Если объект пересекается с препятствием, то необходимо обработать это событие. При этом текущее положение остается прежним.
697 |
698 | #### Пример использования
699 |
700 | ```javascript
701 | const time = 5;
702 | const speed = new Vector(1, 0);
703 | const position = new Vector(5, 5);
704 |
705 | const ball = new Fireball(position, speed);
706 |
707 | const nextPosition = ball.getNextPosition(time);
708 | console.log(`Новая позиция: ${nextPosition.x}: ${nextPosition.y}`);
709 |
710 | ball.handleObstacle();
711 | console.log(`Текущая скорость: ${ball.speed.x}: ${ball.speed.y}`);
712 | ```
713 |
714 | Результат работы кода:
715 | ```
716 | Новая позиция: 10: 5
717 | Текущая скорость: -1: 0
718 | ```
719 |
720 | ### Горизонтальная шаровая молния
721 |
722 | Вам необходимо самостоятельно реализовать класс `HorizontalFireball`. Он будет представлять собой объект, который движется по горизонтали со скоростью `2` и при столкновении с препятствием движется в обратную сторону.
723 |
724 | Конструктор должен принимать один аргумент — координаты текущего положения, _объект_ `Vector`. И создавать объект размером `1:1` и скоростью, равной `2` по оси X.
725 |
726 | ### Вертикальная шаровая молния
727 |
728 | Вам необходимо самостоятельно реализовать класс `VerticalFireball`. Он будет представлять собой объект, который движется по вертикали со скоростью `2` и при столкновении с препятствием движется в обратную сторону.
729 |
730 | Конструктор должен принимать один аргумент: координаты текущего положения, _объект_ `Vector`. И создавать объект размером `1:1` и скоростью, равной `2` по оси Y.
731 |
732 | ### Огненный дождь
733 |
734 | Вам необходимо самостоятельно реализовать класс `FireRain`. Он будет представлять собой объект, который движется по вертикали со скоростью `3` и при столкновении с препятствием начинает движение в том же направлении из исходного положения, которое задано при создании.
735 |
736 | Конструктор должен принимать один аргумент — координаты текущего положения, _объект_ `Vector`. И создавать объект размером `1:1` и скоростью, равной `3` по оси Y.
737 |
738 | ### Монета
739 |
740 | Класс `Coin` реализует поведение монетки на игровом поле. Чтобы привлекать к себе внимание, монетки должны постоянно подпрыгивать в рамках своей ячейки. Класс должен наследовать весь функционал движущегося объекта `Actor`.
741 |
742 | #### Конструктор
743 |
744 | Принимает один аргумент — координаты положения на игровом поле, _объект_ `Vector`.
745 |
746 | Созданный объект должен иметь размер `0,6:0,6`. А его реальные координаты должны отличаться от тех, что переданы в конструктор, на вектор `0,2:0,1`.
747 |
748 | #### Свойства
749 |
750 | Свойство `type` созданного объекта должно иметь значение `coin`.
751 |
752 | Также объект должен иметь следующие свойства:
753 | * Скорость подпрыгивания, `springSpeed`, равная `8`;
754 | * Радиус подпрыгивания, `springDist`, равен `0.07`;
755 | * Фаза подпрыгивания, `spring`, случайное число от `0` до `2π`.
756 |
757 | #### Метод `updateSpring`
758 |
759 | Обновляет фазу подпрыгивания. Это функция времени.
760 |
761 | Принимает один аргумент — время, _число_, по умолчанию `1`.
762 |
763 | Ничего не возвращает. Обновляет текущую фазу `spring`, увеличив её на скорость `springSpeed`, умноженную на время.
764 |
765 | #### Метод `getSpringVector`
766 |
767 | Создает и возвращает вектор подпрыгивания. Не принимает аргументов.
768 |
769 | Так как подпрыгивание происходит только по оси Y, то координата X вектора всегда равна нулю.
770 |
771 | Координата Y вектора равна синусу текущей фазы, умноженному на радиус.
772 |
773 | #### Метод `getNextPosition`
774 |
775 | Обновляет текущую фазу, создает и возвращает вектор новой позиции монетки.
776 |
777 | Принимает один аргумент — время, _число_, по умолчанию `1`.
778 |
779 | Новый вектор равен базовому вектору положения, увеличенному на вектор подпрыгивания. Увеличивать нужно именно базовый вектор положения, который получен в конструкторе, а не текущий.
780 |
781 | #### Метод `act`
782 |
783 | Принимает один аргумент — время. Получает новую позицию объекта и задает её как текущую. Ничего не возвращает.
784 |
785 | ### Игрок
786 |
787 | Класс `Player` содержит базовый функционал движущегося объекта, который представляет игрока на игровом поле. Должен наследовать возможности `Actor`.
788 |
789 | #### Конструктор
790 |
791 | Принимает один аргумент — координаты положения на игровом поле, _объект_ `Vector`.
792 |
793 | Созданный объект, реальное положение которого отличается от того, что передано в конструктор, на вектор `0:-0,5`. Имеет размер `0,8:1,5`. И скорость `0:0`.
794 |
795 | #### Свойства
796 |
797 | Имеет свойство `type`, равное `player`.
798 |
799 | [bitbucket]: https://bitbucket.org/
800 | [github]: https://github.com/
801 | [codepen]: https://codepen.io/
802 | [Repl.it]: https://repl.it/
803 |
804 | ### Как правильно задавать вопросы дипломному руководителю?
805 | Что следует делать, чтобы все получилось:
806 | 1. Попробовать найти ответ сначала самому в интернете. Ведь, именно это скилл поиска ответов пригодится тебе на первой работе. И только после этого спрашивать дипломного руководителя
807 | 2. В одном вопросе должна быть заложена одна проблема
808 | 3. По возможности, прикреплять к вопросу скриншоты и стрелочкой показывать где не получается. Программу для этого можно скачать здесь https://app.prntscr.com/ru/
809 | 4. По возможности, задавать вопросы в комментариях к коду.
810 | Начинать работу над дипломом как можно раньше! Чтобы было больше времени на правки.
811 | 5. Делать диплом по-частям, а не все сразу. Иначе, есть шанс, что нужно будет все переделывать :)
812 | Что следует делать, чтобы ничего не получилось:
813 | 1. Писать вопросы вида “Ничего не работает. Не запускается. Всё сломалось.”
814 | 2. Откладывать диплом на потом.
815 | 3. Ждать ответ на свой вопрос моментально. Дипломные руководители - работающие разработчики, которые занимаются, кроме преподавания, своими проектами. Их время ограничено, поэтому постарайтесь задавать правильные вопросы, чтобы получать быстрые ответы!
816 |
817 |
818 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | hidden {
2 | display: none;
3 | }
4 |
5 | .background {
6 | background: rgb(52, 166, 251);
7 | table-layout: fixed;
8 | border-spacing: 0;
9 | }
10 |
11 | .background td {
12 | padding: 0;
13 | }
14 |
15 | .fireball {
16 | background: rgb(255, 100, 100);
17 | }
18 |
19 | .lava {
20 | background: rgb(255, 100, 100);
21 | }
22 |
23 | .elevator {
24 | background: rgb(229, 229, 229);
25 | }
26 |
27 | .wall {
28 | background: white;
29 | }
30 |
31 | .actor {
32 | position: absolute;
33 | }
34 |
35 | .coin {
36 | background: rgb(241, 229, 89);
37 | }
38 |
39 | .player {
40 | background: rgb(64, 64, 64);
41 | }
42 |
43 | .lost .player {
44 | background: rgb(160, 64, 64);
45 | }
46 |
47 | .won .player {
48 | box-shadow: -4px -7px 8px white, 4px -7px 8px white;
49 | }
50 |
51 | .game {
52 | overflow: hidden;
53 | max-width: 600px;
54 | max-height: 450px;
55 | position: relative;
56 | }
57 |
--------------------------------------------------------------------------------
/game.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function loadLevels() {
4 | return new Promise((done, fail) => {
5 | const xhr = new XMLHttpRequest();
6 | let url = './levels.json';
7 | if (location.hostname !== 'localhost') {
8 | url = 'https://neto-api.herokuapp.com/js/diplom/levels.json';
9 | }
10 | xhr.open('GET', url);
11 | xhr.addEventListener('error', e => fail(xhr));
12 | xhr.addEventListener('load', e => {
13 | if (xhr.status !== 200) {
14 | fail(xhr);
15 | }
16 | done(xhr.responseText);
17 | });
18 | xhr.send();
19 | });
20 | }
21 |
22 | const scale = 30;
23 | const maxStep = 0.05;
24 | const wobbleSpeed = 8, wobbleDist = 0.07;
25 | const playerXSpeed = 7;
26 | const gravity = 30;
27 | const jumpSpeed = 17;
28 |
29 | function elt(name, className) {
30 | var elt = document.createElement(name);
31 | if (className) elt.className = className;
32 | return elt;
33 | }
34 |
35 | class DOMDisplay {
36 | constructor(parent, level) {
37 | this.wrap = parent.appendChild(elt("div", "game"));
38 | this.wrap.setAttribute('autofocus', true)
39 | this.level = level;
40 |
41 | this.actorMap = new Map();
42 | this.wrap.appendChild(this.drawBackground());
43 | this.actorLayer = this.wrap.appendChild(this.drawActors());
44 | this.drawFrame();
45 | }
46 |
47 | drawBackground() {
48 | var table = elt("table", "background");
49 | table.style.width = this.level.width * scale + "px";
50 | this.level.grid.forEach(function(row) {
51 | var rowElt = table.appendChild(elt("tr"));
52 | rowElt.style.height = scale + "px";
53 | row.forEach(function(type) {
54 | rowElt.appendChild(elt("td", type));
55 | });
56 | });
57 | return table;
58 | }
59 |
60 | drawActor(actor) {
61 | return elt('div', `actor ${actor.type}`);
62 | }
63 |
64 | updateActor(actor, rect) {
65 | rect.style.width = actor.size.x * scale + "px";
66 | rect.style.height = actor.size.y * scale + "px";
67 | rect.style.left = actor.pos.x * scale + "px";
68 | rect.style.top = actor.pos.y * scale + "px";
69 | }
70 |
71 | drawActors() {
72 | var wrap = elt('div');
73 | this.level.actors.forEach(actor => {
74 | const rect = wrap.appendChild(this.drawActor(actor));
75 | this.actorMap.set(actor, rect);
76 | });
77 | return wrap;
78 | }
79 |
80 | updateActors() {
81 | for (const [actor, rect] of this.actorMap) {
82 | if (this.level.actors.includes(actor)) {
83 | this.updateActor(actor, rect);
84 | } else {
85 | this.actorMap.delete(actor);
86 | rect.parentElement.removeChild(rect);
87 | }
88 | }
89 | }
90 |
91 | drawFrame() {
92 | this.updateActors();
93 |
94 | this.wrap.className = "game " + (this.level.status || "");
95 | this.scrollPlayerIntoView();
96 | }
97 |
98 | scrollPlayerIntoView() {
99 | var width = this.wrap.clientWidth;
100 | var height = this.wrap.clientHeight;
101 | var margin = width / 3;
102 |
103 | // The viewport
104 | var left = this.wrap.scrollLeft, right = left + width;
105 | var top = this.wrap.scrollTop, bottom = top + height;
106 |
107 | var player = this.level.player;
108 | if (!player) {
109 | return;
110 | }
111 | var center = player.pos.plus(player.size.times(0.5))
112 | .times(scale);
113 |
114 | if (center.x < left + margin)
115 | this.wrap.scrollLeft = center.x - margin;
116 | else if (center.x > right - margin)
117 | this.wrap.scrollLeft = center.x + margin - width;
118 | if (center.y < top + margin)
119 | this.wrap.scrollTop = center.y - margin;
120 | else if (center.y > bottom - margin)
121 | this.wrap.scrollTop = center.y + margin - height;
122 | }
123 |
124 | clear() {
125 | this.wrap.parentNode.removeChild(this.wrap);
126 | }
127 | }
128 |
129 | var arrowCodes = {37: "left", 38: "up", 39: "right"};
130 |
131 | function trackKeys(codes) {
132 | var pressed = Object.create(null);
133 | function handler(event) {
134 | if (codes.hasOwnProperty(event.keyCode)) {
135 | var down = event.type == "keydown";
136 | pressed[codes[event.keyCode]] = down;
137 | event.preventDefault();
138 | }
139 | }
140 | addEventListener("keydown", handler);
141 | addEventListener("keyup", handler);
142 | return pressed;
143 | }
144 |
145 | function runAnimation(frameFunc) {
146 | var lastTime = null;
147 | function frame(time) {
148 | var stop = false;
149 | if (lastTime != null) {
150 | var timeStep = Math.min(time - lastTime, 100) / 1000;
151 | stop = frameFunc(timeStep) === false;
152 | }
153 | lastTime = time;
154 | if (!stop) {
155 | requestAnimationFrame(frame);
156 | }
157 | }
158 | requestAnimationFrame(frame);
159 | }
160 |
161 | function runLevel(level, Display) {
162 | initGameObjects();
163 | return new Promise(done => {
164 | var arrows = trackKeys(arrowCodes);
165 | var display = new Display(document.body, level);
166 | runAnimation(step => {
167 | level.act(step, arrows);
168 | display.drawFrame(step);
169 | if (level.isFinished()) {
170 | display.clear();
171 | done(level.status);
172 | return false;
173 | }
174 | });
175 | });
176 | }
177 |
178 | function initGameObjects() {
179 | if (initGameObjects.isInit) {
180 | return;
181 | }
182 |
183 | initGameObjects.isInit = true;
184 |
185 | Level.prototype.act = function(step, keys) {
186 | if (this.status !== null) {
187 | this.finishDelay -= step;
188 | }
189 |
190 | while (step > 0) {
191 | var thisStep = Math.min(step, maxStep);
192 | this.actors.forEach(actor => {
193 | actor.act(thisStep, this, keys);
194 | });
195 |
196 | if (this.status === 'lost') {
197 | this.player.pos.y += thisStep;
198 | this.player.size.y -= thisStep;
199 | }
200 |
201 | step -= thisStep;
202 | }
203 | };
204 |
205 | Player.prototype.handleObstacle = function (obstacle) {
206 | if (this.wontJump) {
207 | this.speed.y = -jumpSpeed;
208 | } else {
209 | this.speed.y = 0;
210 | }
211 | };
212 |
213 | Player.prototype.move = function (motion, level) {
214 | var newPos = this.pos.plus(motion);
215 | var obstacle = level.obstacleAt(newPos, this.size);
216 | if (obstacle) {
217 | level.playerTouched(obstacle);
218 | this.handleObstacle(obstacle);
219 | } else {
220 | this.pos = newPos;
221 | }
222 | };
223 |
224 | Player.prototype.moveX = function (step, level, keys) {
225 | this.speed.x = 0;
226 | if (keys.left) this.speed.x -= playerXSpeed;
227 | if (keys.right) this.speed.x += playerXSpeed;
228 |
229 | var motion = new Vector(this.speed.x, 0).times(step);
230 | this.move(motion, level);
231 | };
232 |
233 | Player.prototype.moveY = function (step, level, keys) {
234 | this.speed.y += step * gravity;
235 | this.wontJump = keys.up && this.speed.y > 0;
236 |
237 | var motion = new Vector(0, this.speed.y).times(step);
238 | this.move(motion, level);
239 | };
240 |
241 | Player.prototype.act = function (step, level, keys) {
242 | this.moveX(step, level, keys);
243 | this.moveY(step, level, keys);
244 |
245 | var otherActor = level.actorAt(this);
246 | if (otherActor) {
247 | level.playerTouched(otherActor.type, otherActor);
248 | }
249 | };
250 | }
251 |
252 | function runGame(plans, Parser, Display) {
253 | return new Promise(done => {
254 | function startLevel(n) {
255 | runLevel(Parser.parse(plans[n]), Display)
256 | .then(status => {
257 | if (status == "lost") {
258 | startLevel(n);
259 | } else if (n < plans.length - 1) {
260 | startLevel(n + 1);
261 | } else {
262 | done();
263 | }
264 | });
265 | }
266 | startLevel(0);
267 | });
268 | }
269 |
270 | function rand(max = 10, min = 0) {
271 | return Math.floor(Math.random() * (max - min + 1)) + min;
272 | }
273 |
--------------------------------------------------------------------------------
/levels.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | " v ",
4 | " ",
5 | " ",
6 | " ",
7 | " ",
8 | " | w ",
9 | " o o ",
10 | " x = x ",
11 | " x o o x ",
12 | " x @ * xxxxx x ",
13 | " xxxxx x ",
14 | " x!!!!!!!!!!!!!x ",
15 | " xxxxxxxxxxxxxxx ",
16 | " "
17 | ],
18 | [
19 | " v ",
20 | " ",
21 | " ",
22 | " ",
23 | " ",
24 | " | ",
25 | " o o ",
26 | " x = x ",
27 | " x o o x ",
28 | " x @ xxxxx x ",
29 | " xxxxx x ",
30 | " x!!!!!!!!!!!!!x ",
31 | " xxxxxxxxxxxxxxx ",
32 | " "
33 | ],
34 | [
35 | " | | ",
36 | " ",
37 | " ",
38 | " ",
39 | " ",
40 | " ",
41 | " ",
42 | " ",
43 | " ",
44 | " | ",
45 | " ",
46 | " = | ",
47 | " @ | o o ",
48 | "xxxxxxxxx!!!!!!!xxxxxxx",
49 | " "
50 | ],
51 | [
52 | " ",
53 | " ",
54 | " ",
55 | " o ",
56 | " x | x!!x= ",
57 | " x ",
58 | " x",
59 | " ",
60 | " ",
61 | " ",
62 | " xxx ",
63 | " ",
64 | " ",
65 | " xxx | ",
66 | " ",
67 | " @ ",
68 | "xxx ",
69 | " "
70 | ], [
71 | " v v",
72 | " ",
73 | " !o! ",
74 | " ",
75 | " ",
76 | " ",
77 | " ",
78 | " xxx ",
79 | " o ",
80 | " = ",
81 | " @ ",
82 | " xxxx ",
83 | " | ",
84 | " xxx x",
85 | " ",
86 | " ! ",
87 | " ",
88 | " ",
89 | " o x ",
90 | " x x ",
91 | " x ",
92 | " x ",
93 | " xx ",
94 | " "
95 | ]
96 | ]
97 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-game",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "start": "browser-sync start --server --files \"**/*.*\"",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "browser-sync": "^2.18.8"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/res/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netology-code/js-game/d475e6c27a45fa22053d00ba9a78653661822a15/res/preview.png
--------------------------------------------------------------------------------
/test/actor.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс Actor', () => {
4 | let position, size;
5 |
6 | beforeEach(() => {
7 | position = new Vector(30, 50);
8 | size = new Vector(5, 5);
9 | });
10 |
11 | describe('Конструктор new Actor()', () => {
12 | it('Создает объект со свойством pos, который является вектором', () => {
13 | const player = new Actor();
14 |
15 | expect(player.pos).is.instanceof(Vector);
16 | });
17 |
18 | it('Создает объект со свойством size, который является вектором', () => {
19 | const player = new Actor();
20 |
21 | expect(player.size).is.instanceof(Vector);
22 | });
23 |
24 | it('Создает объект со свойством speed, который является вектором', () => {
25 | const player = new Actor();
26 |
27 | expect(player.speed).is.instanceof(Vector);
28 | });
29 |
30 | it('Создает объект со свойством type, который является строкой', () => {
31 | const player = new Actor();
32 |
33 | expect(player.type).to.be.a('string');
34 | });
35 |
36 | it('Создает объект с методом act', () => {
37 | const player = new Actor();
38 |
39 | expect(player.act).is.instanceof(Function);
40 | });
41 |
42 | it('По умолчанию создается объект, расположенный в точке 0:0', () => {
43 | const player = new Actor();
44 |
45 | expect(player.pos).is.eql(new Vector(0, 0));
46 | });
47 |
48 | it('По умолчанию создается объект размером 1x1', () => {
49 | const player = new Actor();
50 |
51 | expect(player.size).is.eql(new Vector(1, 1));
52 | });
53 |
54 | it('По умолчанию создается объект со скоростью 0:0', () => {
55 | const player = new Actor();
56 |
57 | expect(player.speed).is.eql(new Vector(0, 0));
58 | });
59 |
60 | it('По умолчанию создается объект со свойством type равным actor', () => {
61 | const player = new Actor();
62 |
63 | expect(player.type).to.equal('actor');
64 | });
65 |
66 | it('Свойство type нельзя изменить', () => {
67 | const player = new Actor();
68 |
69 | function fn() {
70 | player.type = 'player';
71 | }
72 |
73 | expect(fn).to.throw(Error);
74 | });
75 |
76 | it('Создает объект в заданном расположении, если передать вектор первым аргументом', () => {
77 | const player = new Actor(position);
78 |
79 | expect(player.pos).is.equal(position);
80 | });
81 |
82 | it('Бросает исключение, если передать не вектор в качестве расположения', () => {
83 |
84 | function fn() {
85 | const player = new Actor({ x: 12, y: 24 });
86 | }
87 |
88 | expect(fn).to.throw(Error);
89 | });
90 |
91 | it('Создает объект заданного размера, если передать вектор вторым аргументом', () => {
92 | const player = new Actor(undefined, size);
93 |
94 | expect(player.size).is.equal(size);
95 | });
96 |
97 | it('Бросает исключение, если передать не вектор в качестве размера', () => {
98 |
99 | function fn() {
100 | const player = new Actor(undefined, { x: 12, y: 24 });
101 | }
102 |
103 | expect(fn).to.throw(Error);
104 | });
105 |
106 | it('Бросает исключение, если передать не вектор в качестве скорости', () => {
107 |
108 | function fn() {
109 | const player = new Actor(undefined, undefined, { x: 12, y: 24 });
110 | }
111 |
112 | expect(fn).to.throw(Error);
113 | });
114 | });
115 |
116 | describe('Границы объекта', () => {
117 | it('Имеет свойство left, которое содержит координату левой границы объекта по оси X', () => {
118 | const player = new Actor(position, size);
119 |
120 | expect(player.left).is.equal(30);
121 | });
122 |
123 | it('Имеет свойство right, которое содержит координату правой границы объекта по оси X', () => {
124 | const player = new Actor(position, size);
125 |
126 | expect(player.right).is.equal(35);
127 | });
128 |
129 | it('Имеет свойство top, которое содержит координату верхней границы объекта по оси Y', () => {
130 | const player = new Actor(position, size);
131 |
132 | expect(player.top).is.equal(50);
133 | });
134 |
135 | it('Имеет свойство bottom, которое содержит координату нижней границы объекта по оси Y', () => {
136 | const player = new Actor(position, size);
137 |
138 | expect(player.bottom).is.equal(55);
139 | });
140 | });
141 |
142 | describe('Метод isIntersect', () => {
143 | it('Если передать объект, не являющийся экземпляром Actor, то получим исключение', () => {
144 | const player = new Actor();
145 |
146 | function fn() {
147 | player.isIntersect({ left: 0, top: 0, bottom: 1, right: 1 });
148 | }
149 |
150 | expect(fn).to.throw(Error);
151 | });
152 |
153 | it('Объект не пересекается сам с собой', () => {
154 | const player = new Actor(position, size);
155 |
156 | const notIntersected = player.isIntersect(player);
157 |
158 | expect(notIntersected).is.equal(false);
159 | });
160 |
161 | it('Объект не пересекается с объектом, расположенным очень далеко', () => {
162 | const player = new Actor(new Vector(0, 0));
163 | const coin = new Actor(new Vector(100, 100));
164 |
165 | const notIntersected = player.isIntersect(coin);
166 |
167 | expect(notIntersected).is.equal(false);
168 | });
169 |
170 | it('Объект не пересекается с объектом со смежными границами', () => {
171 | const player = new Actor(position, size);
172 |
173 | const moveX = new Vector(1, 0);
174 | const moveY = new Vector(0, 1);
175 |
176 | const coins = [
177 | new Actor(position.plus(moveX.times(-1))),
178 | new Actor(position.plus(moveY.times(-1))),
179 | new Actor(position.plus(size).plus(moveX)),
180 | new Actor(position.plus(size).plus(moveY))
181 | ];
182 |
183 | coins.forEach(coin => {
184 | const notIntersected = player.isIntersect(coin);
185 |
186 | expect(notIntersected).is.equal(false);
187 | });
188 | });
189 |
190 | it('Объект не пересекается с объектом, расположенным в той же точке, но имеющим отрицательный вектор размера', () => {
191 | const player = new Actor(new Vector(0, 0), new Vector(1, 1));
192 | const coin = new Actor(new Vector(0, 0), new Vector(1, 1).times(-1));
193 |
194 | const notIntersected = player.isIntersect(coin);
195 |
196 | expect(notIntersected).is.equal(false);
197 | });
198 |
199 | it('Объект пересекается с объектом, который полностью содержится в нём', () => {
200 | const player = new Actor(new Vector(0, 0), new Vector(100, 100));
201 | const coin = new Actor(new Vector(10, 10), new Vector());
202 |
203 | const intersected = player.isIntersect(coin);
204 |
205 | expect(intersected).is.equal(true);
206 | });
207 |
208 | it('Объект пересекается с объектом, который частично содержится в нём', () => {
209 | const player = new Actor(position, size);
210 |
211 | const moveX = new Vector(1, 0);
212 | const moveY = new Vector(0, 1);
213 |
214 | const coins = [
215 | new Actor(position.plus(moveX.times(-1)), size),
216 | new Actor(position.plus(moveY.times(-1)), size),
217 | new Actor(position.plus(moveX), size),
218 | new Actor(position.plus(moveY), size)
219 | ];
220 |
221 | coins.forEach(coin => {
222 | const intersected = player.isIntersect(coin);
223 |
224 | expect(intersected).is.equal(true);
225 | });
226 | });
227 |
228 | });
229 | });
230 |
--------------------------------------------------------------------------------
/test/coin.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс Coin', () => {
4 | let position;
5 |
6 | beforeEach(() => {
7 | position = new Vector(5, 5);
8 | });
9 |
10 | describe('Конструктор new Coin', () => {
11 | it('Создает экземпляр Actor', () => {
12 | const coin = new Coin();
13 |
14 | expect(coin).to.be.an.instanceof(Actor);
15 | });
16 |
17 | it('Имеет свойство type, равное coin', () => {
18 | const coin = new Coin();
19 |
20 | expect(coin.type).to.equal('coin');
21 | });
22 |
23 | it('Имеет размер Vector(0.6, 0.6)', () => {
24 | const coin = new Coin();
25 |
26 | expect(coin.size).to.eql(new Vector(0.6, 0.6));
27 | });
28 |
29 | it('Реальная позиция сдвинута на Vector(0.2, 0.1)', () => {
30 | const coin = new Coin(position);
31 | const realPosition = new Vector(5.2, 5.1);
32 |
33 | expect(coin.pos).to.eql(realPosition);
34 | });
35 |
36 | it('Имеет свойство spring, равное случайному числу от 0 до 2π', () => {
37 | const coin = new Coin();
38 |
39 | expect(coin.spring).to.be.within(0, 2 * Math.PI);
40 | });
41 |
42 | it('Имеет свойство springSpeed, равное 8', () => {
43 | const coin = new Coin();
44 |
45 | expect(coin.springSpeed).to.equal(8);
46 | });
47 |
48 | it('Имеет свойство springDist, равное 0.07', () => {
49 | const coin = new Coin();
50 |
51 | expect(coin.springDist).to.equal(0.07);
52 | });
53 | });
54 |
55 | describe('Метод updateSpring', () => {
56 | it('Увеличит свойство spring на springSpeed', () => {
57 | const coin = new Coin();
58 | const initialSpring = coin.spring;
59 |
60 | coin.updateSpring();
61 |
62 | expect(coin.spring).to.equal(initialSpring + 8);
63 | });
64 |
65 | it('Если передать время, увеличит свойство spring на springSpeed, умноженное на время', () => {
66 | const time = 5;
67 | const coin = new Coin();
68 | const initialSpring = coin.spring;
69 |
70 | coin.updateSpring(time);
71 |
72 | expect(coin.spring).to.equal(initialSpring + 40);
73 | });
74 | });
75 |
76 | describe('Метод getSpringVector', () => {
77 | it('Вернет вектор', () => {
78 | const coin = new Coin();
79 |
80 | const vector = coin.getSpringVector();
81 |
82 | expect(vector).to.be.an.instanceof(Vector);
83 | });
84 |
85 | it('Координата x этого вектора равна нулю', () => {
86 | const coin = new Coin();
87 |
88 | const vector = coin.getSpringVector();
89 |
90 | expect(vector.x).to.equal(0);
91 | });
92 |
93 | it('Координата y этого вектора равна синусу от spring, умноженному на springDist', () => {
94 | const coin = new Coin();
95 |
96 | const vector = coin.getSpringVector();
97 |
98 | expect(vector.y).to.equal(Math.sin(coin.spring) * 0.07);
99 | });
100 | });
101 |
102 | describe('Метод getNextPosition', () => {
103 | it('Увеличит spring на springSpeed', () => {
104 | const coin = new Coin(position);
105 | const initialSpring = coin.spring;
106 |
107 | coin.getNextPosition();
108 |
109 | expect(coin.spring).to.equal(initialSpring + 8);
110 | });
111 |
112 | it('Если передать время, увеличит свойство spring на springSpeed, умноженное на время', () => {
113 | const time = 5;
114 | const coin = new Coin();
115 | const initialSpring = coin.spring;
116 |
117 | coin.getNextPosition(time);
118 |
119 | expect(coin.spring).to.equal(initialSpring + 40);
120 | });
121 |
122 | it('Вернет вектор', () => {
123 | const coin = new Coin(position);
124 |
125 | const newPosition = coin.getNextPosition();
126 |
127 | expect(newPosition).to.be.an.instanceof(Vector);
128 | });
129 |
130 | it('Координата x новой позиции не изменится', () => {
131 | const coin = new Coin(position);
132 | const realPosition = coin.pos;
133 |
134 | const newPosition = coin.getNextPosition();
135 |
136 | expect(newPosition.x).to.equal(realPosition.x);
137 | });
138 |
139 | it('Координата y новой позиции будет в пределах исходного значения y и y + 1', () => {
140 | const coin = new Coin(position);
141 |
142 | const newPosition = coin.getNextPosition();
143 | expect(newPosition.y).to.be.within(position.y, position.y + 1);
144 | });
145 |
146 | it('Вернет новую позицию, увеличив старую на вектор подпрыгивания', () => {
147 | const coin = new Coin(position);
148 | const realPosition = coin.pos;
149 |
150 | const newPosition = coin.getNextPosition();
151 | const springVector = coin.getSpringVector();
152 |
153 | expect(newPosition).to.eql(realPosition.plus(springVector));
154 | });
155 |
156 | it('Увеличивается вектор исходной позиции, а не текущей', () => {
157 | const coin = new Coin(position);
158 | const realPosition = coin.pos;
159 |
160 | coin.pos = coin.getNextPosition();
161 |
162 | const newPosition = coin.getNextPosition();
163 | const springVector = coin.getSpringVector();
164 |
165 | expect(newPosition).to.eql(realPosition.plus(springVector));
166 | });
167 | });
168 |
169 | describe('Метод act', () => {
170 | it('Обновит текущую позицию на ту, что вернет getNextPosition', () => {
171 | const time = 5;
172 | const coin = new Coin(position);
173 | const spring = coin.spring;
174 | const newPosition = coin.getNextPosition(time);
175 | coin.spring = spring;
176 |
177 | coin.act(time);
178 |
179 | expect(coin.pos).to.eql(newPosition);
180 | });
181 | });
182 | });
183 |
--------------------------------------------------------------------------------
/test/fireball.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс Fireball', () => {
4 | let time, speed, position;
5 |
6 | beforeEach(() => {
7 | time = 5;
8 | speed = new Vector(1, 0);
9 | position = new Vector(5, 5);
10 | });
11 |
12 | describe('Конструктор new Fireball', () => {
13 | it('Созданный объект является экземпляром Actor', () => {
14 | const ball = new Fireball();
15 |
16 | expect(ball).to.be.an.instanceof(Actor);
17 | });
18 |
19 | it('Имеет свойство type, равное fireball', () => {
20 | const ball = new Fireball();
21 |
22 | expect(ball.type).to.equal('fireball');
23 | });
24 |
25 | it('Имеет свойство speed, равное вектору Vector, переданному вторым аргументом', () => {
26 | const ball = new Fireball(undefined, speed);
27 |
28 | expect(ball.speed).to.eql(speed);
29 | });
30 |
31 | it('Свойство pos равно вектору Vector, переданному первым аргументом', () => {
32 | const ball = new Fireball(position);
33 |
34 | expect(ball.pos).to.equal(position);
35 | });
36 | });
37 |
38 | describe('Метод getNextPosition', () => {
39 | it('Вернет ту же позицию для объекта с нулевой скоростью', () => {
40 | const zeroSpeed = new Vector(0, 0);
41 | const ball = new Fireball(position, zeroSpeed);
42 |
43 | const nextPosition = ball.getNextPosition();
44 |
45 | expect(nextPosition).to.eql(position);
46 | });
47 |
48 | it('Вернет новую позицию, увеличенную на вектор скорости', () => {
49 | const ball = new Fireball(position, speed);
50 |
51 | const nextPosition = ball.getNextPosition();
52 |
53 | expect(nextPosition).to.eql(new Vector(6, 5));
54 | });
55 |
56 | it('Если передать время первым аргументом, то вернет новую позицию, увеличенную на вектор скорости, помноженный на переданное время', () => {
57 | const ball = new Fireball(position, speed);
58 |
59 | const nextPosition = ball.getNextPosition(time);
60 |
61 | expect(nextPosition).to.eql(new Vector(10, 5));
62 | });
63 | });
64 |
65 | describe('Метод handleObstacle', () => {
66 | it('Меняет вектор скорости на противоположный', () => {
67 | const ball = new Fireball(position, speed);
68 |
69 | ball.handleObstacle();
70 |
71 | expect(ball.speed).to.eql(new Vector(-1, -0));
72 | });
73 | });
74 |
75 | describe('Метод act', () => {
76 | it('Если препятствий нет, меняет позицию на ту, что получена с помощью getNextPosition', () => {
77 | const level = {
78 | obstacleAt() {
79 | return undefined;
80 | }
81 | };
82 | const ball = new Fireball(position, speed);
83 | const nextPosition = new Vector(10, 5);
84 |
85 | ball.act(time, level);
86 |
87 | expect(ball.speed).to.eql(speed);
88 | expect(ball.pos).to.eql(nextPosition);
89 | });
90 |
91 | it('При столкновении с препятствием не меняет позицию объекта, меняет вектор скорости на противоположный', () => {
92 | const level = {
93 | obstacleAt() {
94 | return 'wall';
95 | }
96 | };
97 | const ball = new Fireball(position, speed);
98 |
99 | ball.act(time, level);
100 |
101 | expect(ball.speed).to.eql(new Vector(-1, -0));
102 | expect(ball.pos).to.eql(position);
103 | });
104 |
105 | it('Вызывает level.obstacleAt со своим вектором размера', () => {
106 | const ball = new Fireball(position, speed);
107 | let isCalled = false;
108 | const level = {
109 | obstacleAt(pos, size) {
110 | expect(size).to.eql(new Vector(1, 1));
111 | isCalled = true;
112 | }
113 | };
114 |
115 | ball.act(time, level);
116 | expect(isCalled).to.be.true;
117 | });
118 |
119 | it('Вызывает level.obstacleAt с вектором новой позиции', () => {
120 | const ball = new Fireball(position, speed);
121 | let isCalled = false;
122 | const level = {
123 | obstacleAt(pos, size) {
124 | expect(pos).to.eql(new Vector(10, 5));
125 | isCalled = true;
126 | }
127 | };
128 |
129 | ball.act(time, level);
130 | expect(isCalled).to.be.true;
131 | });
132 | });
133 | });
134 |
--------------------------------------------------------------------------------
/test/firerain.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс FireRain', () => {
4 | let position;
5 |
6 | beforeEach(() => {
7 | position = new Vector(5, 5);
8 | });
9 |
10 | describe('Конструктор new FireRain', () => {
11 | it('Создает экземпляр Fireball', () => {
12 | const ball = new FireRain();
13 |
14 | expect(ball).to.be.an.instanceof(Fireball);
15 | });
16 |
17 | it('Имеет скорость Vector(0, 3)', () => {
18 | const ball = new FireRain();
19 |
20 | expect(ball.speed).to.eql(new Vector(0, 3));
21 | });
22 |
23 | it('Имеет свойство type, равное fireball', () => {
24 | const ball = new HorizontalFireball();
25 |
26 | expect(ball.type).to.equal('fireball');
27 | });
28 | });
29 |
30 | describe('Метод handleObstacle', () => {
31 | it('Не меняет вектор скорости', () => {
32 | const ball = new FireRain(position);
33 |
34 | ball.handleObstacle();
35 |
36 | expect(ball.speed).to.eql(new Vector(0, 3));
37 | });
38 |
39 | it('Меняет позицию на исходную', () => {
40 | const ball = new FireRain(position);
41 | ball.pos = new Vector(100, 100);
42 |
43 | ball.handleObstacle();
44 |
45 | expect(ball.pos).to.eql(position);
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/test/horizontalfireball.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс HorizontalFireball', () => {
4 | describe('Конструктор new HorizontalFireball', () => {
5 | it('Создает экземпляр Fireball', () => {
6 | const ball = new HorizontalFireball();
7 |
8 | expect(ball).to.be.an.instanceof(Fireball);
9 | });
10 |
11 | it('Имеет скорость Vector(2, 0)', () => {
12 | const ball = new HorizontalFireball();
13 |
14 | expect(ball.speed).to.eql(new Vector(2, 0));
15 | });
16 |
17 | it('Имеет свойство type, равное fireball', () => {
18 | const ball = new HorizontalFireball();
19 |
20 | expect(ball.type).to.equal('fireball');
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/level.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс Level', () => {
4 | const Player = extend(Actor, { type: { value: 'player' }});
5 | const Mushroom = extend(Actor, { type: { value: 'mushroom' }});
6 | const Gift = extend(Actor, { type: { value: 'gift' }});
7 | const Coin = extend(Actor, { type: { value: 'coin' }});
8 |
9 | let player, mushroom, giftBig, giftSmall, goldCoin, bronzeCoin;
10 |
11 | beforeEach(() => {
12 | player = new Player;
13 | mushroom = new Mushroom;
14 | giftBig = new Gift;
15 | giftSmall = new Gift;
16 | goldCoin = new Coin;
17 | bronzeCoin = new Coin;
18 |
19 | player.title = 'Игрок';
20 | mushroom.title = 'Гриб';
21 | giftBig.title = 'Большой сюрприз';
22 | giftSmall.title = 'Маленький сюрприз';
23 | goldCoin.title = 'Золотая монета';
24 | bronzeCoin.title = 'Бронзовая монета';
25 | });
26 |
27 | describe('Конструктор new Level', () => {
28 | it('Высота пустого уровня равна 0', () => {
29 | const level = new Level();
30 |
31 | expect(level.height).to.equal(0);
32 | });
33 |
34 | it('Ширина пустого уровня равна 0', () => {
35 | const level = new Level();
36 |
37 | expect(level.width).to.equal(0);
38 | });
39 |
40 | it('Высота уровня равна количеству строк сетки', () => {
41 | const lines = 100;
42 | const grid = new Array(lines);
43 |
44 | const level = new Level(grid);
45 |
46 | expect(level.height).to.equal(lines);
47 | });
48 |
49 | it('Ширина уровня равна количеству ячеек сетки', () => {
50 | const lines = 100;
51 | const cells = 50;
52 | const grid = new Array(lines).fill(new Array(cells));
53 |
54 | const level = new Level(grid);
55 |
56 | expect(level.width).to.equal(cells);
57 | });
58 |
59 | it('Если в строках разное количество ячеек, то ширина уровня равна количеству ячеек в самой длинной строке', () => {
60 | const lines = 100;
61 | const cells = 50;
62 | const maxCells = 100;
63 | const grid = new Array(lines).fill(new Array(cells));
64 | grid[73].length = maxCells;
65 |
66 | const level = new Level(grid);
67 |
68 | expect(level.width).to.equal(maxCells);
69 | });
70 |
71 | it('Имеет свойство status, равное null', () => {
72 | const level = new Level();
73 |
74 | expect(level.status).to.be.null;
75 | });
76 |
77 | it('Имеет свойство finishDelay, равное 1', () => {
78 | const level = new Level();
79 |
80 | expect(level.finishDelay).to.equal(1);
81 | });
82 |
83 | it('Имеет свойство actors, в котором все движущиеся объекты, переданные в конструктор', () => {
84 | const actors = [ player ];
85 | const level = new Level(undefined, actors);
86 |
87 | expect(level.actors).to.eql(actors);
88 | });
89 |
90 | it('Имеет свойство player, в котором движущийся объект со свойством type, равным player', () => {
91 | const level = new Level(undefined, [ player, mushroom ]);
92 |
93 | expect(level.player).to.equal(player);
94 | });
95 | });
96 |
97 | describe('Метод isFinished', () => {
98 | it('По умолчанию вернет false', () => {
99 | const level = new Level();
100 |
101 | const isNotFinished = level.isFinished();
102 |
103 | expect(isNotFinished).to.be.false;
104 | });
105 |
106 | it('Вернет true, если status будет не равен null, и finishDelay меньше нуля', () => {
107 | const level = new Level();
108 |
109 | level.status = 'lost';
110 | level.finishDelay = -1;
111 | const isFinished = level.isFinished();
112 |
113 | expect(isFinished).to.be.true;
114 | });
115 |
116 | it('Вернет false, если status будет не равен null, но finishDelay будет больше нуля', () => {
117 | const level = new Level();
118 |
119 | level.status = 'lost';
120 | const isNotFinished = level.isFinished();
121 |
122 | expect(isNotFinished).to.be.false;
123 | });
124 | });
125 |
126 | describe('Метод actorAt', () => {
127 |
128 | it('Выбросит исключение, если передать не движущийся объект Actor', () => {
129 | const level = new Level(undefined, [ player ]);
130 |
131 | function fn() {
132 | level.actorAt({});
133 | }
134 |
135 | expect(fn).to.throw(Error);
136 | });
137 |
138 | it('Вернет undefined для пустого уровня', () => {
139 | const level = new Level();
140 |
141 | const noActor = level.actorAt(player);
142 |
143 | expect(noActor).to.be.undefined;
144 | });
145 |
146 | it('Вернет undefined для уровня, в котором только один движущийся объект', () => {
147 | const level = new Level(undefined, [ player ]);
148 |
149 | const noActor = level.actorAt(player);
150 |
151 | expect(noActor).to.be.undefined;
152 | });
153 |
154 | it('Вернет undefined, если ни один объект игрового поля не пересекается с переданным объектом', () => {
155 | const player = new Player(new Vector(1, 1));
156 | const level = new Level(undefined, [ player, mushroom ]);
157 |
158 | const actor = level.actorAt(player);
159 |
160 | expect(actor).to.be.undefined;
161 | });
162 |
163 | it('Вернет объект игрового поля, который пересекается с переданным объектом', () => {
164 | const level = new Level(undefined, [ player, mushroom ]);
165 |
166 | const actor = level.actorAt(player);
167 |
168 | expect(actor).to.be.equal(mushroom);
169 | });
170 |
171 | });
172 |
173 | describe('Метод obstacleAt', () => {
174 | const gridSize = 2;
175 | let grid, wallGrid, lavaGrid, size;
176 |
177 | beforeEach(() => {
178 | grid = new Array(gridSize).fill(new Array(gridSize));
179 | wallGrid = new Array(gridSize).fill(new Array(gridSize).fill('wall'));
180 | lavaGrid = new Array(gridSize).fill(new Array(gridSize).fill('lava'));
181 | size = new Vector(1, 1);
182 | });
183 |
184 | it('Вернет undefined, если объект не выходит за пределы уровня и ни с чем не пересекается', () => {
185 | const level = new Level(grid);
186 | const position = new Vector(0, 0);
187 |
188 | const wall = level.obstacleAt(position, size);
189 |
190 | expect(wall).to.be.undefined;
191 | });
192 |
193 | it('Вернет строку wall, если левая граница объекта выходит за пределы уровня', () => {
194 | const level = new Level(grid);
195 | const position = new Vector(-1, 0);
196 |
197 | const wall = level.obstacleAt(position, size);
198 |
199 | expect(wall).to.be.equal('wall');
200 | });
201 |
202 | it('Вернет строку wall, если правая граница объекта выходит за пределы уровня', () => {
203 | const level = new Level(grid);
204 | const position = new Vector(gridSize, 0);
205 |
206 | const wall = level.obstacleAt(position, size);
207 |
208 | expect(wall).to.be.equal('wall');
209 | });
210 |
211 | it('Вернет строку wall, если верхняя граница объекта выходит за пределы уровня', () => {
212 | const level = new Level(grid);
213 | const position = new Vector(0, -1);
214 |
215 | const wall = level.obstacleAt(position, size);
216 |
217 | expect(wall).to.be.equal('wall');
218 | });
219 |
220 | it('Вернет строку lava, если нижняя граница объекта выходит за пределы уровня', () => {
221 | const level = new Level(grid);
222 | const position = new Vector(0, gridSize);
223 |
224 | const wall = level.obstacleAt(position, size);
225 |
226 | expect(wall).to.be.equal('lava');
227 | });
228 |
229 | it('Вернет строку wall, если площадь пересекается со стеной', () => {
230 | const level = new Level(wallGrid);
231 | const position = new Vector(0, 0);
232 |
233 | const wall = level.obstacleAt(position, size);
234 |
235 | expect(wall).to.be.equal('wall');
236 | });
237 |
238 | it('Вернет строку lava, если площадь пересекается с лавой', () => {
239 | const level = new Level(lavaGrid);
240 | const position = new Vector(0, 0);
241 |
242 | const wall = level.obstacleAt(position, size);
243 |
244 | expect(wall).to.be.equal('lava');
245 | });
246 |
247 | it('Вернет строку wall, если площадь пересекается со стеной и объект имеет нецелочисленные координаты', () => {
248 | const level = new Level(wallGrid);
249 | const position = new Vector(0.5, 0.5);
250 |
251 | const wall = level.obstacleAt(position, size);
252 |
253 | expect(wall).to.be.equal('wall');
254 | });
255 |
256 | it('Вернет строку wall, если площадь пересекается со стеной и объект имеет нецелочисленный размер', () => {
257 | const level = new Level(wallGrid);
258 | const position = new Vector(0, 0);
259 | const size = new Vector(0.5, 0.5);
260 |
261 | const wall = level.obstacleAt(position, size);
262 |
263 | expect(wall).to.be.equal('wall');
264 | });
265 |
266 | it('Объект не пересекается со стеной, на которой стоит', () => {
267 | const grid = [
268 | Array(4),
269 | Array(4),
270 | Array(4),
271 | Array(4).fill('wall')
272 | ];
273 | const level = new Level(grid);
274 | const position = new Vector(2.1, 1.5);
275 | const size = new Vector(0.8, 1.5);
276 |
277 | const nothing = level.obstacleAt(position, size);
278 |
279 | expect(nothing).to.be.undefined;
280 | });
281 |
282 | it('Объект не пересекается со стеной, которая над ним', () => {
283 | const grid = [
284 | Array(4).fill('wall'),
285 | Array(4),
286 | Array(4),
287 | Array(4)
288 | ];
289 | const level = new Level(grid);
290 | const position = new Vector(2.1, 1);
291 | const size = new Vector(0.8, 1.5);
292 |
293 | const nothing = level.obstacleAt(position, size);
294 |
295 | expect(nothing).to.be.undefined;
296 | });
297 |
298 | it('Объект не пересекается со стеной, которая слева от него', () => {
299 | const grid = [
300 | Array(4),
301 | ['wall', undefined, undefined, undefined],
302 | Array(4),
303 | Array(4)
304 | ];
305 | const level = new Level(grid);
306 | const position = new Vector(1, 1.5);
307 | const size = new Vector(0.8, 1.5);
308 |
309 | const nothing = level.obstacleAt(position, size);
310 |
311 | expect(nothing).to.be.undefined;
312 | });
313 |
314 | it('Объект не пересекается со стеной, которая справа от него', () => {
315 | const grid = [
316 | Array(4),
317 | [undefined, undefined, 'wall', undefined],
318 | Array(4),
319 | Array(4)
320 | ];
321 | const level = new Level(grid);
322 | const position = new Vector(1.2, 1.5);
323 | const size = new Vector(0.8, 1.5);
324 |
325 | const nothing = level.obstacleAt(position, size);
326 |
327 | expect(nothing).to.be.undefined;
328 | });
329 | });
330 |
331 | describe('Метод removeActor', () => {
332 | it('Удаляет переданный движущийся объект', () => {
333 | const level = new Level(undefined, [ mushroom, giftSmall ]);
334 |
335 | level.removeActor(mushroom);
336 |
337 | expect(level.actors.includes(mushroom)).to.be.false;
338 | });
339 |
340 | it('Не удаляет остальные движущиеся объекты', () => {
341 | const level = new Level(undefined, [ mushroom, giftSmall ]);
342 |
343 | level.removeActor(mushroom);
344 |
345 | expect(level.actors.includes(giftSmall)).to.be.true;
346 | });
347 | });
348 |
349 | describe('Метод noMoreActors', () => {
350 | it('Вернет истину, если движущихся объектов нет в уровне', () => {
351 | const level = new Level();
352 |
353 | expect(level.noMoreActors()).to.be.true;
354 | });
355 |
356 | it('Вернет истину, если в уровне нет движущихся объектов заданного типа', () => {
357 | const level = new Level(undefined, [ mushroom, giftSmall ]);
358 |
359 | expect(level.noMoreActors('actor')).to.be.true;
360 | });
361 |
362 | it('Вернет ложь, если в уровне есть движущиеся объекты заданного типа', () => {
363 | const level = new Level(undefined, [ mushroom, giftSmall ]);
364 |
365 | expect(level.noMoreActors('mushroom')).to.be.false;
366 | });
367 | });
368 |
369 | describe('Метод playerTouched', () => {
370 |
371 | it('Если передать lava первым аргументом, меняет статус уровня на lost', () => {
372 | const level = new Level();
373 |
374 | level.playerTouched('lava');
375 |
376 | expect(level.status).to.equal('lost');
377 | });
378 |
379 | it('Если передать fireball первым аргументом, меняет статус уровня на lost', () => {
380 | const level = new Level();
381 |
382 | level.playerTouched('fireball');
383 |
384 | expect(level.status).to.equal('lost');
385 | });
386 |
387 | it('Если передать coin первым аргументом и движущийся объект вторым, удаляет этот объект из уровня', () => {
388 | const level = new Level(undefined, [ goldCoin, bronzeCoin ]);
389 |
390 | level.playerTouched('coin', goldCoin);
391 |
392 | expect(level.actors).to.have.length(1);
393 | expect(level.actors).to.not.include(goldCoin);
394 | expect(level.status).to.equal(null);
395 | });
396 |
397 | it('Если удалить все монеты, то статус меняется на won', () => {
398 | const level = new Level(undefined, [ goldCoin, bronzeCoin ]);
399 |
400 | level.playerTouched('coin', goldCoin);
401 | level.playerTouched('coin', bronzeCoin);
402 |
403 | expect(level.status).to.equal('won');
404 | });
405 |
406 | it('Если удалить все монеты после касания lava, статус уровня остается lost', () => {
407 | const level = new Level(undefined, [ goldCoin, bronzeCoin ]);
408 |
409 | level.playerTouched('lava');
410 | level.playerTouched('coin', goldCoin);
411 | level.playerTouched('coin', bronzeCoin);
412 |
413 | expect(level.status).to.equal('lost');
414 | });
415 |
416 | it('Если удалить все монеты после касания fireball, статус уровня остается lost', () => {
417 | const level = new Level(undefined, [ goldCoin, bronzeCoin ]);
418 |
419 | level.playerTouched('fireball');
420 | level.playerTouched('coin', goldCoin);
421 | level.playerTouched('coin', bronzeCoin);
422 |
423 | expect(level.status).to.equal('lost');
424 | });
425 |
426 | it('Если коснуться lava после удаления всех монет, статус уровня остается won', () => {
427 | const level = new Level(undefined, [ goldCoin, bronzeCoin ]);
428 |
429 | level.playerTouched('coin', goldCoin);
430 | level.playerTouched('coin', bronzeCoin);
431 | level.playerTouched('lava');
432 |
433 | expect(level.status).to.equal('won');
434 | });
435 |
436 | it('Если коснуться fireball после удаления всех монет, статус уровня остается won', () => {
437 | const level = new Level(undefined, [ goldCoin, bronzeCoin ]);
438 |
439 | level.playerTouched('coin', goldCoin);
440 | level.playerTouched('coin', bronzeCoin);
441 | level.playerTouched('fireball');
442 |
443 | expect(level.status).to.equal('won');
444 | });
445 | });
446 | });
447 |
--------------------------------------------------------------------------------
/test/parser.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс LevelParser', () => {
4 | const Mushroom = extend(Actor, { type: { value: 'mushroom' }});
5 | const Gift = extend(Actor, { type: { value: 'gift' }});
6 | class BadActor {}
7 |
8 | describe('Конструктор new LevelParser()', () => {
9 | });
10 |
11 | describe('Метод actorFromSymbol', () => {
12 |
13 |
14 | it('Вернет undefined, если не передать символ', () => {
15 | const parser = new LevelParser();
16 |
17 | const actor = parser.actorFromSymbol();
18 |
19 | expect(actor).to.be.undefined;
20 | });
21 |
22 | it('Вернет undefined, если передать символ, которому не назначен конструктор движимого объекта', () => {
23 | const parser = new LevelParser({ y: Mushroom });
24 |
25 | const actor = parser.actorFromSymbol('z');
26 |
27 | expect(actor).to.be.undefined;
28 | });
29 |
30 | it('Вернет подходящий конструктор движимого объекта, если передать символ, которому он назначен', () => {
31 | const parser = new LevelParser({ y: Mushroom });
32 |
33 | const actor = parser.actorFromSymbol('y');
34 |
35 | expect(actor).to.equal(Mushroom);
36 | });
37 | });
38 |
39 | describe('Метод obstacleFromSymbol', () => {
40 | it('Вернет undefined, если не передать символ', () => {
41 | const parser = new LevelParser();
42 |
43 | const obstacle = parser.obstacleFromSymbol();
44 |
45 | expect(obstacle).to.be.undefined;
46 | });
47 |
48 | it('Вернет undefined, если передать неизвестный символ', () => {
49 | const parser = new LevelParser();
50 |
51 | const obstacle = parser.obstacleFromSymbol('Z');
52 |
53 | expect(obstacle).to.be.undefined;
54 | });
55 |
56 | it('Вернет wall, если передать символ x', () => {
57 | const parser = new LevelParser();
58 |
59 | const obstacle = parser.obstacleFromSymbol('x');
60 |
61 | expect(obstacle).to.equal('wall');
62 | });
63 |
64 | it('Вернет lava, если передать символ !', () => {
65 | const parser = new LevelParser();
66 |
67 | const obstacle = parser.obstacleFromSymbol('!');
68 |
69 | expect(obstacle).to.equal('lava');
70 | });
71 | });
72 |
73 | describe('Метод createGrid', () => {
74 | let plan;
75 |
76 | beforeEach(() => {
77 | plan = [
78 | 'x x',
79 | '!!!!'
80 | ];
81 | });
82 |
83 | it('Вернет пустой массив, если передать пустой план', () => {
84 | const parser = new LevelParser();
85 |
86 | const grid = parser.createGrid([]);
87 |
88 | expect(grid).to.eql([]);
89 | });
90 |
91 | it('Вернет массив того же размера, что и plan', () => {
92 | const parser = new LevelParser();
93 |
94 | const grid = parser.createGrid(plan);
95 |
96 | expect(grid.length).to.equal(plan.length);
97 | });
98 |
99 | it('В ряду будет столько элементов, сколько символов в строке плана', () => {
100 | const parser = new LevelParser();
101 |
102 | const grid = parser.createGrid(plan);
103 |
104 | grid.forEach((row, y) => {
105 | expect(row.length).to.equal(plan[y].length);
106 | });
107 | });
108 |
109 | it('Символы x определит как wall и поместит в соответствующую ячейку', () => {
110 | const parser = new LevelParser();
111 |
112 | const grid = parser.createGrid(plan);
113 |
114 | expect(grid[0]).to.eql(['wall',undefined,undefined,'wall']);
115 | });
116 |
117 | it('Символы ! определит как lava и поместит в соответствующую ячейку', () => {
118 | const parser = new LevelParser();
119 |
120 | const grid = parser.createGrid(plan);
121 |
122 | expect(grid[1]).to.eql(new Array(4).fill('lava'));
123 | });
124 | });
125 |
126 | describe('Метод createActors', () => {
127 | let plan;
128 |
129 | beforeEach(() => {
130 | plan = [
131 | 'o o',
132 | ' z ',
133 | 'o o'
134 | ];
135 | });
136 |
137 | it('Вернет пустой массив, если передать пустой план', () => {
138 | const parser = new LevelParser({ o: Gift, z: Mushroom });
139 |
140 | const actors = parser.createActors([]);
141 |
142 | expect(actors).to.eql([]);
143 | });
144 |
145 | it('Вернет пустой массив, если не определить символы движущихся объектов', () => {
146 | const parser = new LevelParser();
147 |
148 | const actors = parser.createActors(plan);
149 |
150 | expect(actors).to.eql([]);
151 | });
152 |
153 | it('Игнорирует символы, для которых в словаре не задан символ', () => {
154 | const parser = new LevelParser({ z: 'mushroom' });
155 |
156 | const actors = parser.createActors(['m']);
157 |
158 | expect(actors).to.eql([]);
159 | });
160 |
161 | it('Игнорирует символы, для которых в словаре передана не функция', () => {
162 | const parser = new LevelParser({ z: 'mushroom' });
163 |
164 | const actors = parser.createActors(['z']);
165 |
166 | expect(actors).to.eql([]);
167 | });
168 |
169 | it('Игнорирует символы, для которых в словаре передан конструктор не Actor', () => {
170 | const parser = new LevelParser({ b: BadActor });
171 |
172 | const actors = parser.createActors(['b']);
173 |
174 | expect(actors).to.eql([]);
175 | });
176 |
177 | it('Создает движущиеся объекты для конструктора Actor', () => {
178 | const parser = new LevelParser({ z: Actor });
179 |
180 | const actors = parser.createActors(['z']);
181 |
182 | expect(actors).to.have.length(1);
183 | });
184 |
185 | it('Создает движущиеся объекты правильного типа для конструктора Actor ', () => {
186 | const parser = new LevelParser({ z: Actor });
187 |
188 | const actors = parser.createActors(['z']);
189 |
190 | expect(actors[0]).to.be.an.instanceof(Actor);
191 | });
192 |
193 | it('Создает движущиеся объекты для конструкторов типа Actor', () => {
194 | const parser = new LevelParser({ z: Mushroom });
195 |
196 | const actors = parser.createActors(['z']);
197 |
198 | expect(actors).to.have.length(1);
199 | });
200 |
201 | it('Создает движущиеся объекты правильного типа для конструкторов типа Actor ', () => {
202 | const parser = new LevelParser({ z: Mushroom });
203 |
204 | const actors = parser.createActors(['z']);
205 |
206 | expect(actors[0]).to.be.an.instanceof(Mushroom);
207 | });
208 |
209 | it('Вернет массив со всеми движущимися объектами, если передать план', () => {
210 | const parser = new LevelParser({ o: Gift, z: Mushroom });
211 |
212 | const actors = parser.createActors(plan);
213 |
214 | expect(actors).to.have.length(5);
215 | });
216 |
217 | it('Каждый движущийся объект будет экземпляром своего класса', () => {
218 | const parser = new LevelParser({ o: Gift, z: Mushroom });
219 |
220 | const actors = parser.createActors(plan);
221 | const oActors = actors.filter(actor => actor instanceof Gift);
222 | const zActors = actors.filter(actor => actor instanceof Mushroom);
223 |
224 | expect(oActors).to.have.length(4);
225 | expect(zActors).to.have.length(1);
226 | });
227 |
228 | it('Каждый движущийся объект будет иметь координаты той ячейки, где он размещен на плане', () => {
229 | const parser = new LevelParser({ o: Actor, z: Actor });
230 |
231 | const actors = parser.createActors(plan);
232 |
233 | expect(actors.some(actor => actor.pos.x === 0 && actor.pos.y === 0)).to.be.true;
234 | expect(actors.some(actor => actor.pos.x === 4 && actor.pos.y === 0)).to.be.true;
235 | expect(actors.some(actor => actor.pos.x === 2 && actor.pos.y === 1)).to.be.true;
236 | expect(actors.some(actor => actor.pos.x === 0 && actor.pos.y === 0)).to.be.true;
237 | expect(actors.some(actor => actor.pos.x === 4 && actor.pos.y === 2)).to.be.true;
238 | });
239 | });
240 |
241 | describe('Метод parse', () => {
242 | let plan;
243 |
244 | beforeEach(() => {
245 | plan = [
246 | ' oxo ',
247 | '!xzx!',
248 | ' oxo '
249 | ];
250 | });
251 |
252 | it('Вернет объект уровня, Level', () => {
253 | const parser = new LevelParser();
254 |
255 | const level = parser.parse([]);
256 |
257 | expect(level).to.be.an.instanceof(Level);
258 | });
259 |
260 | it('Высота уровня будет равна количеству строк плана', () => {
261 | const parser = new LevelParser();
262 |
263 | const level = parser.parse(plan);
264 |
265 | expect(level.height).to.equal(3);
266 | });
267 |
268 | it('Ширина уровня будет равна количеству символов в максимальной строке плана', () => {
269 | const parser = new LevelParser();
270 |
271 | const level = parser.parse(plan);
272 |
273 | expect(level.width).to.equal(5);
274 | });
275 |
276 | it('Создаст уровень с движущимися объектами из плана', () => {
277 | const parser = new LevelParser({ o: Gift, z: Mushroom });
278 |
279 | const level = parser.parse(plan);
280 |
281 | expect(level.actors).to.have.length(5);
282 | });
283 |
284 | it('Создаст уровень с препятствиями из плана', () => {
285 | const parser = new LevelParser();
286 |
287 | const level = parser.parse(plan);
288 |
289 | expect(level.grid).to.eql([
290 | [undefined, undefined,'wall',undefined,undefined],
291 | ['lava','wall',undefined,'wall','lava'],
292 | [undefined,undefined,'wall',undefined,undefined]
293 | ]);
294 | });
295 | });
296 | });
297 |
--------------------------------------------------------------------------------
/test/player.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс Player', () => {
4 | let position;
5 |
6 | beforeEach(() => {
7 | position = new Vector(10, 5);
8 | });
9 |
10 | describe('Конструктор', () => {
11 | it('Создает объект, реальная позиция которого отличается от той, что передана в конструктор, на вектор 0:-0,5', () => {
12 | const player = new Player(position);
13 |
14 | expect(player.pos).to.eql(new Vector(10, 4.5));
15 | });
16 |
17 | it('Создает объект размером 0,8:1,5', () => {
18 | const player = new Player();
19 |
20 | expect(player.size).to.eql(new Vector(0.8, 1.5));
21 | });
22 |
23 | it('Имеет свойство type, равное player', () => {
24 | const player = new Player();
25 |
26 | expect(player.type).to.equal('player');
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/vector.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс Vector', () => {
4 | const x = 3, y = 7, left = 5, top = 10, n = 5;
5 |
6 | describe('Конструктор new Vector()', () => {
7 | it('создает объект со свойствами x и y, равными аргументам конструктора', () => {
8 | const position = new Vector(left, top);
9 |
10 | expect(position.x).is.equal(left);
11 | expect(position.y).is.equal(top);
12 | });
13 |
14 | it('без аргументов создает объект со свойствами x и y, равными 0', () => {
15 | const position = new Vector();
16 |
17 | expect(position.x).is.equal(0);
18 | expect(position.y).is.equal(0);
19 | });
20 |
21 | });
22 |
23 | describe('Метод plus()', () => {
24 | it('бросает исключение, если передать не вектор', () => {
25 | const position = new Vector(x, y);
26 |
27 | function fn() {
28 | position.plus({ left, top });
29 | }
30 |
31 | expect(fn).to.throw(Error);
32 | });
33 |
34 | it('создает новый вектор', () => {
35 | const position = new Vector(x, y);
36 |
37 | const newPosition = position.plus(new Vector(left, top));
38 |
39 | expect(newPosition).is.instanceof(Vector);
40 | });
41 |
42 | it('координаты нового вектора равны сумме координат суммируемых векторов', () => {
43 | const position = new Vector(x, y);
44 |
45 | const newPosition = position.plus(new Vector(left, top));
46 |
47 | expect(newPosition.x).is.equal(8);
48 | expect(newPosition.y).is.equal(17);
49 | });
50 | });
51 |
52 | describe('Метод times()', () => {
53 | it('создает новый вектор', () => {
54 | const position = new Vector(x, y);
55 |
56 | const newPosition = position.times(n);
57 |
58 | expect(newPosition).is.instanceof(Vector);
59 | });
60 |
61 | it('координаты нового вектора увеличены в n раз', () => {
62 | const position = new Vector(x, y);
63 |
64 | const newPosition = position.times(n);
65 |
66 | expect(newPosition.x).is.equal(15);
67 | expect(newPosition.y).is.equal(35);
68 | });
69 | });
70 |
71 | });
72 |
--------------------------------------------------------------------------------
/test/verticalfireball.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Класс VerticalFireball', () => {
4 | describe('Конструктор new VerticalFireball', () => {
5 | it('Создает экземпляр Fireball', () => {
6 | const ball = new VerticalFireball();
7 |
8 | expect(ball).to.be.an.instanceof(Fireball);
9 | });
10 |
11 | it('Имеет скорость Vector(0, 2)', () => {
12 | const ball = new VerticalFireball();
13 |
14 | expect(ball.speed).to.eql(new Vector(0, 2));
15 | });
16 |
17 | it('Имеет свойство type, равное fireball', () => {
18 | const ball = new HorizontalFireball();
19 |
20 | expect(ball.type).to.equal('fireball');
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------