使用纯JavaScript编写触摸打字模拟器:第2部分

你好 预期“ Fullstack JavaScript Developer”课程的开始,我们的一位作者决定分享他在创建用于触摸打字的模拟器方面的经验。 但是,我们又想向您展示此材料,今天分享其中的最后一部分。




第一部分可以在这里阅读

大家好! 我们将继续使用本地JavaScript编写本地输入模拟器。 在最后一部分中,我们介绍了该应用程序的主要逻辑,其中通过按Enter键加载了二十个字符的第一级字符,这在研究触摸印刷方法时是第一个要研究的字符(J,K,F,D)。 可以在此处查看第一个版本的工作版本。 但是,我们还有一些任务需要改进应用程序。



让我们制定TOR来理解我们到底想要实现什么:

“在游戏开始时,在用户按下邀请Enter键后,该Enter键从左到右动画地移动,依次加载第一至第三级。 通过按下正确的按钮,可以播放正面的游戏信号,如果出现错误,则可以播放负面的游戏信号。 第一级和第二级具有普通符号,第三级是奖金,其中符号特别适合程序员(而不是喜剧演员)。 在该级别的最后,我们有一个结果窗口,该窗口是从localStorage写入并localStorage ,该窗口显示玩家错误的数量和游戏会话的结束时间。 如果有太多的错误,则在成功过渡到下一个音阶的过程中会演奏失落的声音,这是一种主要的旋律。”

好的,现在我们有了TK,让我们开始编程。 显然,我们的代码库将大大增加,这意味着为了处理大量代码并导入必要的库,我们需要webpack 。 但是,让我们从布局开始,看看发生了什么变化。 我不会给出head的内容,因为唯一改变的地方是,现在我们从dist/code.js处理的dist/code.js中提取javascript,但是其他所有内容都保持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; } 

好的js。 我写了最简单的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, } 

太好了,现在我们可以进入js结构。 对于动画,我决定使用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 ,以描述这种游戏可以生成的服务器的操作,例如以匈牙利语,俄语或简体中文-简而言之,可以使用任何字符集(但在此之前当然要不断发展)。 在fetch数据后,该函数本身将被异步调用,这将开始我们的游戏。 我gist.github JSON gist.githubgist.github (可以在这里看到)

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

正如读者已经了解的那样,将在我们的异步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"); //      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           //     ,      }); 

接下来是游戏的启动功能。 一些变化也超过了她-现在我们的错误面板仅在游戏开始时才可见,播放了单击声,删除了邀请字样,并且没有隐藏(我做了一些优化,因为那样的话我们会播放页面上看不见的元素的动画) ,然后调用主要游戏功能:

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

好的,接下来是字母的绘图。 与先前版本的所有不同之处在于,我们现在从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获得的值等于我们生成的字符数(20),则表示该级别成功结束。 我知道,当elements_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(); } } } //      ,    

这是本文的主要部分。 如果您对应用程序的源代码感兴趣,可以在此处找到。 如果您想玩游戏并观看我们的应用程序,请点击此处 。 请在注释中或立即在github上的issues存储库中编写所有错误和问题。

可以在应用程序中添加更多内容-这些是字母,背景音乐的有趣动画(或者您可以在打印时产生不同的声音,以迫使用户以一定的节奏进行打印)。 您可以滚动字母和速度模式-这样您就可以按时打印并记录所达到的精度。 我还希望在关卡开始时看到一个倒数计时,以便玩家可以正确地将手放在键盘上并做好准备。 简而言之,有很多想法(以及该应用程序到React或Vue的非常明显的翻译),我希望哈勃罗夫斯克人也能提供一些建议。 谢谢大家的关注!

好吧,我们正在等待课程中的所有人!

Source: https://habr.com/ru/post/zh-CN481020/


All Articles