from machine import Pin, PWM
import time
# =============================================================
# PIN ASSIGNMENTS (Wokwi Demo — 2 players, 2 locations each)
# =============================================================
START_BTN_PIN = 0
RESET_BTN_PIN = 1
BUZZER_PIN = 2
SERVO_PIN = 3
P1_HOME_PIN = 4
P2_HOME_PIN = 5
P1_LOC_PINS = [8, 9]
P2_LOC_PINS = [10, 11]
P1_LOC_LED_PINS = [16, 17]
P2_LOC_LED_PINS = [18, 19]
P1_IND_PIN = 27
P2_IND_PIN = 28
# =============================================================
# GAME SETTINGS
# =============================================================
GAME_DURATION = 30
LOC_NAMES = ["Fast Food", "Healthy Store"]
LOC_POINTS = [1, 5]
DEBOUNCE_MS = 250
# =============================================================
# HARDWARE INIT
# =============================================================
start_btn = Pin(START_BTN_PIN, Pin.IN, Pin.PULL_DOWN)
reset_btn = Pin(RESET_BTN_PIN, Pin.IN, Pin.PULL_DOWN)
buzzer = PWM(Pin(BUZZER_PIN))
buzzer.duty_u16(0)
servo = PWM(Pin(SERVO_PIN))
servo.freq(50)
# =============================================================
# PLAYER SETUP
# =============================================================
def make_player(name, home_pin, loc_pins, led_pins, ind_pin):
return {
"name": name,
"home_btn": Pin(home_pin, Pin.IN, Pin.PULL_DOWN),
"loc_btns": [Pin(p, Pin.IN, Pin.PULL_DOWN) for p in loc_pins],
"loc_leds": [Pin(p, Pin.OUT) for p in led_pins],
"indicator": Pin(ind_pin, Pin.OUT),
"score": 0,
"carried": 0,
"claimed": [False, False],
"prev_loc": [0, 0],
"last_loc": [0, 0],
"prev_home": 0,
"last_home": 0,
"blink": False,
}
players = []
# =============================================================
# SOUND FUNCTIONS
# =============================================================
def tone(freq, dur_ms):
buzzer.freq(freq)
buzzer.duty_u16(2000)
time.sleep_ms(dur_ms)
buzzer.duty_u16(0)
def beep():
tone(1000, 100)
def triple_beep():
for _ in range(3):
tone(1200, 150)
time.sleep_ms(100)
def coin_sound():
tone(1500, 80)
time.sleep_ms(30)
tone(2000, 120)
def error_tone():
tone(300, 300)
def victory_tune():
for f in [523, 659, 784, 1047]:
tone(f, 150)
time.sleep_ms(50)
def tie_tune():
tone(523, 200)
time.sleep_ms(80)
tone(523, 200)
def game_over_tone():
tone(400, 600)
# =============================================================
# SERVO — keep signal active so Wokwi renders movement
# =============================================================
def set_servo(angle):
# 0deg=1640, 180deg=8190 in duty_u16 at 50Hz
duty = int(1640 + (8190 - 1640) * angle / 180)
servo.duty_u16(duty)
time.sleep_ms(500)
# DO NOT zero out duty — Wokwi needs continuous signal
def gate_open():
set_servo(90)
print("[Gate OPEN]")
def gate_close():
set_servo(0)
print("[Gate CLOSED]")
# =============================================================
# LED HELPERS
# =============================================================
def all_loc_leds(state):
for p in players:
for led in p["loc_leds"]:
led.value(state)
def all_ind_off():
for p in players:
p["indicator"].value(0)
def flash_led(led, times=3):
for _ in range(times):
led.value(1)
time.sleep_ms(100)
led.value(0)
time.sleep_ms(100)
# =============================================================
# EDGE DETECTION
# =============================================================
def loc_rising(p, idx):
cur = p["loc_btns"][idx].value()
prev = p["prev_loc"][idx]
p["prev_loc"][idx] = cur
if cur == 1 and prev == 0:
now = time.ticks_ms()
if time.ticks_diff(now, p["last_loc"][idx]) > DEBOUNCE_MS:
p["last_loc"][idx] = now
return True
return False
def home_rising(p):
cur = p["home_btn"].value()
prev = p["prev_home"]
p["prev_home"] = cur
if cur == 1 and prev == 0:
now = time.ticks_ms()
if time.ticks_diff(now, p["last_home"]) > DEBOUNCE_MS:
p["last_home"] = now
return True
return False
# =============================================================
# WAIT FOR BUTTON
# =============================================================
def wait_for_btn(btn):
while btn.value() == 0:
time.sleep_ms(50)
while btn.value() == 1:
time.sleep_ms(10)
# =============================================================
# MAIN GAME
# =============================================================
def run_game():
global players
players = [
make_player("P1", P1_HOME_PIN, P1_LOC_PINS,
P1_LOC_LED_PINS, P1_IND_PIN),
make_player("P2", P2_HOME_PIN, P2_LOC_PINS,
P2_LOC_LED_PINS, P2_IND_PIN),
]
all_loc_leds(1)
all_ind_off()
gate_close()
print("\n" + "=" * 44)
print(" BOARD GAME — 2 PLAYER DEMO")
print(" Each player has 2 locations:")
for i in range(2):
print(" {}: +{}pt{}".format(
LOC_NAMES[i], LOC_POINTS[i],
"s" if LOC_POINTS[i] > 1 else ""))
print(" Claim locations, then press HOME")
print(" to deposit carried points!")
print(" Press START (green) to begin!")
print("=" * 44)
wait_for_btn(start_btn)
print("\n*** GAME STARTED — {}s ***".format(GAME_DURATION))
print(" P1: yellow/white loc btns, blue home")
print(" P2: orange/purple loc btns, gray home\n")
triple_beep()
gate_open()
start_time = time.time()
last_countdown = -1
blink_timer = time.ticks_ms()
while True:
remaining = GAME_DURATION - (time.time() - start_time)
# ---- TIME'S UP ----
if remaining <= 0:
game_over_tone()
gate_close()
all_loc_leds(0)
all_ind_off()
s1 = players[0]["score"]
s2 = players[1]["score"]
print("\n" + "=" * 44)
print(" TIME'S UP!")
print("-" * 44)
print(" Player 1: {} pts".format(s1))
if players[0]["carried"] > 0:
print(" ({} pts lost — not deposited)".format(
players[0]["carried"]))
print(" Player 2: {} pts".format(s2))
if players[1]["carried"] > 0:
print(" ({} pts lost — not deposited)".format(
players[1]["carried"]))
print("-" * 44)
if s1 > s2:
print(" >>> PLAYER 1 WINS! <<<")
victory_tune()
for _ in range(10):
players[0]["indicator"].value(1)
time.sleep_ms(200)
players[0]["indicator"].value(0)
time.sleep_ms(200)
elif s2 > s1:
print(" >>> PLAYER 2 WINS! <<<")
victory_tune()
for _ in range(10):
players[1]["indicator"].value(1)
time.sleep_ms(200)
players[1]["indicator"].value(0)
time.sleep_ms(200)
else:
print(" >>> IT'S A TIE! <<<")
tie_tune()
for _ in range(10):
players[0]["indicator"].value(1)
players[1]["indicator"].value(1)
time.sleep_ms(200)
players[0]["indicator"].value(0)
players[1]["indicator"].value(0)
time.sleep_ms(200)
print("=" * 44)
print("\nPress RESET (red) to play again.")
wait_for_btn(reset_btn)
return
# ---- Countdown every 10s ----
secs = int(remaining)
if secs % 10 == 0 and secs != last_countdown and secs > 0:
last_countdown = secs
print("[{}s] P1: {}pts (carry {}) | P2: {}pts (carry {})".format(
secs,
players[0]["score"], players[0]["carried"],
players[1]["score"], players[1]["carried"]))
# ---- Blink indicators for carrying ----
if time.ticks_diff(time.ticks_ms(), blink_timer) > 300:
blink_timer = time.ticks_ms()
for p in players:
if p["carried"] > 0:
p["blink"] = not p["blink"]
p["indicator"].value(p["blink"])
else:
p["indicator"].value(0)
# ---- CHECK EACH PLAYER ----
for p in players:
# -- Home: deposit --
if home_rising(p):
if p["carried"] > 0:
p["score"] += p["carried"]
print(">>> {} deposited {}pts! (total: {}) <<<".format(
p["name"], p["carried"], p["score"]))
p["carried"] = 0
beep()
flash_led(p["indicator"])
else:
print("({} home — nothing to deposit)".format(p["name"]))
# -- Locations: claim & carry --
for i in range(2):
if loc_rising(p, i):
if p["claimed"][i]:
print("X {} — {} already claimed!".format(
p["name"], LOC_NAMES[i]))
error_tone()
flash_led(p["loc_leds"][i])
else:
p["claimed"][i] = True
p["carried"] += LOC_POINTS[i]
p["loc_leds"][i].value(0)
coin_sound()
print("{} claimed {} (+{}pts, carrying {})".format(
p["name"], LOC_NAMES[i],
LOC_POINTS[i], p["carried"]))
time.sleep_ms(5)
# =============================================================
# ENTRY POINT
# =============================================================
print("Board Game Demo — 2 Player (Wokwi)")
print("Duration: {}s | Locs per player: 2".format(GAME_DURATION))
print()
while True:
run_game()