Practice your typing skills with a quote from Sherlock Holmes. Click **start** to begin!
```
### 启动应用程序
开发时最好采用迭代方式,随时查看效果。让我们启动应用程序。Visual Studio Code 有一个非常棒的扩展 [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer),它可以在本地托管你的应用程序,并在每次保存时刷新浏览器。
- 按照链接安装 [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
- 浏览器会提示你打开 Visual Studio Code,然后 Visual Studio Code 会提示你进行安装
- 如果有提示,请重启 Visual Studio Code
- 安装完成后,在 Visual Studio Code 中按 Ctrl-Shift-P(或 Cmd-Shift-P)打开命令面板
- 输入 **Live Server: Open with Live Server**
- Live Server 会开始托管你的应用程序
- 打开浏览器,导航到 **https://localhost:5500**
- 你现在应该能看到刚刚创建的页面!
接下来让我们添加一些功能。
## 添加 CSS
HTML 创建完成后,我们需要添加核心样式的 CSS。我们需要高亮玩家应该输入的单词,并在玩家输入错误时为文本框着色。我们将通过两个类来实现这些功能。
创建一个名为 **style.css** 的新文件,并添加以下语法。
```css
/* inside style.css */
.highlight {
background-color: yellow;
}
.error {
background-color: lightcoral;
border: red;
}
```
✅ 关于 CSS,你可以根据自己的喜好设计页面布局。花点时间让页面更美观:
- 选择不同的字体
- 为标题添加颜色
- 调整元素大小
## JavaScript
用户界面创建完成后,我们将重点放在提供逻辑的 JavaScript 上。我们将把它分解为几个步骤:
- [创建常量](../../../../4-typing-game/typing-game)
- [添加开始游戏的事件监听器](../../../../4-typing-game/typing-game)
- [添加输入事件监听器](../../../../4-typing-game/typing-game)
首先,创建一个名为 **script.js** 的新文件。
### 创建常量
我们需要一些内容来简化编程。类似于做菜的配料清单,我们需要以下内容:
- 包含所有句子的数组
- 用于存储当前句子中所有单词的空数组
- 用于存储玩家当前输入单词索引的变量
- 玩家点击开始按钮的时间
我们还需要引用以下用户界面元素:
- 文本框(**typed-value**)
- 显示句子的区域(**quote**)
- 显示消息的区域(**message**)
```javascript
// inside script.js
// all of our quotes
const quotes = [
'When you have eliminated the impossible, whatever remains, however improbable, must be the truth.',
'There is nothing more deceptive than an obvious fact.',
'I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.',
'I never make exceptions. An exception disproves the rule.',
'What one man can invent another can discover.',
'Nothing clears up a case so much as stating it to another person.',
'Education never ends, Watson. It is a series of lessons, with the greatest for the last.',
];
// store the list of words and the index of the word the player is currently typing
let words = [];
let wordIndex = 0;
// the starting time
let startTime = Date.now();
// page elements
const quoteElement = document.getElementById('quote');
const messageElement = document.getElementById('message');
const typedValueElement = document.getElementById('typed-value');
```
✅ 为你的游戏添加更多句子
> **NOTE:** 我们可以通过 `document.getElementById` 在代码中随时获取这些元素。由于我们会经常引用这些元素,为了避免字符串拼写错误,我们使用常量来管理它们。像 [Vue.js](https://vuejs.org/) 或 [React](https://reactjs.org/) 这样的框架可以帮助你更好地集中管理代码。
花点时间观看关于 `const`、`let` 和 `var` 的视频
[](https://youtube.com/watch?v=JNIXfGiDWM8 "变量类型")
> 🎥 点击上方图片观看关于变量的视频。
### 添加开始逻辑
游戏开始时,玩家会点击开始按钮。当然,我们无法预测玩家何时点击开始按钮。这时 [事件监听器](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) 就派上用场了。事件监听器允许我们监听某些事件的发生,并在事件发生时执行代码。在我们的例子中,我们希望在用户点击开始按钮时执行代码。
当用户点击 **开始** 时,我们需要选择一个句子,设置用户界面,并初始化当前单词和计时的追踪。以下是需要添加的 JavaScript,我们将在代码块后进行讲解。
```javascript
// at the end of script.js
document.getElementById('start').addEventListener('click', () => {
// get a quote
const quoteIndex = Math.floor(Math.random() * quotes.length);
const quote = quotes[quoteIndex];
// Put the quote into an array of words
words = quote.split(' ');
// reset the word index for tracking
wordIndex = 0;
// UI updates
// Create an array of span elements so we can set a class
const spanWords = words.map(function(word) { return `${word} `});
// Convert into string and set as innerHTML on quote display
quoteElement.innerHTML = spanWords.join('');
// Highlight the first word
quoteElement.childNodes[0].className = 'highlight';
// Clear any prior messages
messageElement.innerText = '';
// Setup the textbox
// Clear the textbox
typedValueElement.value = '';
// set focus
typedValueElement.focus();
// set the event handler
// Start the timer
startTime = new Date().getTime();
});
```
让我们分解代码!
- 设置单词追踪
- 使用 [Math.floor](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/floor) 和 [Math.random](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random) 随机选择 `quotes` 数组中的一个句子
- 将 `quote` 转换为 `words` 数组,以便追踪玩家当前输入的单词
- 将 `wordIndex` 设置为 0,因为玩家从第一个单词开始
- 设置用户界面
- 创建一个包含每个单词的 `span` 元素的 `spanWords` 数组
- 这样我们可以高亮显示当前单词
- 使用 `join` 方法将数组转换为字符串,并更新 `quoteElement` 的 `innerHTML`
- 这会将句子显示给玩家
- 将第一个 `span` 元素的 `className` 设置为 `highlight`,以黄色高亮显示
- 通过将 `messageElement` 的 `innerText` 设置为空字符串来清空消息区域
- 设置文本框
- 清空 `typedValueElement` 的当前 `value`
- 将焦点设置到 `typedValueElement`
- 通过调用 `getTime` 开始计时
### 添加输入逻辑
当玩家输入时,会触发 `input` 事件。这个事件监听器将检查玩家输入是否正确,并处理游戏的当前状态。在 **script.js** 文件末尾添加以下代码。我们将在代码块后进行讲解。
```javascript
// at the end of script.js
typedValueElement.addEventListener('input', () => {
// Get the current word
const currentWord = words[wordIndex];
// get the current value
const typedValue = typedValueElement.value;
if (typedValue === currentWord && wordIndex === words.length - 1) {
// end of sentence
// Display success
const elapsedTime = new Date().getTime() - startTime;
const message = `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.`;
messageElement.innerText = message;
} else if (typedValue.endsWith(' ') && typedValue.trim() === currentWord) {
// end of word
// clear the typedValueElement for the new word
typedValueElement.value = '';
// move to the next word
wordIndex++;
// reset the class name for all elements in quote
for (const wordElement of quoteElement.childNodes) {
wordElement.className = '';
}
// highlight the new word
quoteElement.childNodes[wordIndex].className = 'highlight';
} else if (currentWord.startsWith(typedValue)) {
// currently correct
// highlight the next word
typedValueElement.className = '';
} else {
// error state
typedValueElement.className = 'error';
}
});
```
让我们分解代码!我们首先获取当前单词和玩家目前输入的内容。然后通过一系列逻辑检查,判断句子是否完成、单词是否完成、单词是否正确,或者是否存在错误。
- 句子完成:当 `typedValue` 等于 `currentWord` 且 `wordIndex` 等于 `words` 长度减一时
- 通过将当前时间减去 `startTime` 计算 `elapsedTime`
- 将 `elapsedTime` 除以 1,000,将毫秒转换为秒
- 显示成功消息
- 单词完成:当 `typedValue` 以空格结尾(表示单词结束)且 `typedValue` 等于 `currentWord` 时
- 将 `typedElement` 的 `value` 设置为空,以便输入下一个单词
- 增加 `wordIndex`,以移动到下一个单词
- 遍历 `quoteElement` 的所有 `childNodes`,将 `className` 设置为空,恢复默认显示
- 将当前单词的 `className` 设置为 `highlight`,标记为下一个需要输入的单词
- 当前单词输入正确(但未完成):当 `currentWord` 以 `typedValue` 开头时
- 通过清空 `className` 确保 `typedValueElement` 显示为默认状态
- 如果以上条件都不满足,则存在错误
- 将 `typedValueElement` 的 `className` 设置为 `error`
## 测试你的应用程序
你已经完成了!最后一步是确保我们的应用程序能够正常运行。试试看吧!如果出现错误也不用担心,**所有开发者**都会遇到错误。检查错误信息并进行调试。
点击 **开始**,然后开始输入!它应该看起来像我们之前看到的动画。

---
## 🚀 挑战
添加更多功能
- 在完成游戏时禁用 `input` 事件监听器,并在点击按钮时重新启用
- 在玩家完成句子时禁用文本框
- 显示一个模态对话框,包含成功消息
- 使用 [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) 存储高分
## 课后测验
[课后测验](https://ff-quizzes.netlify.app/web/quiz/22)
## 复习与自学
阅读 [浏览器中可用的所有事件](https://developer.mozilla.org/docs/Web/Events),并思考每种事件适用的场景。
## 作业
[创建一个新的键盘游戏](assignment.md)
**免责声明**:
本文档使用AI翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们努力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。应以原始语言的文档作为权威来源。对于关键信息,建议使用专业人工翻译。我们对于因使用此翻译而引起的任何误解或误读不承担责任。