前 四 課 ,我 們 學 會 了 使 用 Arduino UNO 來 亮 著 LED 和 如 何 在 Arduino 上 使 用 push button,是 時 候 用 這 些 知 識 來 製 作 小 遊 戲 。
教 孩 子 programming,給 孩 子 功 課 ,要 孩 子 自 己 去 寫 program,其 實 就 是 在 訓 練 小 朋 友 的 創 意 。大 家 應 該 都 了 解 ,創 意 並 不 是 花 幾 千 蚊 去 買 一 盒 頂 級 lego 回 來 砌 完 佢 ,那 只 是 鸚 鵡 學 舌 ,只 是 模 仿 。
等 一 等 ,喂 ,只 有 button 和 LED 兩 樣 野 ,可 以 玩 什 麼 遊 戲 呀 ?
就 像 筆 者 兒 時 的 校 歌 ,「艱 險 我 奮 進 ,困 乏 我 多 情 」,創 意 是 不 可 能 被 貧 乏 限 制 的 。我 們 小 時 候 一 顆 波 子 一 個 衣 夾 ,不 是 亦 會 玩 得 不 亦 樂 乎 麼 ?有 一 堆 LED 和 一 個 push button,再 加 上 一 塊 programmable 的 Arduino,應 該 夠 小 朋 友 發 揮 無 限 想 像 力 的 了 。
筆 者 從 來 只 相 信 身 教 ,你 希 望 孩 子 有 創 意 ,唯 一 的 方 法 就 是 你 自 己 先 要 有 創 意 。嘿 嘿 ,我 們 來 一 起 做 一 個 考 反 應 的 小 遊 戲 吧 。
我 們 先 建 立 好 上 面 的 電 路 吧 ,1 個 push button 用 pull down 的 方 法 接 在 pin 2,作 為 輸 入 。然 後 ,5 顆 LED 分 別 接 到 pins 8 – 12。
其 實 我 們 應 該 是 先 設 計 好 遊 戲 ,再 去 著 手 設 計 電 路 的 。在 這 裡 ,我 先 把 電 路 畫 出 來 ,是 因 為 可 以 方 便 我 們 去 解 說 遊 戲 的 邏 輯 。
1. 首 先 ,遊 戲 會 進 入 待 機 狀 態 ,等 待 玩 家 。這 時 候 單 數 LED 和 雙 數 LED 會 輪 流 閃 亮 。此 時 如 果 有 人 按 動 push button,遊 戲 就 會 開 始 。
2. 遊 戲 開 始 ,LED 會 由 左 至 右 不 停 循 環 閃 動 ,然 後 玩 家 要 在 「中 間 」的 LED 亮 著 時 按 動 push button。
3. 如 果 成 功 ,遊 戲 會 進 入 下 一 個 level,LED 閃 動 會 加 快 。
4. 如 果 失 敗 ,遊 戲 會 停 留 在 這 一 個 level,從 新 開 始 。
5. 遊 戲 會 總 共 進 行 5 次 ,完 成 之 後 ,會 由 LED 顯 示 分 數 。如 果 全 錯 就 沒 有 LED,如 果 按 對 兩 次 ,就 亮 2 顆 LED,如 此 類 推 。
6. 報 完 分 數 ,遊 戲 返 回 待 機 狀 態 。
遊 戲 的 邏 輯 很 好 理 解 吧 ,現 在 要 如 何 把 這 個 邏 輯 轉 化 為 程 序 呢 ?
因 為 唔 知 道 玩 家 會 幾 時 按 按 鈕 ,所 以 程 序 會 是 一 個 無 限 循 環 ,去 檢 查 幾 時 有 玩 家 按 了 按 鈕 。
我 會 先 建 立 一 個 變 數 ,叫 gameState。0 就 是 待 機 ,1 就 是 第 一 次 遊 戲 ,2 就 是 第 二 次 遊 戲 ,…,5 就 是 最 後 一 round,6 就 是 顯 示 分 數 。6 之 後 就 會 回 到 0。這 是 主 宰 整 個 遊 戲 的 變 數 。在 任 何 一 個 循 環 ,程 序 想 知 道 現 在 是 那 一 個 state,檢 查 一 下 gameState 的 值 就 知 道 了 。
然 後 就 來 寫 每 一 個 gameState 內 要 進 行 的 程 序 ,就 由 0 開 始 吧 。在 這 個 gameState,LED 會 單 雙 數 輪 流 閃 動 ,直 至 有 人 按 按 鈕 為 止 。
首 先 ,我 們 新 增 一 個 Make a Block,叫 做 waitUser,用 來 做 待 機 狀 態 的 LED 閃 動 。我 們 再 新 增 一 個 變 數 waitLedState,這 個 waitLedState 只 有 兩 個 狀 態 ,0 時 就 單 數 LED 亮 著 ,1 時 就 雙 數 LED 亮 著 。watiUser 的 功 用 就 是 改 變 waitLedState,是 0 轉 1,是 1 就 轉 0。
在 無 限 循 環 裡 面 ,我 們 增 加 一 個 if then else block,用 來 檢 查 gameState 的 值 ,我 們 現 在 寫 的 是 gameState = 0 的 內 容 ,所 以 要 把 這 一 階 段 寫 的 blocks 都 放 到 if then else block 裡 面 。
這 一 階 段 的 程 式 就 是 兩 個 if block,第 一 個 if 是 檢 查 時 間 ,如 果 夠 0.5 秒 就 轉 一 次 waitLedState,於 是 LED 就 會 輪 流 閃 動 。第 二 個 if 是 檢 查 用 家 有 沒 有 接 push button,如 果 有 ,就 把 遊 戲 推 動 到 下 一 個 gameState。
這 時 候 我 們 就 可 以 測 試 一 下 程 序 了 ,按 Upload to Arduino。完 成 之 後 ,你 就 應 該 看 得 到 我 們 的 小 遊 戲 的 「待 機 畫 面 」了 。這 時 候 ,如 果 你 按 一 下 push button,LED 的 閃 動 就 會 停 止 ,因 為 gameState 已 經 轉 變 為 1 了 。
驟 眼 看 是 不 錯 ,但 有 聽 書 的 小 朋 友 應 該 可 以 發 現 到 問 題 。這 正 正 就 是 第 四 課 所 說 的 ,我 們 沒 有 記 錄 那 個 button 的 完 整 click cycle。假 如 有 個 玩 家 長 按 著 鍵 不 放 ,遊 戲 是 否 應 該 繼 續 呢 ?所 以 我 們 應 該 把 程 序 調 整 一 下 ,是 在 玩 家 放 手 ( 就 是 keyUp event ) 時 才 把 gameState 轉 移 到 下 一 個 state。
我 們 可 以 跟 著 第 四 課 來 改 動 程 序 ,但 問 題 來 了 ,我 們 每 次 都 在 timer > 0.1 時 reset 了 timer,那 timer 永 遠 不 到 達 0.5,那 LED 燈 就 不 會 閃 動 了 ,那 怎 麼 辦 呢 ?
其 實 我 們 可 以 用 variables 來 模 擬 多 幾 個 timer 的 。試 看 以 下 的 程 序 。
我 們 添 加 了 兩 個 變 數 timerA 和 timerB。使 用 的 方 法 是 ,1. 如 果 要 reset timerA,就 用 set timerA to timer;2. 如 果 想 取 得 timerA 的 時 間 ,就 使 用 timer – timerA。timerB 亦 是 一 樣 ,你 可 以 自 行 增 加 多 少 個 timer 都 可 以 。
以 上 的 程 序 執 行 之 後 ,接 在 pin 9 的 LED 會 每 0.5 秒 開 關 一 次 ,pin 11 的 LED 會 每 1 秒 開 關 一 次 。你 可 以 隨 意 設 定 它 們 閃 爍 的 時 間 ,以 觀 察 不 同 的 結 果 。
好 了 ,回 到 我 們 的 小 遊 戲 吧 。
按 照 前 面 提 到 的 方 法 修 改 了 程 序 ,現 在 如 果 玩 家 長 按 button,LED 的 閃 動 是 不 會 停 止 的 ,要 到 玩 家 放 開 button,就 是 keyUp 時 ,遊 戲 才 會 去 到 下 一 個 state。
下 一 步 ,我 們 就 要 開 始 遊 戲 了 ,首 先 要 增 加 幾 個 變 數 。我 們 的 遊 戲 的 速 度 會 提 升 ,所 以 我 們 要 有 initialSpeed 和 currentSpeed。我 們 要 知 道 現 在 是 那 一 pin 亮 燈 ,所 以 會 有 currentPin。遊 戲 會 計 分 ,所 以 亦 有 score。我 們 使 用 roundFinish 去 記 錄 在 每 一 round 玩 家 按 了 鍵 沒 有 。
這 時 我 們 要 建 立 一 個 Make a Block 叫 做 loopLED,作 用 是 令 LED 輪 流 亮 起 ( 亮 起 的 永 遠 是 currentPin )。但 loopLED 要 在 玩 家 未 按 鈕 時 才 工 作 ,所 以 會 加 上 if block。我 們 用 回 timerA 來 設 定 閃 動 的 時 間 ,那 個 時 間 就 用 currentSpeed。
在 主 入 每 一 層 遊 戲 之 前 ,我 們 都 要 初 始 化 各 項 設 定 ,亦 即 是 我 們 的 變 數 。所 以 ,在 我 們 把 gameState 轉 變 為 1 的 時 候 ,要 同 時 設 定 好 各 個 變 數 ,亦 要 reset 一 次 timerA。因 為 每 一 個 round 都 要 initialize 一 次 ,所 以 我 們 可 以 增 加 一 個 Make a Block 叫 initializeRound。
而 當 使 用 者 在 gameState = 1 按 動 按 鈕 時 ,我 們 在 keyDown 就 要 停 止 這 一 個 round,然 後 按 結 果 ( currentPin = 10,亦 即 中 間 的 LED ) 來 計 分 和 看 看 是 不 是 要 增 加 難 度 ( 把 currentSpeed 除 2 )。在 玩 家 放 手 時 就 進 入 下 一 round ( change gameState by 1 ),並 初 始 化 下 一 round 的 變 數 ( initializeRound )。
完 成 之 後 就 是 下 面 的 樣 子 。
好 了 ,我 們 可 以 來 寫 round 2 了 。聰 明 的 讀 者 應 該 可 以 感 應 到 ,round 2 的 程 式 和 round 1 是 沒 有 分 別 的 。所 以 ,我 們 可 以 直 接 duplicate 整 個 round 1 的 blocks ( 在 gameState = 1 那 個 if then else block 去 right click,再 選 duplicate ),然 後 直 接 copy 到 下 面 用 就 可 以 了 。
但 這 是 完 全 沒 有 必 要 的 ,亦 會 令 到 我 們 的 程 式 碼 過 長 ,以 後 很 難 debug。而 且 ,當 要 改 寫 的 時 候 ,亦 要 逐 round 一 個 個 地 去 改 。其 實 我 們 只 要 在 if gameState = 1 then 那 行 ,加 上 2, 3, 4, 5 就 可 以 了 ( 明 顯 地 ,第 3, 4, 5 round 的 程 式 碼 亦 都 是 一 樣 的 )。
終 於 到 了 最 後 一 個 gameState ( = 6 ),就 是 報 分 。我 增 加 了 一 個 Make a Block 叫 showScore。首 先 ,所 有 LED 會 全 滅 ,然 後 按 玩 家 的 得 分 逐 顆 亮 起 ,1 分 1 顆 ,2 分 2 顆 ,如 此 類 推 。報 分 時 可 以 慢 一 點 ( wait 1 sec ),增 加 一 點 懸 疑 性 。到 達 分 數 之 後 ,就 整 體 閃 5 次 ,讓 玩 家 確 認 最 後 得 分 。
showScore 裡 面 全 都 使 用 wait block,因 為 在 gameState = 6 的 時 候 ,我 們 只 要 報 分 ,並 不 需 要 玩 家 輸 入 。在 showScore 完 成 之 後 ,我 們 把 gameState 重 新 設 定 為 0,整 個 遊 戲 從 新 開 始 。
在 這 裡 ,我 想 特 別 提 出 一 下 forever block 裡 面 的 if then else block。試 比 較 以 下 兩 個 情 況 。
在 第 1 幅 圖 ,我 們 一 連 用 了 幾 個 if then else block,一 個 包 住 一 個 ( 這 叫 nesting )。而 在 第 2 個 情 況 ,我 們 用 了 3 個 獨 立 的 if block。雖 說 兩 者 執 行 出 來 的 結 果 是 一 樣 ,但 performance 肯 定 不 一 樣 。如 果 用 單 純 用 3 個 if block,程 序 在 每 個 loop 都 一 定 要 比 對 3 次 。但 如 果 改 用 if then else block,只 要 檢 測 到 了 成 功 的 條 件 ( True ),其 餘 的 if 就 不 會 再 比 對 下 去 。個 人 覺 得 在 比 較 慢 的 硬 件 上 ,還 是 必 須 要 養 成 一 點 寫 程 式 的 好 習 慣 才 行 。
因 為 這 次 的 程 序 比 較 長 ,所 以 我 把 程 序 放 出 來 給 大 家 下 載 。但 希 望 大 家 不 只 是 純 粹 把 程 序 下 載 來 玩 ,而 是 花 少 許 時 間 自 己 建 立 一 次 。
最 後 ,又 是 功 課 時 間 。1. 試 試 把 每 一 round 中 的 遊 戲 速 度 ,變 為 隨 機 的 速 度 ;2. 嘗 試 改 變 「待 機 」LED 的 閃 動 方 式 ;3. 嘗 試 改 變 「報 分 」的 方 式 ;4. 試 試 自 己 設 計 一 個 用 LED 和 push button 來 玩 的 小 遊 戲 吧 。
下 一 堂 ,我 們 開 始 學 習 用 Arduino 去 控 制 複 雜 一 點 的 硬 件 。
檔 案 下 載 : lesson-5.zip
延 伸 課 堂 :自 己 動 手 做 一 個 Arduino Shield