Um teste de digitação é projetado para descobrir com que rapidez alguém digita em um determinado período de tempo. Estaremos projetando um jogo de digitação em JavaScript que apresenta um desafio de digitação simples e encontra o desempenho da digitação calculando os caracteres por minuto (CPM), palavras por minuto (WPM) e a precisão dos caracteres digitados.

O jogo mostra uma série de citações que devem ser digitadas em um determinado limite de tempo, o mais rápido possível. Uma velocidade de digitação mais alta mostraria um valor de WPM mais alto. Os caracteres digitados incorretamente seriam marcados de acordo durante a digitação.

Vamos criar o layout HTML primeiro, estilizá-lo usando CSS e, em seguida, escrever a lógica em JavaScript.
O Layout HTML: O layout HTML define a estrutura do elemento que seria mostrado na página. Isso inclui:

  • Parte do cabeçalho: Esta seção mostra as estatísticas da sessão de digitação atual. Isso inclui a exibição do tempo restante, número de erros, precisão, WPM e CPM.
  • Seção de Citação: Esta seção mostra o texto atual que deve ser digitado na área de entrada.
  • Área de entrada: esta seção contém a área de entrada onde o texto deve ser digitado.
  • Botão de reinicialização: Este é o botão de reinicialização que será mostrado quando o tempo acabar e o jogo terminar.
  • Código:




    <html lang="en">
    <head>
        <title>Simple Speed Typer</title>
      
        <!-- link the CSS file here -->
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
      <div class="container">
        <div class="heading">
          Simple Speed Typing
        </div>
        <div class="header">
          <div class="wpm">
            <div class="header_text">WPM</div>
            <div class="curr_wpm">100</div>
          </div>
          <div class="cpm">
            <div class="header_text">CPM</div>
            <div class="curr_cpm">100</div>
          </div>
          <div class="errors">
            <div class="header_text">Errors</div>
            <div class="curr_errors">0</div>
          </div>
          <div class="timer">
            <div class="header_text">Time</div>
            <div class="curr_time">60s</div>
          </div>
          <div class="accuracy">
            <div class="header_text">% Accuracy</div>
            <div class="curr_accuracy">100</div>
          </div>
        </div>
      
        <div class="quote">
          Click on the area below to start the game.
        </div>
        <textarea class="input_area"
          placeholder="start typing here..."
          oninput="processCurrentText()"
          onfocus="startGame()">
        </textarea>
        <button class="restart_btn"
          onclick="resetValues()">
          Restart
        </button>
      </div>
      
      <!-- link the JavaScript file here -->
      <script src="game.js">
      </script>
    </body>
    </html>

Nota: Cada uma das partes é preenchida com dados fictícios para tornar o estilo mais fácil. O código HTML acima é o seguinte.

O estilo CSS: CSS é usado para estilizar as diferentes partes e torná-las mais visualmente atraentes.

  • A parte do cabeçalho é exibida usando o layout flexível.
  • Preenchimento e margem adequados são dados a cada elemento.
  • O tamanho do texto de cada elemento é de fácil leitura pelo usuário durante o jogo.
  • Duas classes adicionais são definidas para denotar as letras digitadas corretamente ou incorretamente. Essas classes seriam adicionadas ou removidas dinamicamente quando necessário.
  • Código:




    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;
    }

O resultado do layout HTML e estilo CSS seria assim:
html_css_styling

Lógica principal do jogo: A lógica principal do jogo é definida em um arquivo JavaScript. Existem várias funções que funcionam juntas para executar o jogo.

Etapa 1: Seleção de todos os elementos e definição de variáveis

Os elementos necessários no layout HTML são selecionados primeiro usando o querySelector()método. Eles recebem nomes de variáveis ​​para que possam ser facilmente acessados ​​e modificados. Outras variáveis ​​que seriam acessadas ao longo do programa também são definidas no início.

// 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;

Etapa 2: Preparando o texto a ser exibido

updateQuote()É definida uma função que lida com as seguintes coisas:

  • Obtendo o texto As
    citações têm sido usadas como o texto que deve ser digitado para jogar o jogo. Cada citação é obtida uma a uma de uma matriz predefinida. Uma variável rastreia o índice de cotação atual e o incrementa sempre que um novo é solicitado.
  • Dividindo os caracteres em elementos
    Cada um dos caracteres do texto é separado em uma série de <span>elementos. Isso torna possível alterar individualmente a cor de cada caractere, dependendo se ele foi digitado corretamente pelo usuário. Esses elementos são anexados a uma variável quote_text.
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;
}

Etapa 3: Obter o texto digitado atualmente pelo usuário

processCurrentText()É definida uma função que será chamada sempre que o usuário digitar ou alterar qualquer coisa na caixa de entrada. Portanto, é usado com o oninputmanipulador de eventos da caixa de entrada. Esta função lida com o seguinte:

  • Obtendo o valor atual da caixa de entrada
    A valuepropriedade da área de entrada é usada para obter o texto atual digitado pelo usuário. Ele é dividido em uma matriz de caracteres para comparar com o texto de citação. Isso é armazenado em curr_input_array.
  • Colorindo os caracteres do texto da citação
    Os caracteres da citação exibida são coloridos de 'vermelho' ou 'verde', dependendo se ela foi digitada corretamente. Isso é feito selecionando os elementos span da citação que criamos anteriormente e percorrendo-os. O elemento então aplicou as classes criadas acima dependendo se ele corresponde ao texto digitado.
  • Calculando os erros e a precisão
    Cada vez que o usuário comete um erro durante a digitação, a errorsvariável é incrementada. Isso é usado para calcular o valor de precisão, dividindo o número de caracteres digitados corretamente pelo número total de caracteres digitados pelo usuário.
  • Movendo para a próxima citação
    Quando o comprimento do texto de entrada corresponde ao comprimento do texto da citação, a updateQuote()função é chamada, o que altera a citação e limpa a área de entrada. O número total de erros também é atualizado para ser usado na próxima cotação.
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

A coloração dos personagens baseou sua correção

Etapa 4: Iniciando um novo jogo

startGame()É definida uma função que será chamada quando o usuário focar na caixa de entrada. Portanto, é usado com o onfocusmanipulador de eventos da caixa de entrada. Esta função lida com o seguinte:

  • Redefinir todos os valores
    Todos os valores são redefinidos para os valores padrão antes do início de um novo jogo. Criamos uma função diferente chamada resetValues()que trata disso.
  • Atualizar o texto da cotação
    Um novo texto da cotação é preparado e exibido chamando a updateQuote()função.
  • Criando um novo cronômetro
    Um cronômetro registra o número de segundos restantes e o exibe para o usuário. Ele é criado usando o setInterval()método que chama repetidamente a updateTimer()função definida abaixo. Antes de criar um novo cronômetro, a instância anterior do cronômetro é apagada usando clearInterval().
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";
}

Etapa 5: Atualizar o cronômetro

updateTimer()É definida uma função que será chamada a cada segundo para controlar o tempo. Esta função lida com o seguinte:

  • Atualizar os valores de tempo
    Todas as variáveis ​​que controlam o tempo são atualizadas. O timeLeftvalor é decrementado, o timeElapsedvalor é incrementado e o texto do cronômetro é atualizado para o tempo restante atual.
  • Terminando o jogo
    Esta parte é acionada quando o tempo limite é atingido. Ele chama a finishGame()função definida abaixo que termina o jogo.
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();
  }
}

Etapa 6: Finalizando o jogo

finishGame()É definida uma função que será invocada quando o jogo tiver que ser finalizado. Esta função lida com o seguinte:

  • Excluindo o cronômetro
    A instância do cronômetro criada antes é removida.
  • Exibindo o texto e o botão de reinicialização do jogo
    O texto citado exibido ao usuário é alterado para um que indica que o jogo acabou. O botão 'Reiniciar' também é exibido definindo a propriedade de exibição para 'bloquear'.
  • Calculando o CPM e WPM da sessão atual
    1. O número de caracteres por minuto (CPM) é calculado dividindo-se o número total de caracteres digitados com o tempo decorrido e multiplicando o resultado por 60. É arredondado para evitar pontos decimais.
    2. As palavras por minuto (WPM) são calculadas dividindo o CPM por 5 e multiplicando o resultado por 60. O 5 denota o número médio de caracteres por palavra. É arredondado para evitar casas decimais.
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";
}

Demonstração Final

O jogo agora está pronto para ser jogado em qualquer navegador.

out-full2-min

Código-fonte: https://github.com/sayantanm19/js-simple-typing-game