📜  使用 JavaScript 设计打字速度测试游戏

📅  最后修改于: 2022-05-13 01:56:52.513000             🧑  作者: Mango

使用 JavaScript 设计打字速度测试游戏

打字测试旨在找出在给定时间内打字的速度。我们将使用 JavaScript 设计一个打字游戏,它提出了一个简单的打字挑战,并通过计算每分钟字符数 (CPM)、每分钟字数 (WPM) 和输入字符的准确性来找到打字的字符。
游戏显示了一系列必须在指定时间限制内尽快输入的报价。较高的打字速度将显示较高的 WPM 值。输入错误的字符将在输入过程中被相应地标记。
我们将首先创建 HTML 布局,使用 CSS 设置样式,然后用 JavaScript 编写逻辑。
HTML 布局: HTML 布局定义了将在页面上显示的元素结构。这包括:

  • 标题部分:此部分显示当前键入会话的统计信息。这包括显示剩余时间、错误数量、准确性、WPM 和 CPM。
  • 引用部分:此部分显示必须在输入区域中键入的当前文本。
  • 输入区域:此部分包含必须输入文本的输入区域。
  • 重新启动按钮:这是重新启动按钮,一旦时间用完并且游戏结束就会显示。
  • 代码:
html


    Simple Speed Typer
 
    
    


  
    
      Simple Speed Typing     
    
      
        
WPM
        
100
      
      
        
CPM
        
100
      
      
        
Errors
        
0
      
      
        
Time
        
60s
      
      
        
% Accuracy
        
100
      
    
      
      Click on the area below to start the game.     
            
       


html
body {
  background-color: #fe9801;
  color: black;
  text-align: center;
}
 
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
 
.heading {
  margin-bottom: 20px;
  font-size: 3rem;
  color: black;
}
 
.header {
  display: flex;
  align-items: center;
}
 
.timer, .errors, .accuracy,
.cpm, .wpm {
  background-color: #ccda46;
  height: 60px;
  width: 70px;
  margin: 8px;
  padding: 12px;
  border-radius: 20%;
  box-shadow: black 5px 8px 5px;
}
 
.cpm, .wpm  {
  display: none;
}
 
.header_text {
  text-transform: uppercase;
  font-size: 0.6rem;
  font-weight: 600;
}
 
.curr_time, .curr_errors,
.curr_accuracy, .curr_cpm,
.curr_wpm {
  font-size: 2.75rem;
}
 
.quote {
  background-color: #ccda46;
  font-size: 1.5rem;
  margin: 10px;
  padding: 25px;
  box-shadow: black 5px 8px 5px;
}
 
.input_area {
  background-color: #f5f5c6;
  height: 80px;
  width: 40%;
  font-size: 1.5rem;
  font-weight: 600;
  margin: 15px;
  padding: 20px;
  border: 0px;
  box-shadow: black 5px 8px 5px;
}
 
.restart_btn {
  display: none;
  background-color: #326765;
  font-size: 1.5rem;
  padding: 10px;
  border: 0px;
  box-shadow: black 5px 8px 5px;
}
 
.incorrect_char {
  color: red;
  text-decoration: underline;
}
 
.correct_char {
  color: darkgreen;
}


javascript
// define the time limit
let TIME_LIMIT = 60;
 
// define quotes to be used
let quotes_array = [
  "Push yourself, because no one else is going to do it for you.",
  "Failure is the condiment that gives success its flavor.",
  "Wake up with determination. Go to bed with satisfaction.",
  "It's going to be hard, but hard does not mean impossible.",
  "Learning never exhausts the mind.",
  "The only way to do great work is to love what you do."
];
 
// selecting required elements
let timer_text = document.querySelector(".curr_time");
let accuracy_text = document.querySelector(".curr_accuracy");
let error_text = document.querySelector(".curr_errors");
let cpm_text = document.querySelector(".curr_cpm");
let wpm_text = document.querySelector(".curr_wpm");
let quote_text = document.querySelector(".quote");
let input_area = document.querySelector(".input_area");
let restart_btn = document.querySelector(".restart_btn");
let cpm_group = document.querySelector(".cpm");
let wpm_group = document.querySelector(".wpm");
let error_group = document.querySelector(".errors");
let accuracy_group = document.querySelector(".accuracy");
 
let timeLeft = TIME_LIMIT;
let timeElapsed = 0;
let total_errors = 0;
let errors = 0;
let accuracy = 0;
let characterTyped = 0;
let current_quote = "";
let quoteNo = 0;
let timer = null;


javascript
function updateQuote() {
  quote_text.textContent = null;
  current_quote = quotes_array[quoteNo];
 
  // separate each character and make an element
  // out of each of them to individually style them
  current_quote.split('').forEach(char => {
    const charSpan = document.createElement('span')
    charSpan.innerText = char
    quote_text.appendChild(charSpan)
  })
 
  // roll over to the first quote
  if (quoteNo < quotes_array.length - 1)
    quoteNo++;
  else
    quoteNo = 0;
}


javascript
function processCurrentText() {
 
  // get current input text and split it
  curr_input = input_area.value;
  curr_input_array = curr_input.split('');
 
  // increment total characters typed
  characterTyped++;
 
  errors = 0;
 
  quoteSpanArray = quote_text.querySelectorAll('span');
  quoteSpanArray.forEach((char, index) => {
    let typedChar = curr_input_array[index]
 
    // character not currently typed
    if (typedChar == null) {
      char.classList.remove('correct_char');
      char.classList.remove('incorrect_char');
 
      // correct character
    } else if (typedChar === char.innerText) {
      char.classList.add('correct_char');
      char.classList.remove('incorrect_char');
 
      // incorrect character
    } else {
      char.classList.add('incorrect_char');
      char.classList.remove('correct_char');
 
      // increment number of errors
      errors++;
    }
  });
 
  // display the number of errors
  error_text.textContent = total_errors + errors;
 
  // update accuracy text
  let correctCharacters = (characterTyped - (total_errors + errors));
  let accuracyVal = ((correctCharacters / characterTyped) * 100);
  accuracy_text.textContent = Math.round(accuracyVal);
 
  // if current text is completely typed
  // irrespective of errors
  if (curr_input.length == current_quote.length) {
    updateQuote();
 
    // update total errors
    total_errors += errors;
 
    // clear the input area
    input_area.value = "";
  }
}


javascript
function startGame() {
 
  resetValues();
  updateQuote();
 
  // clear old and start a new timer
  clearInterval(timer);
  timer = setInterval(updateTimer, 1000);
}
 
function resetValues() {
  timeLeft = TIME_LIMIT;
  timeElapsed = 0;
  errors = 0;
  total_errors = 0;
  accuracy = 0;
  characterTyped = 0;
  quoteNo = 0;
  input_area.disabled = false;
 
  input_area.value = "";
  quote_text.textContent = 'Click on the area below to start the game.';
  accuracy_text.textContent = 100;
  timer_text.textContent = timeLeft + 's';
  error_text.textContent = 0;
  restart_btn.style.display = "none";
  cpm_group.style.display = "none";
  wpm_group.style.display = "none";
}


javascript
function updateTimer() {
  if (timeLeft > 0) {
    // decrease the current time left
    timeLeft--;
 
    // increase the time elapsed
    timeElapsed++;
 
    // update the timer text
    timer_text.textContent = timeLeft + "s";
  }
  else {
    // finish the game
    finishGame();
  }
}


javascript
function finishGame() {
  // stop the timer
  clearInterval(timer);
 
  // disable the input area
  input_area.disabled = true;
 
  // show finishing text
  quote_text.textContent = "Click on restart to start a new game.";
 
  // display restart button
  restart_btn.style.display = "block";
 
  // calculate cpm and wpm
  cpm = Math.round(((characterTyped / timeElapsed) * 60));
  wpm = Math.round((((characterTyped / 5) / timeElapsed) * 60));
 
  // update cpm and wpm text
  cpm_text.textContent = cpm;
  wpm_text.textContent = wpm;
 
  // display the cpm and wpm
  cpm_group.style.display = "block";
  wpm_group.style.display = "block";
}


注意:每个部分都填充了虚拟数据,以使样式更容易。上面的HTML代码如下。
CSS 样式: CSS 用于设置不同部分的样式并使其更具视觉吸引力。

  • 标题部分使用 flex 布局显示。
  • 为每个元素提供足够的填充和边距。
  • 每个元素的文本大小使得用户在玩游戏时易于阅读。
  • 定义了两个附加类来表示正确或错误键入的字母。这些类将在需要时动态添加或删除。
  • 代码:

html

body {
  background-color: #fe9801;
  color: black;
  text-align: center;
}
 
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
 
.heading {
  margin-bottom: 20px;
  font-size: 3rem;
  color: black;
}
 
.header {
  display: flex;
  align-items: center;
}
 
.timer, .errors, .accuracy,
.cpm, .wpm {
  background-color: #ccda46;
  height: 60px;
  width: 70px;
  margin: 8px;
  padding: 12px;
  border-radius: 20%;
  box-shadow: black 5px 8px 5px;
}
 
.cpm, .wpm  {
  display: none;
}
 
.header_text {
  text-transform: uppercase;
  font-size: 0.6rem;
  font-weight: 600;
}
 
.curr_time, .curr_errors,
.curr_accuracy, .curr_cpm,
.curr_wpm {
  font-size: 2.75rem;
}
 
.quote {
  background-color: #ccda46;
  font-size: 1.5rem;
  margin: 10px;
  padding: 25px;
  box-shadow: black 5px 8px 5px;
}
 
.input_area {
  background-color: #f5f5c6;
  height: 80px;
  width: 40%;
  font-size: 1.5rem;
  font-weight: 600;
  margin: 15px;
  padding: 20px;
  border: 0px;
  box-shadow: black 5px 8px 5px;
}
 
.restart_btn {
  display: none;
  background-color: #326765;
  font-size: 1.5rem;
  padding: 10px;
  border: 0px;
  box-shadow: black 5px 8px 5px;
}
 
.incorrect_char {
  color: red;
  text-decoration: underline;
}
 
.correct_char {
  color: darkgreen;
}

HTML 布局和 CSS 样式的结果如下所示:

html_css_style

游戏的主要逻辑:游戏的主要逻辑定义在一个 JavaScript 文件中。有几个函数协同工作来运行游戏。
第 1 步:选择所有元素并定义变量
首先使用 querySelector() 方法选择 HTML 布局中所需的元素。它们被分配了变量名称,以便可以轻松访问和修改它们。在整个程序中可以访问的其他变量也在开头定义。

javascript

// define the time limit
let TIME_LIMIT = 60;
 
// define quotes to be used
let quotes_array = [
  "Push yourself, because no one else is going to do it for you.",
  "Failure is the condiment that gives success its flavor.",
  "Wake up with determination. Go to bed with satisfaction.",
  "It's going to be hard, but hard does not mean impossible.",
  "Learning never exhausts the mind.",
  "The only way to do great work is to love what you do."
];
 
// selecting required elements
let timer_text = document.querySelector(".curr_time");
let accuracy_text = document.querySelector(".curr_accuracy");
let error_text = document.querySelector(".curr_errors");
let cpm_text = document.querySelector(".curr_cpm");
let wpm_text = document.querySelector(".curr_wpm");
let quote_text = document.querySelector(".quote");
let input_area = document.querySelector(".input_area");
let restart_btn = document.querySelector(".restart_btn");
let cpm_group = document.querySelector(".cpm");
let wpm_group = document.querySelector(".wpm");
let error_group = document.querySelector(".errors");
let accuracy_group = document.querySelector(".accuracy");
 
let timeLeft = TIME_LIMIT;
let timeElapsed = 0;
let total_errors = 0;
let errors = 0;
let accuracy = 0;
let characterTyped = 0;
let current_quote = "";
let quoteNo = 0;
let timer = null;

步骤 2:准备要显示的文本
定义了一个函数updateQuote() 来处理以下事情:

  • 获取文本
    引号已被用作必须输入才能玩游戏的文本。每个报价都是从预定义的数组中一一获取的。一个变量跟踪当前的报价索引,并在请求新报价时将其递增。
  • 将字符拆分为元素
    文本中的每个字符都被分成一系列 元素。这使得可以根据用户是否正确键入每个字符来单独更改每个字符的颜色。这些元素附加到变量quote_text。

javascript

function updateQuote() {
  quote_text.textContent = null;
  current_quote = quotes_array[quoteNo];
 
  // separate each character and make an element
  // out of each of them to individually style them
  current_quote.split('').forEach(char => {
    const charSpan = document.createElement('span')
    charSpan.innerText = char
    quote_text.appendChild(charSpan)
  })
 
  // roll over to the first quote
  if (quoteNo < quotes_array.length - 1)
    quoteNo++;
  else
    quoteNo = 0;
}

第 3 步:获取用户当前输入的文本
定义了一个函数processCurrentText(),每当用户在输入框中键入或更改任何内容时都会调用该函数。因此它与输入框的 oninput 事件处理程序一起使用。这个函数处理以下事情:

  • 获取输入框的当前值
    输入区域的 value 属性用于获取用户当前输入的文本。这被分成一个字符数组以与引用文本进行比较。这存储在 curr_input_array 中。
  • 为引用文本的字符着色
    显示的报价的字符是“红色”或“绿色”,这取决于它是否输入正确。这是通过选择我们之前创建的报价的跨度元素并循环它们来完成的。该元素然后根据它是否与键入的文本匹配来应用上面创建的类。
  • 计算误差和准确性
    每次用户在输入过程中出错时,errors 变量都会增加。这用于通过将正确键入的字符数除以用户键入的字符总数来计算准确度值。
  • 转到下一个报价
    当输入文本的长度与引用文本长度匹配时,调用 updateQuote()函数更改引用并清除输入区域。错误总数也会更新以用于下一个报价。

javascript

function processCurrentText() {
 
  // get current input text and split it
  curr_input = input_area.value;
  curr_input_array = curr_input.split('');
 
  // increment total characters typed
  characterTyped++;
 
  errors = 0;
 
  quoteSpanArray = quote_text.querySelectorAll('span');
  quoteSpanArray.forEach((char, index) => {
    let typedChar = curr_input_array[index]
 
    // character not currently typed
    if (typedChar == null) {
      char.classList.remove('correct_char');
      char.classList.remove('incorrect_char');
 
      // correct character
    } else if (typedChar === char.innerText) {
      char.classList.add('correct_char');
      char.classList.remove('incorrect_char');
 
      // incorrect character
    } else {
      char.classList.add('incorrect_char');
      char.classList.remove('correct_char');
 
      // increment number of errors
      errors++;
    }
  });
 
  // display the number of errors
  error_text.textContent = total_errors + errors;
 
  // update accuracy text
  let correctCharacters = (characterTyped - (total_errors + errors));
  let accuracyVal = ((correctCharacters / characterTyped) * 100);
  accuracy_text.textContent = Math.round(accuracyVal);
 
  // if current text is completely typed
  // irrespective of errors
  if (curr_input.length == current_quote.length) {
    updateQuote();
 
    // update total errors
    total_errors += errors;
 
    // clear the input area
    input_area.value = "";
  }
}
character_coloring

字符的着色基于其正确性

第 4 步:开始新游戏
定义了一个函数startGame(),当用户关注输入框时将调用该函数。因此它与输入框的 onfocus 事件处理程序一起使用。这个函数处理以下事情:

  • 重置所有值
    在新游戏开始之前,所有值都会重置为默认值。我们创建了一个名为 resetValues() 的不同函数来处理这个问题。
  • 更新报价文本
    通过调用 updateQuote()函数准备好并显示新的报价文本。
  • 创建一个新计时器
    计时器跟踪剩余的秒数并将其显示给用户。它是使用 setInterval() 方法创建的,该方法重复调用下面定义的 updateTimer()函数。在创建新计时器之前,使用 clearInterval() 清除先前的计时器实例。

javascript

function startGame() {
 
  resetValues();
  updateQuote();
 
  // clear old and start a new timer
  clearInterval(timer);
  timer = setInterval(updateTimer, 1000);
}
 
function resetValues() {
  timeLeft = TIME_LIMIT;
  timeElapsed = 0;
  errors = 0;
  total_errors = 0;
  accuracy = 0;
  characterTyped = 0;
  quoteNo = 0;
  input_area.disabled = false;
 
  input_area.value = "";
  quote_text.textContent = 'Click on the area below to start the game.';
  accuracy_text.textContent = 100;
  timer_text.textContent = timeLeft + 's';
  error_text.textContent = 0;
  restart_btn.style.display = "none";
  cpm_group.style.display = "none";
  wpm_group.style.display = "none";
}

第 5 步:更新计时器
定义了一个函数updateTimer(),它将每秒调用一次以跟踪时间。这个函数处理以下事情:

  • 更新时间值
    所有跟踪时间的变量都会更新。 timeLeft 值递减,timeElapsed 值递增,计时器文本更新为当前剩余时间。
  • 完成游戏
    此部分在达到时间限制时触发。它调用下面定义的完成游戏的 finishGame()函数。

javascript

function updateTimer() {
  if (timeLeft > 0) {
    // decrease the current time left
    timeLeft--;
 
    // increase the time elapsed
    timeElapsed++;
 
    // update the timer text
    timer_text.textContent = timeLeft + "s";
  }
  else {
    // finish the game
    finishGame();
  }
}

第六步:完成游戏
定义了一个函数finishGame() ,当游戏必须完成时将调用该函数。这个函数处理以下事情:

  • 删除定时器
    之前创建的定时器实例被移除。
  • 显示重启游戏文本和按钮
    向用户显示的引用文本更改为指示游戏结束的文本。通过将显示属性设置为“阻止”,也可以显示“重新启动”按钮。
  • 计算当前会话的 CPM 和 WPM
    1. 每分钟字符数 (CPM) 的计算方法是将输入的字符总数除以经过的时间,然后将结果乘以 60。它被四舍五入以防止出现小数点。
    2. 每分钟字数 (WPM) 的计算方法是将 CPM 除以 5,然后将结果乘以 60。5 表示每个单词的平均字符数。它被四舍五入以防止小数点。

javascript

function finishGame() {
  // stop the timer
  clearInterval(timer);
 
  // disable the input area
  input_area.disabled = true;
 
  // show finishing text
  quote_text.textContent = "Click on restart to start a new game.";
 
  // display restart button
  restart_btn.style.display = "block";
 
  // calculate cpm and wpm
  cpm = Math.round(((characterTyped / timeElapsed) * 60));
  wpm = Math.round((((characterTyped / 5) / timeElapsed) * 60));
 
  // update cpm and wpm text
  cpm_text.textContent = cpm;
  wpm_text.textContent = wpm;
 
  // display the cpm and wpm
  cpm_group.style.display = "block";
  wpm_group.style.display = "block";
}

最终演示
游戏现在可以在任何浏览器中玩了。

满2分钟

源代码: https://github.com/sayantanm19/js-simple-typing-game