/*
Reise nach Jerusalem
Basierend auf: Quiz Buzzersystem für 8 Spieler
Jeder Spieler hat eine LED und einen Button
Wenn er an den Strom angeschlossen wird, dann soll die hier angezeigte Melodie fuer eine zufällige Zeit anfangen zu spielen.
In der ersten Runde sollten 3 Spieler weiter kommen -
das heisst 3 Button werden gedrueckt und dann geht die Melodie weiter und die LED von dem einen Spieler der als letztes gedrueckt hat geht aus.
Das Spiel sollte dann enden sobald nur noch 1 Spieler da ist.
https://forum.arduino.cc/index.php?topic=674983.0
read buttons
switch LEDs
blink LED according "BlinkWithoutDelay"
write non-blocking code
copyright by noiasca
Der Code kann genutzt oder verändert werden, solange der volle Copyright Vermerk und der Verweis auf
die ursprüngliche Download-Quelle:
https://werner.rothschopf.net/microcontroller/202103_arduino_reise_nach_jerusalem.htm
als Kommentar erhalten bleibt.
2021-05-29
*/
// define the GPIO
const uint8_t clearPin = A3; // GPIO zum Zurücksetzen des Spiels (statt dem RESET Button)
const uint8_t walkAroundMin = 4; // kürzeste Spielzeit der Musik (sec)
const uint8_t walkAroundMax = 10; // längste Spielzeit der Musik (sec)
const uint8_t sitdownMax = 30; // Beschränkung der Zeit für das "Niedersetzen" (sec)
const uint8_t song[] {49, 33, 37, 33, 49, 33, 37, 33, 37, 41, 44, 37, 41, 44}; // ein Song als Melodie
class Melody {
private:
uint32_t previousMillis; // Zeitmanagement für die Melodie
uint8_t nextTone = 0; // welche Note im Song wird als nächstes gespielt
bool state; // läuft der Song gerade
const uint16_t interval = 500; // wie viele MS wird jede Note gespielt
const uint8_t peepPin; // GPIO
public:
Melody(const uint8_t peepPin) : peepPin{peepPin} {}
void start()
{
nextTone = 0;
state = true;
run();
}
void stop()
{
noTone(peepPin);
state = false;
}
void run() // Zeitsteuerung für die Melodie, muss laufend aufgerufen werden
{
uint32_t currentMillis = millis();
if (currentMillis - previousMillis > interval) // Es ist Zeit für die nächste Note
{
previousMillis = currentMillis;
tone(peepPin, song[nextTone]);
nextTone++;
if (nextTone >= sizeof(song) / sizeof(song[0])) nextTone = 0;
}
}
};
Melody melody(13); // ein Melodyobjekt mit Übergabe des GPIO
class Player { // ein Klasse für die Spieler
private:
bool state = true; // ist der Spieler noch im Spiel (true) oder bereits ausgeschieden (false)
bool sits = false; // Sitzt der Spieler
const uint8_t buzzer; // GPIO für eine Buzzer / Taster gegen GND
const uint8_t led; // GPIO für eine LED
public:
Player (const uint8_t buzzer, const uint8_t led) :
buzzer(buzzer),
led(led) {}
void begin(void)
{
pinMode(buzzer, INPUT_PULLUP); // Buzzer sind Input, auch hier - alle Button schließen gegen Masse
pinMode(led, OUTPUT); // LEDs sind Output
digitalWrite(led, HIGH); // Zum Spielanfang einschalten
}
bool getState()
{
return state;
}
void setState(bool newState)
{
state = newState;
digitalWrite(led, newState);
}
void setSits(bool newSits)
{
sits = newSits;
}
bool pressed() // drückt der Spieler gerade den Button?
{
if (digitalRead(buzzer) == LOW)
return true;
else
return false;
}
bool getSits()
{
return sits;
}
};
Player player[] { // dann legen wir für unsere Spieler ein Array [] an und weisen die konkreten GPIOs zu
{A0, 2}, // Spieler 0: Buzzer GPIO, LED GPIO
{A1, 3}, // Spieler 1
{A2, 4}, // Spieler 2
{10, 5} // Spieler 3: aufpassen, beim letzten kein komma mehr
};
constexpr size_t noOfPlayer = sizeof(player) / sizeof(player[0]); // einmal die Anzahl der Spieler ermitteln
// eine Enumeration für die einzelnen Zustände der State Machine
enum class State {WALK_AROUND, // Spiele Musik
SIT_DOWN, // Musik aus - langsamsten Teilnehmer ermitteln
WAIT_FOR_CLEAR // Warten auf clear durch Spielleiter
} state; // eine Variable in der wir den aktuellen Status speichern
void setup() {
Serial.begin(115200); // die Serielle aktivieren, damit man sieht was passiert
Serial.println(F("\nMusical chairs, also known as Trip to Jerusalem"));
Serial.println(F("press start to play music ..."));
pinMode(clearPin, INPUT_PULLUP); // internen Pullup verwenden, Taster gegen Masse schalten
for (auto &i : player) i.begin(); // range based for ... i ist nun eine Referenz auf den jeweiligen player
state = State::WAIT_FOR_CLEAR; // Spielbegin: auf eine Freigabe durch den Spielleiter warten
}
void loop() {
static uint8_t currentRound = 1; // current Round
static uint32_t currentPlayTimeMS = 42; // current planed playtime in MS
static uint8_t currentPresses = 0; // wie viele Spieler haben in dieser Runde schon gedrückt
static uint32_t previousMillis; // letzter Aufruf
uint32_t currentMillis = millis(); // aktuellen Zeitstempel merken
switch (state)
{
case State::WALK_AROUND : // Warten auf die Tastendrücke
melody.run(); // dafür sorgen, dass die Musik gegebenenfalls die nächste Note spielt
for (size_t i = 0; i < noOfPlayer; i++) // prüfen ob ein aktiver Spieler zu früh drückt
{
if (player[i].getState() == true && player[i].pressed() == true)
{
Serial.print(F("pressed to early index=")); Serial.println(i);
player[i].setState(false);
melody.stop(); // Musik spielt ja noch, müssen wir also stoppen
state = State::WAIT_FOR_CLEAR;
Serial.println(F("--> WAIT_FOR_CLEAR"));
}
}
if (currentMillis - previousMillis > currentPlayTimeMS) // prüfen auf Zeitablauf
{
melody.stop();
state = State::SIT_DOWN;
Serial.println(F("--> SIT_DOWN"));
previousMillis = currentMillis; // verwenden wir auch für den Timeout zum Niedersetzen
}
break;
case State::SIT_DOWN : // Musik ist aus, die Spielen sollen sich auf freien Plätzen niedersetzen
for (size_t i = 0; i < noOfPlayer; i++) // prüfen ob ein aktiver Spieler gedrückt hat
{
if (player[i].getState() == true && player[i].getSits() == false && player[i].pressed() == true)
{
Serial.print(F("player has seated, player=")); Serial.println(i);
player[i].setSits(true);
currentPresses++;
}
}
if (currentPresses == currentRound) // prüfen ob bis auf einen alle Spieler gedrückt haben
{
Serial.println(F("enough players have pressed"));
// wer steht noch rum?
for (size_t i = 0; i < noOfPlayer; i++)
{
if (player[i].getState() == true && player[i].getSits() == false)
{
player[i].setState(false); // Spieler rauswerfen
}
}
state = State::WAIT_FOR_CLEAR;
Serial.println(F("--> WAIT_FOR_CLEAR"));
}
if (currentMillis - previousMillis > sitdownMax * 1000UL) // Zeit ist abgelaufen
{
Serial.println(F("timeout"));
state = State::WAIT_FOR_CLEAR;
Serial.println(F("--> WAIT_FOR_CLEAR"));
}
break;
case State::WAIT_FOR_CLEAR : // warten, bis Clear gedrückt wird
if (digitalRead(clearPin) == LOW) // checken ob Neustart gedrückt wird
{
currentRound--; // wir zählen um eine Runde runter
if (currentRound == 0) // sollten wir in Runde 0 angelangt sein, fangen wir ein neues Spiel an
{
Serial.println(F("start new game"));
currentRound = noOfPlayer - 1; // minus 1 weil wir einen Spieler je Runde rauswerfen
for (auto &i : player) i.setState(true); // alle Spieler wieder aktivieren
}
else
{
Serial.println(F("start new round"));
}
Serial.print(F("currentRound=")); Serial.println(currentRound);
for (auto &i : player) i.setSits(false); // alle Spieler sollen auf stehen (= sie sitzen nicht mehr)
currentPresses = 0; // noch hat keiner gedrückt
currentPlayTimeMS = random(walkAroundMin * 1000UL, walkAroundMax * 1000UL); // Laufzeitzeit für Melodie festlegen
previousMillis = currentMillis; // aktuelle Zeit zurücksetzen
melody.start(); // Melodie einschalten
state = State::WALK_AROUND;
Serial.println(F("--> WALK_AROUND"));
}
break;
}
}