Escrevendo um simulador de digitação por toque usando JavaScript puro: Parte 2

Olá Antecipando o início do curso "Fullstack JavaScript Developer", um de nossos autores decidiu compartilhar sua experiência na criação de um simulador para digitação por toque. Mas, por sua vez, queremos mostrar esse material para você e hoje compartilhamos a parte final.




A primeira parte pode ser lida aqui.

Olá pessoal! Continuamos a escrever um simulador de digitação nativo usando JavaScript nativo. Na última parte, você e eu fizemos a lógica principal do aplicativo, na qual, pressionando a tecla Enter, foi carregado o primeiro nível de vinte caracteres, que ao estudar o método de impressão por toque é um dos primeiros a ser estudado (J, K, F, D). A versão de trabalho da primeira versão pode ser vista aqui . No entanto, ainda temos algumas tarefas para melhorar o aplicativo.



Vamos formular o TOR para entender exatamente o que queremos alcançar:

“No início do jogo, depois que o usuário pressiona o convidativo Enter, que se move animadamente da esquerda para a direita, o primeiro ao terceiro nível é carregado sequencialmente. Ao pressionar o botão correto, um sinal de jogo positivo é reproduzido; em caso de erro, um sinal de jogo negativo é reproduzido. O primeiro e o segundo níveis têm símbolos comuns, o terceiro nível é bônus, no qual os símbolos são especialmente para programadores (bastante cômicos). No final do nível, temos uma janela de resultados que é escrita e retirada do localStorage ; essa janela exibe o número de erros do jogador e o horário de término da sessão do jogo. No caso de muitos erros, o som da perda é reproduzido, no caso de uma transição bem-sucedida para o próximo nível - uma melodia importante ".

Ok, agora temos TK, vamos começar a programar. Obviamente, nossa base de códigos aumentará significativamente, o que significa que, para lidar com muito código e importar as bibliotecas necessárias, precisamos do webpack . Mas vamos começar com o layout e ver quais mudanças ocorreram. Não vou dar o conteúdo do head , porque a única coisa que mudou lá é que agora estamos puxando o javascript do dist/code.js processado pelo dist/code.js , mas tudo o resto permanece o dist/code.js .

No corpo da página, adicionei uma janela modal que ainda não está visível e na qual escreveremos os resultados da aprovação do jogo na 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> 

Obviamente, o fato de termos uma janela modal diretamente na página causa sofrimento físico para os amantes do SPA - estruturas (porque eles gostariam de decompor tudo isso em componentes). Mas eu não queria pintar a geração desse conteúdo em JS. Vejamos a próxima parte do nosso 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> 

De fato, apenas três coisas mudaram desde a última vez: agora há um nome de nível que preencheremos de js, o painel de erros está inicialmente oculto e o nome do próprio aplicativo foi alterado.

Prestes a iniciar o JS, adicionarei um pouco de CSS para corrigir um pouco do que não poderia fazer com a Bulma :

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

Ok js. Eu escrevi a configuração mais simples do webpack. Do que está na primeira página da documentação do webpack , ele difere apenas na presença de alterações de rastreamento nos arquivos. Na maioria das vezes, eu preciso disso para usar as importações no arquivo index.js principal e, eventualmente, ter um arquivo minificado:

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

Ótimo, e agora podemos seguir para a estrutura js. Para animações, eu decidi usar anime.js - embora eu entenda bem que a quantidade de animação que precisamos pode ser feita em css em 10 linhas. Talvez no futuro adicionaremos mais animações, então eu arrastei todo o anime.es.js . Além disso, adicionei uma função de geração aleatória de arquivo a um arquivo separado - apenas por conveniência:

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

Decidi colocar a função showResult em um arquivo separado, que estava envolvido no desenho dos resultados depois que o usuário conseguiu passar o jogo inteiro. Essa é uma função bastante universal que leva o primeiro argumento para a entrada do elemento que será gravado nas informações do localStorage e acabado de receber e o segundo - as informações que precisam ser gravadas (no nosso caso, esse é o número de erros do jogador). Além disso, implementei chaves de classificação do localStorage , para que os dados na tabela fossem classificados na hora em que foram criados. Na segunda parte da função, a tabela exibe os dados das tentativas anteriores do jogador. No entanto, menos palavras, mais código:

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

Ótimo, com os arquivos adicionais terminados, finalmente podemos ir para o arquivo principal. Primeiro fazemos as importações:

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

Bibliotecas recebidas. Vamos fazer uma animação da nossa inscrição convidativa desde o início:

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

Ótimo! Finalmente, o próprio aplicativo. Decidi mudar todos os dados para json , a fim de representar a operação do servidor que esse jogo pode gerar, por exemplo, em húngaro, russo ou chinês simplificado - enfim, com qualquer conjunto de caracteres (mas antes disso, é claro, crescem e crescem). Após receber os dados na fetch , a própria função é chamada de forma assíncrona, o que iniciará o jogo. gist.github JSON no gist.github (pode ser visto aqui )

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

Como o leitor já entendeu, em nossa função read_data assíncrona, todas as outras funções do jogo serão localizadas. Portanto, o restante do código já estará dentro desta função:

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

Encontrei sons de 8 bits em bancos gratuitos na Internet. Agora vamos ver os elementos que recebo da árvore 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           //     ,      }); 

Em seguida, vem a função de início do jogo. Algumas mudanças também a ultrapassaram - agora nosso painel de erros fica visível apenas no início do jogo, um clique é reproduzido, a inscrição convidativa é excluída e não se esconde (eu fiz isso para uma pequena otimização, porque teríamos reproduzido uma animação de um elemento invisível na página) e a função principal do jogo é chamada:

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

Ok, a seguir vem o desenho das letras. Toda a diferença da versão anterior é que agora pegamos os dados do objeto 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>` ); } } 

Em seguida, chamamos a função do jogo principal com informações de JSON, que nossa função assíncrona ainda levou longe como argumento:

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

Então, a última função press em nosso código será executada, na qual determinamos a perda e a vitória. Se o usuário marcou muitos erros, você precisa dizer a ele que ele perdeu e tocar a melodia que é responsável pelo "fiasco". Um final bem-sucedido do nível acontece se a variável count_right obtiver o valor igual ao número de caracteres que geramos (20). Estou ciente de que a transição para o próximo nível pode ser feita quando o comprimento da matriz elements_arr se tornar igual a 0, mas, por enquanto, temos essa solução. Se o usuário tiver concluído com êxito os três níveis, um quadro de resultados será exibido com o número de erros:

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

Esta é a parte principal do nosso artigo. Se você está interessado no código fonte do aplicativo, pode encontrá-lo aqui . Se você quer jogar e ver nossa aplicação em ação, seja bem-vindo aqui . Por favor, escreva todos os bugs e perguntas nos comentários ou imediatamente no repositório de issues no github.

Muito mais pode ser adicionado ao aplicativo - estas são algumas animações engraçadas de letras, música em segundo plano (ou você pode ter certeza de que, ao imprimir, sons diferentes são feitos para forçar o usuário a imprimir em um determinado ritmo). Você pode rolar as letras e o modo de velocidade - para poder imprimir a tempo e registrar a precisão alcançada. Eu também gostaria de ver uma contagem regressiva no início do nível para que o jogador possa posicionar corretamente as mãos no teclado e se preparar. Em suma, existem muitas idéias (bem como uma tradução muito óbvia dessa aplicação para React ou Vue), e espero que o pessoal de Habrovsk também aconselhe algo. Obrigado a todos pela atenção!

Bem, estamos esperando por todos no curso !

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


All Articles