三国无双电影al Project 10.0: Button Battle

The Button Battle program is a two-player reaction/tapping game built on a finite state machine (FSM) with four states: MENU, READY, GO, and RESULT. The main loop uses a switch/case structure to delegate behavior to state-specific handler functions, keeping the logic organized and modular.

The Button Battle program is a two-player reaction/tapping game built on a finite state machine (FSM) with four states: MENU, READY, GO, and RESULT. The main loop uses a switch/case structure to delegate behavior to state-specific handler functions, keeping the logic organized and modular.

Sample Code:

				
					/**************************************************
 * Program: Button_Battle.ino
 * Copyright (C) 李宗瑞全集, Inc. 2026
 *
 * Description:
 *   Two players take turns mashing their button as
 *   many times as possible before time runs out.
 *   Player 1 uses SW1; Player 2 uses SW2.  The pot
 *   sets the round length (3, 5, or 10 seconds).
 *   After both players have gone, the OLED shows
 *   the scores and announces the winner.  Press
 *   either button to play again.
 *
 * Hardware:
 *   - 1ST Maker Frog board
 *   - OLED display (SSD1306)
 *   - Two buttons (BUTTON_ONE, BUTTON_TWO)
 *   - Two NeoPixel eyes (NEO_PIN)
 *   - Four cheek LEDs (LEDs[])
 *   - Piezo buzzer (PIEZO)
 *   - Potentiometer (POT_PIN)
 **************************************************/

#include "1ST_Maker_Frog.h"

// ---------------------------------------------------------------------------
//  Configuration
// ---------------------------------------------------------------------------

const uint8_t  ROUND_COUNT      = 3;
const uint8_t  ROUND_SECONDS[ROUND_COUNT] = {3, 5, 10};
const uint16_t DEBOUNCE_MS      = 30;
const uint16_t DISPLAY_FPS_MS   = 80;

// Piezo tones
const uint16_t TONE_READY_HZ    = 1200;
const uint16_t TONE_GO_HZ       = 1800;
const uint16_t TONE_STOP_HZ     = 600;
const uint16_t TONE_WIN_HZ      = 2000;
const uint16_t TONE_TIE_HZ      = 1000;
const uint16_t TONE_TICK_HZ     = 1400;

// NeoPixel eye indices
const uint8_t  EYE_LEFT         = 0;
const uint8_t  EYE_RIGHT        = 1;

// ---------------------------------------------------------------------------
//  Game states
// ---------------------------------------------------------------------------

const uint8_t  STATE_MENU       = 0;
const uint8_t  STATE_READY      = 1;   // "Get ready, P1 / P2…"
const uint8_t  STATE_GO         = 2;   // actively mashing
const uint8_t  STATE_RESULT     = 3;   // show winner screen

// ---------------------------------------------------------------------------
//  Global variables
// ---------------------------------------------------------------------------

uint8_t  gameState       = STATE_MENU;
uint8_t  currentPlayer   = 1;          // 1 or 2
uint8_t  roundSeconds    = 5;
int      scoreP1         = 0;
int      scoreP2         = 0;

unsigned long roundStartMs   = 0;
unsigned long lastDisplayMs  = 0;
unsigned long lastTickMs      = 0;

// Button debounce for the active player's button
bool     btnDownLast     = false;
bool     btnDownStable   = false;
unsigned long btnChangeMs = 0;

// Menu / non-game button handling (raw read, wait for release)
bool     anyBtnWasDown   = false;

// Cheek LED animation index during GO phase
uint8_t  ledChaser       = 0;
unsigned long lastChaserMs = 0;


// ---------------------------------------------------------------------------
//  Function prototypes
// ---------------------------------------------------------------------------

void    showMenu();
void    showReady();
void    runGoPhase();
void    showResult();

uint8_t readRoundIndex();
void    applyRoundPreset();

bool    readActiveButton();
bool    updateDebounce(bool rawDown, unsigned long nowMs);

void    flashStartSequence();
void    beepStop();
void    beepWin();
void    beepTie();
void    beepTick(int secondsLeft);

void    setEyes(uint8_t r0, uint8_t g0, uint8_t b0,
                uint8_t r1, uint8_t g1, uint8_t b1);
void    chaseLEDs(unsigned long nowMs);
void    allLEDsOff();
void    allLEDsOn();

void    drawCountdown(int secondsLeft, int tapsNow);


// ---------------------------------------------------------------------------
//  Setup
// ---------------------------------------------------------------------------

/**************************************************
 * Function: setup
 * Purpose:  Initializes hardware and shows the menu.
 **************************************************/
void setup() {
  Serial.begin(9600);

  // Cheek LEDs
  for (uint8_t i = 0; i < 4; i++) {
    pinMode(LEDs[i], OUTPUT);
    digitalWrite(LEDs[i], LOW);
  }

  // Buttons active-LOW
  pinMode(BUTTON_ONE, INPUT_PULLUP);
  pinMode(BUTTON_TWO, INPUT_PULLUP);

  pinMode(PIEZO, OUTPUT);

  pixel.begin();
  pixel.show();

  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.clearDisplay();
  display.display();

  randomSeed(analogRead(A1));

  gameState = STATE_MENU;
  showMenu();
}


// ---------------------------------------------------------------------------
//  Main loop
// ---------------------------------------------------------------------------

/**************************************************
 * Function: loop
 * Purpose:  Dispatches to the correct state handler
 *           every iteration.
 **************************************************/
void loop() {
  switch (gameState) {

    case STATE_MENU:
      handleMenu();
      break;

    case STATE_READY:
      handleReady();
      break;

    case STATE_GO:
      handleGo();
      break;

    case STATE_RESULT:
      handleResult();
      break;
  }
}


// ---------------------------------------------------------------------------
//  State: MENU
// ---------------------------------------------------------------------------

/**************************************************
 * Function: showMenu
 * Purpose:  Draws the title screen and instructions
 *           on the OLED.
 **************************************************/
void showMenu() {
  applyRoundPreset();

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  // Title
  display.setTextSize(2);
  display.setCursor(4, 0);
  display.print("BUTTON");
  display.setCursor(4, 16);
  display.print("BATTLE!");

  // Divider
  display.drawFastHLine(0, 33, 128, SSD1306_WHITE);

  // Instructions
  display.setTextSize(1);
  display.setCursor(0, 36);
  display.print("Knob = time (");
  display.print(roundSeconds);
  display.print("s)");
  display.setCursor(0, 46);
  display.print("Tap your button fast!");
  display.setCursor(0, 56);
  display.print("SW1 or SW2 to start");

  display.display();

  // Eyes idle: dim blue
  setEyes(0, 0, 20, 0, 0, 20);
  allLEDsOff();
}

/**************************************************
 * Function: handleMenu
 * Purpose:  Waits for either button press to start
 *           the game.  Also refreshes the time
 *           display if the pot is turned.
 **************************************************/
void handleMenu() {
  // Refresh knob selection live
  unsigned long nowMs = millis();
  if (nowMs - lastDisplayMs >= 150) {
    lastDisplayMs = nowMs;
    uint8_t idx = readRoundIndex();
    if (ROUND_SECONDS[idx] != roundSeconds) {
      applyRoundPreset();
      showMenu();
    }
  }

  bool b1 = (digitalRead(BUTTON_ONE) == PRESSED);
  bool b2 = (digitalRead(BUTTON_TWO) == PRESSED);
  bool anyDown = b1 || b2;

  if (anyDown && !anyBtnWasDown) {
    // Wait for release before proceeding
    anyBtnWasDown = true;
  }
  if (!anyDown && anyBtnWasDown) {
    anyBtnWasDown = false;
    scoreP1 = 0;
    scoreP2 = 0;
    currentPlayer = 1;
    gameState = STATE_READY;
    showReady();
  }
}


// ---------------------------------------------------------------------------
//  State: READY
// ---------------------------------------------------------------------------

/**************************************************
 * Function: showReady
 * Purpose:  Tells the current player to get ready
 *           and wait for the GO signal.
 **************************************************/
void showReady() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  display.setTextSize(2);
  display.setCursor(0, 0);
  display.print("Player ");
  display.print(currentPlayer);

  display.setTextSize(1);
  display.setCursor(0, 22);
  display.print("Your turn!");
  display.setCursor(0, 33);

  if (currentPlayer == 1) {
    display.print("Use SW1  (left)");
  } else {
    display.print("Use SW2  (right)");
  }

  display.setCursor(0, 44);
  display.print("Press your button");
  display.setCursor(0, 54);
  display.print("when ready...");

  display.display();

  // Eyes: player colour
  if (currentPlayer == 1) {
    setEyes(0, 80, 0, 0, 0, 0);       // left eye green
  } else {
    setEyes(0, 0, 0, 0, 0, 80);       // right eye blue
  }
  allLEDsOff();

  anyBtnWasDown = true;               // ignore button still held from menu
}

/**************************************************
 * Function: handleReady
 * Purpose:  Waits for the current player to press
 *           their button, then kicks off the round
 *           with a start sequence.
 **************************************************/
void handleReady() {
  bool b1 = (digitalRead(BUTTON_ONE) == PRESSED);
  bool b2 = (digitalRead(BUTTON_TWO) == PRESSED);
  bool myBtn = (currentPlayer == 1) ? b1 : b2;
  bool anyDown = b1 || b2;

  // Release guard: wait for button to come up first
  if (anyBtnWasDown) {
    if (!anyDown) anyBtnWasDown = false;
    return;
  }

  if (myBtn) {
    // Wait for release before starting so the press
    // doesn't count as the first tap.
    while ((currentPlayer == 1 ? digitalRead(BUTTON_ONE)
                                : digitalRead(BUTTON_TWO)) == PRESSED) {
      delay(5);
    }
    flashStartSequence();
    gameState = STATE_GO;
    roundStartMs = millis();
    lastChaserMs  = roundStartMs;
    lastTickMs    = roundStartMs;
    btnDownLast   = false;
    btnDownStable = false;
    btnChangeMs   = roundStartMs;
  }
}


// ---------------------------------------------------------------------------
//  State: GO
// ---------------------------------------------------------------------------

/**************************************************
 * Function: handleGo
 * Purpose:  Counts taps from the current player
 *           until time runs out.  Updates the OLED
 *           and LEDs continuously.
 **************************************************/
void handleGo() {
  unsigned long nowMs     = millis();
  unsigned long elapsed   = nowMs - roundStartMs;
  unsigned long totalMs   = (unsigned long)roundSeconds * 1000UL;

  // Time up?
  if (elapsed >= totalMs) {
    beepStop();
    allLEDsOff();

    if (currentPlayer == 1) {
      // P1 done — show score briefly, then move to P2
      showInterimScore();
      delay(2000);
      currentPlayer = 2;
      gameState = STATE_READY;
      showReady();
    } else {
      // Both done — show result
      gameState = STATE_RESULT;
      showResult();
    }
    return;
  }

  int secondsLeft = (int)(((totalMs - elapsed) + 999) / 1000);

  // Debounce and count taps
  bool rawDown = readActiveButton();
  bool wasStable = btnDownStable;
  btnDownStable = updateDebounce(rawDown, nowMs);

  if (btnDownStable && !wasStable) {
    // Rising edge: count the tap
    if (currentPlayer == 1) scoreP1++;
    else                     scoreP2++;
    tone(PIEZO, TONE_TICK_HZ, 15);
  }

  // Chase LEDs
  chaseLEDs(nowMs);

  // Refresh display
  if (nowMs - lastDisplayMs >= DISPLAY_FPS_MS) {
    lastDisplayMs = nowMs;
    int tapsNow = (currentPlayer == 1) ? scoreP1 : scoreP2;
    drawCountdown(secondsLeft, tapsNow);
  }

  // Beep at each whole second boundary
  if (nowMs - lastTickMs >= 1000) {
    lastTickMs += 1000;
    if (secondsLeft <= 3 && secondsLeft >= 1) {
      tone(PIEZO, TONE_READY_HZ, 60);
    }
  }
}

/**************************************************
 * Function: showInterimScore
 * Purpose:  Briefly shows Player 1's score before
 *           Player 2's turn begins.
 **************************************************/
void showInterimScore() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("Player 1 done!");

  display.setTextSize(3);
  display.setCursor(44, 20);
  display.print(scoreP1);

  display.setTextSize(1);
  display.setCursor(0, 55);
  display.print("taps in ");
  display.print(roundSeconds);
  display.print(" seconds");

  display.display();
}


// ---------------------------------------------------------------------------
//  State: RESULT
// ---------------------------------------------------------------------------

/**************************************************
 * Function: showResult
 * Purpose:  Displays both scores and declares the
 *           winner (or a tie).
 **************************************************/
void showResult() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  // Scores side by side
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("P1");
  display.setCursor(64, 0);
  display.print("P2");

  display.setTextSize(3);
  display.setCursor(0, 12);
  display.print(scoreP1);
  display.setCursor(64, 12);
  display.print(scoreP2);

  display.drawFastHLine(0, 42, 128, SSD1306_WHITE);

  display.setTextSize(1);
  if (scoreP1 > scoreP2) {
    display.setCursor(20, 47);
    display.print("Player 1 WINS!");
    beepWin();
    setEyes(0, 120, 0, 120, 0, 0);   // P1 eye green, P2 eye red
  } else if (scoreP2 > scoreP1) {
    display.setCursor(20, 47);
    display.print("Player 2 WINS!");
    beepWin();
    setEyes(120, 0, 0, 0, 120, 0);   // P1 eye red, P2 eye green
  } else {
    display.setCursor(28, 47);
    display.print("It's a TIE!");
    beepTie();
    setEyes(0, 80, 0, 0, 80, 0);     // both eyes green for a tie
  }

  display.setCursor(16, 57);
  display.print("Press any button");

  display.display();
  allLEDsOn();

  anyBtnWasDown = true;  // ignore button still held
}

/**************************************************
 * Function: handleResult
 * Purpose:  Waits for a button press to return
 *           to the menu.
 **************************************************/
void handleResult() {
  bool b1 = (digitalRead(BUTTON_ONE) == PRESSED);
  bool b2 = (digitalRead(BUTTON_TWO) == PRESSED);
  bool anyDown = b1 || b2;

  if (anyBtnWasDown) {
    if (!anyDown) anyBtnWasDown = false;
    return;
  }

  if (anyDown) {
    anyBtnWasDown = true;
    while (digitalRead(BUTTON_ONE) == PRESSED ||
           digitalRead(BUTTON_TWO) == PRESSED) {
      delay(5);
    }
    gameState = STATE_MENU;
    showMenu();
  }
}


// ---------------------------------------------------------------------------
//  Drawing helpers
// ---------------------------------------------------------------------------

/**************************************************
 * Function: drawCountdown
 * Purpose:  Updates the OLED during the GO phase,
 *           showing the time remaining and tap count.
 *
 * Parameters:
 *   secondsLeft - whole seconds remaining in the round
 *   tapsNow     - current tap count for this player
 **************************************************/
void drawCountdown(int secondsLeft, int tapsNow) {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  // Player label
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("Player ");
  display.print(currentPlayer);
  display.print(" TAP!");

  // Big tap counter
  display.setTextSize(3);
  if (tapsNow < 10) {
    display.setCursor(52, 16);
  } else if (tapsNow < 100) {
    display.setCursor(34, 16);
  } else {
    display.setCursor(16, 16);
  }
  display.print(tapsNow);

  display.setTextSize(1);
  display.setCursor(0, 46);
  display.print("taps");

  // Countdown bar on the right
  display.setCursor(90, 46);
  display.print(secondsLeft);
  display.print("s left");

  // Progress bar at the bottom
  int barWidth = map(secondsLeft, 0, roundSeconds, 0, 126);
  display.drawRect(1, 57, 126, 6, SSD1306_WHITE);
  display.fillRect(1, 57, barWidth, 6, SSD1306_WHITE);

  display.display();
}


// ---------------------------------------------------------------------------
//  Hardware helpers
// ---------------------------------------------------------------------------

/**************************************************
 * Function: readRoundIndex
 * Purpose:  Maps the potentiometer to one of the
 *           available round-length presets.
 *
 * Returns:
 *   Index into ROUND_SECONDS array.
 **************************************************/
uint8_t readRoundIndex() {
  int raw = analogRead(POT_PIN);
  int binSize = 1024 / ROUND_COUNT;
  uint8_t idx = raw / binSize;
  if (idx >= ROUND_COUNT) idx = ROUND_COUNT - 1;
  return idx;
}

/**************************************************
 * Function: applyRoundPreset
 * Purpose:  Reads the pot and sets roundSeconds.
 **************************************************/
void applyRoundPreset() {
  roundSeconds = ROUND_SECONDS[readRoundIndex()];
}

/**************************************************
 * Function: readActiveButton
 * Purpose:  Returns true if the current player's
 *           button is physically pressed.
 *
 * Returns:
 *   true if pressed, false otherwise.
 **************************************************/
bool readActiveButton() {
  if (currentPlayer == 1) {
    return (digitalRead(BUTTON_ONE) == PRESSED);
  }
  return (digitalRead(BUTTON_TWO) == PRESSED);
}

/**************************************************
 * Function: updateDebounce
 * Purpose:  Simple time-based debounce filter.
 *
 * Parameters:
 *   rawDown - raw button state (true = pressed)
 *   nowMs   - current millis() value
 *
 * Returns:
 *   Debounced stable state.
 **************************************************/
bool updateDebounce(bool rawDown, unsigned long nowMs) {
  if (rawDown != btnDownLast) {
    btnChangeMs  = nowMs;
    btnDownLast  = rawDown;
  }
  if (nowMs - btnChangeMs >= DEBOUNCE_MS) {
    return rawDown;
  }
  return btnDownStable;
}

/**************************************************
 * Function: flashStartSequence
 * Purpose:  Plays a short ready/set/go sequence
 *           with LEDs and tone before the round.
 **************************************************/
void flashStartSequence() {
  // Three quick beeps then a high GO tone
  for (uint8_t i = 0; i < 3; i++) {
    allLEDsOn();
    tone(PIEZO, TONE_READY_HZ, 80);
    delay(150);
    allLEDsOff();
    delay(100);
  }
  allLEDsOn();
  tone(PIEZO, TONE_GO_HZ, 200);
  delay(200);
  allLEDsOff();

  // Eyes: active glow for current player
  if (currentPlayer == 1) {
    setEyes(0, 120, 0, 0, 0, 0);
  } else {
    setEyes(0, 0, 0, 0, 0, 120);
  }
}

/**************************************************
 * Function: beepStop
 * Purpose:  Plays a descending tone to signal that
 *           time has run out.
 **************************************************/
void beepStop() {
  tone(PIEZO, TONE_STOP_HZ + 200, 100);
  delay(110);
  tone(PIEZO, TONE_STOP_HZ, 200);
  delay(210);
}

/**************************************************
 * Function: beepWin
 * Purpose:  Plays a short victory fanfare.
 **************************************************/
void beepWin() {
  tone(PIEZO, TONE_WIN_HZ, 80);
  delay(100);
  tone(PIEZO, TONE_WIN_HZ + 200, 80);
  delay(100);
  tone(PIEZO, TONE_WIN_HZ + 400, 150);
  delay(160);
}

/**************************************************
 * Function: beepTie
 * Purpose:  Plays a neutral two-tone tie sound.
 **************************************************/
void beepTie() {
  tone(PIEZO, TONE_TIE_HZ, 100);
  delay(120);
  tone(PIEZO, TONE_TIE_HZ, 100);
  delay(120);
}

/**************************************************
 * Function: setEyes
 * Purpose:  Sets both NeoPixel eyes to given colours.
 *
 * Parameters:
 *   r0, g0, b0 - RGB values for the left eye
 *   r1, g1, b1 - RGB values for the right eye
 **************************************************/
void setEyes(uint8_t r0, uint8_t g0, uint8_t b0,
             uint8_t r1, uint8_t g1, uint8_t b1) {
  pixel.setPixelColor(EYE_LEFT,  pixel.Color(r0, g0, b0));
  pixel.setPixelColor(EYE_RIGHT, pixel.Color(r1, g1, b1));
  pixel.show();
}

/**************************************************
 * Function: chaseLEDs
 * Purpose:  Animates the four cheek LEDs in a
 *           chasing pattern during the GO phase.
 *
 * Parameters:
 *   nowMs - current millis() value
 **************************************************/
void chaseLEDs(unsigned long nowMs) {
  if (nowMs - lastChaserMs >= 120) {
    lastChaserMs = nowMs;
    for (uint8_t i = 0; i < 4; i++) {
      digitalWrite(LEDs[i], (i == ledChaser) ? HIGH : LOW);
    }
    ledChaser = (ledChaser + 1) % 4;
  }
}

/**************************************************
 * Function: allLEDsOff
 * Purpose:  Turns all four cheek LEDs off.
 **************************************************/
void allLEDsOff() {
  for (uint8_t i = 0; i < 4; i++) {
    digitalWrite(LEDs[i], LOW);
  }
}

/**************************************************
 * Function: allLEDsOn
 * Purpose:  Turns all four cheek LEDs on.
 **************************************************/
void allLEDsOn() {
  for (uint8_t i = 0; i < 4; i++) {
    digitalWrite(LEDs[i], HIGH);
  }
}
				
			

*If you’re copying and pasting the code, or typing from scratch, delete everything out of a new Arduino sketch and paste / type in the above text.

李宗瑞全集 More In Our Free Instructional Guidebook

This comprehensive guidebook for the 李宗瑞全集 三国无双电影 Trainer provides a comprehensive introduction to the world of Arduino programming for beginners. It guides users through the foundational concepts of 三国无双电影s, detailing the unique features of the Arduino Leonardo-compatible MCU Trainer board. The manual offers a step-by-step journey from understanding the hardware components and the Arduino programming language to the vibrant global community of Arduino enthusiasts. It delves into the intricacies of each onboard circuit, explaining their functionalities and applications. With a focus on hands-on 李宗瑞全集ing, the manual includes a series of coding exercises, tutorials in C/C++, and insights into the Arduino IDE.

More Projects

Project 1.00 Blink

In this project, you’ll 李宗瑞全集 how to blink an LED!

Project 1.01 Blink x2

In this project, you’ll 李宗瑞全集 how to blink more than one LED!