Hola En previsión del inicio del curso "Fullstack JavaScript Developer", uno de nuestros autores decidió compartir su experiencia en la creación de un simulador para la escritura táctil. Pero nosotros, a su vez, queremos mostrarle este material y hoy compartimos la parte final del mismo.
La primera parte se puede leer
aquí.Hola a todos! Continuamos escribiendo un simulador de escritura nativo usando JavaScript nativo. En la última parte, hicimos la lógica principal de la aplicación, en la que, al presionar la tecla Intro, se cargó el primer nivel de veinte caracteres, que al estudiar el método de impresión táctil es uno de los primeros en estudiarse (J, K, F, D). La versión de trabajo de la primera versión se puede ver
aquí . Sin embargo, todavía tenemos algunas tareas para mejorar la aplicación.

Formulemos los TOR para comprender qué es exactamente lo que queremos lograr:
“Al comienzo del juego, después de que el usuario presiona la tecla Intro, que se mueve animadamente de izquierda a derecha, los niveles primero a tercero se cargan secuencialmente. Al presionar el botón correcto, se reproduce una señal de juego positiva, en caso de error, se reproduce una señal de juego negativa. El primer y segundo nivel tienen símbolos ordinarios, el tercer nivel es bonus, en el que los símbolos son especialmente para programadores (más bien cómicos). Al final del nivel, tenemos una ventana de resultados que se escribe y se toma de localStorage
, esta ventana muestra el número de errores del jugador y la hora de finalización de la sesión del juego. En caso de demasiados errores, se reproduce el sonido de la pérdida, en caso de una transición exitosa al siguiente nivel, una melodía importante ".Bien, ahora tenemos TK, comencemos a programar. Obviamente, nuestra base de código aumentará significativamente, lo que significa que para hacer frente a una gran cantidad de código e importar las bibliotecas necesarias, necesitamos
webpack
. Pero comencemos con el diseño y veamos qué cambios han ocurrido. No daré el contenido de
head
, porque lo único que ha cambiado allí es que ahora estamos retirando javascript del
dist/code.js
procesado por el
dist/code.js
, pero todo lo demás sigue
dist/code.js
.
En el cuerpo de la página, agregué una ventana modal que aún no está visible y en la que escribiremos los resultados de pasar el juego en la
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>
Por supuesto, el hecho de que tengamos una ventana modal justo en la página causa dolor físico a los amantes del SPA: marcos (porque les gustaría descomponer todo esto en componentes). Pero no quería pintar la generación de este contenido en JS. Veamos la siguiente parte de nuestro 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 hecho, solo tres cosas han cambiado desde la última vez: ahora hay un nombre de nivel que completaremos desde js, el panel de error está inicialmente oculto y el nombre de la aplicación en sí ha cambiado.
A punto de comenzar JS, solo agregaré un poco de CSS para arreglar un poco de lo que no pude hacer con
Bulma
:
body{ max-height:40vh !important; } .promo{ margin-top: 1rem; }
Ok js. Escribí la configuración webpack más simple. De lo que se encuentra en la primera página de la documentación del
webpack
, solo difiere en presencia de cambios de seguimiento en los archivos. En su mayor parte, lo necesito para usar las importaciones en el archivo
index.js
principal y, finalmente, tener un archivo minificado:
const path = require("path"); module.exports = { entry: './js/index.js', output: { filename: 'code.js', path: path.resolve(__dirname, 'dist'), }, watch: true, }
Genial, y ahora podemos pasar a la estructura js. Para las animaciones, decidí usar
anime.js
, aunque entiendo bien que la cantidad de animación que necesitamos se puede hacer en CSS en 10 líneas. Quizás en el futuro agreguemos más animaciones, así que arrastré todo el
anime.es.js
. Además, agregué una función aleatoria de generación de archivos a un archivo separado, solo por conveniencia:
export default function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); }
Luego decidí poner la función
showResult
en un archivo separado, que se ocupó de dibujar los resultados después de que el usuario pudo revisar todo el juego. Esta es una función bastante universal que lleva el primer argumento a la entrada en la cual la información de
localStorage
será escrita y recién recibida, y la segunda: la información que necesita ser escrita (en nuestro caso, este es el número de errores del jugador). Además, implementé las claves de clasificación de
localStorage
, de modo que los datos de la tabla se ordenaron en el momento en que se crearon. En la segunda parte de la función, la tabla muestra los datos de los intentos anteriores del jugador. Sin embargo, menos palabras, más 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> `); } })(); }
Genial, con los archivos adicionales terminados, finalmente podemos ir al archivo principal. Primero hacemos las importaciones:
import anime from "./anime.es"; import getRandomInt from "./random"; import showResult from "./showResult";
Bibliotecas recibidas. Hagamos una animación de nuestra atractiva inscripción desde el principio:
anime({ targets: ".anim-elem", translateX: [-50, 50], easing: "linear", direction: "alternate", duration: 1000, loop: true });
Genial Finalmente, la aplicación en sí. Decidí cambiar todos los datos a
json
para representar el funcionamiento del servidor que dicho juego puede generar, por ejemplo, en húngaro, ruso o chino simplificado, en resumen, con cualquier conjunto de caracteres (pero antes de eso, por supuesto, crecer y crecer). Después de recibir datos en la
fetch
, la función en sí se llama de forma asincrónica, lo que iniciará nuestro juego.
gist.github
JSON a
gist.github
(se puede ver
aquí )
function get_data() { fetch(
Como el lector ya ha entendido, en nuestra función asincrónica
read_data
todas las demás funciones del juego. Por lo tanto, el resto del código ya irá dentro de esta función:
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"); // .
Encontré sonidos de 8 bits en bancos gratuitos en Internet. Ahora veamos los elementos que obtengo del árbol DOM:
let modal = document.querySelector(".modal");
Luego viene la función de inicio del juego. Algunos cambios también la afectaron: ahora nuestro panel de error se vuelve visible solo al comienzo del juego, se reproduce un sonido de clic, se elimina la inscripción de invitación y no se oculta (hice esto para una pequeña optimización, porque entonces habríamos reproducido una animación de un elemento invisible en la página) , y luego la función principal del juego se llama:
function StartGame(e) { if (e.key == "Enter") { error_panel.classList.remove("is-hidden");
Ok, luego viene el dibujo de las letras. Toda la diferencia con la versión anterior es que ahora tomamos los datos del 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>` ); } }
A continuación, llamamos a la función del juego principal con información de JSON, que nuestra función asincrónica todavía tomó como argumento:
function mainGame() { drawBoard(information); document.addEventListener("keydown", press);
Luego, la última función
press
en nuestro código irá, en la que determinamos la pérdida y la ganancia. Si el usuario obtuvo demasiados errores, debe decirle que perdió y tocar la melodía responsable del "fiasco". Un final exitoso del nivel ocurre si la variable
count_right
gana el valor igual al número de caracteres que generamos (20). Soy consciente de que la transición al siguiente nivel se puede hacer cuando la longitud de la matriz
elements_arr
es igual a 0, pero por ahora tenemos esa solución. Si el usuario ha completado con éxito los tres niveles, se muestra una tabla de resultados con la cantidad de errores:
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 es la parte principal de nuestro artículo. Si está interesado en el código fuente de la aplicación, puede encontrarlo
aquí . Si quieres jugar y ver nuestra aplicación en acción, bienvenido
aquí . Escriba todos los errores y preguntas en los comentarios o inmediatamente en el repositorio de
issues
en github.
Se puede agregar mucho más a la aplicación: estas son algunas animaciones divertidas de letras, música de fondo (o podría hacer que se produzcan diferentes sonidos al imprimir para obligar al usuario a imprimir en un cierto ritmo). Puede desplazarse por las letras y el modo de velocidad, para poder imprimir a tiempo y registrar la precisión lograda. También me gustaría ver una cuenta regresiva al comienzo del nivel para que el jugador pueda colocar correctamente sus manos en el teclado y prepararse. En resumen, hay muchas ideas (así como una traducción muy obvia de esta aplicación a React o Vue), y espero que la gente de Habrovsk también aconseje algo. ¡Gracias a todos por su atención!
Bueno, ¡estamos esperando a todos en el
curso !