#168Mittel

Raspberry Pi Pico LED-Tauziehen (NeoPixel-Streifen + 2 Taster + Summer)

Ein Tasten-Duell für zwei! Der **Raspberry Pi Pico** macht aus einem **WS2812B-NeoPixel-Streifen** mit 30 LEDs ein Seil: links Rot, rechts Blau. Jeder Druck schiebt deine Farbe eine LED weiter; wer den ganzen Streifen einfärbt, gewinnt die Runde, wer zuerst 3 Runden holt, das Match. Frühstarts im Countdown werden bestraft, der **passive Summer** gibt jedem Spieler einen eigenen Klick, und am Ende gibt es einen Regenbogen. `neopixel`, Entprellung und nicht blockierender Ton in MicroPython.

0.0(0)
Raspberry Pi Pico LED-Tauziehen (NeoPixel-Streifen + 2 Taster + Summer)
Ein leuchtendes Tauziehen, bei dem du mit einem Freund um die schnelleren Finger kämpfst. Eine Hälfte des Lichtstreifens mit 30 LEDs ist rot, die andere blau; wer schneller auf seinen Knopf drückt, zieht den Knoten in der Mitte zu sich, und wer den ganzen Streifen in seiner Farbe leuchten lässt, gewinnt die Runde. Frühstarts werden bestraft, wer zuerst drei Runden holt, gewinnt das Spiel, und dann gibt es eine Regenbogen-Show mit Siegesmelodie.

Video

Schaltplan

Raspberry Pi Pico LED-Tauziehen (NeoPixel-Streifen + 2 Taster + Summer) - Circuit

Quellcode

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Raspberry Pi Pico LED-Tauziehen (NeoPixel-Streifen + 2 Taster + Summer)
4# ─────────────────────────────────────────
5# Tasten-Duell für zwei Spieler. Jeder Druck schiebt deine Farbe einen
6# Schritt am Streifen entlang; wer zuerst das ferne Ende erreicht, gewinnt
7# die Runde, wer zuerst 3 Runden hat, gewinnt das Match. Ein zu früher
8# Druck während des Countdowns kostet dich 2 Schritte.
9#
10# Pins:
11#   GP2  -> Streifen DIN
12#   GP14 -> Taste Spieler 1 (ROT), anderes Beinchen an GND
13#   GP16 -> Taste Spieler 2 (BLAU), anderes Beinchen an GND
14#   GP15 -> Buzzer (+)
15
16from machine import Pin, PWM
17from neopixel import NeoPixel
18import time
19import math
20
21# ============ EINSTELLUNGEN ============
22NUM_LEDS = 30          # LEDs am Streifen
23BRIGHTNESS = 0.6       # ~0.4 A mit Farben, für USB unbedenklich
24WIN_ROUNDS = 3         # Runden zum Gewinn des Matches
25FALSE_START_STEPS = 2  # Strafe für Fehlstart
26SOUND_ON = True        # Soundeffekte
27
28RED = (255, 0, 0)      # Spieler 1
29BLUE = (0, 60, 255)    # Spieler 2
30
31# Knoten = Anzahl der roten LEDs (die Grenze zwischen den Farben). Beide Spieler brauchen dieselbe
32# Anzahl Schritte.
33START = NUM_LEDS // 2                   # Startrand
34GOAL = min(START, NUM_LEDS - START)     # Zum Gewinnen nötige Schritte
35
36# ============ HARDWARE ============
37strip = NeoPixel(Pin(2), NUM_LEDS)
38btn1 = Pin(14, Pin.IN, Pin.PULL_UP)   # Spieler 1
39btn2 = Pin(16, Pin.IN, Pin.PULL_UP)   # Spieler 2
40buzzer = PWM(Pin(15))
41buzzer.duty_u16(0)
42
43
44# ============ SOUND ============
45class Sound:
46    """
47    TR: Bloklamayan nota calici: oyun ses calarken de butonlari okur
48    EN: Non-blocking note player: the game keeps reading buttons while sound plays
49    """
50
51    def __init__(self, pwm):
52        self.pwm = pwm
53        self.queue = []
54        self.until = 0
55
56    def play(self, notes):
57        # notes = [(Frequenz, ms), ...], Frequenz 0 = Pause
58        if SOUND_ON:
59            self.queue = list(notes)
60            self.until = 0
61
62    def update(self):
63        now = time.ticks_ms()
64        if self.until and time.ticks_diff(now, self.until) < 0:
65            return
66        if not self.queue:
67            if self.until:
68                self.pwm.duty_u16(0)
69                self.until = 0
70            return
71        freq, ms = self.queue.pop(0)
72        if freq:
73            self.pwm.freq(freq)
74            self.pwm.duty_u16(20000)
75        else:
76            self.pwm.duty_u16(0)
77        self.until = time.ticks_add(now, ms)
78
79
80sound = Sound(buzzer)
81
82
83# ============ TASTE ============
84class Button:
85    """
86    TR: 15 ms debounce ile yeni basislari yakalar
87    EN: Catches new presses with a 15 ms debounce
88    """
89
90    def __init__(self, pin):
91        self.pin = pin
92        self.state = 1
93        self.raw = 1
94        self.changed = time.ticks_ms()
95
96    def pressed(self):
97        now = time.ticks_ms()
98        raw = self.pin.value()
99        if raw != self.raw:
100            self.raw = raw
101            self.changed = now
102        if time.ticks_diff(now, self.changed) > 15 and raw != self.state:
103            self.state = raw
104            return raw == 0
105        return False
106
107    def sync(self):
108        # Veraltete Tastendrücke vergessen (eine zwischen den Runden gehaltene Taste wird nicht
109        # bestraft)
110        self.state = self.raw = self.pin.value()
111        self.changed = time.ticks_ms()
112
113
114b1 = Button(btn1)
115b2 = Button(btn2)
116
117
118# ============ ZEICHEN-HILFSFUNKTIONEN ============
119def dim(color, k=1.0):
120    # Eine Farbe mit der globalen Helligkeit und k skalieren
121    s = BRIGHTNESS * k
122    return (int(color[0] * s), int(color[1] * s), int(color[2] * s))
123
124
125def fill(color, k=1.0):
126    c = dim(color, k)
127    for i in range(NUM_LEDS):
128        strip[i] = c
129    strip.write()
130
131
132def draw_rope(knot):
133    # `knot` LEDs sind rot (links), der Rest blau (rechts); je 2 LEDs auf jeder Seite der Grenze
134    # bilden den hellen "Knoten"
135    for i in range(NUM_LEDS):
136        if i < knot:
137            k = 1.0 if knot - i <= 2 else 0.45   # LEDs nahe dem Knoten sind heller
138            strip[i] = dim(RED, k)
139        else:
140            k = 1.0 if i - knot < 2 else 0.45
141            strip[i] = dim(BLUE, k)
142    strip.write()
143
144
145def wheel(pos):
146    # 0-255 in eine Regenbogenfarbe umrechnen
147    pos %= 256
148    if pos < 85:
149        return (255 - pos * 3, pos * 3, 0)
150    if pos < 170:
151        pos -= 85
152        return (0, 255 - pos * 3, pos * 3)
153    pos -= 170
154    return (pos * 3, 0, 255 - pos * 3)
155
156
157def wait_ms(ms):
158    # Warten und dabei den Sound laufen lassen
159    end = time.ticks_add(time.ticks_ms(), ms)
160    while time.ticks_diff(end, time.ticks_ms()) > 0:
161        sound.update()
162        time.sleep_ms(5)
163
164
165# ============ SPIELPHASEN ============
166def attract():
167    # Warten: Beide Farben atmen, jede Taste startet das Spiel
168    print("Baslamak icin bir butona bas / Press a button to start")
169    b1.sync()
170    b2.sync()
171    t0 = time.ticks_ms()
172    while True:
173        sound.update()
174        t = time.ticks_diff(time.ticks_ms(), t0)
175        k = 0.25 + 0.75 * (math.sin(t / 400) + 1) / 2
176        for i in range(NUM_LEDS):
177            if i < START:
178                strip[i] = dim(RED, k)
179            else:
180                strip[i] = dim(BLUE, 1.25 - k)   # Atmet in Gegenphase
181        strip.write()
182        if b1.pressed() or b2.pressed():
183            return
184        time.sleep_ms(15)
185
186
187def countdown():
188    # 3-2-1-Countdown; zu frühe Tastendrücke werden bestraft. Gibt den Startknoten zurück
189    knot = START
190    b1.sync()
191    b2.sync()
192    colors = [(255, 0, 0), (255, 150, 0), (0, 255, 0)]   # Rot, Gelb, Grün
193    for step in range(3):
194        fill(colors[step], 0.6)
195        if step == 2:
196            sound.play([(1200, 200)])   # GO!
197            break
198        sound.play([(600, 150)])
199        end = time.ticks_add(time.ticks_ms(), 800)
200        while time.ticks_diff(end, time.ticks_ms()) > 0:
201            sound.update()
202            if b1.pressed():
203                knot -= FALSE_START_STEPS
204                print("Oyuncu 1 erken basti! / Player 1 false start!")
205                sound.play([(150, 200)])
206            if b2.pressed():
207                knot += FALSE_START_STEPS
208                print("Oyuncu 2 erken basti! / Player 2 false start!")
209                sound.play([(150, 200)])
210            time.sleep_ms(5)
211    # Strafen dürfen den Knoten nicht an ein Ende schieben
212    return max(START - GOAL + 2, min(START + GOAL - 2, knot))
213
214
215def play_round(knot):
216    # Tastenduell; gibt den Gewinner (1 oder 2) zurück, wenn der Knoten ein Ende erreicht
217    draw_rope(knot)
218    while True:
219        sound.update()
220        moved = False
221        if b1.pressed():
222            knot += 1
223            sound.play([(440, 25)])
224            moved = True
225        if b2.pressed():
226            knot -= 1
227            sound.play([(660, 25)])
228            moved = True
229        if moved:
230            if knot >= START + GOAL:   # Streifen ganz rot
231                return 1
232            if knot <= START - GOAL:   # Streifen ganz blau
233                return 2
234            draw_rope(knot)
235        time.sleep_ms(1)
236
237
238def celebrate_round(winner, scores):
239    # Die Gewinnerfarbe blinkt 3-mal, dann werden die Punkte des Punktestands gezeigt
240    color = RED if winner == 1 else BLUE
241    print("Turu kazanan / Round winner: Oyuncu / Player", winner, "| Skor / Score:", scores[0], "-", scores[1])
242    sound.play([(523, 120), (659, 120), (784, 120), (1047, 300)])
243    for _ in range(3):
244        fill(color)
245        wait_ms(180)
246        fill((0, 0, 0))
247        wait_ms(120)
248    # Punktestand: rote Punkte von links, blaue Punkte von rechts
249    for i in range(NUM_LEDS):
250        strip[i] = (0, 0, 0)
251    for s in range(scores[0]):
252        strip[s * 2] = dim(RED)
253    for s in range(scores[1]):
254        strip[NUM_LEDS - 1 - s * 2] = dim(BLUE)
255    strip.write()
256    wait_ms(1800)
257
258
259def celebrate_match(winner):
260    # Ende des Matches: Regenbogenwelle und Siegesmelodie
261    print("MACI KAZANAN / MATCH WINNER: Oyuncu / Player", winner)
262    sound.play([(784, 150), (784, 150), (784, 150), (1047, 450), (0, 80), (988, 150), (1047, 500)])
263    t0 = time.ticks_ms()
264    while time.ticks_diff(time.ticks_ms(), t0) < 3500:
265        sound.update()
266        offset = time.ticks_diff(time.ticks_ms(), t0) // 8
267        for i in range(NUM_LEDS):
268            strip[i] = dim(wheel(i * 256 // NUM_LEDS + offset))
269        strip.write()
270        time.sleep_ms(15)
271    fill(RED if winner == 1 else BLUE)
272    wait_ms(1000)
273
274
275# ============ HAUPTSCHLEIFE ============
276print("== BlueGrays · bluegrays.com ==")
277print("Pico LED Halat Cekme / LED Tug of War")
278
279scores = [0, 0]
280while True:
281    attract()
282    while max(scores) < WIN_ROUNDS:
283        start_knot = countdown()
284        winner = play_round(start_knot)
285        scores[winner - 1] += 1
286        celebrate_round(winner, scores)
287    celebrate_match(1 if scores[0] > scores[1] else 2)
288    scores = [0, 0]
289

FAQ

Mein Streifen hat nicht 30 LEDs. Kann ich eine andere Länge verwenden?▾

Ja. Setze `NUM_LEDS` am Anfang des Codes auf die LED-Zahl deines Streifens; Startpunkt (`START`) und die nötigen Schritte zum Sieg (`GOAL`) werden automatisch berechnet. Bei 60 LEDs braucht jeder Spieler 30 Drücke, die Runden dauern also länger.

Brauche ich den 330-Ω-Widerstand und den 470-µF-Kondensator wirklich?▾

In Wokwi nicht, in der echten Schaltung werden sie empfohlen. Setze einen 330-Ω-Widerstand in Reihe zwischen GP2 und DIN des Streifens und einen 470-µF-Kondensator zwischen 5V und GND des Streifens (langes Bein an 5V). Das schützt die erste LED und verhindert Flackern und zufällige Farben.

Reicht der USB-Anschluss des Pico, um den Streifen zu versorgen?▾

Mit 30 LEDs und `BRIGHTNESS = 0.6` zieht der Streifen höchstens etwa 0,4 A, das schafft USB. VBUS liefert 5V nur, solange USB eingesteckt ist. Für einen längeren Streifen oder mehr Helligkeit nimm ein separates 5V/2A-Netzteil und verbinde die GNDs.

Wie ändere ich die Rundenzahl und die Strafe für einen Frühstart?▾

`WIN_ROUNDS` ist die Zahl der Runden für den Matchsieg (Standard 3). `FALSE_START_STEPS` legt fest, um wie viele Schritte ein Frühstarter zurückgeschoben wird (Standard 2; mit 0 ist die Strafe aus). Für ein stilles Spiel setze `SOUND_ON = False`.

Raspberry Pi Pico LED-Tauziehen mit NeoPixel und 2 Tastern | BlueGrays