Écrire un simulateur de saisie tactile en utilisant du JavaScript pur: Partie 2

Bonjour En prévision du début du cours "Fullstack JavaScript Developer", l' un de nos auteurs a décidé de partager son expérience dans la création d'un simulateur de saisie tactile. Mais à notre tour, nous voulons vous montrer ce matériel et nous en partageons aujourd'hui la dernière partie.




La première partie peut être lue ici.

Bonjour à tous! Nous continuons d'écrire un simulateur de frappe natif à l'aide de JavaScript natif. Dans la dernière partie, vous et moi avons fait la logique principale de l'application, dans laquelle, en appuyant sur la touche Entrée, le premier niveau de vingt caractères a été chargé, qui lors de l'étude de la méthode d'impression tactile est l'un des premiers à être étudié (J, K, F, D). La version de travail de la première version peut être consultée ici . Cependant, nous avons encore quelques tâches pour améliorer l'application.



Formulons le mandat pour comprendre exactement ce que nous voulons réaliser:

«Au début du jeu, après que l'utilisateur a appuyé sur la touche d'entrée invitante, qui se déplace de manière animée de gauche à droite, les premier au troisième niveaux sont chargés séquentiellement. En appuyant sur le bon bouton, un signal de jeu positif est joué, en cas d'erreur, un signal de jeu négatif est joué. Les premier et deuxième niveaux ont des symboles ordinaires, le troisième niveau est bonus, dans lequel les symboles sont spécialement destinés aux programmeurs (plutôt comiques). À la fin du niveau, nous avons une fenêtre de résultats qui est écrite et tirée de localStorage , cette fenêtre affiche le nombre d'erreurs du joueur et l'heure de fin de la session de jeu. En cas d'erreurs trop nombreuses, le son de la perte est joué, en cas de transition réussie vers le niveau suivant - une mélodie majeure. "

Bon, maintenant nous avons TK, commençons la programmation. De toute évidence, notre base de code augmentera considérablement, ce qui signifie que pour faire face à beaucoup de code et importer les bibliothèques nécessaires, nous avons besoin de webpack . Mais commençons par la mise en page et voyons quels changements se sont produits. Je ne donnerai pas le contenu de head , car la seule chose qui a changé, c'est que maintenant nous dist/code.js javascript du dist/code.js traité par le dist/code.js , mais tout le reste reste le dist/code.js .

Dans le corps de la page, j'ai ajouté une fenêtre modale qui n'est pas encore visible et dans laquelle nous écrirons les résultats du passage du jeu dans le 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> 

Bien sûr, le fait que nous ayons une fenêtre modale directement sur la page cause une douleur physique aux amateurs de SPA - les cadres (après tout, ils aimeraient décomposer soigneusement tout cela en composants). Mais je ne voulais pas peindre la génération de ce contenu en JS. Regardons la prochaine partie de notre 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> 

En fait, seulement trois choses ont changé depuis la dernière fois: maintenant il y a un nom pour le niveau que nous allons remplir à partir de js, le panneau d'erreur est initialement caché et le nom de l'application elle-même a changé.

Au moment de démarrer JS, je vais juste ajouter un peu de CSS pour corriger un peu ce que je ne pouvais pas faire avec Bulma :

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

Ok js. J'ai écrit la configuration de webpack la plus simple. De ce qui se trouve sur la première page de la documentation du webpack , il ne diffère qu'en présence de suivi des modifications dans les fichiers. Pour la plupart, j'en ai besoin pour utiliser les importations dans le fichier index.js principal, et éventuellement avoir un fichier minifié:

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

Très bien, et maintenant nous pouvons passer à la structure js. Pour les animations, j'ai décidé d'utiliser anime.js - bien que je comprenne bien que la quantité d'animation dont nous avons besoin peut être faite en CSS en 10 lignes. Peut-être qu'à l'avenir, nous ajouterons plus d'animations, j'ai donc fait glisser tout le anime.es.js . De plus, j'ai ajouté une fonction de génération de fichiers aléatoires à un fichier séparé - uniquement pour des raisons de commodité:

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

J'ai alors décidé de mettre la fonction showResult dans un fichier séparé, qui était engagé dans le dessin des résultats après que l'utilisateur ait pu parcourir tout le jeu. Il s'agit d'une fonction assez universelle qui prend le premier argument à l'entrée de l'élément qui sera écrit dans les informations de localStorage et qui vient d'être reçu, et le second - les informations qui doivent être écrites (dans notre cas, c'est le nombre d'erreurs du joueur). De plus, j'ai implémenté des clés de tri à partir de localStorage , afin que les données de la table soient triées au moment de leur création. Dans la deuxième partie de la fonction, le tableau affiche les données des tentatives précédentes du joueur. Cependant, moins de mots, plus de 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> `); } })(); } 

Très bien, avec les fichiers supplémentaires terminés, nous pouvons enfin passer au fichier principal. Nous faisons d'abord les importations:

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

Bibliothèques reçues. Faisons une animation de notre inscription invitante depuis le tout début:

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

Super! Enfin, l'application elle-même. J'ai décidé de déplacer toutes les données vers json pour représenter le serveur que ce jeu peut générer, par exemple, en hongrois, en russe ou en chinois simplifié - en bref, avec n'importe quel ensemble de caractères (mais avant cela, bien sûr, grandit et grandit). Après avoir reçu des données lors de la fetch , la fonction elle-même est appelée de manière asynchrone, ce qui démarre notre jeu. J'ai gist.github JSON sur gist.github (peut être vu ici )

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

Comme le lecteur l'a déjà compris, dans notre fonction asynchrone read_data , toutes les autres fonctions du jeu seront localisées. Par conséquent, le reste du code ira déjà à l'intérieur de cette fonction:

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

J'ai trouvé des sons 8 bits dans des banques gratuites sur Internet. Voyons maintenant les éléments que j'obtiens de l'arborescence DOM:
  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           //     ,      }); 

Vient ensuite la fonction de démarrage du jeu. Certains changements l'ont dépassée aussi - maintenant notre panneau d'erreur ne devient visible qu'au début du jeu, un son de clic est joué, l'inscription invitante est supprimée, et elle ne se cache pas (je l'ai fait pour une petite optimisation, car alors nous aurions joué une animation d'un élément invisible sur la page) , puis la fonction principale du jeu est appelée:

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

Ok, vient ensuite le dessin des lettres. Toute la différence avec la version précédente est que nous prenons maintenant les données de l'objet 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>` ); } } 

Ensuite, nous appelons la fonction du jeu principal avec des informations de JSON, que notre fonction asynchrone a toujours pris comme argument:

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

Ensuite, la dernière fonction press dans notre code ira, dans laquelle nous déterminerons la perte et la victoire. Si l'utilisateur a marqué trop d'erreurs, vous devez lui dire qu'il a perdu et jouer la mélodie responsable du "fiasco". Une fin réussie du niveau se produit si la variable count_right gagne la valeur égale au nombre de caractères que nous générons (20). Je suis conscient que la transition vers le niveau suivant peut se faire lorsque la longueur du tableau elements_arr devient égale à 0, mais pour l'instant nous avons une telle solution. Si l'utilisateur a réussi les trois niveaux, un tableau de résultats s'affiche avec le nombre d'erreurs:

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

Ceci est la partie principale de notre article. Si vous êtes intéressé par le code source de l'application, vous pouvez le trouver ici . Si vous voulez jouer et voir notre application en action, bienvenue ici . Veuillez écrire tous les bugs et questions dans les commentaires ou immédiatement dans le référentiel des issues sur github.

Il y a beaucoup plus à ajouter à l'application - ce sont des animations amusantes de lettres, de musique en arrière-plan (ou vous pouvez vous assurer que lorsque vous imprimez, différents sons sont émis pour forcer l'utilisateur à imprimer à un certain rythme). Vous pouvez faire défiler les lettres et le mode vitesse - afin d'imprimer à temps et d'enregistrer la précision obtenue. J'aimerais aussi voir un compte à rebours au début du niveau pour que le joueur puisse positionner correctement ses mains sur le clavier et se préparer. En bref, il y a beaucoup d'idées (ainsi qu'une traduction très évidente de cette application dans React ou Vue), et j'espère que les Habrovsk conseilleront également quelque chose. Merci à tous pour votre attention!

Eh bien, nous attendons tout le monde sur le parcours !

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


All Articles