Multiplayer Gameshow Buzzer - Teil 2 - Spielererweiterung und Updates

In den Kommentaren zum ersten Beitrag Multiplayer Game Show Buzzer wurde gefragt, wie man den Aufbau von 4 auf 8 Spieler erweitern kann. Diese Frage und noch weitere Ideen für kleine Updates haben mich dazu bewogen, einen zweiten Teil zu schreiben. Dabei verwende ich die dynamische Speicherverwaltung, so dass die Anzahl der Spieler zwischen 1 und (inklusive) 12 frei gewählt werden kann. Ich zeige außerdem, dass man die Eingangspins nicht unbedingt in der setup() Routine initialisieren muss und außerdem kommen noch einige Gimmicks dazu, die den Spieler etwas mehr schwitzen lassen. Los geht’s.

Was wir benötigen

Anzahl

Bauteil

1

Nano V3 ATMega328 oder Mikrocontroller Board mit ATmega328P

bis zu 12

Taster

1

RGB WS2812B LED Ring mit 12 LEDs

1

Passives Buzzer Modul KY-006

mehrere

Verbindungskabel

2

Breadboard oder Breadboard Kit

 

Schaltplan

Schaltplan

Schaltplan zum anklicken

Mikrocontroller

RGB LED Ring

D2

IN

+5V

VCC

GND

GND

 

Buzzer

D3

(S)ignal

GND

-

 

Taster 1-12

D4 - D12

A0 - A2

jeweils Pin 1

GND

jeweils Pin 2

 

Der aufmerksame Leser wird nun merken, dass wir D13 nicht verwenden. Dieser Pin sollte nicht als Eingangspin am Nano V3.0 mit Atmega328 verwendet werden, da sich dort die Onboard-LED samt Widerstand befindet. Für die größeren Versionen des ATmega328 sollte das funktionieren. Da ich aber Herausforderungen mag, bleiben wir bei der kleinen Ausführung und Pin Nummer 13 setzt eine Runde aus. Da wir auf dem RGB LED Ring ganze 12 LEDs zur Verfügung haben, die offiziellen digitalen Pins nun aber zur Neige gehen, werden wir die analogen Pins als digitale Eingänge zweckentfremden.

Der Aufbau entspricht ansonsten dem aus Teil 1. Wir erweitern nur die Taster 5 bis 12.

Exposé

Mein neues Ziel ist, die Taster dynamisch zu erweitern. Das hält sich natürlich in Grenzen, da die Pins als Aus- oder Eingänge im Quellcode initialisiert werden müssen. Diese Aufgabe kann nicht an den User ausgelagert werden. Wir beschränken uns also auf maximal 12 Spieler. Schaltet man den Mikrocontroller ein, möchte ich zuerst festlegen, wie viele Spieler teilnehmen werden. Der erste Taster ist Pflicht. Den benötigen wir für den Spielersetup-Modus. Man drückt zu Beginn so oft auf diesen Taster, wie Spieler dabei sind. Für jeden Spieler leuchtet eine weitere LED auf dem Ring auf. Beim letzten gewünschten Spieler hält man den Taster länger gedrückt, so dass das Spielersetup beendet wird. Möchte man alle 12 Taster nutzen, drückt man so lange, bis alle LEDs leuchten. Dann wird das Setup automatisch beendet und es kann losgehen.

In der ersten Version hatte ich die Spieler-LED einige Sekunden leuchten lassen, nachdem ein Taster betätigt wurde. Ich habe mir gedacht, dass man das nutzen kann, um die Antwortzeit eines Spielers zu begrenzen, nachdem er gebuzzert hat. Um den Spieler noch etwas „unter Druck“ zu setzen, habe ich nun einen Piepton hinzugefügt, der im Sekundentakt zu hören ist. Ist die Antwortzeit abgelaufen, hört man einen langen Ton.

Der Quellcode

Im Sketch habe ich die maximalen Buttons auf 12 erweitert und Pin 4 des Mikrocontrollers als DEFAULTBUTTONPIN definiert. Das Array uint32_t color[MAX_BUTTONS], dass die Farbwerte enthält, habe ich auf 12 erweitert. An dieser Stelle ist es schwierig, den Speicher dynamisch anzulegen. Man könnte die Farbwerte zufällig erzeugen lassen und dann, so wie ich es gleich mit den anderen Arrays zeige, dynamisch erweitern. Das habe ich mir gespart und habe die Farben festgelegt.

Die Arrays für die Eingangspins und die Zustände der Taster habe ich folgendermaßen verändert:

 int *BUTTON_PINS = NULL;
 int *BUTTON_PINS_TEMP = NULL;
 boolean *oldState = NULL;
 boolean *oldState_temp = NULL;
 boolean *newState = NULL;
 boolean *newState_temp = NULL;

Eventuell stellt sich jetzt die Frage: was tut er da?

Pointer, bzw. Zeiger, sind für viele ein Buch mit sieben Siegeln. Ein Array kann man in Arrayschreibweise programmieren. Das sieht in etwa so aus:

 int BUTTON_PINS[1] = 0;

Wie man jetzt bereits erkennt, wird die Anzahl der Elemente beim Deklarieren festgelegt. Wir wissen aber während der Programmierung noch gar nicht, wieviele Taster später verwendet werden und ein Array kann man nicht zur Laufzeit erweitern. Wir können nur die maximale Anzahl festlegen. Klar könnte man jetzt auch ein Array mit den 12 Feldern anlegen. Aber wir wollen es ein wenig spannender machen, Speicher sparen und dynamisch arbeiten. Also nutzen wir die Pointerschreibweise. Die Definition mit NULL (was keiner 0 entspricht, sondern einem NULL-Pointer) sollte man durchführen, damit der Zeiger nicht ins Leere zeigt und ein unbestimmter Zustand auftritt. Man kann sich das wie einen Blindstopfen in der realen Welt vorstellen. Ein Leerrohr wird verlegt, aber noch nicht benutzt und mit einem Blindstopfen erst einmal abgesichert. Neben malloc() gibt es noch calloc(), bei dem alle Werte mit 0 definiert werden und realloc(), dass wir später brauchen, um das Array zu vergrößern.

Ich habe dann noch einen weiteren Ton C6 in das Töne-Array hinzugefügt. In dem Abschnitt, den ich GAME CONFIGURATION genannt habe, können die Antwortzeit (jetzt 5 Sekunden / 5000 ms), das Pieps-Intervall der Antwortzeit (jetzt 1 Sekunde / 1000 ms) und das Gedrückthalten des Tasters während des Setups (jetzt 2 Sekunden / 2000 ms) geändert werden.

Im setup() werden dann die Arrays mit Speicherplatz versehen und mit den Zeigern verbunden, die ich zuvor deklariert habe:

   BUTTON_PINS = (int*)malloc(player_count*(sizeof(int)));
   oldState = (boolean*)malloc(player_count*(sizeof(boolean)));
   newState = (boolean*)malloc(player_count*(sizeof(boolean)));
   BUTTON_PINS[0] = DEFAULTBUTTONPIN;

Die Funktion malloc() reserviert speicher in der angegebenen Größe und gibt einen Zeiger auf diesen Speicherplatz zurück. Mit der Zuweisung an BUTTON_PINS wird unser Array mit diesem Speicherplatz verknüpft. Die Variable player_count enthält bei Programmstart den Wert 1. Die Funktion sizeof(int) gibt die Größe eines Integers zurück, also reservieren wir 1 x 2 Bytes. Hätten wir player_count wie sonst üblich mit 0 beginnen lassen, könnte kein Speicher reserviert werden, da 0 x irgendwas eben 0 bleibt. Wir können dann zwar diesen Zähler mehrfach nutzen, müssen aber später aufpassen, wenn wir die Variable als Index für das Array nutzen, denn dort beginnen wir beim Zählen mit 0.

Das Array BUTTON_PINS ist quasi die Liste, die für jeden Spieler den Eingangspin enthält. So kann ich allen Spielern von 0 bis 11 die Pins zuweisen und später über diese Liste iterieren. Spieler 0 enthält den DEFAULTBUTTONPIN 4.

Nach der Startanimation, die ich nicht verändert habe, wird die LED des ersten Spielers eingeschaltet:

   strip.setPixelColor(player_count - 1, color[player_count - 1]);
   strip.show();

Ich habe dieses Mal darauf verzichtet, das Offset so einzustellen, dass die obere LED dem ersten Spieler zugewiesen wird. Das wäre etwas aufwendiger.

In der loop() muss nun zwischen dem Spielersetup und dem normalen Spielmodus unterschieden werden. Das macht die Variable setupMode. Bei Programmstart befinden wir uns also im Spielersetup, die erste LED leuchtet und der erste Taster ist bereits als Eingang initialisiert. Wird der Taster betätigt, werden die Arrays mit realloc() erweitert.

Hinweis: malloc(), calloc() und realloc() sollte man auf solchen Mikrocontrollern nur begrenzt einsetzen, da der Programmspeicher fragmentiert wird und irgendwann zur Neige geht. Da wir hier auf 12 Spieler begrenzen, sollte das kein Problem sein.

Mit der Erweiterung des Pin-Arrays wird auch der jeweilige Pin als Eingang initialisiert. Das kann man nämlich nicht nur im setup(), sondern auch zu jeder Zeit später im Programm machen. Etwas knifflig ist es nun, wenn man den kleinen Nano V3 ATmega328 verwendet, da Pin 13 nicht als Eingang initialisiert und mit Tastern verwendet werden sollte. Mit folgendem Trick und einer weiteren Variable können wir in das Pin-Array die Pins so eintragen, wie wir es benötigen:

 //Pin 13 ueberspringen
 if (player_count < 10) {
     pin_number = DEFAULTBUTTONPIN + (player_count - 1);
 }
 else {
     pin_number = DEFAULTBUTTONPIN + (player_count);
 }

Spieler Nr. 9 hat den Pin D12. Bis dahin bleibt es bei der Reihenfolge, die mit Hilfe des DEFAULTBUTTONPIN und der Spielernummer berechnet wird. Pin 4 ist im Array an Position 0 bereits vorhanden. Pin 5 bis 12 werden hier nacheinander eingetragen. Danach wird mit Pin 14 (A0) fortgefahren. Wenn die maximale Anzahl an Spielern eingetragen wird, sieht das Array wie folgt aus:

Feld

0

1

2

3

4

5

6

7

8

9

10

11

Pin

4

5

6

7

8

9

10

11

12

14

15

16

Iteriert man nun über die Feldnummer, erhält man immer den passenden Eingangspin, was wir für digitalRead() in der Hauptschleife benötigen. Die Arrays oldState und newState werden ebenso erweitert. Nutzt man einen anderen Mikrocontroller, kann man diesen kleinen Algorithmus wieder auskommentieren bzw. anpassen.

Ist die maximale Anzahl der Spieler erreicht, wird das Spielersetup beendet, die LEDs ausgeschaltet und die Gameshow kann beginnen.

Möchte man nun die Spieleranzahl geringer halten, soll der Taster gedrückt bleiben und nach der vorausgewählten Zeit das Spielersetup beendet werden. Dafür nehmen wir mit buttonpress_time = millis(); die Startzeit bei Drücken des Tasters. Weiter unten folgt dann die Abfrage dafür:

   // Gedrueckt halten fuer Beenden des Spielersetups
   if (setupMode && (newState[button_count] == LOW) && (oldState[button_count] == LOW)) {
     if (millis() - buttonpress_time >= buttonlongpress_time) {
       setupMode = false;
       for (int i = 0; i < PIXEL_COUNT; i++) {
         strip.setPixelColor(i, strip.Color(  0,   0,   0));
         strip.show();
      }
    }
  }

An dieser Stelle wird geprüft, ob der Eingangspin LOW war, LOW ist und ob sich das Programm noch im setupMode befindet. Wenn ja, wird geprüft, ob die Zeit für das Gedrückthalten erreicht wurde. Wenn ja, wird hier ebenfalls das Spielersetup beendet.

Den gesamten Quellcode finden Sie hier als Download.

Fazit

Wirklich dynamisch (also flexibel in der Spieleranzahl) ist nur bedingt möglich. Das Array für die Farben könnte man noch automatisch füllen lassen. Ansonsten hängt es davon ab, welcher Mikrocontroller verwendet wird und wie viele Eingangspins zur Verfügung stehen. Mit wenigen Anpassungen kann man die Anzahl beliebig erweitern. Mit 12 Spielern ist man allerdings schon gut dabei. Man müsste dann auch den RGB-Ring ersetzen, so dass mehr LEDs zur Verfügung stehen.

Viel Spaß beim Nachbasteln

Andreas Wolter

für AZ-Delivery Blog

Für arduinoProjekte für anfänger

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert

Empfohlene Blogbeiträge

  1. ESP32 jetzt über den Boardverwalter installieren
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP Programmieren über WLAN