Raspberry Pi Pico LED Halat Çekme Oyunu (NeoPixel Şerit + 2 Buton + Buzzer)
İki kişilik bir buton düellosu! **Raspberry Pi Pico**, 30 LED'lik **WS2812B NeoPixel şeridi** bir halata çevirir: solu kırmızı oyuncunun, sağı mavi oyuncunun. Butonuna her basışta kendi rengin bir LED ilerler; şeridi tamamen kendi rengine boyayan turu, 3 turu ilk alan maçı kazanır. Geri sayımda erken basan geri itilir, **pasif buzzer** her oyuncuya farklı tık sesi çalar ve maç gökkuşağıyla biter. MicroPython ile `neopixel`, debounce ve bloklamayan ses tek projede.

Video
Devre Şeması

Kaynak Kodu
1# ─────────────────────────────────────────
2# BlueGrays · bluegrays.com
3# Raspberry Pi Pico LED Halat Çekme Oyunu (NeoPixel Şerit + 2 Buton + Buzzer)
4# ─────────────────────────────────────────
5# İki oyunculu buton düellosu. Her basışta kendi rengin şeritte bir adım
6# ilerler; rengini şeridin sonuna ilk ulaştıran turu kazanır, 3 tur alan
7# maçı kazanır. Geri sayımda erken basan 2 adım geri itilir.
8#
9# Pinler:
10# GP2 -> WS2812B şerit DIN
11# GP14 -> Oyuncu 1 butonu (KIRMIZI)
12# GP16 -> Oyuncu 2 butonu (MAVİ)
13# GP15 -> Buzzer (+)
14
15from machine import Pin, PWM
16from neopixel import NeoPixel
17import time
18import math
19
20# ============ AYARLAR ============
21NUM_LEDS = 30 # Şeritteki LED sayısı
22BRIGHTNESS = 0.6 # 0-1; 30 LED renkli kullanımda ~0,4 A, USB için uygun
23WIN_ROUNDS = 3 # Maçı kazanmak için tur
24FALSE_START_STEPS = 2 # Erken basma cezası
25SOUND_ON = True # Ses efektleri
26
27RED = (255, 0, 0) # Oyuncu 1
28BLUE = (0, 60, 255) # Oyuncu 2
29
30# Düğüm = kırmızı LED sayısı (iki rengin sınırı). İki oyuncu da aynı adım sayısıyla kazanır.
31START = NUM_LEDS // 2 # Başlangıç sınırı
32GOAL = min(START, NUM_LEDS - START) # Kazanmak için adım
33
34# ============ DONANIM ============
35strip = NeoPixel(Pin(2), NUM_LEDS)
36btn1 = Pin(14, Pin.IN, Pin.PULL_UP) # Oyuncu 1
37btn2 = Pin(16, Pin.IN, Pin.PULL_UP) # Oyuncu 2
38buzzer = PWM(Pin(15))
39buzzer.duty_u16(0)
40
41
42# ============ SES ============
43class Sound:
44 """
45 TR: Bloklamayan nota calici: oyun ses calarken de butonlari okur
46 EN: Non-blocking note player: the game keeps reading buttons while sound plays
47 """
48
49 def __init__(self, pwm):
50 self.pwm = pwm
51 self.queue = []
52 self.until = 0
53
54 def play(self, notes):
55 # notes = [(frekans, ms), ...], frekans 0 = sus
56 if SOUND_ON:
57 self.queue = list(notes)
58 self.until = 0
59
60 def update(self):
61 now = time.ticks_ms()
62 if self.until and time.ticks_diff(now, self.until) < 0:
63 return
64 if not self.queue:
65 if self.until:
66 self.pwm.duty_u16(0)
67 self.until = 0
68 return
69 freq, ms = self.queue.pop(0)
70 if freq:
71 self.pwm.freq(freq)
72 self.pwm.duty_u16(20000)
73 else:
74 self.pwm.duty_u16(0)
75 self.until = time.ticks_add(now, ms)
76
77
78sound = Sound(buzzer)
79
80
81# ============ BUTON ============
82class Button:
83 """
84 TR: 15 ms debounce ile yeni basislari yakalar
85 EN: Catches new presses with a 15 ms debounce
86 """
87
88 def __init__(self, pin):
89 self.pin = pin
90 self.state = 1
91 self.raw = 1
92 self.changed = time.ticks_ms()
93
94 def pressed(self):
95 now = time.ticks_ms()
96 raw = self.pin.value()
97 if raw != self.raw:
98 self.raw = raw
99 self.changed = now
100 if time.ticks_diff(now, self.changed) > 15 and raw != self.state:
101 self.state = raw
102 return raw == 0
103 return False
104
105 def sync(self):
106 # Eski basışları unut (tur arasında basılı kalan buton ceza almasın)
107 self.state = self.raw = self.pin.value()
108 self.changed = time.ticks_ms()
109
110
111b1 = Button(btn1)
112b2 = Button(btn2)
113
114
115# ============ ÇİZİM YARDIMCILARI ============
116def dim(color, k=1.0):
117 # Rengi genel parlaklık ve k ile ölçekle
118 s = BRIGHTNESS * k
119 return (int(color[0] * s), int(color[1] * s), int(color[2] * s))
120
121
122def fill(color, k=1.0):
123 c = dim(color, k)
124 for i in range(NUM_LEDS):
125 strip[i] = c
126 strip.write()
127
128
129def draw_rope(knot):
130 # knot kadar LED kırmızı (solda), gerisi mavi (sağda); sınırdaki 2'şer LED parlak "düğüm"
131 for i in range(NUM_LEDS):
132 if i < knot:
133 k = 1.0 if knot - i <= 2 else 0.45 # Düğüme yakın LED'ler parlak
134 strip[i] = dim(RED, k)
135 else:
136 k = 1.0 if i - knot < 2 else 0.45
137 strip[i] = dim(BLUE, k)
138 strip.write()
139
140
141def wheel(pos):
142 # 0-255 arası değeri gökkuşağı rengine çevir
143 pos %= 256
144 if pos < 85:
145 return (255 - pos * 3, pos * 3, 0)
146 if pos < 170:
147 pos -= 85
148 return (0, 255 - pos * 3, pos * 3)
149 pos -= 170
150 return (pos * 3, 0, 255 - pos * 3)
151
152
153def wait_ms(ms):
154 # Bekle ama sesi çalmaya devam et
155 end = time.ticks_add(time.ticks_ms(), ms)
156 while time.ticks_diff(end, time.ticks_ms()) > 0:
157 sound.update()
158 time.sleep_ms(5)
159
160
161# ============ OYUN AŞAMALARI ============
162def attract():
163 # Bekleme: iki renk nefes alır, herhangi bir butona basınca oyun başlar
164 print("Baslamak icin bir butona bas / Press a button to start")
165 b1.sync()
166 b2.sync()
167 t0 = time.ticks_ms()
168 while True:
169 sound.update()
170 t = time.ticks_diff(time.ticks_ms(), t0)
171 k = 0.25 + 0.75 * (math.sin(t / 400) + 1) / 2
172 for i in range(NUM_LEDS):
173 if i < START:
174 strip[i] = dim(RED, k)
175 else:
176 strip[i] = dim(BLUE, 1.25 - k) # Ters fazda nefes
177 strip.write()
178 if b1.pressed() or b2.pressed():
179 return
180 time.sleep_ms(15)
181
182
183def countdown():
184 # 3-2-1 geri sayım; erken basan ceza alır. Dönüş: başlangıç düğüm konumu
185 knot = START
186 b1.sync()
187 b2.sync()
188 colors = [(255, 0, 0), (255, 150, 0), (0, 255, 0)] # Kırmızı, sarı, yeşil
189 for step in range(3):
190 fill(colors[step], 0.6)
191 if step == 2:
192 sound.play([(1200, 200)]) # BAŞLA!
193 break
194 sound.play([(600, 150)])
195 end = time.ticks_add(time.ticks_ms(), 800)
196 while time.ticks_diff(end, time.ticks_ms()) > 0:
197 sound.update()
198 if b1.pressed():
199 knot -= FALSE_START_STEPS
200 print("Oyuncu 1 erken basti! / Player 1 false start!")
201 sound.play([(150, 200)])
202 if b2.pressed():
203 knot += FALSE_START_STEPS
204 print("Oyuncu 2 erken basti! / Player 2 false start!")
205 sound.play([(150, 200)])
206 time.sleep_ms(5)
207 # Ceza düğümü uca kadar itmesin
208 return max(START - GOAL + 2, min(START + GOAL - 2, knot))
209
210
211def play_round(knot):
212 # Buton düellosu; düğüm bir uca ulaşınca kazanan döner (1 veya 2)
213 draw_rope(knot)
214 while True:
215 sound.update()
216 moved = False
217 if b1.pressed():
218 knot += 1
219 sound.play([(440, 25)])
220 moved = True
221 if b2.pressed():
222 knot -= 1
223 sound.play([(660, 25)])
224 moved = True
225 if moved:
226 if knot >= START + GOAL: # Şerit tamamen kırmızı
227 return 1
228 if knot <= START - GOAL: # Şerit tamamen mavi
229 return 2
230 draw_rope(knot)
231 time.sleep_ms(1)
232
233
234def celebrate_round(winner, scores):
235 # Kazanan renk 3 kez yanıp söner, sonra skor noktaları gösterilir
236 color = RED if winner == 1 else BLUE
237 print("Turu kazanan / Round winner: Oyuncu / Player", winner, "| Skor / Score:", scores[0], "-", scores[1])
238 sound.play([(523, 120), (659, 120), (784, 120), (1047, 300)])
239 for _ in range(3):
240 fill(color)
241 wait_ms(180)
242 fill((0, 0, 0))
243 wait_ms(120)
244 # Skor: soldan kırmızı noktalar, sağdan mavi noktalar
245 for i in range(NUM_LEDS):
246 strip[i] = (0, 0, 0)
247 for s in range(scores[0]):
248 strip[s * 2] = dim(RED)
249 for s in range(scores[1]):
250 strip[NUM_LEDS - 1 - s * 2] = dim(BLUE)
251 strip.write()
252 wait_ms(1800)
253
254
255def celebrate_match(winner):
256 # Maç sonu: gökkuşağı dalgası ve zafer melodisi
257 print("MACI KAZANAN / MATCH WINNER: Oyuncu / Player", winner)
258 sound.play([(784, 150), (784, 150), (784, 150), (1047, 450), (0, 80), (988, 150), (1047, 500)])
259 t0 = time.ticks_ms()
260 while time.ticks_diff(time.ticks_ms(), t0) < 3500:
261 sound.update()
262 offset = time.ticks_diff(time.ticks_ms(), t0) // 8
263 for i in range(NUM_LEDS):
264 strip[i] = dim(wheel(i * 256 // NUM_LEDS + offset))
265 strip.write()
266 time.sleep_ms(15)
267 fill(RED if winner == 1 else BLUE)
268 wait_ms(1000)
269
270
271# ============ ANA DÖNGÜ ============
272print("== BlueGrays · bluegrays.com ==")
273print("Pico LED Halat Cekme / LED Tug of War")
274
275scores = [0, 0]
276while True:
277 attract()
278 while max(scores) < WIN_ROUNDS:
279 start_knot = countdown()
280 winner = play_round(start_knot)
281 scores[winner - 1] += 1
282 celebrate_round(winner, scores)
283 celebrate_match(1 if scores[0] > scores[1] else 2)
284 scores = [0, 0]
285Sıkça Sorulanlar
Şeridim 30 LED'lik değil, başka uzunlukta şerit kullanabilir miyim?▾
Evet. Kodun başındaki `NUM_LEDS` değerini şeridindeki LED sayısı yap; başlangıç noktası (`START`) ve kazanmak için gereken adım (`GOAL`) kendiliğinden hesaplanır. 60 LED'lik şeritte her oyuncu 30 basışla kazanır, turlar uzar.
330 Ω direnç ve 470 µF kondansatör şart mı?▾
Wokwi'de gerekmez, gerçek devrede önerilir. GP2 ile şeridin DIN girişi arasına seri 330 Ω direnç, şeridin 5V-GND girişine 470 µF kondansatör tak (uzun bacak 5V'a). İlk LED'i korur, titremeyi ve rastgele renkleri önler.
Şeridi Pico'nun USB'sinden beslemek yeterli mi?▾
30 LED ve `BRIGHTNESS = 0.6` ayarıyla şerit en fazla yaklaşık 0,4 A çeker, USB yeter. VBUS yalnız USB takılıyken 5V verir. Daha uzun şerit ya da daha yüksek parlaklık için ayrı bir 5V/2A adaptör kullan ve GND'leri birleştir.
Tur sayısını ve erken basma cezasını nasıl değiştiririm?▾
`WIN_ROUNDS` maçı kazanmak için gereken tur sayısıdır (varsayılan 3). `FALSE_START_STEPS` erken basanın kaç adım geri itileceğini belirler (varsayılan 2; 0 yaparsan ceza kalkar). Sessiz oynamak için `SOUND_ON = False` yap.
