2048 - популярная однопользовательская игра. В игре используется сетка 4 на 4 для плиток. Каждая плитка может быть пустой или содержать карточку со значением. Как игрок, вы можете перемещаться влево, вправо, вверх и вниз. Когда карты объединяются, номера на картах также меняются. Цель состоит в том, чтобы число на карте было как можно большим. Игра заканчивается, когда все плитки заполнены и не остается следующих ходов.
Вы можете найти демо-проект здесь, а репозиторий Github здесь. В этом сообщении блога (часть 1) и следующем сообщении блога (часть 2) мы шаг за шагом рассмотрим, как мы создаем игру!
Начать работу с проектными каркасами
Во-первых, нам нужно настроить каркас проекта для создания проекта Vue. Мы собираемся использовать простой javascript, чтобы понять, что происходит. Начнем с клонирования простой основы JavaScript Vue. Scaffold предоставляет нам несколько каталогов:
- активы. Все сторонние библиотеки javascript, таблицы стилей и изображения.
- компоненты. Все собственные компоненты Vue.
- миксины. Все нативные миксины Vue.
- магазин. Магазин Vuex для управления состоянием.
- main.js. Место, где инициируется новый экземпляр Vue.
- index.html. Место, где отображается все приложение.
Шаг 1. Настройте игровую доску
На этом этапе мы:
- создать
game
компонент сboard
- создать теневую доску
- создать функцию посева
Сначала у нас есть game
компонент, отвечающий за плату. Доска имеет много плиток, и каждая плитка имеет значение по умолчанию 0. Мы можем начать с регистрации компонент game
:
src / components / game.js
((() => { const html = ` <div class="game"> </div> ` Vue.component("game", { template: html, data () { return { board: [], } }, }) }))()
В компоненте Vue game
у нас есть свойство данных board
, которое содержит массив плиток, каждая плитка имеет значение по умолчанию 0. Затем мы добавляем теневую доску в качестве базовый слой настольной игры внутри нашего html
.
src / components / game.js
((() => { const html = ` <div class="game"> <div class="game-container"> <div class="board shadow-board"> <div v-for="n in board.length" :key="n" class="tile shadow-tile"></div> </div> </div> </div> ` ... }))()
Теперь, когда вы откроете index.html
, это должно выглядеть так:
Чтобы начать игру и продолжать играть, нам нужно присвоить некоторым плиткам игровое значение (т.е. значение ›0). Для этого мы создадим метод seedTwo (). В начале каждой игры мы будем использовать это для создания начального состояния, а после правильного хода мы будем использовать это, чтобы игра продолжала двигаться вперед.
src / components / game.js
Vue.component("game", { ... methods: { seedTwo() { const self = this let getRandomItem = function() { let row = self.board[Math.floor(Math.random()*self.board.length)] return row[Math.floor(Math.random()*row.length)] } let initialRandomItem = getRandomItem() while (initialRandomItem.value != 0) { initialRandomItem = getRandomItem() } initialRandomItem.value = 2 } } }) ...
Шаг 2. определение объектных моделей
На этом этапе мы:
- создать
Tile()
компонент - изменить
Tile()
класс CSS в зависимости от того, пуста плитка или нет - создать
GameMenu()
компонент
Доска может содержать 16 плиток в виде плоского массива, хотя мы представим его на доске как двумерный массив. Добавляем еще один компонент tile
под src/components/tile.js
. Компоненты tile принимают объект плитки как обязательные свойства, которые должны включать {value: X}
как часть объекта.
src / components / tile.js
((() => { const html = ` <div class="tile"> {{ value }} </div> ` Vue.component("tile", { template: html, props: { tile: { type: Object, required: true }, }, computed: { value() { return this.tile.value }, } }) }))()
Обратите внимание, что в нашем конечном продукте стиль каждой плитки меняется, когда мы меняем плитку с пустой (значение 0) на непустую (значение не 0). Внутри компонента мы конструируем вычисляемые свойства на основе значения, а позже мы используем вычисляемое свойство для переключения стиля с помощью привязки класса:
src / components / tile.js
((() => { const html = ` <div class="tile"> {{ displayingValue }} </div> ` Vue.component("tile", { template: html, props: { tile: { type: Object, required: true }, }, computed: { value() { return this.tile.value }, displayingValue() { if (this.value > 0) { return this.value } return null }, emptyTile() { return this.displayingValue === null }, } }) }))()
Если у нас есть компонент tile, мы снова подключаем tile
к компоненту game:
src / components / game.js
((() => { const html = ` <div class="game"> <div class="game-container"> <tile v-for="tile in board" :tile="tile" :key="tile.id"></tile> <div class="board shadow-board"> <div v-for="n in board.length" :key="n" class="tile shadow-tile"></div> </div> </div> </div> ` ... }))()
Теперь, когда у нас есть вычисленное свойство emptyTile
, мы можем подключиться к нашему html
и переключить класс:
src / components / tile.js
((() => { const html = ` <div class="tile" v-bind:class="{'tile-empty': emptyTile}"> {{ displayingValue }} </div> ` Vue.component("tile", { ... }) }))()
Затем мы можем определить game-menu
, чтобы показать текущий результат, лучший результат и начать новую игру. Начнем с простой лески:
src / components / game-menu.js
((() => { const html = ` <div class="game-menu"> <div class="row"> <div class="title">2048</div> <div class="scores space-right"> <div class="score"> <div class="score-title">SCORE</div> <div class="score-value">0000</div> </div> <div class="score"> <div class="score-title">BEST</div> <div class="score-value">0000</div> </div> </div> </div> <a class="button space-right">New Game</a> </div> ` Vue.component("game-menu", { template: html, }) }))()
Подключим кнопку Новая игра! Потому что game-menu
компонент вложен в game
компонент. Мы можем рассматривать game-menu
как дочерний компонент для родительского компонента game
. Vue имеет прекрасную парадигму для взаимодействия дочерних и родительских элементов, когда дочерний компонент может генерировать события до родительского компонента и позволять родительскому компоненту обрабатывать событие. Эта парадигма называется данные вниз, событие вверх. Подробнее об обработке событий Vue можно прочитать здесь.
Итак, давайте зарегистрируем события на game-menu.js
src / components / game-menu.js
((() => { const html = ` <div class="game-menu"> ... <a class="button space-right" @click="newGame()">New Game</a> </div> ` Vue.component("game-menu", { template: html, methods: { newGame() { this.$emit("new-game") } } }) }))()
this. $ emit («new-game») отправляет событие своему родительскому game
, и внутри game.js
нам нужно обработать событие. При получении события new-game
компонент game
вызывает метод newGame (), который сбрасывает доску и заполняет две плитки, дважды вызывая seedTwo ().
src / components / game.js
((() => { const html = ` <div class="game"> <game-menu @new-game="newGame()"></game-menu> ... </div> ` Vue.component("game", { template: html, ... methods: { seedTwo() { ... }, newGame() { this.resetBoard() this.seedTwo() this.seedTwo() }, resetBoard() { this.board = Array.apply(null, { length: 16 }) .map(function (_, index) { return { id: index, value: 0 } }) } } }) }))()
Шаг 3. Выполните слияние и сдвиньте плитки
На этом этапе мы:
- Используя
moveRight()
в качестве примера, поймите, как алгоритмически объединять и сдвигать плитки - Используя
moveRight()
в качестве примера, реализуйте слияние и слайд - Зарегистрируйте элементы управления в компоненте
game
с помощью Vue mixin
По сути, у нас есть четыре разных метода реализации moveRight
, moveLeft
, moveUp
и moveDown
. Как только мы реализуем один метод, остальные будут следовать тому же шаблону и, следовательно, их будет легче реализовать. Давайте рассмотрим moveRight()
в качестве примера.
moveRight()
состоит из двух этапов: слияние и слайд. На этапе слияния мы выясняем две плитки, которые нужно объединить в одну; На втором шаге мы выясняем будущее положение каждой плитки. Поскольку мы движемся горизонтально для moveRight()
, алгоритм просматривает каждую строку и запускает mergeRight, затем slideRight. Анимация ниже демонстрирует, как алгоритм moveRight()
работает с одной строкой:
Ниже приведен код, реализующий алгоритм для moveRight()
:
src / mixins / control.js
mergeRight(board, a) { let i = board.length - 2 let j = board.length - 1 while (i >= 0) { if (board[a][i].value === 0 && board[a][j].value === 0) { // if both elements are zero j -- i -- } else if (board[a][i].value === board[a][j].value) { // if two elements have same value board[a][j].value = board[a][i].value + board[a][j].value board[a][i].value = 0 j-- i-- } else if (board[a][j].value === 0) { // if the right most has 0 j-- i-- } else if (board[a][i].value != 0 && board[a][j].value != 0 && (i + 1 == j)) { // if both are non zero and next from each other j-- i-- } else if (board[a][i].value != 0 && board[a][j].value != 0) { // if both are non zero and not next from each other j-- } else if (board[a][i].value === 0) { // if the left most element is zero i-- } } }, slideRight(board, a) { let k = board.length - 2 let l = board.length - 1 while (k >= 0) { if (board[a][l].value !== 0) { // if right most element is 0 l -- k -- } else if (board[a][l].value !== 0 && board[a][k].value !== 0) { // if right most and left most elements are not 0 l -- k -- } else if (board[a][l].value === 0 && board[a][k].value === 0) { // if right most and left most elements are 0 k -- } else if (board[a][l].value === 0 && board[a][k].value !== 0) { // if right most element is 0 and left most element is not 0 board[a][l].value = board[a][k].value + board[a][l].value board[a][k].value = 0 l -- k -- } } }
После реализации moveRight
, moveLeft
, moveUp
и moveDown
. Мы можем изолировать все эти методы в src/mixins/control.js
, а затем мы можем включить элемент управления как миксин для game
компонента. Теперь, когда установлен game
, мы регистрируем элементы управления:
src / components / game.js
((() => { const html = ` ... ` Vue.component("game", { template: html, mixins: [window.app.mixins.control], ... methods() { setupBoard() { this.newGame() this.registerControl() } } }) }))()
Мы довольно далеко зашли в этом сообщении в блоге! Во второй части этой серии блогов мы рассмотрим, как:
- Добавьте анимацию FLIP к движению наших плиток с помощью Vue.js
- Используйте Vuex для отслеживания результатов и состояния игры.