PK Creative празднует Китайский Новый год. Я участвую в "Творческом конкурсе Весеннего фестиваля". Подробности см.:Творческий конкурс "Праздник весны"
Адрес игры:Надеюсь_top.git ee.IO/новогодняя карма…
Язык разработки: vue
Запуск платформы: Chrome
гит адрес:git ee.com/я надеюсь_топ/вы…
адрес гитхаба:GitHub.com/Хе Юншэн…
Игра была с открытым исходным кодом, вы можете попробовать ее, или вы можете изменить ее самостоятельно, чтобы использовать ее в качестве ежегодной игры для встреч компании и т. д.
Технология недостаточно креативна, чтобы составить вторую пулю серии——Vue Chinese New Year Game - быстро хватай билетОнлайн, добро пожаловать, чтобы щелкнуть, чтобы испытать
Для справки, эта статья была включена в Неделю Наггетсов.2022.01.12 Выпуск
предисловие
Уважаемые Nuggets xdm, это еще один год китайского Нового года, вот новогодний привет всем братьям заранее, желаю всем крепкого здоровья и всего наилучшего. Сегодняшняя статья создана для эссе о весеннем фестивале Nuggets. Вот небольшая игра, специально написанная для вас. Так называемая технология недостаточно креативна, чтобы ее компенсировать. Хотя технология, используемая в игре, очень общая и простая, она также делает меня готовил очень много времени,все мини-игры пройдены сами.Ресурсы,арт и звуковые эффекты которые собраны онлайн могут быть не идеальными.Все сделают.Надеюсь всем понравится.Настоятельно рекомендуется перейти по ссылке с игрой перед прочтением статьи.Хэ Юншэн.GitHub.IO/game/index.…Иди и испытай два.
Коллекция поздравлений и поздравлений с окончанием игры
Я считаю, что заинтересованные студенты уже испытали игру, поэтому вы наверняка видели благословения, которые появлялись на шквалах во время игры, и благословения для игроков, которые появлялись в конце игры.Хотите, чтобы ваши благословения появились в шквал? , тогда, пожалуйста, оставьте сообщение в области комментариев, и я вовремя обновлю сообщение, которое вы видите, до шквала.
заградительное сообщение
Формат сообщения барраж: барраж + никнейм + благословение (одно предложение, не слишком длинное), например барраж + хэнаньский парень + желаю народу всей страны скорейшей победы над новой короной
Благословение победы в игре
Формат благословения на победу: победа + никнейм + благословение (одно предложение, не слишком длинное), например, победа + пользователь Nuggets Сяомин + желаю вам в новом году гладкой работы
Вы можете сделать снимок экрана со случайным сообщением, которое вы получили, когда выиграли игру, и отправить его в Точку кипения, чтобы узнать, встретите ли вы кого-то, кто пошлет вам благословения.
Затем мы официально начнем объяснение разработки игры.
В мини-игре очень много контента, а неважные части будут опущены или опущены.Если кому-то интересна технология, не упомянутая в игре, он может разместить ее в области комментариев, а последующие статьи могут быть объяснены целенаправленно. Кроме того, код в тексте публикует только ключевые части. Код, если вы хотите просмотреть полный код, перейдите на gitee или GitHub.
правила игры
Игроки должны нажать и удерживать фейерверк, чтобы двигаться влево и вправо, чтобы атаковать зверя Нянь Вопросы будут появляться в середине экрана через равные промежутки времени Ответ на правильный вопрос увеличит силу атаки Время ответа на каждый вопрос 8 секунд, а интервал между вопросами 5 секунд.Игра заканчивается, когда HP Nian Beast становится равным 0. Чем меньше времени потребуется, чтобы победить Nian Beast, тем лучше.
Меню и глобальная конфигурация
Глобальная конфигурация
setting: {
isPlay: false,
showBulletChat: true
}
На самом деле есть две глобальные конфигурации,управление звуком и управление экраном пули.Потому что после тестирования игра будет зависать на машинах с очень плохой производительностью.Все они дают контроль над отображением экрана пули.Что касается размера,цвета, и плотность экрана пули, это из-за отношения времени не написано. Что касается управления звуком, то оно однозначно необходимо, во-первых, чтобы внезапное воспроизведение музыки не влияло на пользователей, а во-вторых, в браузере тоже есть ограничения, запрещающие автоматическое воспроизведение звука.
меню
Про верстку рассказывать не буду.Здесь кратко расскажу о своих задумках при генерации меню.Поскольку в меню добавлены звуковые эффекты скольжения мыши и нажатия,я используюv-for
Метод зацикливания данных лучше, иначе события мыши будут записываться несколько раз. Конкретный код выглядит следующим образом
<div class="menu-box">
<div
class="menu-item"
v-for="(item, index) in menuList"
:key="index"
@mouseover="$store.commit('playAudio', hoverMusic)"
@click="$store.commit('playAudio', clickMusic),item.clickHandle()"
v-show="item.show()"
>
{{item.name}}
</div>
</div>
// 节选
menuList: [
{
name: '开始游戏',
clickHandle: () => {
this.gameBegin()
},
show: () => true
},
{
name: '打开声音(强烈建议)',
clickHandle: () => {
this.$store.commit('tooglePlay', true)
},
show: () => !this.$store.state.setting.isPlay
},
{
name: '关闭声音',
clickHandle: () => {
this.$store.commit('tooglePlay', false)
},
show: () => this.$store.state.setting.isPlay
}
],
Каждый элемент меню в основном имеет три атрибута: имя, событие щелчка и отображение управления, поскольку некоторые элементы меню должны решать, отображать ли их в соответствии с реальной ситуацией, например, включение звука и выключение звука, необходимо судить, кто показывает, а кто прячется по тому, включен текущий звук или нет.Если мы напрямую назначим переменную, управляющую звуком, на шоу, когда мы определяем данные, то при последующем изменении звука шоу не будет динамически обновляться , Здесь мы назначаем функцию, чтобы показать, чтобы достичь цели зимнего обновления.
звук
Как может в игре быть без звука, цитируемая здесь музыка - это прелюдия, хахаха, в ней сразу чувствуется вкус нового года? В игре есть два основных типа звуков: один — длительное воспроизведение, которым необходимо управлять для воспроизведения и паузы, например фоновая музыка, а другой — мгновенный, например звук скольжения меню, звук попадания пули и т. д. ., так что пример фоновой музыки Нам нужно ее хранить, а звуковые эффекты в реальном времени можно строить по мере необходимости.Я тут поленился и не стал писать отдельный файл конфигурации звука, а напрямую прописал его в vuex.
Фоновая музыка
window.backMusic = new Audio()
window.backMusic.src = require('../assets/mp3/back.mp3')
window.backMusic.loop = true
window.backMusic.load()
window.backMusic.currentTime = 127.2 // 背景音乐默认定位到舒缓片段
Таким образом, мы можем напрямую вызвать или изменить управление воспроизведением в любом месте.window.backMusic
Вот и все.
Мгновенный звук
playAudio (state, src) {
if (state.setting.isPlay) {
const audio = new Audio()
audio.src = src
audio.load()
audio.volume = .5
audio.play()
}
}
При воспроизведении звуковых эффектов здесь необходимо определить, включен ли текущий звуковой переключатель.Если он включен, он воспроизводится.Обратите внимание, что вы не можете воспроизводить различные звуковые эффекты, изменяя адрес одного звукового объекта, потому что если воспроизводится текущий звук, измените Адрес звукового эффекта сообщит об ошибке.
Заградительный огонь
Эта идея пришла мне в голову, когда я слушал фоновую музыку Увертюры к Празднику Весны, потому что, когда я слушал это, я думал о Гала-концерте Праздника Весны и о людях со всей страны в коротком видео, посылающем благословения, так я хотел добавить это и объединить фоновую музыку, это все сразу? Я также надеюсь, что каждый может отправить свои собственные благословения, и я также обновлю ваши благословения до шквала. Шквал здесь только для удовлетворения потребностей игры, и он не будет слишком сложным.
Прежде всего, нам нужно разобраться с потребностями и точками внимания для экрана пули.
- Заграждение не может перекрываться по горизонтали и вертикали
- Интервал между двухдневными барражами лучше всего делать случайным
- Если шквал превышает экран, он будет автоматически удален
Прежде всего, поговорим о проблеме, что экраны пуль не могут перекрываться.Если экраны пуль не могут перекрываться по вертикали, то нам нужно иметь понятие баллистики, то есть пусть у каждого экрана пули есть своя дорожка, и каждый пойдет своей по-своему.Конечно, он не будет пересекаться. По высоте экрана я разделил баллистику на 10. Изначально чем больше экран, тем больше баллистика, но учитывая проблему с производительностью, была принята эта схема.
Второй-предотвратить горизонтальное перекрытие заградительного огня.Когда я был на Baidu, я видел проблемы с отслеживанием, упомянутые другими авторами.Однако я подонок, и я не очень хорошо в этом разбирался, поэтому я подумал о решение сам.Здесь у нас есть каждый барраж.Скорость движения одинаковая,поэтому нужно учитывать время появления каждого экрана пули.Нам нужно генерировать следующий экран пули после предыдущего экрана пули той же пули полностью появился, и мы можем добавить один посередине.Случайное расстояние диапазона, что более красиво.
Посмотрим, как реализован код.
ballistic: 0, // 弹道数量
bulletSpeed: 2, // 弹幕速度
bulletInterval: [300, 500], // 弹幕间隔
screenWidth: document.documentElement.clientWidth, // 屏幕宽度
screenHeight: document.documentElement.clientHeight, // 屏幕高度
/**
* @description: 展示弹幕
* @param {*}
* @return {*}
*/
showBullet () {
// 此处直接设定了10条弹道,也可根据屏幕高度和弹幕高度计算弹道数
let ballisticArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// 按随机顺序在所有的弹道添加弹幕
let ballisticLaunch = () => {
let randomIndex = Math.floor(Math.random() * ballisticArr.length)
let ballisticIndex = ballisticArr.splice(randomIndex, 1)[0]
this.createBullet(ballisticIndex)
if (ballisticArr.length > 0) {
setTimeout(ballisticLaunch, Math.random() * 1000)
}
}
ballisticLaunch()
// this.createBullet(2)
},
Мой метод здесь состоит в том, чтобы сначала установить номер баллистики, а затем поместить серийные номера этих баллистик в массив.В начале напрямую взять номер из этого массива, вложить экран пули в эту баллистику, а затем зациклить, пока каждый баллистический.После того, как все израсходовано, тогда возникает вопрос.В настоящее время у нас есть только один экран пули для каждой пули, как сгенерировать последующий экран пули?Идея здесь состоит в том, чтобы судить о расстоянии перемещения каждого экрана пули, когда он движется .Когда расстояние подходит (оно появляется полностью на экране и расстояние между двумя заданными нами заграждениями достигается с правой стороны экрана), вызывается метод загрузки следующего заграждения, и передается собственный баллистический код в, плюс В нашем случае шквал идет с равномерной скоростью, так что проблемы перекрытия не будет.
/**
* @description: 添加弹幕
* @param {*} index 弹道索引
* @return {*}
*/
createBullet (index) {
let bullet = document.createElement('div')
let bulletHeight = document.documentElement.clientHeight / 10
bullet.className = 'bullet-chat'
bullet.style.left = this.screenWidth + 'px'
bullet.style.top = index * bulletHeight + 'px'
bullet.createNext = false // 是否已创建下一个弹幕
bullet.nextSpace = Math.random() * (this.bulletInterval[1] - this.bulletInterval[0]) + this.bulletInterval[0] // 下一个弹幕间隔
// 从弹幕库随机取弹幕
let dataLength = this.blessingData.length
let randomIndex = Math.floor(Math.random() * dataLength)
let blessing = this.blessingData[randomIndex]
bullet.innerText = blessing.name + ":" + blessing.value
this.$refs.bulletChat.appendChild(bullet)
// 弹幕移动
let bulletMove = () => {
bullet.style.left = bullet.offsetLeft - this.bulletSpeed + 'px'
if (!bullet.createNext) {
// 如果弹幕距离屏幕右侧距离超出弹幕间隔,则加载下一条弹幕
if (bullet.offsetLeft < (this.screenWidth - bullet.offsetWidth - bullet.nextSpace)) {
this.createBullet(index)
bullet.createNext = true
}
}
// 如果弹幕距离右侧距离大于等于屏幕宽度,则移除弹幕
if (bullet.offsetLeft < (-bullet.offsetWidth)) {
this.$refs.bulletChat.removeChild(bullet)
} else {
requestAnimationFrame(bulletMove)
}
}
bulletMove()
}
Здесь мы представили библиотеку заграждений и каждый раз случайным образом выбираем одну из нее, чтобы избежать проблемы, заключающейся в том, что старый заграждение не виден. Кроме того, все его видели. Используемый здесь метод синхронизации — requestAnimationFrame, который на самом деле лучше, чем setinterval.К счастью, этот проект в основном используется во всех местах, где используется анимация.Также всем рекомендуется использовать этот метод вместо setinterval.Преимуществ много.
Зодиак
Эта милая штучка - наш зверь Нянь.Композиция зверя Нянь очень проста, маленькая иконка плюс объем крови, а затем мы можем заставить его двигаться вперед и назад. Когда объем крови равен 0, мы позволяем ему исчезнуть.
<!-- 年兽 -->
<div
class="nianshou"
:style="'marginLeft:' + nianshouLeft + 'px'"
v-show="nianshouHP"
>
<p>HP: {{ nianshouHP }}</p>
<img src="../assets/nianshou.png" class="nianshou-img" />
</div>
nianshouLeft: 0, // 年兽距离左边的距离
nianshouMove () {
// 更新游戏时间
this.gameDuration = new Date().getTime() - this.gameBeginTime
if (this.nianshouLeft + 200 >= this.screenWidth) {
this.nianshouMoveDir = -4
} else if (this.nianshouLeft < 0) {
this.nianshouMoveDir = 4
}
this.nianshouLeft += this.nianshouMoveDir
this.nianshouInterval = requestAnimationFrame(this.nianshouMove)
},
Правило нашей игры заключается в том, что чем меньше времени требуется, тем мощнее она, поэтому нам нужно рассчитать, сколько времени занимает игра.Здесь мы берем время, когда зверь нян начинает двигаться, как время начала игры, и нам также нужно двигаться в противоположном направлении, когда зверь нянь ударяется о стену, поэтому здесь мы оцениваем расстояние между зверем нянь и левой и правой сторонами экрана, как только оно достигает определенного значения, меняем направление движения, то есть , измените положительное и отрицательное значение движущегося значения
петарды
Этот гаджет - наша петарда, а также наше оружие.Я изначально хотел найти фейерверк для запуска фейерверков, но ресурсы ограничены, поэтому я буду использовать только этот. Этот маленький фейерверк будет непрерывно излучать световые лучи, чтобы поразить зверя Нянь.Фейерверк здесь должен добавить событие движения при нажатии мыши, чтобы он мог двигаться влево и вправо.
Первый шаг, безусловно, движение петарды.Мы не делаем это слишком сложно, просто позвольте мыши перетаскивать, чтобы двигаться влево и вправо, и не позволяйте петарде двигаться вверх и вниз, чтобы вы держали петарду на лице зверя в предыдущие годы.
Идея состоит в том, чтобы щелкнуть фейерверк мышью, чтобы добавить события движения на всю область.Если вы не добавите события движения в фейерверк, легко превысить диапазон фейерверка, если мышь будет двигаться слишком быстро, что приведет к плохой игровой опыт.Когда мышь поднимается, мы удаляем это событие. Что касается движения, нам нужно определитьclientx
, каждый раз, когда мышь перемещается, сохраняйте расстояние между мышью и левой стороной экрана.Когда мышь снова перемещается, мы предлагаем расстояние от левой стороны текущего курсора до только что сохраненного, и тогда мы можем получить расстояние, на которое перемещается мышь, а затем мы помещаем этоизменение стоимостиназначен на петардыmargin-left
<!-- 鞭炮 -->
<div
class="paozhu"
ref="paozhu"
@mousedown="addMove"
:style="'marginLeft:' + paozhuLeft + 'px'"
>
<img src="../assets/paozhu.png" alt="" />
</div>
clientX: 0, // 鼠标上次的位置
paozhuLeft: 0 // 炮竹距离左边的距离
// 鼠标按下,添加移动事件
addMove (e) {
e = e || window.event
this.clientX = e.clientX
this.clientY = e.clientY
this.$refs.gemeWrap.onmousemove = this.moveFunc
},
// 鼠标拖动,移动炮竹
moveFunc (e) {
e = e || window.event
e.preventDefault()
let movementX = e.clientX - this.clientX
this.paozhuLeft += movementX
this.clientX = e.clientX
},
// 鼠标抬起,移除移动事件
removeMove () {
this.$refs.gemeWrap.onmousemove = null
},
пуля
Назовем пока луч, испускаемый хлопушкой, пулей. Принцип действия пули очень прост. Пуля выстреливается через равные промежутки времени. меньше или равно высоте зверя нянь, судить о совпадении горизонтальной координаты пули с горизонтальной координатой зверя. , если не совпадает, удалить пулю, когда она убегает за пределы экрана.
Здесь мы задаем скорость полета пули.Если вы играли в игру, то обязательно обнаружите, что в начале стрелять непросто, хахаха, это можно считать увеличением сложности.Конечно, если вы правильно ответите на вопрос , скорострельность, скорость атаки, урон соответственно увеличатся.
createBulletInterval: null, // 创建子弹的定时器
frequency: 5, // 发射子弹频率
bulletSpeed: 10, // 子弹飞行速度
damage: 2,// 子弹攻击力
lastBulletTime: 0, // 上次发射子弹时间
// 生成子弹
createBullet () {
// 子弹
let now = new Date().getTime()
if (now - this.lastBulletTime > (1000 / this.frequency)) {
let bullet = document.createElement('div')
bullet.className = 'bullet'
bullet.style.left = this.paozhuLeft + 25 + 'px'
bullet.style.top = this.screenHeight - 123 + 'px'
this.$refs.gemeWrap.appendChild(bullet)
this.$store.commit('playAudio', require('../assets/mp3/emit.mp3'))
// 子弹移动
let bulletMove = () => {
bullet.style.top = bullet.offsetTop - this.bulletSpeed + 'px'
// 如果子弹距离顶部的距离为年兽的高度时,判断子弹和年兽的水平位置是否重合
if (bullet.offsetTop <= 250 && bullet.offsetLeft >= this.nianshouLeft && bullet.offsetLeft <= this.nianshouLeft + 200) {
// 年兽掉血
this.nianshouHP -= this.damage
this.$store.commit('playAudio', require('../assets/mp3/boom.wav'))
if (this.nianshouHP <= 0) {
this.nianshouHP = 0
this.gameOver()
}
// 子弹消失
this.$refs.gemeWrap.removeChild(bullet)
// cancelAnimationFrame(bulletMove)
} else if (bullet.offsetTop <= 0) {
this.$refs.gemeWrap.removeChild(bullet)
// cancelAnimationFrame(bulletMove)
} else {
requestAnimationFrame(bulletMove)
}
}
bulletMove()
this.lastBulletTime = now
}
this.createBulletInterval = requestAnimationFrame(this.createBullet)
}
Так как requestAnimationFrame не может задать время интервала, здесь мы записываем время генерации пуль при генерации буллетов.Когда requestAnimationFrame запускается в следующий раз, мы судим, соответствует ли временной интервал нашим требованиям к частоте пуль, и если да, то выполняем его по нисходящей. не удовлетворены, пропустите это выполнение.
вопрос
Главной особенностью этой игры является добавление системы ответов.Иначе какой смысл сражаться с монстрами в биубиубиу?ХП нянского зверя 2021,и драться долго с начальной скоростью атаки и уроном.Если вы ответьте на вопрос правильно, вы увеличите бафф, и способность победить зверя Нянь увеличится.
Во-первых, давайте проанализируем потребности проблемы
- Время ответа на каждый вопрос составляет 8 секунд, независимо от того, выбран он заранее или нет, он будет отображаться в течение 8 секунд.
- Ответьте правильно на вопрос и увеличьте баф
- Правильный ответ будет отображаться, если вы ответите неправильно или если ответ не выбран в конце обратного отсчета.
- Интервал между каждым вопросом 5 секунд
- Каждый раз, когда задается вопрос, вопрос выбирается случайным образом из банка вопросов, и появившийся вопрос не будет выбран во второй раз.
Начнем с самого простого и извлечем вопросы из банка вопросов
questionJson: require('@/assets/data/question.json'), //问题源数据
questionData: [], // 本轮游戏题库
questionList: [],// 问题列表
let dataLength = this.questionData.length
let randomIndex = Math.floor(Math.random() * dataLength)
let question = this.questionData.splice(randomIndex, 1)[0]
Очень просто, следующий шаг - добавить обратный отсчет. Первый - обратный отсчет интервала вопроса. Когда вопрос добавлен, отображается 5-секундный обратный отсчет, а затем отображается вопрос, и начинается обратный отсчет, чтобы ответить на вопрос. .
// 添加展示倒计时
let showCountDown = () => {
data.showTime--
if (data.showTime > 0) {
setTimeout(showCountDown, 1000)
} else {
// 倒计时结束,展示问题并开始答题倒计时
answerCountDown()
}
}
Далее идет обратный отсчет для ответов на вопросы.В игре задано 5 вопросов.По окончании каждого вопроса в первую очередь будет определяться ответил ли пользователь.Если ответа нет,то результат будет автоматически установлен как неверный ответ , Продолжайте добавлять, пока у вас не будет 5 блюд.
// 添加回答倒计时
let answerCountDown = () => {
data.answerTime--
if (data.answerTime > 0) {
setTimeout(() => {
showCountDown()
}, 1000)
} else {
// 倒计时结束,如果没有选择正确答案,则添加一道错误答案
if (!data.result) {
data.result = '2021'
}
// 如果问题不足5道,则添加一道问题
if (this.questionList.length < 5) {
this.addQuestion()
}
}
}
Следующим шагом будет ответ на вопрос, давайте посмотрим на структуру dom панели вопросов.
<!-- 问题面板 -->
<div
class="question-panel panel-item"
:class="{ clientCenter: question.answerTime > 0 }"
v-for="(question, index) in questionList"
:key="index"
>
<p class="show-count-down" v-if="question.showTime > 0">
{{ question.showTime }}
</p>
<div class="question-wrap" v-else>
<div class="count-down" v-if="question.answerTime > 0">
<p>请在{{ question.answerTime }}秒内点击正确答案</p>
</div>
<div class="question-panel-title">问题 {{ index + 1 }}</div>
<div class="question-container">
<div class="question-title">{{ question.question.title }}</div>
<div class="answer-wrap show" v-if="!question.result">
<div
class="answer-item"
v-for="item in question.question.option"
:key="item.key"
@mouseover="$store.commit('playAudio', hoverMusic)"
@click="answerQuestion(item.key, question)"
>
{{ item.key }}:{{ item.value }}
</div>
</div>
<div class="answer-wrap result" v-else>
<div
class="answer-item"
v-for="item in question.question.option"
:key="item.key"
:class="{
result: question.question.answer === item.key,
}"
>
{{ item.key }}:{{ item.value }}
<span class="check" v-if="question.result === item.key">◇</span>
</div>
</div>
<div
class="buff"
v-if="question.result === question.question.answer"
>
攻速+1 射速+1 伤害+1
</div>
<div
class="desc"
v-if="
question.result && question.result !== question.question.answer
"
>
{{ question.question.desc }}
</div>
</div>
</div>
</div>
Посмотрите на структуру вопросов в банке вопросов
{
"title": "以下哪位是神舟十三号航天员?",
"option": [
{
"key": "A",
"value": "翟志刚"
},
{
"key": "B",
"value": "刘伯明"
},
{
"key": "C",
"value": "聂海胜"
}
],
"answer": "A",
"desc": "神舟十三号航天员是翟志刚、王亚平、叶光富"
},
В сочетании с нашим обратным отсчетом, ответом и т. д. полная структура вопроса должна быть следующей:
{
question: question, // 题库中的题目
answerTime: 9, // 回答倒计时,
showTime: 6, // 展示倒计时
result: null, // 用户回答的答案
}
Таким образом, с этим намного проще справиться: нам нужно только присвоить значение параметра результату, когда мы снова нажмем параметр, а затем судить, ответил ли пользователь на вопрос или нет, в соответствии со значением результата.
Здесь на самой внешней структуре DOM есть такая строка кода
:class="{ clientCenter: question.answerTime > 0 }"
По этому судят, окончен ли обратный отсчет до ответа на вопрос.Если он не окончен, он будет отображаться в центре экрана, что удобно для просмотра и выбора пользователями.Если он закончился, он будет отображаться на в левой части экрана, чтобы пользователи могли просматривать и делиться ими.
игра окончена
В конце игры будут отображены результаты игры, и для отображения случайным образом будет выбрано одно из пользовательских благословений.
Здесь пройдена вся игра. Из-за ограниченного места действительно невозможно подробно объяснить каждую деталь. Если у вас есть какие-либо вопросы о том, где вы находитесь, вы можете задать вопросы в области комментариев или перейти на github или код Облако для оформления вопроса.Здесь я заранее передам вам новогодние поздравления! Желаю всем удачи в работе, крепкого здоровья, семейного согласия и всего самого наилучшего!