simple-type-game 简单的打字游戏

751 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 2 天,点击查看活动详情

前言

看了微软的一个课程 “Web Development for Beginners - A Curriculum” 中的一篇后,打算用 vue3.2 来重写,并小小的优化一下,当做课外作业了。

一、需求整理

目标:打字计时、记录成绩

功能点:

  • 目标文字和输入文字实时对比结果
  • 计时
  • 成绩记录
  • 切换目标文字
  • 支持中英文对比

二、流程设计

  1. 用户键入文字开始计时,目标文字根据对比结果渲染不一样的颜色,当输入文字与目标文字全都对比成功时,提示输入完成,烟花庆祝。
  2. 用户输入过程中,有一个文字(包括空格)对比失败,目标文字行会在对应位置提示。
  3. 当用户清除全部键入文字,会重新计时。
  4. 切换目标文字或切换语言会清空输入状况和计时
  5. 成绩持久化存储

三、数据结构设计

注意,以下 JS 代码都写在 helloword 组件的 标签中

const quote = ref('') //目标文字
const willInput = ref('')  // 输入文字
const successList = ref([]) // 文字对比结果列表
const language = ref('english') // 中英文切换
const quotes = reactive(priQuotes[language.value]) // 句子列表
const startTime = ref(new Date().getTime()) // 开始时间
// 知识点:vue3用ref创造基本类型的响应式变量,用reactive创造对象类型的响应式变量 

更多有关 Vue3 ref 的请点击 此链接

接着我们先编写第一段逻辑,初始化,看代码:

import { onMounted, ref, watch, computed, reactive } from 'vue'

const initPage = () => {
  willInput.value = '';
  quote.value = quotes[Math.floor(Math.random() * quotes.length)];
  successList.value = new Array(quote.value.length).fill(undefined);
}

// mounted生命周期,vue3.2里在setup script里用onMounted调用
onMounted(() => {
  initPage();
})

这段代码的作用很简单,就是初始化我们的目标文字,和将我们的数据框清空,判断结果设置为未判断,很简单的做了这些事,接着我们下一步

当开始键入文字的时候,我们开始计时

// 初始化开始时间
const startTime = ref(new Date().getTime())
const spendTime = ref(0)
// 比较输入的文字和quote的文字
const compare = () => {
  // 开始输入文字记录当前时间
  // 被清空重新计时
  if (willInput.value.length === 1 || willInput.value.length === 0) {
    startTime.value = new Date().getTime();
  }
  // 比较当前的文字即可
  for (let i = 0; i < quote.value.length; i++) {
    if (i >= willInput.value.length) {
      successList.value[i] = undefined;
      continue;
    }
    if (quote.value[i] === willInput.value[i]) {
      successList.value[i] = true;
    } else {
      successList.value[i] = false;
    }
  }
}
const showBoom = ref(false)

// 观测输入的文字,每次输入都会触发,成功提示
watch(successList, (nVal, oVal) => {
  if (nVal.every(item => item === true)) {
    spendTime.value = (new Date().getTime() - startTime.value) / 1000;
    updateFastTime();
    message.success(`恭喜你,用时${spendTime.value}秒完成!`);
    showBoom.value = true;
    initPage();
    const timer = setTimeout(() => {
      showBoom.value = false;
      localStorage.setItem('fastestTimeMap', JSON.stringify(fastestTimeMap));
      clearTimeout(timer);
    }, 2000);
  }
}, {
  deep: true
})
const updateFastTime = () => {
  const temp = fastestTimeMap[quote.value];
  if (temp && temp > spendTime.value) {
    fastestTimeMap[quote.value] = spendTime.value;
  } else if (!temp) {
    fastestTimeMap[quote.value] = spendTime.value;
  }
}
const handleFaker = () => {
  window.alert('不要作弊');
  initPage();
}

<template>
  ...
      <div class="quote text-item">
      <span v-for="(quo, idx) of quote" :key="idx" :idx="idx"
        :class="{ 'success': successList[idx] === true, 'error': successList[idx] === 
          false, 'bg-error': successList[idx] === false && quo === ' ' }">
          {{ quo }}</span>
    </div>
    <input class="keyboard-text text-item" autofocus v-model="willInput" 
      @input="compare" @paste.prevent="handleFaker"
      :maxlength="quote.length" />
     <div class="boom" v-show="showBoom">
      <div class="fire"></div>
      <div class="fire2"></div>
     </div>
  ...
</template>

这里我们把目标文字拆开为单个字符,用span标签渲染,并根据输入的文字是否和当前字符相同(successList[idx])来动态添加 class,然后组织了粘贴行为 handleFaker,烟花爆竹效果 showBoom,记录成绩fastestTimeMap

到这里字符比较的核心逻辑已经完成,接下来我们完善这整个流程,添加切换文字和显示成绩功能

// 切换目标文字
const changeQuote = () => {
  willInput.value = '';
  const temp = quotes.map(item => {
    if (item !== quote.value) {
      return item;
    }
  }).filter(item => item !== undefined);
  quote.value = temp[Math.floor(Math.random() * temp.length)];
  successList.value = new Array(quote.value.length).fill(undefined);
}
// 切换语言版本
const changeLanguage = () => {
  language.value = language.value === 'english' ? '中文' : 'english';
  quotes = priQuotes[language.value];
  initPage();
}
const fastestTimeMap = JSON.parse(localStorage.getItem('fastestTimeMap')) || {};
const fastestTime = computed(() => {
  return fastestTimeMap[quote.value] || 0;
})
<template>
      <div class="header" :style="{ background: 'rgb(190, 200, 200)', 
        padding: '26px 16px 16px' }">
      <a-button type="dashed" ghost @click="changeQuote">换一个</a-button>
      <a-button type="dashed" @click="changeLanguage">{{ language }}</a-button>
      <a-button type="primary" disabled>
        本题个人最快速度:<span>{{ fastestTime }}</span> &nbsp;秒!
      </a-button>
    </div>
</template>

在这块我们添加了切换目标文字 changeQuote 和切换中英文功能 changeLanguage,以及显示当前文字历史最快记录 fastestTime

在给页面元素加点样式我们就能得到一个简单的打字游戏了

体验地址

代码仓库

希望大家来点 CR