⚡ 電流急急棒遊戲

打造一個刺激的電流急急棒遊戲!手持金屬環沿著彎曲的金屬線前進,碰到就會警報大響!

📷 收納櫃+零件實照(視窗內開啟) 另開新分頁 ↗

🔌 本功課所需杜邦線

電流急急棒專案接線請依下表準備(多為公母線連接 Arduino 與麵包板;直接對照右側「收納位置」欄到收納盒取線即可)。上方為數量表,下方為接線圖

顏色長度規格數量收納位置
30 cm公母3C2
30 cm公母1C2
20 cm公母1C2
功課五電流急急棒:電路接線圖

圖:功課五(電流急急棒)接線圖

🎯 遊戲挑戰

製作一個電流急急棒遊戲機!結合按鈕、RGB LED、數碼管計時器、蜂鳴器和震動馬達:

按下按鈕開始 → 計時器啟動 → 小心前進 → 碰到警報!→ 到達終點成功!

📜 遊戲規則

  1. 🟢 按下按鈕開始遊戲,藍燈亮起,計時開始
  2. ⏱️ 數碼管即時顯示經過的秒數
  3. 🏆 手持金屬環到達終點線(D8),綠燈亮起 → 挑戰成功!
  4. 💥 途中碰到障礙線(D10),紅燈亮起 + 蜂鳴器響 + 馬達震動 → 挑戰失敗!
  5. 🔄 隨時按下按鈕可重新開始遊戲

🔄 遊戲狀態流程

這個遊戲使用「狀態機」邏輯來控制:

⏸️
IDLE 待命
等待按鈕
數碼管顯示 00:00
🏃
RUNNING 進行中
藍燈亮起
計時器運轉
D8 終點
D10 障礙
🏆
成功!
觸碰終點線
綠燈亮起
💥
失敗!
碰到障礙線
紅燈 + 警報

🔌 接線對照表

這個專案需要用到多個元件,請按照以下表格仔細接線:

Arduino 接腳 連接元件 說明
D2按鈕啟動 / 重新開始遊戲
D3紅色 LED碰到障礙時亮起
D4藍色 LED遊戲開始時亮起(程式碼中為 bluePin)
D5綠色 LED到達終點時亮起
D6TM1637 DIO數碼管資料線
D7TM1637 CLK數碼管時鐘線
D8終點線金屬環到達終點的感測
D9震動馬達碰到障礙時震動警報
D10障礙線金屬環碰到彎曲線的感測
D11蜂鳴器碰到障礙時聲音警報

💡 急急棒製作小提示

  • 障礙線:用粗鐵絲(或銅線)彎成蜿蜒的路徑,一端接 GND,另一端接 D10
  • 手持環:用鐵絲彎成小圈,套在障礙線上,接 GND
  • 終點線:在路徑尾端放置一小段金屬,一端接 D8
  • D8 和 D10 使用 INPUT_PULLUP,平時為 HIGH,接觸(接地)時變 LOW
🤖

與ChatGPT協作

你可以試著問ChatGPT這些問題來獲得幫助:

我想用Arduino製作一個「電流急急棒」遊戲機。 功能需求: 1. 按下按鈕開始遊戲,藍燈亮起,數碼管開始計時 2. 玩家手持金屬環沿著彎曲金屬線前進 3. 碰到障礙線(D10)→ 紅燈亮起 + 蜂鳴器響 + 馬達震動 0.5秒 4. 到達終點線(D8)→ 綠燈亮起,遊戲成功 5. 數碼管即時顯示經過的秒數 6. 按鈕可隨時重新開始 硬體接線: - 按鈕:D2(INPUT_PULLUP) - 紅色LED:D3 - 藍色LED:D4 - 綠色LED:D5 - TM1637 DIO:D6 - TM1637 CLK:D7 - 終點線:D8(INPUT_PULLUP) - 震動馬達:D9 - 障礙線:D10(INPUT_PULLUP) - 蜂鳴器:D11 請幫我寫Arduino程式碼,使用狀態機設計,並加上詳細註解。

💡 提問技巧

  • 說明遊戲的完整規則和流程
  • 告訴AI你使用了哪些感測接腳(終點線、障礙線)
  • 詢問什麼是「狀態機」以及為什麼要用它
  • 如果警報不夠明顯,可以請AI加入更多提示效果

📝 程式碼詳細解說

讓我們一步步了解這個電流急急棒遊戲的程式碼!

📚 第一部分:引入函式庫與腳位定義

使用 TM1637 函式庫來控制數碼管,定義所有元件的接腳

#include <TM1637Display.h> // 腳位定義 const int buttonPin = 2; const int redPin = 3; const int bluePin = 4; const int greenPin = 5; const int displayDIO = 6; const int displayCLK = 7; const int goalPin = 8; // 終點線 const int obstaclePin = 10; // 障礙線 const int motorPin = 9; const int buzzerPin = 11;

🎮 第二部分:狀態機設計

使用 enum(列舉)定義遊戲的三種狀態,讓程式邏輯更清晰

TM1637Display display(displayCLK, displayDIO); // 遊戲有三種狀態:待命、進行中、已停止 enum GameState { IDLE, RUNNING, STOPPED }; GameState state = IDLE; unsigned long startTime = 0; float elapsed = 0; bool buttonPressed = false; bool gameStopped = false;

🧠 什麼是狀態機?

狀態機就像一個「遊戲規則管理員」!它定義了遊戲可能的幾種狀態,以及每種狀態下該做什麼:

  • IDLE(待命):什麼都不做,等待玩家按下按鈕
  • RUNNING(進行中):計時器在跑,隨時偵測是否碰到障礙或到達終點
  • STOPPED(已停止):遊戲結束(成功或失敗),等待重新開始

有了狀態機,程式就不會亂!比如在 IDLE 狀態時,碰到障礙線不會觸發警報。

⏱️ 第三部分:防彈跳與顯示更新

按鈕防彈跳和數碼管的更新頻率控制

// 防彈跳用 unsigned long lastButtonTime = 0; const unsigned long debounceDelay = 50; // 顯示更新用 unsigned long lastDisplayTime = 0; const unsigned long displayInterval = 100; // 每 100ms 更新一次顯示

💡 為什麼需要控制顯示更新頻率?

如果每次 loop() 都更新數碼管,會產生閃爍的問題。設定每 100ms 才更新一次(每秒 10 次),既能即時顯示,又不會閃爍。這就是 millis() 計時法的好處!

⚙️ 第四部分:setup 開機設定

初始化所有接腳和元件

void setup() { Serial.begin(9600); pinMode(buttonPin, INPUT_PULLUP); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(goalPin, INPUT_PULLUP); // 終點線用內建上拉電阻 pinMode(obstaclePin, INPUT_PULLUP); // 障礙線用內建上拉電阻 pinMode(motorPin, OUTPUT); pinMode(buzzerPin, OUTPUT); display.setBrightness(0x0f); display.showNumberDecEx(0, 0b01000000, true); setRGB(LOW, LOW, LOW); digitalWrite(motorPin, LOW); digitalWrite(buzzerPin, LOW); Serial.println("系統啟動,等待按鈕"); }

🔑 INPUT_PULLUP 的重要性

終點線(D8)和障礙線(D10)都設為 INPUT_PULLUP

  • 平時電壓被「拉高」到 HIGH(沒碰到)
  • 金屬環碰到時,接通 GND,變成 LOW(觸發!)
  • 不需要外接電阻,Arduino 內建的就夠了

🔄 第五部分:loop 主迴圈

遊戲的核心邏輯都在這裡!

void loop() { // 即時偵測 D8、D10 int goalState = digitalRead(goalPin); int obstacleState = digitalRead(obstaclePin); // 按鈕偵測 + 防彈跳 if (digitalRead(buttonPin) == LOW) { if (millis() - lastButtonTime > debounceDelay) { if (!buttonPressed) { buttonPressed = true; handleButtonPress(); } lastButtonTime = millis(); } } else { buttonPressed = false; } // 遊戲進行中 if (state == RUNNING && !gameStopped) { // 即時偵測是否觸發 if (goalState == LOW) { gameStopped = true; setRGB(LOW, HIGH, LOW); // 綠燈 Serial.println("終點觸發 → 遊戲成功"); } if (obstacleState == LOW) { gameStopped = true; setRGB(HIGH, LOW, LOW); // 紅燈 Serial.println("碰到障礙 → 遊戲失敗"); triggerAlarm(); } // 每 100ms 更新一次顯示 if (millis() - lastDisplayTime >= displayInterval) { elapsed = (millis() - startTime) / 1000.0; int displayValue = (int)(elapsed + 0.05); display.showNumberDecEx(displayValue, 0b01000000, true); lastDisplayTime = millis(); } } if (state == IDLE) { display.showNumberDecEx(0, 0b01000000, true); } }

⚠️ 為什麼不用 delay()?

在這個遊戲中,我們不能delay() 來計時!因為:

  • delay() 會讓 Arduino「暫停」,暫停期間無法偵測金屬環碰到障礙
  • 使用 millis() 可以在計時的同時持續偵測所有感測器
  • 這叫做「非阻塞式程式設計」,是遊戲開發的基本技巧!

🎮 第六部分:按鈕處理函式

按下按鈕時重置所有狀態,開始新一局

void handleButtonPress() { state = RUNNING; gameStopped = false; startTime = millis(); elapsed = 0; setRGB(LOW, LOW, HIGH); // 藍燈亮起 digitalWrite(motorPin, LOW); digitalWrite(buzzerPin, LOW); Serial.println("遊戲開始,計時中..."); }

💡 第七部分:輔助函式

RGB 燈控制與警報觸發

// --- 控制RGB燈 --- void setRGB(bool r, bool g, bool b) { digitalWrite(redPin, r); digitalWrite(greenPin, g); digitalWrite(bluePin, b); } // --- 警報觸發(馬達 + 蜂鳴器 0.5秒)--- void triggerAlarm() { digitalWrite(motorPin, HIGH); digitalWrite(buzzerPin, HIGH); delay(500); digitalWrite(motorPin, LOW); digitalWrite(buzzerPin, LOW); Serial.println("警報觸發 (馬達 + 蜂鳴器 0.5秒)"); }

🔔 triggerAlarm 的設計考量

這裡用了 delay(500),因為遊戲已經結束(gameStopped = true),不需要再偵測感測器了。0.5 秒的警報既能讓玩家明確感受到「碰到了!」,又不會太長讓人不耐煩。

📋 完整程式碼

點擊展開完整程式碼
#include <TM1637Display.h> // 腳位定義 const int buttonPin = 2; const int redPin = 3; const int bluePin = 4; const int greenPin = 5; const int displayDIO = 6; const int displayCLK = 7; const int goalPin = 8; // 終點線 const int obstaclePin = 10; // 障礙線 const int motorPin = 9; const int buzzerPin = 11; TM1637Display display(displayCLK, displayDIO); enum GameState { IDLE, RUNNING, STOPPED }; GameState state = IDLE; unsigned long startTime = 0; float elapsed = 0; bool buttonPressed = false; bool gameStopped = false; // 防彈跳用 unsigned long lastButtonTime = 0; const unsigned long debounceDelay = 50; // 顯示更新用 unsigned long lastDisplayTime = 0; const unsigned long displayInterval = 100; void setup() { Serial.begin(9600); pinMode(buttonPin, INPUT_PULLUP); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); pinMode(goalPin, INPUT_PULLUP); pinMode(obstaclePin, INPUT_PULLUP); pinMode(motorPin, OUTPUT); pinMode(buzzerPin, OUTPUT); display.setBrightness(0x0f); display.showNumberDecEx(0, 0b01000000, true); setRGB(LOW, LOW, LOW); digitalWrite(motorPin, LOW); digitalWrite(buzzerPin, LOW); Serial.println("系統啟動,等待按鈕"); } void loop() { int goalState = digitalRead(goalPin); int obstacleState = digitalRead(obstaclePin); if (digitalRead(buttonPin) == LOW) { if (millis() - lastButtonTime > debounceDelay) { if (!buttonPressed) { buttonPressed = true; handleButtonPress(); } lastButtonTime = millis(); } } else { buttonPressed = false; } if (state == RUNNING && !gameStopped) { if (goalState == LOW) { gameStopped = true; setRGB(LOW, HIGH, LOW); Serial.println("終點觸發 → 遊戲成功"); } if (obstacleState == LOW) { gameStopped = true; setRGB(HIGH, LOW, LOW); Serial.println("碰到障礙 → 遊戲失敗"); triggerAlarm(); } if (millis() - lastDisplayTime >= displayInterval) { elapsed = (millis() - startTime) / 1000.0; int displayValue = (int)(elapsed + 0.05); display.showNumberDecEx(displayValue, 0b01000000, true); lastDisplayTime = millis(); } } if (state == IDLE) { display.showNumberDecEx(0, 0b01000000, true); } } void handleButtonPress() { state = RUNNING; gameStopped = false; startTime = millis(); elapsed = 0; setRGB(LOW, LOW, HIGH); digitalWrite(motorPin, LOW); digitalWrite(buzzerPin, LOW); Serial.println("遊戲開始,計時中..."); } void setRGB(bool r, bool g, bool b) { digitalWrite(redPin, r); digitalWrite(greenPin, g); digitalWrite(bluePin, b); } void triggerAlarm() { digitalWrite(motorPin, HIGH); digitalWrite(buzzerPin, HIGH); delay(500); digitalWrite(motorPin, LOW); digitalWrite(buzzerPin, LOW); Serial.println("警報觸發 (馬達 + 蜂鳴器 0.5秒)"); }

🎉 功課五學習重點

完成電流急急棒後,你學會了:

🎓 到目前為止你完成的學習路程: