أهلا وسهلا بك! تحسبا لبدء دورة "Fullstack JavaScript Developer" ، قرر أحد مؤلفينا مشاركة تجربته في إنشاء جهاز محاكاة للكتابة باللمس. لكننا بدورنا نريد أن نعرض هذه المواد لك ونشارك اليوم الجزء الأخير منها.
الجزء الأول يمكن قراءته
هنا.مرحبا بالجميع! نواصل كتابة محاكي كتابة أصلي باستخدام JavaScript أصلي. في الجزء الأخير ، توصلنا إلى المنطق الرئيسي للتطبيق ، حيث تم تحميل المستوى الأول من 20 حرفًا عن طريق الضغط على مفتاح Enter ، والذي يعد عند دراسة طريقة الطباعة باللمس أحد الأوليات التي تتم دراستها (J ، K ، F ، D). يمكن الاطلاع على نسخة العمل من الإصدار الأول
هنا . ومع ذلك ، لا يزال لدينا بعض المهام لتحسين التطبيق.

دعونا نصوغ الاختصاصات لفهم ما نريد تحقيقه بالضبط:
"في بداية اللعبة ، بعد قيام المستخدم بالضغط على مفتاح Enter الجذاب ، والذي يتحرك بشكل متحرك من اليسار إلى اليمين ، يتم تحميل المستويات الأولى إلى الثالثة بالتسلسل. بالضغط على الزر الصحيح ، يتم تشغيل إشارة إيجابية للعبة ، في حالة حدوث خطأ ، يتم تشغيل إشارة سلبية للعبة. يحتوي المستويان الأول والثاني على رموز عادية ، والمستوى الثالث هو المكافأة ، حيث تكون الرموز خاصة للمبرمجين (هزلية إلى حد ما). في نهاية المستوى ، لدينا نافذة نتائج مكتوبة localStorage
من localStorage
، تعرض هذه النافذة عدد أخطاء اللاعبين ووقت انتهاء جلسة اللعبة. في حالة وجود الكثير من الأخطاء ، يتم تشغيل صوت الخسارة ، في حالة الانتقال الناجح إلى المستوى التالي - لحن كبير ".حسنًا ، الآن لدينا المعارف التقليدية ، لنبدأ البرمجة. من الواضح أن قاعدة الكود الخاصة بنا سوف تزيد بشكل كبير ، مما يعني أنه من أجل التعامل مع الكثير من الأكواد واستيراد المكتبات اللازمة ، نحتاج إلى
webpack
. ولكن لنبدأ بالتخطيط ونرى التغييرات التي حدثت. لن أعطي محتويات
head
، لأن الشيء الوحيد الذي تغير هناك هو أننا الآن
dist/code.js
javascript من
dist/code.js
التي تتم معالجتها بواسطة
dist/code.js
، لكن كل شيء آخر يبقى كما هو.
في نص الصفحة ، أضفت نافذة مشروطة غير مرئية بعد والتي سنكتب فيها نتائج تمرير اللعبة في
table
:
<body class="has-background-black-bis"> <div class="modal"> <div class="modal-background has-background-link"></div> <div class="modal-content has-background-white"> <h3 class="is-size-4"> </h3> <table class="table"> <thead> <tr> <th> </th> <th> </th> </tr> </thead> <tbody> <tr class="target_error"> </tr> </tbody> </table> <div> </div> <button class="modal-close is-large" aria-label="close"></button> </div> </div>
بطبيعة الحال ، حقيقة أن لدينا نافذة مشروط على الصفحة مباشرة تسبب الألم الجسدي لمحبي SPA - الأطر (لأنهم يرغبون في تقسيم كل هذا بدقة إلى مكونات). لكنني لا أريد أن أرسم جيل من هذا المحتوى في JS. لنلقِ نظرة على الجزء التالي من html:
<section class="hero is-primary is-large"> <div class="hero-head container"> <h1 class="label is-size-4 has-text-white promo"> 3000</h1> <h3 class="label is-size-4 has-text-danger has-text-centered name-level"></h3> <div class="error-panel is-hidden"> <progress id="prog" class="progress is-danger" value="0" max="20"> </progress> </div> </div> <div class="hero-body has-background-black-bis main-board"> <div id="columns"> <h3 class="label is-size-2 has-text-white has-text-centered begin anim-elem">Press Enter to Start</h3> <div class="buttons columns is-half is-centered"> </div> </div> </div> </section> </body>
في الواقع ، تم تغيير ثلاثة أشياء فقط من آخر مرة: الآن يوجد اسم للمستوى الذي سنملؤه من js ، يتم إخفاء لوحة الأخطاء في البداية وتغير اسم التطبيق نفسه.
على وشك بدء تشغيل JS ، سأضيف فقط القليل من CSS لإصلاح ما لا يمكنني فعله مع
Bulma
:
body{ max-height:40vh !important; } .promo{ margin-top: 1rem; }
حسنا شبيبة. كتبت أبسط التكوين webpack. مما هو موجود في الصفحة الأولى من وثائق
webpack
، فهو يختلف فقط في وجود تتبع التغييرات في الملفات. بالنسبة للجزء الأكبر ، أحتاجه من أجل استخدام عمليات الاستيراد في ملف
index.js
الرئيسي ، وفي النهاية يكون لدي ملف مصغر:
const path = require("path"); module.exports = { entry: './js/index.js', output: { filename: 'code.js', path: path.resolve(__dirname, 'dist'), }, watch: true, }
عظيم ، والآن يمكننا الانتقال إلى هيكل شبيبة. بالنسبة للرسوم المتحركة ، قررت استخدام
anime.js
- على الرغم من أنني أفهم جيدًا أن مقدار الرسوم المتحركة التي نحتاجها يمكن القيام به في css في 10 سطور. ربما في المستقبل ، سنضيف المزيد من الرسوم المتحركة ، لذلك قمت بسحب
anime.es.js
بالكامل. بالإضافة إلى ذلك ، أضفت وظيفة إنشاء ملف عشوائي إلى ملف منفصل - لمجرد الراحة:
export default function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); }
بعد ذلك قررت وضع وظيفة
showResult
في ملف منفصل ، والذي شارك في رسم النتائج بعد أن تمكن المستخدم من متابعة اللعبة بأكملها. هذه هي وظيفة عالمية جميلة تأخذ الوسيطة الأولى إلى المدخلات التي سيتم بها كتابة المعلومات من
localStorage
وتلقيها للتو ، والثانية - المعلومات التي يجب كتابتها (في حالتنا ، هذا هو عدد أخطاء المشغل). بالإضافة إلى ذلك ، قمت بتنفيذ مفاتيح الفرز من
localStorage
، بحيث تم فرز البيانات في الجدول حسب وقت إنشائها. في الجزء الثاني من الوظيفة ، يعرض الجدول بيانات محاولات اللاعب السابقة. ومع ذلك ، أقل الكلمات ، المزيد من التعليمات البرمجية:
export default function showResult(target_El, content){ localStorage.setItem(+new Date, content); (function drawOnLoad() { let temp_arr = []; for (let i = 0; i < localStorage.length; i++) { temp_arr.push(+localStorage.key(i)); } temp_arr.sort(); for(let i = 0; i< temp_arr.length; i++){ let item_time = new Date(temp_arr[i]); target_El.insertAdjacentHTML('afterend', `<th>${item_time.getDate()} / ${item_time.getMonth()} ${item_time.getHours()} : ${item_time.getMinutes()} </th> <th> ${localStorage.getItem(String(temp_arr[i]))}</th> `); } })(); }
رائع ، مع انتهاء الملفات الإضافية ، يمكننا أخيرًا الانتقال إلى الملف الرئيسي. أولاً نصنع الواردات:
import anime from "./anime.es"; import getRandomInt from "./random"; import showResult from "./showResult";
المكتبات المستلمة. لنقم بعمل رسم متحرك لنقشنا الجذاب من البداية:
anime({ targets: ".anim-elem", translateX: [-50, 50], easing: "linear", direction: "alternate", duration: 1000, loop: true });
سوبر! وأخيرا ، فإن التطبيق نفسه. قررت تحويل جميع البيانات إلى
json
أجل تصوير عملية الخادم التي يمكن أن
json
هذه اللعبة ، على سبيل المثال ، باللغة الهنغارية أو الروسية أو الصينية المبسطة - باختصار ، مع أي مجموعة من الأحرف (ولكن قبل ذلك ، بالطبع ينمو وينمو). بعد تلقي البيانات على
fetch
، تسمى الوظيفة نفسها بشكل غير متزامن ، والتي ستبدأ لعبتنا. لقد قمت
gist.github
JSON إلى
gist.github
(يمكن رؤيته
هنا )
function get_data() { fetch(
كما فهم القارئ بالفعل ، في
read_data
غير المتزامنة ،
read_data
جميع الوظائف الأخرى للعبة. لذلك ، ستذهب بقية الكود بالفعل داخل هذه الوظيفة:
var number_of_level = 0; // 0 var error_sound = new Audio("sounds/error_sound.wav"); var fail_sound = new Audio("sounds/fail_sound.wav"); // var press_sound = new Audio("sounds/press_sound.wav"); var succes_sound = new Audio("sounds/succes_sound.wav"); // .
لقد وجدت أصوات 8 بت في البنوك المجانية على الإنترنت. الآن دعونا نلقي نظرة على العناصر التي أحصل عليها من شجرة DOM:
let modal = document.querySelector(".modal");
التالي يأتي وظيفة بداية اللعبة. لقد تجاوزتها بعض التغييرات - الآن تصبح لوحة الأخطاء الخاصة بنا مرئية فقط في بداية اللعبة ، يتم تشغيل صوت نقرة ، يتم حذف الكتابة الجذابة ، ولا تخفيها (لقد قمت بذلك من أجل التحسين قليلاً ، لأنه بعد ذلك كنا سنلعب رسمًا متحركًا لعنصر غير مرئي على الصفحة) ، ثم تسمى وظيفة اللعبة الرئيسية:
function StartGame(e) { if (e.key == "Enter") { error_panel.classList.remove("is-hidden");
حسنا ، بعد ذلك يأتي رسم الحروف. كل الاختلاف عن الإصدار السابق هو أننا نأخذ الآن البيانات من كائن JSON:
function drawBoard(info) { let str_arr = info.level_info[number_of_level].symbols; // - , webpack, .... name_level.innerHTML = info.level_info[number_of_level].name_level; let col_arr = info.symbol_colors; for (let i = 0; i < 20; i++) { // 20 let rand = getRandomInt(str_arr.length); buttons.insertAdjacentHTML( "afterbegin", `<button class='game-button button is-large ${col_arr[rand]}' id='${str_arr[rand]}'> ${str_arr[rand]}</button>` ); } }
بعد ذلك ، نسمي وظيفة اللعبة الرئيسية بمعلومات من JSON ، والتي ما زالت وظيفتنا غير المتزامنة تأخذ حجة:
function mainGame() { drawBoard(information); document.addEventListener("keydown", press);
بعد ذلك ، ستذهب الوظيفة الأخيرة في الكود الخاص بنا ، حيث نحدد الخسارة والفوز. إذا سجل المستخدم الكثير من الأخطاء ، فأنت بحاجة إلى إخباره بأنه فقد ، ولعب اللحن المسؤول عن "الإخفاق". تحدث النهاية الناجحة للمستوى إذا
count_right
المتغير
count_right
على قيمة مساوية لعدد الأحرف التي
count_right
(20). أدرك أنه يمكن إجراء الانتقال إلى المستوى التالي عندما يصبح طول صفيف element_arr مساوياً لـ 0 ، ولكن لدينا حلاً الآن. إذا كان المستخدم قد أكمل بنجاح جميع المستويات الثلاثة ، يتم عرض لوحة النتائج مع عدد من الأخطاء:
var errors_count = 0; var count_right = 0; function press(e) { let elements_arr = document.querySelectorAll(".game-button"); // if (e.key == elements_arr[0].id) { // querySelector, elements_arr[0].remove(); count_right++; // press_sound.play(); } else { errors_count++; // error_sound.play(); progress.value = errors_count; // if (errors_count > 20) { // , fail_sound.play(); // name_level.innerHTML = ' !'; setTimeout(() => { window.location.reload(); // }, 2500); } } if (count_right == 20) { count_right = 0; number_of_level++; if (number_of_level == 3) { // 3 modal.classList.add("is-active"); // showResult(target_error, errors_count); modal_close.onclick = async function() { modal.classList.remove("is-active"); window.location.reload(); //, , }; } succes_sound.play(); mainGame(); } } } // ,
هذا هو الجزء الرئيسي من مقالتنا. إذا كنت مهتمًا بالكود المصدري للتطبيق ، فيمكنك العثور عليه
هنا . إذا كنت ترغب في اللعب ومشاهدة تطبيقنا قيد التنفيذ ، فمرحبا بكم
هنا . يرجى كتابة جميع الأخطاء والأسئلة إما في التعليقات أو على الفور في مستودع
issues
على جيثب.
هناك الكثير مما يمكن إضافته إلى التطبيق - هذه بعض الرسوم المتحركة المضحكة للرسائل والموسيقى في الخلفية (أو يمكنك إصدار أصوات مختلفة عند الطباعة لإجبار المستخدم على الطباعة بإيقاع معين). يمكنك التنقل بين الحروف ووضع السرعة - حتى تتمكن من الطباعة في الوقت المحدد وتسجيل الدقة التي تحققت. أود أيضًا أن أرى العد التنازلي في بداية المستوى حتى يتمكن اللاعب من وضع يديه بشكل صحيح على لوحة المفاتيح والاستعداد. باختصار ، هناك الكثير من الأفكار (بالإضافة إلى ترجمة واضحة جدًا لهذا التطبيق إلى React أو Vue) ، وآمل أن ينصح الناس في هابروفسك أيضًا بشيء ما. شكرا لكم جميعا على اهتمامكم!
حسنًا ، نحن ننتظر الجميع في
الدورة !