Skip to content

Commit

Permalink
final version, fixes, readme
Browse files Browse the repository at this point in the history
  • Loading branch information
j-tap committed Jun 15, 2022
1 parent b3c3668 commit f4e3909
Show file tree
Hide file tree
Showing 22 changed files with 125 additions and 33 deletions.
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,82 @@
# BlastGame
Simple browser game
Demo on [blast-game.netlify.app](https://blast-game.netlify.app/)

#### Used in the project
- [Phaser 3](https://github.com/photonstorm/phaser)

#### Install

Node.js 16

```sh
# clone repo
npm i # install dependencies
npm run favicon # generate favicon and meta
npm run build || npm run dev # build to dist or run dev server
```

<details>
<summary>Task description</summary>

###### Задача
Реализовать _прототип игры_ в жанре “головоломка с механикой Blast”.
Описание общей механики игры
Игра состоит из игрового поля произвольного размера N*M. В каждой ячейке поля находится игровой объект (далее именуемый тайл) определенного цвета. Количество возможных вариантов цветов равно C.

Начальное состояние поля задается случайно (вероятность цвета тайла является равновероятной). При клике на тайл сжигается (удаляется) область, состоящая из группы прилегающих тайлов того же цвета, размер группы не может быть меньше чем K (по умолчанию K=2). На месте удаленных должны образоваться пустые места.

Далее происходит перемещение тайлов на пустые места сверху вниз. Если верхняя ячейка становится пустой, необходимо сгенерировать новый тайл и переместить его в эту ячейку. Процесс перемещения и добавление новых тайлов должен быть непрерывный и происходит до тех пор, пока поле снова не будет полностью заполнено.

_На заполненном поле всегда можно сжечь тайлы. Если такой возможности нет, то необходимо перемешать тайлы на поле (количество перемешиваний S). Если же после перемешивания нет возможности сжечь тайлы, то такая ситуация является проигрышем для игрока._

Цель игры – набрать X очков за Y ходов, иначе проигрыш. Значение количества очков и ходов для выигрыша, а также формула начисления очков остается на усмотрение соискателя.

**_Перед выполнением тестового задания советуем ознакомится со списком игр с механикой “Blast” (см. ниже) для лучшего понимания игрового процесса._**

###### Основное задание:
1. **Выполнить реализацию на JavaScript + Canvas**
1. Допустимо использование следующих фреймворков и игровых движков: **Pixi.js / Phaser.js / Cocos2d JS / CocosCreator**
2. **Недопустимо** использование **React.js**, **Vue.js** и другие производные фреймворки для работы с DOM
3. Использовать при разработке принципы **SOLID**
4. Отделить логику игры и отображение
5. Разбить игру на отдельные состояния (сцены)
2. Реализовать анимации для перемещения и сжигания тайлов
3. Отображение количества оставшихся ходов и набранных очков
4. Обработать состояние выигрыша или проигрыша
5. Использовать приложенный набор ассетов (см. ниже)
6. Исходники выложить на **github.com / gitlab.com / bitbucket.com**
7. **Предоставить ссылку на рабочий проект на общедоступном хостинге** (например [https://pages.github.com](https://pages.github.com) или подобные)

###### Дополнительные задания на выбор:
1. Реализовать “бустер бомба” \
_Описание:_
При активации бустера и клика по полю, в данной клетке сжигаются тайлы в радиусе R клеток.
2. Реализовать “бустер телепорт”
_Описание:_
_Бустер позволяет поменять два тайла местами._
3. Реализовать механику “супер тайла” \
_Описание:_ \
Если при уничтожении размер группы тайлов больше чем L, то тогда на месте клетки, по которой был клик, появится новый тайл. По клику на него активизируется определенная логика, возможные варианты:
1. Сжигается вся строка в которой находится тайл
2. Сжигается весь столбец в котором находится тайл
3. Сжигаются тайлы в радиусе R клеток
4. Сжигается всё поле

###### Будет большим плюсом:
1. _Применение модульного тестирования_
2. _Атомарные коммиты в репозитории_
3. _Использование ES6 + Babel_
4. _Сборка проекта gulp / webpack_

###### Оценка задания:
**Необходимо выполнение основного задания, а также минимум одного дополнительного задания.**

###### Ассеты для игры:
* _[https://drive.google.com/file/d/1gkeMJy3u01oAisAzByzgQEYgW13RKzP2/view?usp=sharing](https://drive.google.com/file/d/1gkeMJy3u01oAisAzByzgQEYgW13RKzP2/view?usp=sharing)_

###### Список игр с механикой “Blast”:
* [https://play.google.com/store/apps/details?id=com.rovio.blast](https://play.google.com/store/apps/details?id=com.rovio.blast)
* [https://play.google.com/store/apps/details?id=com.superbox.aos.jewelblast](https://play.google.com/store/apps/details?id=com.superbox.aos.jewelblast)
* [https://play.google.com/store/apps/details?id=toy.blast.pop.cubes.puzzle](https://play.google.com/store/apps/details?id=toy.blast.pop.cubes.puzzle)
</details>
Binary file modified dist/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified dist/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified dist/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed dist/assets/img/315f4f40430c4367bf79.png
Binary file not shown.
Binary file removed dist/assets/img/7b4da64c18cb692bb83e.png
Binary file not shown.
Binary file removed dist/assets/img/bbdb3539cd5c788b358b.png
Binary file not shown.
Binary file modified dist/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified dist/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions dist/js/bundle.js

Large diffs are not rendered by default.

Binary file modified dist/mstile-150x150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/img/game/icon-unknow.png
Binary file not shown.
Binary file removed src/assets/img/game/pause-btn.png
Binary file not shown.
Binary file removed src/assets/img/game/plus-btn.png
Binary file not shown.
13 changes: 3 additions & 10 deletions src/configs/scenes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,9 @@ export const scene1 = {
h: 9,
},
tiles: ['tile-1', 'tile-2', 'tile-3', 'tile-4', 'tile-5'],
minScore: 100,
minScore: 150,
minTilesTarget: 2,
bonuses: [
{
name: 'bomb',
title: 'Bomb!',
amount: 1,
params: { range: 1 },
},
],
bonuses: [],
autoShufl: 10,
maxMoves: 10,
nextScene: 'Scene2',
Expand All @@ -25,7 +18,7 @@ export const scene2 = {
h: 9,
},
tiles: ['tile-1', 'tile-2', 'tile-3', 'tile-4', 'tile-5'],
minScore: 300,
minScore: 500,
minTilesTarget: 2,
bonuses: [
{
Expand Down
2 changes: 1 addition & 1 deletion src/faviconData.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"result":{"status":"success"},"favicon":{"package_url":"https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/favicon_package_v0.16.zip","files_urls":["https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/android-chrome-192x192.png","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/android-chrome-512x512.png","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/apple-touch-icon.png","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/browserconfig.xml","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/favicon-16x16.png","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/favicon-32x32.png","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/favicon.ico","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/mstile-150x150.png","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/safari-pinned-tab.svg","https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/package_files/site.webmanifest"],"html_code":"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n<link rel=\"manifest\" href=\"/site.webmanifest\">\n<link rel=\"mask-icon\" href=\"/safari-pinned-tab.svg\" color=\"#00539e\">\n<meta name=\"msapplication-TileColor\" content=\"#00539e\">\n<meta name=\"theme-color\" content=\"#ffffff\">","compression":"false","overlapping_markups":["link[rel=\"apple-touch-icon\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]","link[rel=\"mask-icon\"]"]},"files_location":{"type":"path","path":"/"},"preview_picture_url":"https://realfavicongenerator.net/files/817d14dc31f58896a4b18888c2d511c88d869ff3/favicon_preview.png","version":"0.16"}
{"result":{"status":"success"},"favicon":{"package_url":"https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/favicon_package_v0.16.zip","files_urls":["https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/android-chrome-192x192.png","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/android-chrome-512x512.png","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/apple-touch-icon.png","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/browserconfig.xml","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/favicon-16x16.png","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/favicon-32x32.png","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/favicon.ico","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/mstile-150x150.png","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/safari-pinned-tab.svg","https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/package_files/site.webmanifest"],"html_code":"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n<link rel=\"manifest\" href=\"/site.webmanifest\">\n<link rel=\"mask-icon\" href=\"/safari-pinned-tab.svg\" color=\"#00539e\">\n<meta name=\"msapplication-TileColor\" content=\"#00539e\">\n<meta name=\"theme-color\" content=\"#ffffff\">","compression":"false","overlapping_markups":["link[rel=\"apple-touch-icon\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]","link[rel=\"mask-icon\"]"]},"files_location":{"type":"path","path":"/"},"preview_picture_url":"https://realfavicongenerator.net/files/55162896881f5ef7a68f8c0d4b004d004439dd31/favicon_preview.png","version":"0.16"}
7 changes: 7 additions & 0 deletions src/objects/Bonus.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ export default class Bonus
}
return this
}

reset ()
{
this.amountLeft = this.amount
this.active = false
return this
}
}
21 changes: 13 additions & 8 deletions src/objects/SceneLevel.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ export default class SceneLevel extends SceneGame

draw ()
{
this.bonusesBlocks = []
const { fontFamily, colorTextBar } = this.configGame
const { width } = this.cameras.main
this.bonusesBlocks = []
const bonuses = this.bonusesService.getBonuses()
const bonuses = this.bonusesService.getBonusesList()
const padding = 20
const styleText = {
fontFamily,
Expand All @@ -86,7 +86,7 @@ export default class SceneLevel extends SceneGame
this.scoreBar = this.add.scoreBar(0, 160)
this.scoreBar.setX(width - this.scoreBar.displayWidth - padding)

if (Object.keys(bonuses).length)
if (bonuses.length)
{
this.add.text(
width - this.scoreBar.displayWidth / 2 - padding,
Expand All @@ -97,12 +97,15 @@ export default class SceneLevel extends SceneGame
.setOrigin(.5, 0)
}

Object.keys(bonuses).forEach((name, i) =>
bonuses.forEach((bonus, i) =>
{
const y = this.scoreBar.y + this.scoreBar.displayHeight + 60
this.bonusesBlocks[name] = this.add.bonusBlock(0, y, bonuses[name])
const x = width - this.scoreBar.displayWidth - 70 + this.bonusesBlocks[name].displayWidth * i
this.bonusesBlocks[name].setX(x)
const { displayWidth, displayHeight, y } = this.scoreBar
const bonusY = y + displayHeight + 60

this.bonusesBlocks[bonus.name] = this.add.bonusBlock(0, bonusY, bonus)

const bonusX = width - displayWidth - 70 + this.bonusesBlocks[bonus.name].displayWidth * i
this.bonusesBlocks[bonus.name].setX(bonusX)
})
}

Expand Down Expand Up @@ -156,6 +159,8 @@ export default class SceneLevel extends SceneGame
.resetScores()
.resetMoves()

this.bonusesService.reset()

this.scene.stop()
}

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/bonusBlock/BonusBlockGameObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class BonusBlockGameObject extends GameObjects.Container
const titleText = this.scene.make.text({
x: this.imageBg.displayWidth / 2,
y: this.imageBg.displayHeight / 4,
text: this.title,
text: this.bonus.title,
style: styleText,
})
.setOrigin(.5, 0)
Expand Down
1 change: 0 additions & 1 deletion src/plugins/gridTiles/GridTilesGameObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default class GridTilesGameObject extends GameObjects.Container
{
super(scene, x, y)

this.scene = scene
this.imageBg = 'grid-bg'
this.grid = grid
this.tilesFrames = tiles
Expand Down
6 changes: 0 additions & 6 deletions src/scenes/ScenePreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import SceneGame from '@/objects/SceneGame'

import bonusBgImg from '@/assets/img/game/bonus-bg.png'
import gridBgImg from '@/assets/img/game/grid-bg.png'
import iconUnknowImg from '@/assets/img/game/icon-unknow.png'
import pauseBtnImg from '@/assets/img/game/pause-btn.png'
import plusBtnImg from '@/assets/img/game/plus-btn.png'
import purpleBtnImg from '@/assets/img/game/purple-btn.png'
import redBtnImg from '@/assets/img/game/red-btn.png'
import scoreBgImg from '@/assets/img/game/score-bg.png'
Expand Down Expand Up @@ -36,9 +33,6 @@ export default class ScenePreload extends SceneGame

this.load.image('bonus-bg', bonusBgImg)
this.load.image('grid-bg', gridBgImg)
this.load.image('icon-unknow', iconUnknowImg)
this.load.image('pause-btn', pauseBtnImg)
this.load.image('plus-btn', plusBtnImg)
this.load.image('purple-btn', purpleBtnImg)
this.load.image('red-btn', redBtnImg)
this.load.image('score-bg', scoreBgImg)
Expand Down
21 changes: 17 additions & 4 deletions src/services/BonusesService.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,34 @@ export default class BonusesService
return this.#bonuses
}

getBonusesList ()
{
return Object.values(this.#bonuses)
}

getBonus (name)
{
return this.#bonuses[name]
}

getActive ()
{
const bonuses = this.getBonuses()
return Object.keys(bonuses).find(n => bonuses[n].active)
return this.getBonusesList()
.find(o => o.active)
?.name
}

setActive (name)
{
const bonuses = this.getBonuses()
Object.keys(bonuses).find(n => bonuses[n].activate(n === name))
this.getBonusesList()
.map(o => o.activate(o.name === name))
return this
}

reset()
{
this.getBonusesList()
.find(o => o.reset())
return this
}
}

0 comments on commit f4e3909

Please sign in to comment.