จุดที่ทำให้ทุกจอเห็นเลขตรงกันเป๊ะ อยู่ตรงนี้ — เรา ไม่หัก HP ตอนกดปุ่ม แต่หักตอนข้อความเด้งกลับมาจาก broker

เพราะ broker ส่งดาเมจของเราเองกลับมาด้วย (echo) ถ้าเราหักตอนกดอีก HP จะโดนหักสองเด้ง แล้วเลขจะเพี้ยนจากเพื่อน เราจึงเลือกหัก "ที่เดียว" คือตอนรับ ทุกบอร์ดจึงเดินเลขเดียวกันเสมอ
นี่คือหลัก single source of truth — มี "ทางเข้า" ของการเปลี่ยนสถานะแค่ทางเดียว ทุกคนถึงไม่ทะเลาะกันเรื่องเลข
ต่อ WiFi ก่อนเสมอ แล้วค่อยต่อ MQTT จากนั้น subscribe ทั้ง topic ดาเมจและ topic HP
if not mqtt.connect(BROKER, PORT, client_id=PLAYER):
print("ต่อ broker ไม่ได้")
raise SystemExit
mqtt.subscribe(TOPIC_DMG)
mqtt.subscribe(TOPIC_HP)
print("ต่อ broker แล้ว ทุกบอร์ดอยู่ห้องเดียวกัน")

client_id=PLAYER ต้องไม่ซ้ำกันทั้งห้อง ไม่งั้น broker เตะกันออก
ทางลัด: ต่อ WiFi ผ่านเมนู Wi-Fi บนจอก่อนครั้งเดียว แล้ว
wifi.connect()ในโค้ดจะผ่านทันที
อ่านปุ่มยิงด้วย game.keys().a แล้วนับเฉพาะ "ขอบขาลง" เพื่อยิงทีละครั้ง (กดค้างไม่รัว)
fire = game.keys().a
if fire and not prev_fire:
mqtt.publish(TOPIC_DMG, "%s:%d" % (PLAYER, HIT_DAMAGE))
game.sfx("fire")
prev_fire = fire
prev_fire คือเทคนิค edge-detection เดียวกับปุ่ม FSM ในคาบต้น ๆ — กันยิงซ้ำตอนกดค้าง

get_message() ไม่บล็อก (ไม่มีของก็คืน None) เกมจึงลื่น เราหัก HP แล้ววาดหลอดใหม่
msg = mqtt.get_message()
if msg is not None:
topic, data = msg
if topic == TOPIC_DMG:
_, dmg = data.decode().split(":")
apply_damage(int(dmg))
game.sfx("hit")
apply_damage() หัก boss_hp แล้วย่อความกว้างหลอดด้วย bar_fg.resize(...) ให้ตรงกันทุกจอ

หมายเหตุ: ดาเมจของเราเองก็เด้งกลับมาทาง broker เราจึงหัก HP จาก "ข้อความที่รับ" ที่เดียว — ทุกบอร์ดเห็นเลขเดียวกันเป๊ะ
โค้ดเราเรียกเสียงสองจังหวะ: game.sfx("fire") ตอนกดยิง และ game.sfx("hit") ตอนดาเมจเด้งกลับมาหัก HP เสียงทุกคนจึงดังพร้อมกันเวลาช่วยกันรัว ตอนเลือดบอสหมดค่อยปิดท้ายด้วยเสียงระเบิด
เสียงในคาบนี้ — เกมยิง Boss ใช้เสียงตอนกดยิง (fire) ตอนยิงโดน (hit) และเสียงระเบิดตอนบอสล้ม (กดเล่นฟังได้จริง):
เสียงไม่ได้มีแค่ "ดัง" — มันมี รูปร่าง ดูเสียงระเบิดตอนบอสล้มเป็น spectrogram (แกนนอน=เวลา แกนตั้ง=ความถี่ สีสว่าง=ดัง) จะเห็นพลังงานปะทุเป็นชั้น ๆ แล้วค่อยจางหายไป:

ที่มา: spectrogram ของเสียง shoot_explode สร้างจาก bento_sfx (BENTO course asset)
ลองสลับเสียงระเบิดไปใส่ตอน "BOSS DOWN!" เพื่อให้จังหวะปิดเกมหนักแน่นขึ้น — ทุกจอจะได้ยินพร้อมกันเพราะวาดจากเลขก้อนเดียวกัน
วิธีรัน: เปิด codes/ex_a4_coop_boss.py ใน BENTO IDE แล้วกด Program to Device (ปุ่มเขียวมุมซ้ายบน) — โค้ดเดียวกันนี้ลงได้ทุกบอร์ด

ปุ่ม Program to Device มุมซ้ายบนของ BENTO IDE — กดแล้วเซฟเป็น main.py ลงบอร์ดและรันทันที (เห็น "Programming main.py to device" ใน Terminal) · ที่มา: BENTO IDE (course asset)
PLAYER คนละชื่อ ("board-01", "board-02")TOPIC และ BROKER เหมือนกันทุกตัว

เกณฑ์ผ่าน: กดยิงแล้วหลอด HP ทุกจอ ลดพร้อมกัน และขึ้น "BOSS DOWN!" พร้อมกันเมื่อเลือดหมด
หลอด HP ที่เราวาด คือ HP ที่เหลือเทียบกับเต็ม คิดเป็นสมการความกว้างตรง ๆ:
ดาเมจรวมต่อวินาที = จำนวนบอร์ด คูณดาเมจต่อนัด คูณความถี่ยิง ดังนั้นเวลาล้มบอส:

เพราะ อยู่ที่ "ตัวหาร" เวลาจึงลดแบบ — จาก 1 เป็น 2 บอร์ดเวลาหายครึ่ง แต่จาก 5 เป็น 6 แทบไม่ต่าง นี่คือเหตุผลว่าทำไม "เพื่อนคนแรก ๆ ที่มาช่วย" สำคัญที่สุด
ทุกจอวาดจากเลขเดียวกัน (HP ที่รับจาก broker) หลอดจึงเดินตรงกันเป๊ะ จนขึ้น BOSS DOWN พร้อมกัน

บอร์ด A หรือ B ยิงทีไหน ดาเมจวิ่งผ่าน broker แล้วเด้งกลับให้ทุกคนหัก HP ก้อนเดียวกัน — ไม่มีใครเห็นเลขต่างกันเลย
ลองจินตนาการเปลี่ยน "หลอด HP" เป็น "ยอดผลิตรวมของไลน์" หรือ "อุณหภูมิเฉลี่ยของฟาร์ม" ภาพก็เหมือนกันเป๊ะ
สิ่งที่เราเพิ่งทำคือ shared real-time state ผ่าน MQTT — แก่นเดียวกับงานจริงหลายแบบ
ลองทำเพิ่ม (เลือก 1):
game.tilt() แทนปุ่ม: เอียงบอร์ดแรง = ดาเมจมากขึ้นgpio.led(0) กะพริบทุกครั้งที่ยิงโดนโปรเจกต์นี้พิสูจน์ว่าบอร์ดเล็ก ๆ ก็เป็น node ในระบบ multiplayer/IoT จริงได้ ด้วยโค้ดไม่ถึง 80 บรรทัด
เกม co-op เกมเดียวนี้ ร้อยวิชาพื้นฐานเข้าด้วยกันหมด — ลองมองทะลุเกมไปที่วิศวกรรมข้างใต้:
data.decode().split(":") คือ string parsing · fire and not prev_fire คือ edge-detection ด้วยตัวแปรจำสถานะ · update() ที่ game.run เรียกทุกเฟรม คือ callback
เล่นเป็นเกมวันนี้ พรุ่งนี้สเกลขึ้นเป็นระบบจริงได้เลย เพราะรากฐานมันตัวเดียวกัน
fit-css