📡 Level 3 – Advanced Communication

Project 3.11: "Cooperative Game"

 

What you’ll learn

  • Goal 1: Create a digital ping‑pong game with synchronized turns and score keeping.
  • Goal 2: Add robot‑style feedback for a cooperative obstacle course (sensors + signals).
  • Goal 3: Build a collaborative puzzle that requires both boards to share clues.
  • Goal 4: Design a teamwork simulator with roles (Leader/Helper) and fair difficulty.
  • Goal 5: Enable free creative play with safe defaults and clean communication.

Key ideas

  • Coordination: Use simple messages to synchronize turns and actions.
  • Signals: LEDs, buzzers, or LCD lines display live feedback to players.
  • Fairness: Balance pace and penalties to keep the challenge fun and not frustrating.
  • Clarity: Short messages, readable UI, and frequent but not spammy updates.

Blocks glossary

  • Bluetooth peripheral/central: Send and receive short text messages for turns, scores, and clues.
  • IR send/receive: Optional quick one‑way signals (serve, ping, sync).
  • Digital input (pull‑up): Read buttons for serve, hit, confirm, or solve.
  • Digital output: Drive LEDs/buzzer for feedback (hit, miss, win).
  • LCD print (I2C): Show score, round, hints, and timers.
  • Serial print: Minimal logs for debugging and fairness checks.
  • def function: Helpers for send_msg(), set_led(), show_line(), and validate moves.
  • Loop: Turn‑based cycles and steady pacing for cooperative play.

What you need

PartHow many?Example pins (R32)
D1 R32 (Board A)1Buttons A(26), B(25); LED 13; Bluetooth central
D1 R32 (Board B)1Buttons C(17), D(16); LED 13; Bluetooth peripheral (“Game‑R32”)
Optional LCD 1602 (I2C)1SCL 26, SDA 5, VCC 5V, GND
Optional sensors2–4IR RX 26; light sensor ADC2; touch switch on any digital pin
  • Keep boards within 1–3 m for reliable Bluetooth.
  • If using IR, aim TX to RX with clear line of sight (20–50 cm).
  • Share GND for any wired shared components (e.g., common buzzer circuit).

Before you start

  • Open two serial monitors: Board A (Controller/Leader) and Board B (Peripheral/Helper).
  • Quick test:
print("Ready!")  # Confirm serial is working

🎮 Microprojects (5 mini missions)

🎮 Microproject 3.11.1 – Digital ping‑pong game (turn sync + scoring)

Goal: A “ball” moves back and forth as timed turns. Players press at the right moment to “return.” Misses give a point to the other side. Best of 7 wins.

# Microproject 3.11.1 – Ping-Pong Game (Board B = Peripheral, Board A = Central)
# Run this on Board B (Peripheral). Board A code follows below.

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware pin library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Game-R32')  # Peripheral named 'Game-R32'
handle = ble_handle.Handle()                             # Callback handle
print("[B] BLE 'Game-R32' ready")                        # Serial: Bluetooth initialized

led = machine.Pin(13, machine.Pin.OUT)                   # LED for hit/miss feedback
print("[B] LED on Pin 13 ready")                         # Serial: LED initialized

score_A = 0                                              # Score for Board A
score_B = 0                                              # Score for Board B
turn = "A"                                               # Ball starts at A
round_active = False                                     # Whether a rally is active

def set_led(on):                                         # Helper: turn LED on/off
    led.value(1 if on else 0)                            # LED state set by boolean
    print("[B] LED:", "ON" if on else "OFF")             # Serial: LED state log

def send(text):                                          # Helper: send a Bluetooth message
    ble_p.send(text)                                     # Transmit text to Board A
    print("[B] TX:", text)                               # Serial: transmission log

def start_round():                                       # Helper: start a rally
    global round_active, turn                            # Use global round and turn
    round_active = True                                  # Mark rally active
    turn = "A"                                           # Serve to A
    send("SERVE:A")                                      # Notify A to serve
    set_led(True)                                        # LED on to mark start

def process_hit(player):                                 # Helper: handle a hit
    global turn                                          # Use global turn state
    if player == "A":                                    # If A hits
        turn = "B"                                       # Ball goes to B
        send("BALL:B")                                   # Notify next receiver B
    else:                                                # Else B hits
        turn = "A"                                       # Ball goes to A
        send("BALL:A")                                   # Notify next receiver A

def process_miss(player):                                # Helper: handle a miss
    global score_A, score_B, round_active                # Use global scores and round
    if player == "A":                                    # If A missed
        score_B += 1                                     # B gains a point
        send("MISS:A|SCORE:" + str(score_A) + "-" + str(score_B))  # Notify miss and score
    else:                                                # If B missed
        score_A += 1                                     # A gains a point
        send("MISS:B|SCORE:" + str(score_A) + "-" + str(score_B))  # Notify miss and score
    round_active = False                                 # End rally
    set_led(False)                                       # LED off to mark end

def handle_method(msg):                                  # Callback: receive events from A
    s = str(msg)                                         # Ensure string type
    print("[B] RX:", s)                                  # Serial: show incoming
    if s == "SERVE?":                                    # A asks to start
        start_round()                                    # Begin rally
    elif s == "HIT:A" and round_active and turn == "A":  # A returns correctly
        process_hit("A")                                 # Switch ball to B
    elif s == "HIT:B" and round_active and turn == "B":  # B returns correctly
        process_hit("B")                                 # Switch ball to A
    elif s == "MISS:A" and round_active and turn == "A": # A misses
        process_miss("A")                                # Score for B
    elif s == "MISS:B" and round_active and turn == "B": # B misses
        process_miss("B")                                # Score for A
    elif s.startswith("RESET"):                          # Reset scores
        score_A = 0                                      # Zero A score
        score_B = 0                                      # Zero B score
        send("ACK:RESET")                                # Acknowledge reset

handle.recv(handle_method)                                # Register callback
print("[B] Callback registered")                          # Serial: callback ready

while True:                                               # Idle loop; reactions via callback
    time.sleep_ms(150)                                    # Keep CPU cool
# Microproject 3.11.1 – Ping-Pong Game (Board A = Central)
# Run this on Board A (Central). Uses two buttons to HIT or MISS.

import ble_central                                # Load Bluetooth central helper
import ble_handle                                 # Load Bluetooth handle helper
import machine                                     # Load hardware pin library
import time                                        # Load time library

pin_hit = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)  # Button HIT
pin_miss = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP) # Button MISS
print("[A] Buttons HIT=26 MISS=25 ready")                      # Serial: buttons ready

central = ble_central.BLESimpleCentral()           # Create central object
handle  = ble_handle.Handle()                      # Create callback handle
print("[A] Central + handle ready")                # Serial: Bluetooth initialized

def connect_peer():                                # Helper: connect to peripheral
    central.scan()                                 # Scan for devices
    time.sleep_ms(600)                             # Short scan wait
    central.connect('Game-R32')                    # Connect by name
    print("[A] Connected to Game-R32")             # Serial: connection success

def send(text):                                    # Helper: send command text
    central.send(text)                             # Transmit message
    print("[A] TX:", text)                         # Serial: send log

def handle_method(msg):                            # Callback: print peer feedback
    print("[A] RX:", msg)                          # Serial: show incoming

handle.recv(handle_method)                         # Register callback
print("[A] Callback registered")                   # Serial: callback active

connect_peer()                                     # Connect to Board B

send("SERVE?")                                     # Ask peer to start a rally

turn = "A"                                         # Track whose turn locally

while True:                                        # Player input loop
    if pin_hit.value() == 0:                       # If HIT button pressed (LOW)
        send("HIT:" + turn)                        # Send HIT with current turn
        turn = "B" if turn == "A" else "A"         # Flip local turn expectation
        time.sleep_ms(300)                         # Debounce press
    if pin_miss.value() == 0:                      # If MISS button pressed (LOW)
        send("MISS:" + turn)                       # Send MISS with current turn
        turn = "B" if turn == "A" else "A"         # Flip turn (rally ends in peer)
        time.sleep_ms(300)                         # Debounce press
    time.sleep_ms(40)                              # Short poll delay

Reflection: You synchronized a rally and kept fair score.
Challenge: Add “Best of 7” win message and auto reset when someone reaches 4 points.


🎮 Microproject 3.11.2 – Cooperative obstacle course (sensor feedback)

Goal: Board B reads a sensor (e.g., light on ADC2). Board A requests “GO” and gets PASS/FAIL and hints.

# Microproject 3.11.2 – Obstacle course (Board B = Peripheral sensor judge)

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware ADC/pin library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Course-R32')  # Peripheral named 'Course-R32'
handle = ble_handle.Handle()                                 # Callback handle
print("[B] BLE 'Course-R32' ready")                          # Serial: Bluetooth initialized

adc2 = machine.ADC(machine.Pin(2))                          # Light sensor on ADC2
adc2.atten(machine.ADC.ATTN_11DB)                           # Full voltage range
adc2.width(machine.ADC.WIDTH_12BIT)                         # 12-bit resolution
print("[B] ADC2 ready")                                      # Serial: ADC ready

led = machine.Pin(13, machine.Pin.OUT)                      # LED for PASS/FAIL
print("[B] LED on 13 ready")                                 # Serial: LED ready

THRESH = 1800                                                # Threshold for PASS
print("[B] THRESH =", THRESH)                                # Serial: show threshold

def send(text):                                              # Helper: send feedback
    ble_p.send(text)                                         # Transmit text
    print("[B] TX:", text)                                   # Serial: send log

def judge():                                                 # Helper: evaluate sensor
    val = adc2.read()                                        # Read ADC value
    print("[B] ADC2:", val)                                  # Serial: show reading
    if val >= THRESH:                                        # If above threshold
        led.value(1)                                         # LED ON for PASS
        send("PASS:ADC2=" + str(val))                        # Send PASS with value
    else:                                                    # Otherwise fail
        led.value(0)                                         # LED OFF for FAIL
        send("FAIL:ADC2=" + str(val) + "|HINT:More light")   # Send FAIL with hint

def handle_method(msg):                                      # Callback: process GO command
    s = str(msg)                                             # Ensure string
    print("[B] RX:", s)                                      # Serial: log RX
    if s == "GO":                                            # If GO command
        judge()                                              # Evaluate sensor and respond
    elif s.startswith("THRESH="):                            # If threshold update
        val = int(s.split("=")[1])                           # Parse integer
        global THRESH                                        # Use global threshold
        THRESH = val                                         # Update threshold
        send("ACK:THRESH=" + str(THRESH))                    # Acknowledge change

handle.recv(handle_method)                                   # Register callback
print("[B] Callback registered")                             # Serial: callback active

while True:                                                  # Idle loop; reacts via callback
    time.sleep_ms(150)                                       # Keep CPU cool

Reflection: You built a judge that gives clear PASS/FAIL with hints — cooperative feedback.
Challenge: Add a second sensor (touch switch) and require both conditions to PASS.


🎮 Microproject 3.11.3 – Collaborative puzzle (clues exchange)

Goal: Board A and B exchange clues; only combined input unlocks “SOLVE”.

# Microproject 3.11.3 – Collaborative puzzle (Board B = Peripheral keeps secret code)

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Puzzle-R32')  # Peripheral named 'Puzzle-R32'
handle = ble_handle.Handle()                                 # Callback handle
print("[B] BLE 'Puzzle-R32' ready")                          # Serial: Bluetooth initialized

SECRET = "SUN"                                               # Secret code held by Board B
partial_A = ""                                               # Track A's input
partial_B = ""                                               # Track B's side (if any)
print("[B] SECRET =", SECRET)                                 # Serial: show secret

def send(text):                                              # Helper: send message
    ble_p.send(text)                                         # Transmit message
    print("[B] TX:", text)                                   # Serial: log send

def handle_method(msg):                                      # Callback: handle clues
    global partial_A, partial_B                              # Use global partials
    s = str(msg)                                             # Ensure string
    print("[B] RX:", s)                                      # Serial: show message
    if s.startswith("CLUE:"):                                # If a clue from A
        partial_A = s.split(":")[1]                          # Store A's clue text
        send("ACK:CLUE")                                     # Acknowledge
    elif s == "REQ:CLUE":                                    # A requests a hint
        send("HINT:Starts with 'S'")                         # Provide hint
    elif s.startswith("TRY:"):                               # A attempts a solution
        guess = s.split(":")[1]                              # Extract guess
        if guess == SECRET:                                  # If correct
            send("SOLVE:OK")                                 # Confirm success
        else:                                                # If incorrect
            send("SOLVE:NO")                                 # Deny
    else:                                                    # Unknown input
        send("ERR:UNKNOWN")                                  # Send error feedback

handle.recv(handle_method)                                   # Register callback
print("[B] Callback registered")                             # Serial: active

while True:                                                  # Idle; actions via callback
    time.sleep_ms(200)                                       # Keep CPU cool

Reflection: Clear clues and explicit SOLVE steps keep puzzles fair and fun.
Challenge: Add “TRIES left” counter and reduce on each wrong TRY.


🎮 Microproject 3.11.4 – Teamwork simulator (roles + timers + fairness)

Goal: Board A is Leader; Board B is Helper. Leader dispatches tasks; Helper confirms. Both track timers; no overload.

# Microproject 3.11.4 – Teamwork simulator (Board A = Leader, Board B = Helper)
# Run on Board A (Leader). Board B mirrors confirmations.

import ble_central                                # Load Bluetooth central helper
import ble_handle                                 # Load Bluetooth handle helper
import machine                                     # Load hardware pin library
import time                                        # Load time library

btn_task = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)  # Button to send task
print("[A] Button TASK=26 ready")                              # Serial: button ready

central = ble_central.BLESimpleCentral()           # Central object
handle  = ble_handle.Handle()                      # Callback handle
print("[A] Central + handle ready")                # Serial: Bluetooth initialized

def connect_peer():                                # Helper: connect to helper device
    central.scan()                                 # Scan for devices
    time.sleep_ms(600)                             # Short scan wait
    central.connect('Helper-R32')                  # Connect by name
    print("[A] Connected to Helper-R32")           # Serial: connected

def send(text):                                    # Helper: send text
    central.send(text)                             # Transmit command
    print("[A] TX:", text)                         # Serial: log send

def handle_method(msg):                            # Callback: log confirmations
    print("[A] RX:", msg)                          # Serial: show incoming

handle.recv(handle_method)                         # Register callback
print("[A] Callback registered")                   # Serial: callback active

connect_peer()                                     # Connect to Board B

last_task_ms = 0                                   # Last task dispatch time
gap_ms       = 1000                                # Min gap to avoid overload

def now_ms():                                      # Helper: current milliseconds
    return time.ticks_ms()                         # Return ms ticks

while True:                                        # Leader loop
    if btn_task.value() == 0:                      # If task button pressed (LOW)
        elapsed = time.ticks_diff(now_ms(), last_task_ms)  # Check spacing
        if elapsed >= gap_ms:                      # If enough time passed
            send("TASK:SCAN")                      # Dispatch task
            last_task_ms = now_ms()                # Update dispatch time
            time.sleep_ms(250)                     # Debounce button
        else:                                      # If too soon
            print("[A] Throttle: wait", gap_ms - elapsed, "ms")  # Serial: fairness
    time.sleep_ms(40)                              # Short poll
# Microproject 3.11.4 – Teamwork simulator (Board B = Helper)
# Run on Board B (Helper). Confirms tasks with timers.

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware pin library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Helper-R32')  # Peripheral named 'Helper-R32'
handle = ble_handle.Handle()                                 # Callback handle
print("[B] BLE 'Helper-R32' ready")                          # Serial: Bluetooth initialized

led = machine.Pin(13, machine.Pin.OUT)                      # LED indicates processing
print("[B] LED=13 ready")                                    # Serial: LED ready

def send(text):                                              # Helper: send confirmation
    ble_p.send(text)                                         # Transmit text
    print("[B] TX:", text)                                   # Serial: log send

def process_task(name):                                      # Helper: simulate task
    led.value(1)                                             # LED ON (working)
    print("[B] Task:", name)                                 # Serial: log task name
    time.sleep_ms(600)                                       # Simulate work time
    led.value(0)                                             # LED OFF (done)
    send("ACK:TASK=" + name)                                 # Send acknowledgment

def handle_method(msg):                                      # Callback: process task requests
    s = str(msg)                                             # Ensure string
    print("[B] RX:", s)                                      # Serial: show incoming
    if s.startswith("TASK:"):                                # If a task instruction
        process_task(s.split(":")[1])                        # Extract task name and run
    else:                                                    # Unknown request
        send("ERR:UNKNOWN")                                  # Send error feedback

handle.recv(handle_method)                                   # Register callback
print("[B] Callback registered")                             # Serial: active

while True:                                                  # Idle loop
    time.sleep_ms(150)                                       # Keep CPU cool

Reflection: You modeled fair dispatch, helper confirmations, and simple throttling.
Challenge: Add “TASK:WAIT(ms)” so Helper reports progress every 200 ms until done.


🎮 Microproject 3.11.5 – Free creative play (safe defaults + hooks)

Goal: Provide a template both boards can modify: inputs to actions, logs, and feedback.

# Microproject 3.11.5 – Free play template (Board B = Peripheral ready for custom game)

import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware pin library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Play-R32')  # Peripheral named 'Play-R32'
handle = ble_handle.Handle()                               # Callback handle
print("[B] BLE 'Play-R32' ready")                          # Serial: Bluetooth initialized

led = machine.Pin(13, machine.Pin.OUT)                     # LED feedback
btn = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP) # Button input
print("[B] LED=13, BTN=16 ready")                          # Serial: hardware ready

state = {"mode": "IDLE", "score": 0}                       # Simple mutable game state
print("[B] State init:", state)                            # Serial: show init state

def send(text):                                            # Helper: send message
    ble_p.send(text)                                       # Transmit string
    print("[B] TX:", text)                                 # Serial: log send

def set_mode(m):                                          # Helper: update mode safely
    state["mode"] = m                                      # Store new mode
    send("ACK:MODE=" + m)                                  # Acknowledge mode change

def handle_method(msg):                                    # Callback: act on commands
    s = str(msg)                                           # Ensure string
    print("[B] RX:", s)                                    # Serial: log RX
    if s == "MODE:PLAY":                                   # Switch to play mode
        set_mode("PLAY")                                   # Update mode
    elif s == "MODE:IDLE":                                 # Switch to idle mode
        set_mode("IDLE")                                   # Update mode
    elif s == "SCORE:+1":                                  # Increase score
        state["score"] += 1                                # Change score
        send("ACK:SCORE=" + str(state["score"]))           # Confirm score
    else:                                                  # Unknown command
        send("ERR:UNKNOWN")                                # Error feedback

handle.recv(handle_method)                                 # Register callback
print("[B] Callback registered")                           # Serial: active

while True:                                                # Free play loop
    if state["mode"] == "PLAY":                            # If in PLAY mode
        if btn.value() == 0:                               # If button pressed
            led.value(1)                                   # LED ON (event)
            send("EVENT:BTN")                              # Report event
            time.sleep_ms(200)                             # Debounce
        else:                                              # If button not pressed
            led.value(0)                                   # LED OFF
    time.sleep_ms(60)                                      # Small loop delay

Reflection: A safe playground lets students experiment without breaking the structure.
Challenge: Add LCD and show “Mode” top row, “Score” bottom row, updating only when changed.


✨ Main project – Cooperative game suite (two boards, shared signals)

System outline

  • Board A (Leader/Central): Sends turns, tasks, and solve attempts; reads buttons; keeps minimal logs.
  • Board B (Helper/Peripheral): Judges timing, sensors, and puzzle; sends clear ACK/ERR; shows LEDs.
  • Communication: Bluetooth text messages with short labels; optional IR for fast sync.
  • Fairness: Debounce and spacing to avoid overload; explicit PASS/FAIL and hints.
# Project 3.11 – Cooperative Game Suite (A = Central Leader, B = Peripheral Helper)
# Includes ping-pong, obstacle judge, puzzle, and teamwork control via clean messages.

# ---------- BOARD B (Helper / Peripheral) ----------
import ble_peripheral                               # Load Bluetooth peripheral helper
import ble_handle                                   # Load Bluetooth callback helper
import machine                                       # Load hardware pin/ADC library
import time                                          # Load time library

ble_p = ble_peripheral.BLESimplePeripheral('Coop-R32')  # Peripheral named 'Coop-R32'
handle = ble_handle.Handle()                               # Callback handle
print("[B] BLE 'Coop-R32' ready")                          # Serial: Bluetooth initialized

led = machine.Pin(13, machine.Pin.OUT)                     # LED feedback
adc2 = machine.ADC(machine.Pin(2))                         # Sensor on ADC2 (light/touch converter)
adc2.atten(machine.ADC.ATTN_11DB)                          # Full range
adc2.width(machine.ADC.WIDTH_12BIT)                        # Resolution
print("[B] LED=13, ADC2 ready")                            # Serial: hardware ready

scoreA = 0                                                 # Ping-pong score A
scoreB = 0                                                 # Ping-pong score B
turn   = "A"                                               # Ball starts A
round_active = False                                       # Rally state
THRESH = 1800                                              # Obstacle threshold
SECRET = "SUN"                                             # Puzzle code
print("[B] Init scores 0-0, turn=A, THRESH=1800, SECRET=SUN")  # Serial: init state

def send(text):                                            # Helper: transmit text
    ble_p.send(text)                                       # Send message
    print("[B] TX:", text)                                 # Serial: log send

def set_led(on):                                           # Helper: LED toggle
    led.value(1 if on else 0)                              # LED state
    print("[B] LED:", "ON" if on else "OFF")               # Serial: LED log

def start_rally():                                         # Helper: begin ping-pong rally
    global round_active, turn                              # Use global states
    round_active = True                                    # Rally active
    turn = "A"                                             # Serve to A
    send("SERVE:A")                                        # Notify A to serve
    set_led(True)                                          # LED marks rally start

def hit(player):                                           # Helper: handle HIT
    global turn                                            # Use global turn
    if player == "A":                                      # If A hit
        turn = "B"                                         # Ball to B
        send("BALL:B")                                     # Notify B turn
    else:                                                  # Else B hit
        turn = "A"                                         # Ball to A
        send("BALL:A")                                     # Notify A turn

def miss(player):                                          # Helper: handle MISS
    global scoreA, scoreB, round_active                    # Use global scores
    if player == "A":                                      # If A missed
        scoreB += 1                                        # B scores
        send("MISS:A|SCORE:" + str(scoreA) + "-" + str(scoreB))  # Notify
    else:                                                  # If B missed
        scoreA += 1                                        # A scores
        send("MISS:B|SCORE:" + str(scoreA) + "-" + str(scoreB))  # Notify
    round_active = False                                   # Rally ends
    set_led(False)                                         # LED off

def judge_obstacle():                                      # Helper: judge obstacle
    val = adc2.read()                                      # Read sensor
    print("[B] ADC2:", val)                                # Serial: reading
    if val >= THRESH:                                      # If PASS
        set_led(True)                                      # LED ON
        send("PASS:ADC2=" + str(val))                      # PASS feedback
    else:                                                  # Else FAIL
        set_led(False)                                     # LED OFF
        send("FAIL:ADC2=" + str(val) + "|HINT:More light") # FAIL + hint

def handle_method(msg):                                    # Callback: main protocol router
    global THRESH                                         # Use global threshold
    s = str(msg)                                          # Ensure string
    print("[B] RX:", s)                                   # Serial: incoming log
    # Ping-pong control
    if s == "SERVE?":                                     # Start rally request
        start_rally()                                     # Begin rally
    elif s == "HIT:A" and round_active and turn == "A":   # A hits correctly
        hit("A")                                          # Switch ball to B
    elif s == "HIT:B" and round_active and turn == "B":   # B hits correctly
        hit("B")                                          # Switch ball to A
    elif s == "MISS:A" and round_active and turn == "A":  # A misses
        miss("A")                                         # Score B
    elif s == "MISS:B" and round_active and turn == "B":  # B misses
        miss("B")                                         # Score A
    # Obstacle judge
    elif s == "GO":                                       # Judge obstacle now
        judge_obstacle()                                  # Evaluate sensor
    elif s.startswith("THRESH="):                         # Threshold update
        THRESH = int(s.split("=")[1])                     # Parse new threshold
        send("ACK:THRESH=" + str(THRESH))                 # Ack threshold change
    # Puzzle
    elif s == "REQ:CLUE":                                 # Request hint
        send("HINT:Starts with 'S'")                      # Provide hint
    elif s.startswith("TRY:"):                            # Attempt solution
        guess = s.split(":")[1]                           # Extract guess
        send("SOLVE:OK" if guess == SECRET else "SOLVE:NO")  # Reply OK/NO
    # Reset scores (optional)
    elif s == "RESET":                                    # Reset scores
        scoreA = 0                                        # A=0
        scoreB = 0                                        # B=0
        send("ACK:RESET")                                 # Ack reset
    else:                                                 # Unknown message
        send("ERR:UNKNOWN")                               # Error feedback

handle.recv(handle_method)                                # Register callback
print("[B] Callback registered")                          # Serial: callback active

while True:                                               # Idle loop; all via callback
    time.sleep_ms(150)                                    # Keep CPU cool

# ---------- BOARD A (Leader / Central) ----------
import ble_central                                # Load Bluetooth central helper
import ble_handle                                 # Load Bluetooth handle helper
import machine                                     # Load hardware pin library
import time                                        # Load time library

btn_hit  = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)  # HIT button
btn_miss = machine.Pin(25, machine.Pin.IN, machine.Pin.PULL_UP)  # MISS button
btn_go   = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP)  # GO judge
btn_try  = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP)  # TRY puzzle
print("[A] Buttons HIT=26 MISS=25 GO=17 TRY=16 ready")           # Serial: buttons ready

central = ble_central.BLESimpleCentral()           # Central object
handle  = ble_handle.Handle()                      # Callback handle
print("[A] Central + handle ready")                # Serial: Bluetooth initialized

def connect_peer():                                # Helper: connect to Coop-R32
    central.scan()                                 # Scan for devices
    time.sleep_ms(600)                             # Short scan delay
    central.connect('Coop-R32')                    # Connect by name
    print("[A] Connected to Coop-R32")             # Serial: connection OK

def send(text):                                    # Helper: transmit text
    central.send(text)                             # Send command
    print("[A] TX:", text)                         # Serial: log send

def handle_method(msg):                            # Callback: print feedback
    print("[A] RX:", msg)                          # Serial: log incoming feedback

handle.recv(handle_method)                         # Register callback
print("[A] Callback registered")                   # Serial: callback active

connect_peer()                                     # Connect to Board B

send("SERVE?")                                     # Ask to start ping-pong rally

turn = "A"                                         # Track ping-pong turn locally
guess = "SUN"                                      # Example puzzle guess

while True:                                        # Leader control loop
    if btn_hit.value() == 0:                       # HIT pressed
        send("HIT:" + turn)                        # Send HIT message
        turn = "B" if turn == "A" else "A"         # Flip expected turn
        time.sleep_ms(250)                         # Debounce
    if btn_miss.value() == 0:                      # MISS pressed
        send("MISS:" + turn)                       # Send MISS message
        turn = "B" if turn == "A" else "A"         # Flip turn
        time.sleep_ms(250)                         # Debounce
    if btn_go.value() == 0:                        # GO judge pressed
        send("GO")                                 # Request obstacle judge
        time.sleep_ms(250)                         # Debounce
    if btn_try.value() == 0:                       # TRY puzzle pressed
        send("TRY:" + guess)                       # Attempt puzzle solution
        time.sleep_ms(250)                         # Debounce
    time.sleep_ms(40)                              # Poll delay

External explanation

  • What it teaches: How to structure small cooperative games with simple messages, fair timing, and clear feedback.
  • Why it works: Bluetooth callbacks keep responses immediate; helpers clarify roles; debounced inputs prevent frustration; short labels keep bandwidth small.
  • Key concept: Team play is about synchronization and feedback — your code mirrors those principles.

Story time

Two tiny teammates: one calls “serve!”, the other returns on time. Together they judge obstacles, trade clues, and confirm tasks. When everything clicks, the LEDs glow like high‑fives.


Debugging (2)

Debugging 3.11.A – Imbalance difficulty

  • Symptom: Rally is too fast or the sensor threshold too strict.
  • Fix: Add spacing and widen thresholds.
# In Board A: increase debounce to 300–350 ms for human timing
time.sleep_ms(300)  # Friendlier input pacing

# In Board B: lower THRESH or make PASS window wider
THRESH = 1600       # More forgiving sensor threshold

Debugging 3.11.B – Inconsistent communication

  • Symptom: Turns desync or messages feel out of order.
  • Fix: Keep messages short and state‑dependent; log minimal context.
# Only accept HIT/MISS when round_active and matches current turn
elif s == "HIT:A" and round_active and turn == "A":
    hit("A")  # State-consistent action

Final checklist

  • Ping‑pong rally starts on “SERVE” and alternates properly.
  • Scores update fairly with clear MISS messages.
  • Obstacle judge returns PASS/FAIL and a helpful hint.
  • Puzzle accepts clues and responds SOLVE OK/NO.
  • Teamwork simulator throttles tasks and confirms completion.

Extras

  • Student tip: Rename devices (Game‑R32, Course‑R32, Coop‑R32) so pairing is quick.
  • Instructor tip: Let students adjust THRESH and debounce to tune difficulty for their style.
  • Glossary:
    • Serve: Start a rally or turn.
    • Hint: Guidance toward success without giving the answer.
    • Debounce: A short delay to avoid repeated actions from one press.
  • Mini tips:
    • Keep messages under ~16 characters.
    • Print concise serial logs; too many prints cause lag.
    • Confirm actions with LEDs or short beeps for delight and clarity.
On this page