diff --git a/4-typing-game/translations/README.zh-tw.md b/4-typing-game/translations/README.zh-tw.md new file mode 100644 index 00000000..d7b62c81 --- /dev/null +++ b/4-typing-game/translations/README.zh-tw.md @@ -0,0 +1,30 @@ +# 事件驅動程式設計 ── 建立一款打字遊戲 + +## 大綱 + +打字可說是開發者被嚴重低估的技能之一,將腦中的想法快速地轉換到編輯器中,讓你流暢地發揮你的想像力。其中一個訓練方法就是遊玩遊戲! + +> 因此,讓我們來開發一款打字遊戲吧! + +你會使用到你學到的 JavaScript、HTML 與 CSS 技法來建立打字遊戲。這款遊戲提供隨機的引文作為玩家的目標(使用[夏洛克·福爾摩斯](https://zh.wikipedia.org/wiki/%E6%AD%87%E6%B4%9B%E5%85%8B%C2%B7%E7%A6%8F%E5%B0%94%E6%91%A9%E6%96%AF)的引文),計算玩家準確輸入所需要花費的時間。 + +![demo](../images/demo.gif) + +## 開始之前 + +這堂課會假設你已經熟悉下列的概念: + +- 建立文字輸入及按鈕控制 +- CSS 與 class 的造型設定 +- JavaScript 基礎觀念 + - 建立矩陣 + - 建立隨機數 + - 取得目前時間 + +## 課程 + +[使用事件驅動程式設計,建立一款打字遊戲](../typing-game/translations/README.zh-tw.md) + +## 參與人員 + +由 [Christopher Harrison](http://www.twitter.com/geektrainer) 用滿滿的 ♥️ 來編寫。 diff --git a/4-typing-game/typing-game/translations/README.zh-tw.md b/4-typing-game/typing-game/translations/README.zh-tw.md new file mode 100644 index 00000000..797e8ee3 --- /dev/null +++ b/4-typing-game/typing-game/translations/README.zh-tw.md @@ -0,0 +1,339 @@ +# 使用事件建立遊戲 + +## 課前測驗 + +[課前測驗](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/21) + +## 事件驅動程式設計 + +當我們建立專為瀏覽器設計的應用程式時,我們會提供 Graphical User Interface (GUI) 給用戶使用,在我們建立的格式上進行互動。最常見的互動方式是透過點擊或輸入在多樣的物件。開發者面臨的問題是,我們不了解用戶會何時對這些物件產生互動! + +[事件驅動程式設計](https://zh.wikipedia.org/zh-tw/%E4%BA%8B%E4%BB%B6%E9%A9%85%E5%8B%95%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88)是一種程式設計的方式,以建立我們的 GUI。若拆解該名詞的話,我們知道主軸關鍵會是**事件(Event)**。根據 Merriam-Webster,[事件](https://www.merriam-webster.com/dictionary/event)according定義為「將發生的事」。它能有效地解決我們面臨的問題。我們知道當用戶產生互動時,什麼程式必須回應其要求,只差在我們不知道用戶會何時產生互動。 + +藉由建立新的函式,我們可以標記這段將被運行的程式碼。我們回顧一下[程序式程式設計](https://zh.wikipedia.org/wiki/%E8%BF%87%E7%A8%8B%E5%BC%8F%E7%BC%96%E7%A8%8B),函式會依照順序一行一行的被運行。這同樣也會被實踐在事件驅動程式設計上,差別在於**如何**去呼叫這些函式。 + +要處理這些事件:點擊按鈕、輸入字串等等,我們需註冊**事件監聽者(Event Listeners)**。事件監聽者是函式之一,負責回應當事件觸發時,提供相對應的回應。事件監聽者可以根據用戶的行為,更新使用者介面,呼叫伺服器,或是任何你想要它做的事。我們利用[addEventListener](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)新增事件監聽者,提供要被運行的函式。 + +> **注意** 值得注意我們有許多建立事件監聽者的方式。你可以使用匿名函式(anonymous functions),或是有名字的;你可以使用多種的快捷,好比直接設定 `click` 屬性,或使用 `addEventListener`。在我們練習過程中,主要專注在 `addEventLister` 與匿名函式上,它們可能是開發者最常見的網頁開發技巧。同時,也是彈性最高的: `addEventListener` 作用在任何事件,任何以參數方式輸入的事件名稱。 + +### 常見事件 + +創造應用時,這邊有[數種事件](https://developer.mozilla.org/docs/Web/Events)提供給你監聽。基本上,使用者在網頁上做的任何行為都會觸發事件,你需要花大量時間、大量精力確保它們有相對應的使用者體驗。幸運的是,你只需要處理少部分的事件類型。這邊是一些常見的事件類型,我們會使用其中兩種來建立遊戲: + +- [點擊](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event): 使用者點擊物件,通常會是按鈕或是連結。 +- [右鍵選單](https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event): 使用者點擊滑鼠右鍵。 +- [選取](https://developer.mozilla.org/en-US/docs/Web/API/Element/select_event): 使用者標記特定文字。 +- [輸入](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event): 使用者輸入文字。 + +## 建立遊戲 + +現在我們藉由建立遊戲,了解事件是如何在 JavaScript 上運作的。我們的遊戲會測試玩家的打字技巧,一項程式開發員被忽略的技能之一。我們應該時刻練習打字技術!大致的遊戲流程如下: + +- 玩家點擊「開始」按鈕並產生一行要被輸入的引文 +- 玩家盡快地輸入這段文字到文字框中 + - 當單字輸入完畢時,立即標記下一個單字。 + - 當玩家打錯字時,將文字框轉為紅色。 + - 當玩家完成引文輸入時,顯示祝賀語與花費的時間。 + +讓我們開始建立遊戲,學習事件驅動吧! + +### 檔案結構 + +我們總共需要三個檔案:**index.html**、**script.js**與**style.css**。我們來設定它們,以完成後續的步驟。 + +- 建立新的資料夾存放我們的遊戲,開啟 Console 或是終端機,輸入下列指令: + +```bash +# Linux 或 macOS +mkdir typing-game && cd typing-game + +# Windows +md typing-game && cd typing-game +``` + +- 打開文字編輯器 Visual Studio Code + +```bash +code . +``` + +- 現在,在 Visual Studio Code 中新增三個檔案到資料夾中,分別為: + - index.html + - script.js + - style.css + +## 建立使用者介面 + +藉由回顧我們的需求,我們在 HTML 頁面上新增一些元素。這就像是看一份食譜,你需要對應的食材: + +- 一個地方呈現將被輸入的引文 +- 一個地方呈現任何訊息,好比祝賀文 +- 一個玩家輸入的文字框 +- 一個開始按鈕。 + +每一個物件都需要 ID ,讓 JavaScript 程式能控制它們。另外,在 HTML 檔案匯入 CSS 與 JavaScript 檔,我們等一下會編輯它們。 + +在新的 **index.html** 檔案中,加入下列程式碼: + +```html + + + + Typing game + + + +

Typing game!

+

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),點擊連結中的 **Install** + - 瀏覽器要求開啟 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。我們需要標記玩家需要輸入的單字,若單字輸入錯誤時需要改變文字框的顏色。利用兩組 class 來完成: + +在檔案 **style.css** 加入下列語法: + +```css +/* 在 style.css 中 */ +.highlight { + background-color: yellow; +} + +.error { + background-color: lightcoral; + border: red; +} +``` + +✅ 處理 CSS 時,你可以規劃任何你想要的介面布局。花點時間讓你的網頁更迷人: + +- 變更其他字型 +- 改變標題顏色 +- 改變物件大小 + +## JavaScript + +建立完使用者介面後,我們要專注在 JavaScript 上,提供網頁邏輯處理的能力。我們將工作分為下列步驟: + +- [建立常數](#建立常數) +- [事件監聽者 - 開始遊戲](#加入開始邏輯) +- [事件監聽者 - 輸入文字](#加入打字邏輯) + +首先,我們先編輯檔案 **script.js**。 + +### 建立常數 + +加入一些變數給程式使用。同樣地,就像食譜一樣,我們需要的食材如下: + +- 矩陣,儲存所有引文 +- 空矩陣,儲存單一引文的所有單字 +- 變數,儲存空矩陣的索引,標記玩家現在面對的單字 +- 變數,紀錄玩家點擊開始時的時間 + +我們也需要將使用者介面上的物件做連結: + +- 文字框 (**typed-value**) +- 顯示引文 (**quote**) +- 訊息欄 (**message**) + +```javascript +// 在檔案 script.js 中 +// 所有的引文內容 +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.', +]; +// 儲存單字列表及目前要輸入的單字索引 +let words = []; +let wordIndex = 0; +// 開始時間 +let startTime = Date.now(); +// 網頁物件連結 +const quoteElement = document.getElementById('quote'); +const messageElement = document.getElementById('message'); +const typedValueElement = document.getElementById('typed-value'); +``` + +✅ 試著加入更多的引文到你的遊戲中。 + +> **筆記** 我們可以接收任何物件,只要使用程式碼 `document.getElementById`。因為我們需要定期參考這些元素,所以使用常數來確認是否有單字輸入錯誤的問題。框架如[Vue.js](https://vuejs.org/)或[React](https://reactjs.org/)可以幫助你更好管理你的程式碼。 + +花點時間觀看下列關於 `const`、`let` 與 `var` 的影片。 + +[![變數類型](https://img.youtube.com/vi/JNIXfGiDWM8/0.jpg)](https://youtube.com/watch?v=JNIXfGiDWM8 "變數類型") + +> 點擊上方圖片以觀賞關於變數的影片。 + +### 加入開始邏輯 + +為了開始我們的遊戲,玩家會點擊開始按鈕。當然,我們不知道何時玩家會開始遊戲,這就是為什麼我們使用[事件監聽者](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)到程式中。一個事件監聽者允許我們監看事件的觸發與對應的回應程式。在這個例子,我們希望當使用者點擊開始時,執行某些程式。 + +當玩家點擊 **start** 按鈕後,我們需要挑選一段引文、設定使用者介面並追蹤現在玩家的要輸入的單字與時間。下列為我們需要新增的程式碼,我們會在之後逐行解釋。 + +```javascript +// 在 script.js 末端 +document.getElementById('start').addEventListener('click', () => { + // 取得一行引文 + const quoteIndex = Math.floor(Math.random() * quotes.length); + const quote = quotes[quoteIndex]; + // 將引文分成許多單字,存在矩陣中。 + words = quote.split(' '); + // 重制單字索引來做追蹤 + wordIndex = 0; + + // 更新使用者介面 + // 建立 span 元素的矩陣,設定 class 用。 + const spanWords = words.map(function(word) { return `${word} `}); + // 轉換成字串並以 innerHTML 顯示引文 + quoteElement.innerHTML = spanWords.join(''); + // 標記第一個單字 + quoteElement.childNodes[0].className = 'highlight'; + // 清除訊息欄之前的訊息 + messageElement.innerText = ''; + + // 設定文字框 + // 清除文字框 + typedValueElement.value = ''; + // 設定 focus + typedValueElement.focus(); + // 設定事件驅動程式 + + // 開始計時器 + 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,玩家會從第一的單字開始輸入 +- 設定使用者介面 + - 建立矩陣 `spanWords`,將每一個單字包在 `span` 元素中 + - 這讓我們能高光標記單字 + - `join` 矩陣來建立字串,我們可以在 `quoteElement` 上更新 `innerHTML` + - 這會顯示引文給玩家檢視 + - 設定第一個 `span` 元素的 `className` 成 `highlight`,來標記單字呈黃色 + - 修改 `messageElement`的 `innerText` 成 `''`,這會清除訊息欄的內容 +- 設定文字框 + - 清除目前 `typedValueElement` 的 `value` + - 設定 `typedValueElement` 成 `focus` +- 呼叫 `getTime` 來啟始計時器 + +### 加入打字邏輯 + +當玩家開始打字時,`input` 事件會被觸發。對應的事件監聽者需要檢查玩家是否輸入正確的單字,監控目前的遊戲狀況。回到檔案 **script.js**,加入下方程式碼到檔案最下方。我們會在後續解釋程式碼。 + +```javascript +// script.js 最末端 +typedValueElement.addEventListener('input', () => { + // 取得目前的單字 + const currentWord = words[wordIndex]; + // 取得目前輸入的數值 + const typedValue = typedValueElement.value; + + if (typedValue === currentWord && wordIndex === words.length - 1) { + // 句子最末端 + // 顯示成功 + 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) { + // 單字最末端 + // 清除輸入的數值,準備給新的單字使用 + typedValueElement.value = ''; + // 移動到下一個單字 + wordIndex++; + // 重設所有引文子元素的 class 名稱 + for (const wordElement of quoteElement.childNodes) { + wordElement.className = ''; + } + // 標記新單字 + quoteElement.childNodes[wordIndex].className = 'highlight'; + } else if (currentWord.startsWith(typedValue)) { + // 單字目前輸入正確 + // 標記下一個單字 + typedValueElement.className = ''; + } else { + // 單字輸入錯誤 + typedValueElement.className = 'error'; + } +}); +``` + +讓我們分解程式碼吧!我們開始取得目前的單字與玩家輸入的數值。我們建立一系列的邏輯,檢查引文是否輸入完成,單字是否輸入完成,單字是否正確、是否錯誤。 + +- 引文完成,檢查 `typedValue` 與 `currentWord` 相等且 `wordIndex` 與 `words` 的 `length` 減一相等。 + - 計算 `elapsedTime` ,利用目前時間減去 `startTime` 取得遊戲時長 + - `elapsedTime` 除以 1,000 ,轉化毫秒單位為秒單位 + - 顯示成功訊息 +- 單字完成,以 `typedValue` 間的空白為界,檢查 `typedValue` 是否與 `currentWord` 相等 + - 設定 `typedElement` 的 `value` 成 `''` ,準備給下一個單字輸入進來 + - 增加 `wordIndex` 到下一個單字 + - 進迴圈,每一個 `quoteElement` 的 `childNodes` ,它們的 `className` 都被設為 `''` ,代表預設的單字呈現規則 + - 設定單字的 `className` 成 `highlight` 來標記為下一個被輸入的單字 +- 單字目前輸入正確但未完成,從 `typedValue` 開始檢查 `currentWord` + - 確保清除 `typedValueElement` 的 `className`,顯示預設的呈現方式。 +- 若此時輸入錯誤,我們加上錯誤規則 + - 設定 `typedValueElement` 的 `className` 成 `error` + +## 測試你的應用程式 + +我們做到最後了!最後一步就是確保我們的應用程式運作正常。試試看!不要擔心程式出現錯誤,**所有的開發者**都會面臨錯誤。有需要時,檢查程式訊息並偵錯。 + +點擊按鈕 **start**,馬上開始輸入單字!你可以看看這預覽動畫。 + +![遊戲中的動畫](../4-typing-game/images/demo.gif) + +--- + +## 🚀 挑戰 + +加入更多功能。 + +- 在完成遊戲時,關閉 `input` 事件監聽者;遊戲重新開始時,再重新開啟它。 +- 當玩家完成引文時,關閉文字框 +- 以對話窗格的方式顯示恭賀訊息 +- 利用[localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage)儲存最高分的資料 + +## 課後測驗 + +[課後測驗](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/22) + +## 複習與自學 + +在瀏覽器上閱讀[所有開發者可運用的事件](https://developer.mozilla.org/en-US/docs/Web/Events),想想你能在什麼樣的場合使用各個事件。 + +## 作業 + +[建立一款新的鍵盤遊戲](assignment.zh-tw.md) diff --git a/4-typing-game/typing-game/translations/assignment.zh-tw.md b/4-typing-game/typing-game/translations/assignment.zh-tw.md new file mode 100644 index 00000000..a85f9f8b --- /dev/null +++ b/4-typing-game/typing-game/translations/assignment.zh-tw.md @@ -0,0 +1,11 @@ +# 建立一款新的鍵盤遊戲 + +## 簡介 + +建立一款使用鍵盤事件的小遊戲。它可以是不同的鍵盤輸入遊戲:使用鍵盤在視窗上繪製像素點的繪圖遊戲。激發你的創意吧! + +## 學習評量 + +| 作業內容 | 優良 | 普通 | 待改進 | +| -------- | ------------------ | ------------ | ------------ | +| | 呈現完整的遊戲內容 | 遊戲內容單調 | 遊戲出現問題 |