#168Menengah

Game Tarik Tambang LED Raspberry Pi Pico (Strip NeoPixel + 2 Tombol + Buzzer)

Duel tombol untuk dua pemain! Raspberry Pi Pico mengubah strip NeoPixel WS2812B 30 LED menjadi tali: merah di kiri, biru di kanan. Setiap tekanan mendorong warnamu satu LED ke depan; penuhi seluruh strip untuk memenangkan ronde, dan 3 ronde untuk memenangkan pertandingan. Menekan terlalu cepat ketika hitung mundur membuatmu mundur, buzzer pasif memberi setiap pemain bunyi klik sendiri, dan pemenang disambut pelangi. neopixel, debounce, dan suara non-blocking di MicroPython.

0.0(0)
1 selesai
Game Tarik Tambang LED Raspberry Pi Pico (Strip NeoPixel + 2 Tombol + Buzzer)
Game tarik tambang bercahaya tempat kamu dan temanmu adu cepat jari. Satu sisi strip lampu 30 LED berwarna merah dan sisi lainnya biru; siapa pun yang menekan tombolnya lebih cepat menarik simpul di tengah ke sisinya, dan yang pertama mewarnai seluruh strip dengan warnanya memenangkan ronde. Mencuri start ada hukumannya, yang pertama memenangkan tiga ronde memenangkan pertandingan, dan pertunjukan pelangi dengan lagu kemenangan menyusul.

Video

Skema rangkaian

Game Tarik Tambang LED Raspberry Pi Pico (Strip NeoPixel + 2 Tombol + Buzzer) - Circuit

Kode sumber

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Game Tarik Tambang LED Raspberry Pi Pico (Strip NeoPixel + 2 Tombol + Buzzer)
4# ─────────────────────────────────────────
5# Duel tombol dua pemain. Setiap penekanan mendorong warnamu satu langkah
6# di sepanjang strip; yang pertama mencapai ujung seberang memenangkan
7# ronde, yang pertama mencapai 3 ronde memenangkan pertandingan. Menekan
8# terlalu cepat selama hitung mundur membuatmu kehilangan 2 langkah.
9#
10# Pin:
11#   GP2  -> DIN strip
12#   GP14 -> Tombol pemain 1 (RED), kaki lainnya ke GND
13#   GP16 -> Tombol pemain 2 (BLUE), kaki lainnya ke GND
14#   GP15 -> Buzzer (+)
15
16from machine import Pin, PWM
17from neopixel import NeoPixel
18import time
19import math
20
21# ============ PENGATURAN ============
22NUM_LEDS = 30          # LED di strip
23BRIGHTNESS = 0.6       # ~0.4 A dengan warna, aman untuk USB
24WIN_ROUNDS = 3         # Ronde untuk memenangkan pertandingan
25FALSE_START_STEPS = 2  # Penalti start terlalu cepat
26SOUND_ON = True        # Efek suara
27
28RED = (255, 0, 0)      # Pemain 1
29BLUE = (0, 60, 255)    # Pemain 2
30
31# Knot = jumlah LED merah (batas antar warna). Kedua pemain butuh jumlah langkah yang sama.
32START = NUM_LEDS // 2                   # Batas awal
33GOAL = min(START, NUM_LEDS - START)     # Langkah yang dibutuhkan untuk menang
34
35# ============ HARDWARE ============
36strip = NeoPixel(Pin(2), NUM_LEDS)
37btn1 = Pin(14, Pin.IN, Pin.PULL_UP)   # Pemain 1
38btn2 = Pin(16, Pin.IN, Pin.PULL_UP)   # Pemain 2
39buzzer = PWM(Pin(15))
40buzzer.duty_u16(0)
41
42
43# ============ SUARA ============
44class Sound:
45    """
46    TR: Bloklamayan nota calici: oyun ses calarken de butonlari okur
47    EN: Non-blocking note player: the game keeps reading buttons while sound plays
48    """
49
50    def __init__(self, pwm):
51        self.pwm = pwm
52        self.queue = []
53        self.until = 0
54
55    def play(self, notes):
56        # notes = [(frekuensi, ms), ...], frekuensi 0 = jeda
57        if SOUND_ON:
58            self.queue = list(notes)
59            self.until = 0
60
61    def update(self):
62        now = time.ticks_ms()
63        if self.until and time.ticks_diff(now, self.until) < 0:
64            return
65        if not self.queue:
66            if self.until:
67                self.pwm.duty_u16(0)
68                self.until = 0
69            return
70        freq, ms = self.queue.pop(0)
71        if freq:
72            self.pwm.freq(freq)
73            self.pwm.duty_u16(20000)
74        else:
75            self.pwm.duty_u16(0)
76        self.until = time.ticks_add(now, ms)
77
78
79sound = Sound(buzzer)
80
81
82# ============ TOMBOL ============
83class Button:
84    """
85    TR: 15 ms debounce ile yeni basislari yakalar
86    EN: Catches new presses with a 15 ms debounce
87    """
88
89    def __init__(self, pin):
90        self.pin = pin
91        self.state = 1
92        self.raw = 1
93        self.changed = time.ticks_ms()
94
95    def pressed(self):
96        now = time.ticks_ms()
97        raw = self.pin.value()
98        if raw != self.raw:
99            self.raw = raw
100            self.changed = now
101        if time.ticks_diff(now, self.changed) > 15 and raw != self.state:
102            self.state = raw
103            return raw == 0
104        return False
105
106    def sync(self):
107        # Lupakan penekanan lama (tombol yang ditahan di antara ronde tidak dikenai penalti)
108        self.state = self.raw = self.pin.value()
109        self.changed = time.ticks_ms()
110
111
112b1 = Button(btn1)
113b2 = Button(btn2)
114
115
116# ============ HELPER MENGGAMBAR ============
117def dim(color, k=1.0):
118    # Skala warna dengan kecerahan global dan k
119    s = BRIGHTNESS * k
120    return (int(color[0] * s), int(color[1] * s), int(color[2] * s))
121
122
123def fill(color, k=1.0):
124    c = dim(color, k)
125    for i in range(NUM_LEDS):
126        strip[i] = c
127    strip.write()
128
129
130def draw_rope(knot):
131    # `knot` LED berwarna merah (kiri), sisanya biru (kanan); 2 LED di tiap sisi batas membentuk
132    # "knot" yang terang
133    for i in range(NUM_LEDS):
134        if i < knot:
135            k = 1.0 if knot - i <= 2 else 0.45   # LED di dekat knot lebih terang
136            strip[i] = dim(RED, k)
137        else:
138            k = 1.0 if i - knot < 2 else 0.45
139            strip[i] = dim(BLUE, k)
140    strip.write()
141
142
143def wheel(pos):
144    # Ubah 0-255 menjadi warna pelangi
145    pos %= 256
146    if pos < 85:
147        return (255 - pos * 3, pos * 3, 0)
148    if pos < 170:
149        pos -= 85
150        return (0, 255 - pos * 3, pos * 3)
151    pos -= 170
152    return (pos * 3, 0, 255 - pos * 3)
153
154
155def wait_ms(ms):
156    # Tunggu sambil menjaga suara tetap berjalan
157    end = time.ticks_add(time.ticks_ms(), ms)
158    while time.ticks_diff(end, time.ticks_ms()) > 0:
159        sound.update()
160        time.sleep_ms(5)
161
162
163# ============ FASE PERMAINAN ============
164def attract():
165    # Menunggu: kedua warna bernapas, tombol mana pun memulai permainan
166    print("Baslamak icin bir butona bas / Press a button to start")
167    b1.sync()
168    b2.sync()
169    t0 = time.ticks_ms()
170    while True:
171        sound.update()
172        t = time.ticks_diff(time.ticks_ms(), t0)
173        k = 0.25 + 0.75 * (math.sin(t / 400) + 1) / 2
174        for i in range(NUM_LEDS):
175            if i < START:
176                strip[i] = dim(RED, k)
177            else:
178                strip[i] = dim(BLUE, 1.25 - k)   # Bernapas dengan fase berlawanan
179        strip.write()
180        if b1.pressed() or b2.pressed():
181            return
182        time.sleep_ms(15)
183
184
185def countdown():
186    # Hitung mundur 3-2-1; penekanan terlalu cepat dikenai penalti. Mengembalikan knot awal
187    knot = START
188    b1.sync()
189    b2.sync()
190    colors = [(255, 0, 0), (255, 150, 0), (0, 255, 0)]   # Merah, kuning, hijau
191    for step in range(3):
192        fill(colors[step], 0.6)
193        if step == 2:
194            sound.play([(1200, 200)])   # GO! (mulai!)
195            break
196        sound.play([(600, 150)])
197        end = time.ticks_add(time.ticks_ms(), 800)
198        while time.ticks_diff(end, time.ticks_ms()) > 0:
199            sound.update()
200            if b1.pressed():
201                knot -= FALSE_START_STEPS
202                print("Oyuncu 1 erken basti! / Player 1 false start!")
203                sound.play([(150, 200)])
204            if b2.pressed():
205                knot += FALSE_START_STEPS
206                print("Oyuncu 2 erken basti! / Player 2 false start!")
207                sound.play([(150, 200)])
208            time.sleep_ms(5)
209    # Penalti tidak boleh mendorong knot sampai ke ujung
210    return max(START - GOAL + 2, min(START + GOAL - 2, knot))
211
212
213def play_round(knot):
214    # Duel tombol; mengembalikan pemenang (1 atau 2) ketika knot mencapai ujung
215    draw_rope(knot)
216    while True:
217        sound.update()
218        moved = False
219        if b1.pressed():
220            knot += 1
221            sound.play([(440, 25)])
222            moved = True
223        if b2.pressed():
224            knot -= 1
225            sound.play([(660, 25)])
226            moved = True
227        if moved:
228            if knot >= START + GOAL:   # Strip penuh merah
229                return 1
230            if knot <= START - GOAL:   # Strip penuh biru
231                return 2
232            draw_rope(knot)
233        time.sleep_ms(1)
234
235
236def celebrate_round(winner, scores):
237    # Warna pemenang berkedip 3 kali, lalu titik skor ditampilkan
238    color = RED if winner == 1 else BLUE
239    print("Turu kazanan / Round winner: Oyuncu / Player", winner, "| Skor / Score:", scores[0], "-", scores[1])
240    sound.play([(523, 120), (659, 120), (784, 120), (1047, 300)])
241    for _ in range(3):
242        fill(color)
243        wait_ms(180)
244        fill((0, 0, 0))
245        wait_ms(120)
246    # Skor: titik merah dari kiri, titik biru dari kanan
247    for i in range(NUM_LEDS):
248        strip[i] = (0, 0, 0)
249    for s in range(scores[0]):
250        strip[s * 2] = dim(RED)
251    for s in range(scores[1]):
252        strip[NUM_LEDS - 1 - s * 2] = dim(BLUE)
253    strip.write()
254    wait_ms(1800)
255
256
257def celebrate_match(winner):
258    # Akhir pertandingan: gelombang pelangi dan lagu kemenangan
259    print("MACI KAZANAN / MATCH WINNER: Oyuncu / Player", winner)
260    sound.play([(784, 150), (784, 150), (784, 150), (1047, 450), (0, 80), (988, 150), (1047, 500)])
261    t0 = time.ticks_ms()
262    while time.ticks_diff(time.ticks_ms(), t0) < 3500:
263        sound.update()
264        offset = time.ticks_diff(time.ticks_ms(), t0) // 8
265        for i in range(NUM_LEDS):
266            strip[i] = dim(wheel(i * 256 // NUM_LEDS + offset))
267        strip.write()
268        time.sleep_ms(15)
269    fill(RED if winner == 1 else BLUE)
270    wait_ms(1000)
271
272
273# ============ LOOP UTAMA ============
274print("== BlueGrays · bluegrays.com ==")
275print("Pico LED Halat Cekme / LED Tug of War")
276
277scores = [0, 0]
278while True:
279    attract()
280    while max(scores) < WIN_ROUNDS:
281        start_knot = countdown()
282        winner = play_round(start_knot)
283        scores[winner - 1] += 1
284        celebrate_round(winner, scores)
285    celebrate_match(1 if scores[0] > scores[1] else 2)
286    scores = [0, 0]
287

FAQ

Strip-ku tidak berisi 30 LED. Bisakah aku memakai panjang yang berbeda?

Ya. Atur `NUM_LEDS` di bagian atas kode sesuai jumlah LED di strip-mu; titik awal (`START`) dan langkah yang dibutuhkan untuk menang (`GOAL`) dihitung otomatis. Di strip 60 LED, setiap pemain butuh 30 tekanan, jadi rondenya lebih lama.

Apakah aku benar-benar butuh resistor 330 Ω dan kapasitor 470 µF?

Tidak di Wokwi, tetapi disarankan untuk rangkaian sungguhan. Pasang resistor 330 Ω secara seri di antara GP2 dan DIN strip, dan kapasitor 470 µF di antara 5V dan GND strip (kaki panjang ke 5V). Keduanya melindungi LED pertama serta mencegah kedipan dan warna acak.

Apakah USB Pico cukup untuk memberi daya strip?

Dengan 30 LED dan `BRIGHTNESS = 0.6`, strip menarik paling banyak sekitar 0,4 A, yang masih sanggup ditangani USB. VBUS hanya memberi 5V selama USB tercolok. Untuk strip yang lebih panjang atau kecerahan lebih tinggi, pakai adaptor 5V/2A terpisah dan sambungkan ground-nya.

Bagaimana cara mengubah jumlah ronde dan hukuman mencuri start?

`WIN_ROUNDS` adalah jumlah ronde yang dibutuhkan untuk memenangkan pertandingan (bawaan 3). `FALSE_START_STEPS` mengatur berapa langkah pemain yang menekan terlalu cepat didorong mundur (bawaan 2; atur ke 0 untuk mematikan hukuman). Untuk game tanpa suara, atur `SOUND_ON = False`.