Schreiben eines Touch-Typing-Simulators mit reinem JavaScript: Teil 2

Hallo! Im Vorgriff auf den Beginn des Kurses "Fullstack JavaScript Developer" hat einer unserer Autoren beschlossen, seine Erfahrungen bei der Erstellung eines Simulators für das Tippen von Berührungen mitzuteilen. Aber wir wollen Ihnen dieses Material zeigen und heute den letzten Teil davon teilen.




Der erste Teil kann hier gelesen werden.

Hallo allerseits! Wir schreiben weiterhin einen nativen Schreibsimulator mit nativem JavaScript. Im letzten Teil haben Sie und ich die Hauptlogik der Anwendung erstellt, in der durch Drücken der Eingabetaste die erste Ebene von zwanzig Zeichen geladen wurde, die beim Erlernen der Methode des Berührungsdrucks als eine der ersten untersucht wurde (J, K, F, D). Die Arbeitsversion der ersten Version kann hier eingesehen werden . Wir haben jedoch noch einige Aufgaben, um die Anwendung zu verbessern.



Formulieren wir die TOR, um zu verstehen, was genau wir erreichen wollen:

„Zu Beginn des Spiels werden die Ebenen 1 bis 3 nacheinander geladen, nachdem der Benutzer die einladende Eingabetaste gedrückt hat, die sich animiert von links nach rechts bewegt. Durch Drücken der richtigen Taste wird ein positives Spielesignal abgespielt, im Fehlerfall ein negatives Spielesignal. Die erste und zweite Ebene haben gewöhnliche Symbole, die dritte Ebene ist Bonus, wobei die Symbole speziell für Programmierer sind (eher komisch). Am Ende des Levels haben wir ein Ergebnisfenster, das von localStorage geschrieben und entnommen wurde. Dieses Fenster zeigt die Anzahl der Spielerfehler und die Endzeit der Spielsitzung an. Bei zu vielen Fehlern wird der Sound des Verlusts abgespielt, bei erfolgreichem Übergang in die nächste Ebene - eine Hauptmelodie. "

Okay, jetzt haben wir TK, fangen wir an zu programmieren. Natürlich wird unsere Codebasis erheblich erweitert, was bedeutet, dass wir ein webpack benötigen, um mit viel Code fertig zu werden und die erforderlichen Bibliotheken zu webpack . Beginnen wir jedoch mit dem Layout und sehen, welche Änderungen aufgetreten sind. Ich werde den Inhalt von head , da sich dort nur geändert hat, dass wir jetzt Javascript aus der dist/code.js ziehen, die vom dist/code.js verarbeitet wird, aber alles andere bleibt dist/code.js .

Im Hauptteil der Seite habe ich ein modales Fenster hinzugefügt, das noch nicht sichtbar ist und in dem wir die Ergebnisse des Bestehens des Spiels in table schreiben werden:

 <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> 

Natürlich bereitet die Tatsache, dass wir ein modales Fenster direkt auf der Seite haben, den SPA-Liebhabern körperliche Schmerzen - Frameworks (schließlich möchten sie all dies ordentlich in Komponenten zerlegen). Aber ich wollte die Erzeugung dieses Inhalts nicht in JS malen. Schauen wir uns den nächsten Teil unseres HTML an:

  <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> 

Tatsächlich haben sich seit dem letzten Mal nur drei Dinge geändert: Jetzt gibt es einen Namen für die Ebene, die wir aus js ausfüllen werden, die Fehleranzeige ist anfangs ausgeblendet und der Name der Anwendung selbst hat sich geändert.

Kurz vor dem Start von JS füge ich nur ein wenig CSS hinzu, um ein wenig zu korrigieren, was ich mit Bulma nicht machen konnte:

 body{ max-height:40vh !important; } .promo{ margin-top: 1rem; } 

Ok js. Ich habe die einfachste Webpack-Konfiguration geschrieben. Es unterscheidet sich von dem, was auf der ersten Seite der webpack Dokumentation steht, nur durch das Nachverfolgen von Änderungen in Dateien. Zum größten Teil benötige ich es, um Importe in der Hauptdatei index.js verwenden und schließlich eine verkleinerte Datei zu haben:

 const path = require("path"); module.exports = { entry: './js/index.js', output: { filename: 'code.js', path: path.resolve(__dirname, 'dist'), }, watch: true, } 

Großartig, und jetzt können wir zur js-Struktur übergehen. Für Animationen habe ich mich für anime.js - obwohl ich gut verstehe, dass die Menge an Animationen, die wir benötigen, in CSS in 10 Zeilen erstellt werden kann. Vielleicht werden wir in Zukunft weitere Animationen hinzufügen, also habe ich die ganze anime.es.js . Außerdem habe ich einer separaten Datei eine Funktion zur Erzeugung zufälliger Dateien hinzugefügt - aus Gründen der Benutzerfreundlichkeit:

 export default function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); } 

Dann entschloss ich mich, die showResult Funktion in eine separate Datei zu showResult , die die Ergebnisse zeichnete, nachdem der Benutzer das ganze Spiel durchlaufen hatte. Dies ist eine ziemlich universelle Funktion, die das erste Argument für die Eingabe in das Element verwendet, das in die Informationen von localStorage und gerade empfangen wird, und das zweite Argument - die Informationen, die geschrieben werden müssen (in unserem Fall ist dies die Anzahl der Player-Fehler). Außerdem habe ich Sortierschlüssel aus localStorage , sodass die Daten in der Tabelle nach dem Zeitpunkt ihrer localStorage sortiert wurden. Im zweiten Teil der Funktion werden in der Tabelle die Daten der vorherigen Versuche des Spielers angezeigt. Weniger Wörter, mehr Code:

 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> `); } })(); } 

Toll, mit den zusätzlichen Dateien können wir endlich zur Hauptdatei gehen. Zuerst machen wir die Importe:

  import anime from "./anime.es"; import getRandomInt from "./random"; import showResult from "./showResult"; 

Bibliotheken erhalten. Machen wir von Anfang an eine Animation unserer einladenden Inschrift:

 anime({ targets: ".anim-elem", translateX: [-50, 50], easing: "linear", direction: "alternate", duration: 1000, loop: true }); 

Großartig! Schließlich die Anwendung selbst. Ich habe beschlossen, alle Daten auf json zu verlagern, um den Server darzustellen, den dieses Spiel zum Beispiel auf Ungarisch, Russisch oder vereinfachtem Chinesisch erzeugen kann - kurz gesagt, mit einem beliebigen Zeichensatz (aber davor wächst und wächst natürlich). Nach dem Empfang der Daten beim fetch wird die Funktion selbst asynchron aufgerufen, wodurch unser Spiel gestartet wird. Ich habe JSON auf gist.github ( hier zu sehen)

 function get_data() { fetch( // "    ,   json" ) //     json  -     , //  -       .then(res => res.json()) .then(data => { //    ,     read_data(data); }) .catch(err => { console.warn(" "); console.warn(err.name); //   }); } get_data(); //      , ...       

Wie der Leser bereits verstanden hat, befinden sich in unserer asynchronen Funktion read_data alle anderen Funktionen des Spiels. Daher wird der Rest des Codes bereits in dieser Funktion enthalten sein:

 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"); //       .         

Ich habe 8-Bit-Sounds in freien Banken im Internet gefunden. Schauen wir uns nun die Elemente an, die ich aus dem DOM-Baum bekomme:
  let modal = document.querySelector(".modal"); //      var target_error = document.querySelector(".target_error"); //          let error_panel = document.querySelector(".error-panel"); //    let begin = document.querySelector(".begin"); //    ,     enter   .      let progress = document.getElementById("prog"); //     let buttons = document.querySelector(".buttons"); //         let name_level = document.querySelector(".name-level"); //       let modal_close = document.querySelector(".modal-close"); // ,                document.addEventListener("keydown", StartGame, { once: true // once           //     ,      }); 

Als nächstes kommt die Startfunktion des Spiels. Es wurde auch von einigen Änderungen überholt - jetzt wird unser Fehlerfenster erst zu Beginn des Spiels angezeigt, es wird ein Klickgeräusch abgespielt, die einladende Inschrift wird gelöscht und verschwindet nicht (ich habe dies aus Optimierungsgründen getan, da wir dann eine Animation eines auf der Seite unsichtbaren Elements abgespielt hätten). und dann heißt die Hauptspielfunktion:

  function StartGame(e) { if (e.key == "Enter") { error_panel.classList.remove("is-hidden"); //     press_sound.play(); begin.remove(); //    mainGame(); //    } } 

Ok, als nächstes kommt die Zeichnung der Buchstaben. Der einzige Unterschied zur vorherigen Version besteht darin, dass wir jetzt die Daten aus dem JSON-Objekt übernehmen:

  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>` ); } } 

Als nächstes rufen wir die Funktion des Hauptspiels mit Informationen aus JSON auf, die unsere asynchrone Funktion noch als Argument gedeutet hat:

  function mainGame() { drawBoard(information); document.addEventListener("keydown", press); 

Dann geht die letzte Funktionstaste in unserem Code, in der wir den Verlust und den Gewinn bestimmen. Wenn der Benutzer zu viele Fehler erzielt hat, müssen Sie ihm mitteilen, dass er verloren hat, und die Melodie abspielen, die für das "Fiasko" verantwortlich ist. Ein erfolgreiches Ende des Levels findet statt, wenn die Variable count_right den Wert erhält, der der Anzahl der von uns generierten Zeichen entspricht (20). Ich bin mir bewusst, dass der Übergang zur nächsten Ebene erfolgen kann, wenn die Länge des elements_arr Arrays gleich 0 wird, aber für den Moment haben wir eine solche Lösung. Wenn der Benutzer alle drei Ebenen erfolgreich abgeschlossen hat, wird eine Ergebnistafel mit der Anzahl der Fehler angezeigt:

  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(); } } } //      ,    

Dies ist der Hauptteil unseres Artikels. Wenn Sie sich für den Quellcode der Anwendung interessieren, finden Sie ihn hier . Wenn Sie unsere Anwendung in Aktion sehen möchten, begrüßen wir Sie hier . Bitte schreibe alle Bugs und Fragen entweder in die Kommentare oder sofort in das issues Repository auf github.

Es gibt noch viel mehr, das der Anwendung hinzugefügt werden könnte - dies sind einige lustige Animationen von Buchstaben, Musik im Hintergrund (oder Sie können sicherstellen, dass beim Drucken verschiedene Töne erzeugt werden, um den Benutzer zum Drucken in einem bestimmten Rhythmus zu zwingen). Sie können im Buchstaben- und Geschwindigkeitsmodus blättern, um rechtzeitig zu drucken und die erreichte Genauigkeit aufzuzeichnen. Ich würde auch gerne einen Countdown am Anfang des Levels sehen, damit der Spieler seine Hände richtig auf der Tastatur positionieren und sich fertig machen kann. Kurz gesagt, es gibt viele Ideen (sowie eine sehr offensichtliche Übersetzung dieser Anwendung in React or Vue), und ich hoffe, dass die Habrowsker auch etwas raten werden. Vielen Dank für Ihre Aufmerksamkeit!

Nun, wir warten auf alle auf dem Kurs !

Source: https://habr.com/ru/post/de481020/


All Articles