5.2.6 Schere-Stein-Papier

5.2.6.1 Übersicht

Img

Hier spielen wir Schere-Stein-Papier über die drahtlose Kommunikation des micro:bit. Spieler wählen ihren Zug (Stein, Papier oder Schere) über die Tasten, wobei Daten zwischen den Geräten ausgetauscht werden. Das Spiel folgt dem Best-of-Three-Prinzip; wenn alle drei Runden unentschieden enden oder in einem Sieg-Niederlage-Unentschieden, wird ein viertes Spiel ausgelöst.

Jedes Ergebnis wird auf der micro:bit-Matrix angezeigt (W für Sieg, L für Niederlage, = für Unentschieden) und durch die RGB-Lichter (grün für Sieg, rot für Niederlage, gelb für Unentschieden) an Pin P8 enthüllt. Nach Abschluss einer Runde setzen die beiden Geräte alle Daten und Lichter zurück und bereiten sich auf das nächste Spiel vor.

Das Gameplay integriert nahtlos drahtlose Interaktion mit dem Mehrrunden-Kampf.

Img

5.2.6.2 Komponentenwissen

Img

Microbit drahtlose Kommunikation

Img

Das micro:bit-Board integriert zwei praktische drahtlose Kommunikationsfunktionen: 2.4GHz-Radio und Low-Power-Bluetooth (BLE). Sie können jedoch nicht gleichzeitig verwendet werden.

Ersteres erfordert keine Kopplung und unterstützt bis zu 255 unabhängige Pakete, um Interferenzen zu minimieren, mit einer Kommunikationsreichweite von 10–30 Metern, was eine schnelle Übertragung von digitalen Daten und Zeichenketten ermöglicht. Letzteres wird hauptsächlich zum Koppeln mit Smartphones, Tablets und anderen intelligenten Geräten für IoT-Anwendungen wie Sensor-Daten-Upload und Fernsteuerung über mobile Apps verwendet.

Sie erweitern die kreativen Entwicklungsmöglichkeiten des micro:bit.

5.2.6.3 Benötigte Teile

Img

Img

Img

micro:bit V2 Board (selbst mitgebracht) ×2

micro:bit Smart Gamepad (montiert) ×2

AAA Batterie (selbst mitgebracht) ×8

5.2.6.4 Codeablauf

Img

5.2.6.5 Testcode

Vollständiger Code:

from microbit import *
import neopixel
import radio

# Global Variables
round2 = 1
check = 1
me = 0
you = 0
wins = 0
loses = 0
draws = 0
gameResults = []
strip = None

pin13.set_pull(pin13.PULL_UP)
pin15.set_pull(pin15.PULL_UP)
pin16.set_pull(pin16.PULL_UP)
# Initialize LED strip (4 LEDs, connected to pin P8)
strip = neopixel.NeoPixel(pin8, 4)

# Reset game state
def resetGame():
    global me, you, round2, wins, loses, draws, gameResults, check
    me = 0
    you = 0
    round2 = 1
    wins = 0
    loses = 0
    draws = 0
    gameResults = []
    check = 1
    resetLights()
    display.show(Image.HEART)

# Receive opponent\\'s choice via radio
def on_received_message(receivedMsg):
    global you
    if you == 0:
        # Convert string to integer if needed
        if isinstance(receivedMsg, str) and receivedMsg in [\'1\', \'2\', \'3\']:
            you = int(receivedMsg)
        # Use directly if it\\'s an integer
        elif isinstance(receivedMsg, int) and receivedMsg in [1, 2, 3]:
            you = receivedMsg

# Turn off all LEDs
def resetLights():
    for i in range(4):
        strip[i] = (0, 0, 0)  # Off
    strip.show()

# Check if a 4th round is needed
def needFourthRound():
    # Case 1: All 3 draws -> need 4th round, return 2
    if wins == 0 and loses == 0 and draws == 3:
        return 2
    # Case 2: 1 win, 1 loss, 1 draw -> need 4th round, return 1
    if wins == 1 and loses == 1 and draws == 1:
        return 1
    # No 4th round needed
    return 0

# Show round result on LED strip
def showRoundResult(roundNum, result):
    if roundNum <= 4:
        if result == 1:
            # Win: Green
            strip[roundNum - 1] = (0, 255, 0)
        elif result == 0:
            # Draw: Yellow
            strip[roundNum - 1] = (255, 255, 0)
        else:
            # Lose: Red
            strip[roundNum - 1] = (255, 0, 0)
        strip.show()

# Game initialization
radio.on()
radio.config(group=1)
check = 1
me = 0
you = 0
strip.clear()
strip.show()
display.show(Image.HEART)

# Main game loop
while True:

    # Process result when both players have chosen
    if me != 0 and you != 0:
        # Current round result: 1=win, 0=draw, -1=lose
        resultSymbol = "="
        # Determine round outcome
        if me == you:
            resultSymbol = "="
            # Draw
            result2 = 0
            draws += 1
        elif me == 2 and you == 1 or (me == 3 and you == 2 or me == 1 and you == 3):
            resultSymbol = "W"
            # Win
            result2 = 1
            wins += 1
        else:
            resultSymbol = "L"
            # Lose
            result2 = -1
            loses += 1

        # Save round result
        gameResults.append(result2)

        # Display result symbol
        display.show(resultSymbol)

        # Update LED strip
        showRoundResult(round2, result2)

        sleep(3000)

        # Check if game continues
        if round2 == 3:
            # After 3 rounds, check for 4th round
            fourth_round_needed = needFourthRound()
            if fourth_round_needed:
                # Go to 4th round
                round2 = 4
                if fourth_round_needed == 2:
                    display.scroll("FINAL")
                sleep(1000)
                display.show(Image.HEART)
                check = 1
                me = 0
                you = 0
            else:
                # End game
                if wins > loses:
                    display.scroll("WINNER")
                elif loses > wins:
                    display.scroll("LOSER")
                else:
                    display.scroll("TIE")
                sleep(3000)
                resetGame()
        elif round2 == 4:
            # 4th round finished, game over
            display.scroll("GAME OVER")
            sleep(3000)
            resetGame()
        else:
            # Next round (1st or 2nd)
            round2 += 1
            display.show(Image.HEART)
            check = 1
            me = 0
            you = 0

    # Check button input
    if check == 1:
        if pin13.read_digital() == 0:
            # Paper -> send \'3\'
            radio.send(\'3\')
            display.show(Image.SQUARE)
            me = 3
            check = 0
            sleep(200)
        elif pin15.read_digital() == 0:
            # Scissors -> send \'1\'
            radio.send(\'1\')
            display.show(Image(\'99009:\'
                                \'99090:\'
                                \'00900:\'
                                \'99090:\'
                                \'99009\'))
            me = 1
            check = 0
            sleep(200)
        elif pin16.read_digital() == 0:
            # Rock -> send \'2\'
            radio.send(\'2\')
            display.show(Image.SQUARE_SMALL)
            me = 2
            check = 0
            sleep(200)

    # Receive radio data
    try:
        received = radio.receive()
        if received is not None:
            on_received_message(received)
    except:
        pass

    sleep(100)

Img

Kurze Erklärung:

① Importieren Sie die relevanten Bibliotheken, initialisieren Sie globale Variablen und konfigurieren Sie Pins.

from microbit import *
import neopixel
import radio

# Global Variables
round2 = 1
check = 1
me = 0
you = 0
wins = 0
loses = 0
draws = 0
gameResults = []
strip = None

pin13.set_pull(pin13.PULL_UP)
pin15.set_pull(pin15.PULL_UP)
pin16.set_pull(pin16.PULL_UP)
# Initialize LED strip (4 LEDs, connected to pin P8)
strip = neopixel.NeoPixel(pin8, 4)

resetGame setzt alle Spielzustände zurück.

Es wird normalerweise zu Beginn eines Spiels oder nach Abschluss einer Runde aufgerufen, um alle globalen Variablen, die mit dem Spielfortschritt zusammenhängen – einschließlich Spielerauswahlen, Rundenzählungen, Gewinn-/Verlust-/Unentschieden-Zählungen und historischen Ergebnissen – auf ihre Anfangswerte zurückzusetzen.

resetLights() schaltet alle NeoPixel-LEDs aus und zeigt ein Herzsymbol (Image.HEART) an, das anzeigt, dass das Spiel bereit ist zu beginnen.

# Reset game state
def resetGame():
    global me, you, round2, wins, loses, draws, gameResults, check
    me = 0
    you = 0
    round2 = 1
    wins = 0
    loses = 0
    draws = 0
    gameResults = []
    check = 1
    resetLights()
    display.show(Image.HEART)

on_received_message verarbeitet die über Funk empfangene Auswahl des Gegners. Es verarbeitet Funkmeldungen von einem anderen Micro:bit (Schere, Stein oder Papier).

Um die Genauigkeit zu gewährleisten, wird der Nachrichtentyp überprüft: Wenn die Nachricht eine Zeichenkette (‚1‘, ‚2‘ oder ‚3‘) ist, wird sie in eine Ganzzahl umgewandelt; wenn es sich um eine Ganzzahl (1, 2 oder 3) handelt, wird sie direkt verwendet.

Der Wert von you wird nur aktualisiert, wenn you=0 (keine Gegnerauswahl empfangen), wodurch Mehrfachempfang verhindert wird.

# Receive opponent\\'s choice via radio
def on_received_message(receivedMsg):
    global you
    if you == 0:
        # Convert string to integer if needed
        if isinstance(receivedMsg, str) and receivedMsg in [\'1\', \'2\', \'3\']:
            you = int(receivedMsg)
        # Use directly if it\\'s an integer
        elif isinstance(receivedMsg, int) and receivedMsg in [1, 2, 3]:
            you = receivedMsg

resetLights schaltet alle NeoPixel-LEDs aus. Es durchläuft alle vier LEDs, um ihre Farben auf Schwarz ((0, 0, 0)) zu setzen, d.h. aus.

strip.show() sendet diese Farbaktualisierungen an den Lichtstreifen, um sicherzustellen, dass alle LEDs ausgeschaltet sind.

# Turn off all LEDs
def resetLights():
    for i in range(4):
        strip[i] = (0, 0, 0)  # Off
    strip.show()

needFourthRound bestimmt, ob nach den drei Runden eine vierte Runde erforderlich ist.

Es behandelt zwei Sonderfälle: Wenn alle drei Runden unentschieden enden (wins == 0 and loses == 0 and draws == 3), wird 2 für eine vierte Runde zurückgegeben – das endgültige entscheidende Spiel; Wenn es einen Sieg-Niederlage-Unentschieden gibt (wins == 1 and loses == 1 and draws == 1), wird ebenfalls 1 für eine zusätzliche Runde zurückgegeben. In allen anderen Fällen (wo es einen klaren Gewinner/Verlierer gibt), wird 0 zurückgegeben (keine 4. Runde erforderlich).

# Check if a 4th round is needed
def needFourthRound():
    # Case 1: All 3 draws -> need 4th round, return 2
    if wins == 0 and loses == 0 and draws == 3:
        return 2
    # Case 2: 1 win, 1 loss, 1 draw -> need 4th round, return 1
    if wins == 1 and loses == 1 and draws == 1:
        return 1
    # No 4th round needed
    return 0

showRoundResult zeigt das Ergebnis jeder Runde auf dem LED-Streifen an.

Es akzeptiert die aktuelle Rundennummer (roundNum) und das Ergebnis (result: 1 für Sieg, 0 für Unentschieden, -1 für Niederlage). Basierend auf dem Ergebnis leuchtet es verschiedene Farben auf der entsprechenden LED auf: grün für Sieg, gelb für Unentschieden und rot für Niederlage.

roundNum-1 konvertiert die Rundennummer in einen nullbasierten Index für die LEDs.

# Show round result on LED strip
def showRoundResult(roundNum, result):
    if roundNum <= 4:
        if result == 1:
            # Win: Green
            strip[roundNum - 1] = (0, 255, 0)
        elif result == 0:
            # Draw: Yellow
            strip[roundNum - 1] = (255, 255, 0)
        else:
            # Lose: Red
            strip[roundNum - 1] = (255, 0, 0)
        strip.show()

⑦ Initialisierung beim Spielstart.

Es wird einmal ausgeführt, wenn das Programm startet. Es aktiviert die Micro:bit-Radiofunktion und setzt group=1. Als Nächstes setzt es check auf 1 (selektiv für Spieler), me und you auf 0 (warten auf Spieler- und Gegnerauswahl).

Der NeoPixel-Lichtstreifen wird gelöscht und aktualisiert, um alle LEDs auszuschalten. Und Micro:bit zeigt ein Herzsymbol (Image.HEART) als anfängliche Aufforderung zur Spielereingabe an.

# Game initialization
radio.on()
radio.config(group=1)
check = 1
me = 0
you = 0
strip.clear()
strip.show()
display.show(Image.HEART)

⑧ Verarbeiten Sie die Runden-Ergebnisse und steuern Sie den Spielfluss.

Dieser Code repräsentiert die Kernlogik des Spiels, die in einer Endlosschleife läuft. Er überprüft zuerst, ob sowohl Spieler als auch Gegner ihre Entscheidungen getroffen haben (me != 0 and you != 0).

Wenn ja, bestimmt er das Ergebnis der aktuellen Runde gemäß den Schere-Stein-Papier-Regeln, aktualisiert die Zähler wins, loses, draws und zeigt das entsprechende Symbol (“W”, “L”, “=”) auf der Matrix an.

showRoundResult schaltet die LED auf dem NeoPixel in den entsprechenden Farben für die vorherige Runde ein.

Nachdem die Ergebnisse 3 Sekunden lang angezeigt wurden, wird das Spiel basierend auf der aktuellen Rundenzahl fortgesetzt:

  • Wenn es sich um die 3. Runde handelt (round2 == 3), bestimmt needFourthRound(), ob die endgültige entscheidende Runde erforderlich ist. Wenn ja, wird die vierte Runde fortgesetzt; andernfalls wird basierend auf dem Gesamtergebnis ein GEWINNER/VERLIERER/UNENTSCHIEDEN angezeigt und das Spiel zurückgesetzt.

  • Wenn es sich um die vierte Runde handelt (round2 == 4), wird “GAME OVER” deklariert und das Spiel zurückgesetzt.

  • Wenn es die erste oder zweite Runde ist, wird die Runde um 1 erhöht (round2 += 1), das Herzsymbol angezeigt, die Auswahl zurückgesetzt und die Vorbereitung auf die nächste Runde getroffen.

# Main game loop
while True:

    # Process result when both players have chosen
    if me != 0 and you != 0:
        # Current round result: 1=win, 0=draw, -1=lose
        resultSymbol = "="
        # Determine round outcome
        if me == you:
            resultSymbol = "="
            # Draw
            result2 = 0
            draws += 1
        elif me == 2 and you == 1 or (me == 3 and you == 2 or me == 1 and you == 3):
            resultSymbol = "W"
            # Win
            result2 = 1
            wins += 1
        else:
            resultSymbol = "L"
            # Lose
            result2 = -1
            loses += 1

        # Save round result
        gameResults.append(result2)

        # Display result symbol
        display.show(resultSymbol)

        # Update LED strip
        showRoundResult(round2, result2)

        sleep(3000)

        # Check if game continues
        if round2 == 3:
            # After 3 rounds, check for 4th round
            fourth_round_needed = needFourthRound()
            if fourth_round_needed:
                # Go to 4th round
                round2 = 4
                if fourth_round_needed == 2:
                    display.scroll("FINAL")
                sleep(1000)
                display.show(Image.HEART)
                check = 1
                me = 0
                you = 0
            else:
                # End game
                if wins > loses:
                    display.scroll("WINNER")
                elif loses > wins:
                    display.scroll("LOSER")
                else:
                    display.scroll("TIE")
                sleep(3000)
                resetGame()
        elif round2 == 4:
            # 4th round finished, game over
            display.scroll("GAME OVER")
            sleep(3000)
            resetGame()
        else:
            # Next round (1st or 2nd)
            round2 += 1
            display.show(Image.HEART)
            check = 1
            me = 0
            you = 0

⑨ Verarbeiten Sie die Tasten-Eingabe des Spielers.

Es erkennt die Auswahl der Spieler über externe Tasten (verbunden mit pin13, pin15, pin16). Es erkennt einen Tastendruck nur, wenn check = 1 (Auswahl ist erlaubt).

  • Wenn pin13 gedrückt wird (niedrig), wird Papier gewählt (3), und Micro:bit sendet \'3\' und zeigt ein großes Quadrat an.

  • Wenn pin15 gedrückt wird, wird Schere gewählt (1), sendet \'1\' und zeigt ein Schere-Symbol an.

  • Wenn pin16 gedrückt wird, wird Stein gewählt (2), sendet \'2\' und zeigt ein kleines Quadrat an.

Nach der Auswahl aktualisieren Sie me, check = 0 (vermeiden Sie doppelte Auswahl) und verzögern Sie 200 ms zur Entprellung.

    # Check button input
    if check == 1:
        if pin13.read_digital() == 0:
            # Paper -> send \'3\'
            radio.send(\'3\')
            display.show(Image.SQUARE)
            me = 3
            check = 0
            sleep(200)
        elif pin15.read_digital() == 0:
            # Scissors -> send \'1\'
            radio.send(\'1\')
            display.show(Image(\'99009:\'
                                \'99090:\'
                                \'00900:\'
                                \'99090:\'
                                \'99009\'))
            me = 1
            check = 0
            sleep(200)
        elif pin16.read_digital() == 0:
            # Rock -> send \'2\'
            radio.send(\'2\')
            display.show(Image.SQUARE_SMALL)
            me = 2
            check = 0
            sleep(200)

⑩ Funkdatenempfang und Schleifenverzögerung behandeln.

Es versucht, während jeder Hauptschleife Funkdaten zu empfangen. radio.receive() erfasst alle eingehenden Nachrichten. Wenn eine Nachricht empfangen wird (received is not None), rufen Sie on_received_message() auf, um die Auswahl des Gegners zu verarbeiten.

Um zu verhindern, dass das Programm aufgrund fehlender Nachrichten blockiert wird, fängt try-except mögliche Anomalien ab (obwohl radio.receive() in MicroPython normalerweise keine Ausnahmen direkt auslöst, ist es eine gute Programmiergewohnheit).

sleep(100) pausiert das Programm für 100 ms, reguliert die Ausführungsfrequenz der Hauptschleife, um übermäßigen Prozessorverbrauch zu vermeiden und Zeit für die Tastenerkennung und Bildschirmanzeige zu ermöglichen.

    # Receive radio data
    try:
        received = radio.receive()
        if received is not None:
            on_received_message(received)
    except:
        pass

    sleep(100)

5.2.6.6 Testergebnis

Img

Nach dem Brennen des Codes stecken Sie das micro:bit-Board in den Steckplatz des Gamepads (Batterien eingelegt) und schalten Sie den Schalter auf „ON“.

Die Matrix zeigt zunächst Img an. Spieler drücken Tasten, um ihren Zug auszuwählen (E für Stein, D für Papier oder C für Schere), wobei die Spieldaten zwischen den beiden Geräten ausgetauscht werden. Sie bestimmen das Ergebnis der aktuellen Runde: Ein Sieg wird durch “W” mit grünem RGB-Licht angezeigt, ein Unentschieden durch “=” mit gelbem Licht und eine Niederlage durch “L” mit rotem Licht (das erste RGB-Licht leuchtet nach der ersten Runde auf usw.). Die nächste Runde folgt, wenn das Spiel nicht beendet ist.

Das Spiel wird im Best-of-Three-Modus gespielt: Wenn alle drei Runden unentschieden enden oder in einem Sieg-Niederlage-Unentschieden, wird ein viertes Spiel ausgelöst.

Wenn es nach drei Runden einen Gewinner gibt, wird “WINNER” für den Sieg und “LOSER” für die Niederlage angezeigt. Sobald das Ergebnis angezeigt wird, erscheint “GAME OVER”, um das Spiel zurückzusetzen. Wenn die vierte Runde unentschieden bleibt, ist das Spiel ebenfalls beendet.

Img

Tipp: Warten Sie, bis das Herzsymbol erscheint, bevor Sie die nächste Runde fortsetzen. Wenn das Board nicht reagiert, drücken Sie bitte die Reset-Taste auf der Rückseite des micro:bit-Boards.

Img