Arduino Workshop

Am 07.10. hat in Europa die CodeWeek begonnen und bei Code For Heilbronn standen an diesem Wochenende die Türen offen für die Teilnahme an einem Arduino-Workshop für Einsteiger und Experten. Dabei waren die Teilnehmer hart im Nehmen: Grundkenntnisse C++, Elektronik und dann auch noch Programmierung eines Tic-Tac-Toe Spiels. Dafür haben sie es sich aber verdient, dass sie die fertige Platine nach dem Workshop behalten durften.

Und Was haben wir gemacht?

Angefangen hat der Workshop auf rein theoretischer Ebene, bei der die Grundkenntnisse der Programmiersprache C++ übermittelt wurden. Weil unsere Teilnehmer alle noch sehr jung waren und darin, haben wir uns Mühe gegeben, alles so anzustellen, dass es auch jeder versteht: unkompliziert und idiotensicher.

Gleich danach konnten die frisch geschlüpften Programmierer das anwenden, was sie nach der Theoriestunde noch im Kopf hatten. Für die Praxisphase wurden dann der Einstieg mit 2 kleinen Programmierübungen vereinfacht: ein Programm um die gedrückte Taste einzulesen und ein Programm um die Status-LED der Platine blinken zu lassen.

Danach wurde es Ernst: wir haben gemeinsam ein Tic-Tac-Toe Spiel programmiert. Wer das Spiel fertig bekommen hat (glücklicherweise alle, weil wir keinen Programmiereinsteiger auf der Strecke liegen lassen), durfte die Platine mit dem fertigen Tic-Tac-Toe Spiel mit Heim nehmen.

Platine? Sagt bloß …

Ja, wir haben das Spiel auf eine Platine gepackt. Mittig war das Herz der Platine: ein Arduino Nano. Unser Spielfeld war eine 3x3 RGB LED Matrix, über der sich noch eine weitere RGB LED als Status-LED befang. Warum RGB? - Naja, man braucht für 2 Spieler 2 Farben und wer möchte, kann sein Programm so konfigurieren, dass er seine Lieblingsfarben für jeden Spieler verwendet. Bei den Tasten haben wir ein wenig mit den Pins vom Arduino gespart: ein Spannungsteiler, bei den über den AD-Wandler eingelesen wurde, welche Taste gedrückt wurde. Gerade für Anfänger ist es nicht schwer, zu verstehen, wie ein Spannungsteiler und ein Mutiplexer funktioniert.

Da wir 20 Platinen für genauso viele Teilnehmer bestellt haben, sind wir natürlich überaus froh, Meet and Code als Sponsor bekommen zu haben. Andererseits hätten die Teilnehmer ihre Platinen nicht mit heim nehmen können.

Die von uns erstellte Platine ist nicht nur zum Tic-Tac-Toe Spielen gedacht, sondern ein richtiges Multitalent. Sie wurde so entwickelt, dass die Teilnehmer des Workshops nach ihrer eigenen Kreativität Spiele dafür entwickeln können, wie z.B. das Spiel Senso oder ein Programm, mit dem man die Platine als Würfel verwenden kann.

Und das fertige Spiel?

Das Programm wurde so entwickelt, dass es gerade für Einsteiger leichter zu verstehen ist. Natürlich ist es Open Source und für jeden frei verwendbar.

int Spielfeld[9];
int Spieler;
int Gewinner;

// Alle Pins, die mit den LEDs verbunden sind, auf Ausgang setzen
void Init(void)
{
    for (int i = 2; i < 15; i++) {
        pinMode(i, OUTPUT);
    }
}

// Um alle LEDs aus zu schalten, müssen die damit verbundenen Pins auf LOW gesetzt werden.
void AllesAus(void)
{
    for (int i = 2; i < 15; i++) {
        // Der Pin 11 muss abeer auf HIGH gesetzt werden, weil er nicht mit einem Transistor verbunden ist
        if (i != 11) {
            digitalWrite(i, HIGH);
        }
        else {
            digitalWrite(i, LOW);
        }
    }
}

//  Zu Beginn eines Spiels werden alle Variablen auf ihren Standardwert gesetzt
void InitVariablen(void)
{
    // Alle Elemente des Arrays Spielfeld werden mit 0 belegt
    for (int i = 0; i < 9; i++) {
        Spielfeld[i] = 0;
    }

    // Spieler 1 darf mit dem Spiel beginnen
    Spieler = 1;

    // Noch hat niemand gewonnen. Deshalb wird diese Variable mit 0 belegt
    Gewinner = 0;
}

// Diese Funktion liefert zurück, welche Taste atuell gedrückt wird.
// Alle Tasten bilden einen Spannungsteiler, mit dem das Verhältnis der Widerstände zueinander über einen AD-Wandler ausgelesen wird.
// Falls keine Tate gedrückt wird, zieht der Pulldown-Widerstand die Spannung auf 0V
int TasteGedrueckt(void)
{
    // 10 bedeutet, dass keine Taste gedrückt wird
    int Taste = 10;

    int ADC_Wert = analogRead(A7);

    // Im Folgenen werden die Werte des AD-Wandlers verglichen. daraus wird dann ermittelt, welche taste gedrückt wird

    if ((ADC_Wert > 360) && (ADC_Wert < 370)) {
        Taste = 0;
    }
    if ((ADC_Wert > 560) && (ADC_Wert < 570)) {
        Taste = 1;
    }
    if ((ADC_Wert > 635) && (ADC_Wert < 645)) {
        Taste = 2;
    }
    if ((ADC_Wert > 390) && (ADC_Wert < 400)) {
        Taste = 3;
    }
    if ((ADC_Wert > 500) && (ADC_Wert < 515)) {
        Taste = 4;
    }
    if ((ADC_Wert > 725) && (ADC_Wert < 735)) {
        Taste = 5;
    }
    if ((ADC_Wert > 415) && (ADC_Wert < 430)) {
        Taste = 6;
    }
    if ((ADC_Wert > 460) && (ADC_Wert < 470)) {
        Taste = 7;
    }
    if ((ADC_Wert > 845) && (ADC_Wert < 860)) {
        Taste = 8;
    }

    //Serial.println( Taste );

    // Der ermittelte wert für die Taste wird anschließend zurückgegeben
    return Taste;
}

// Diese Funktion kümmert sich um die Ausgabe der Status-LED
void AusgabeStatusLED(void)
{
    // Je nachdem, welcher Spieler gerade dran ist, leuchtet die LED in einer bestimmten Farbe
    switch (Spieler) {
    case 0: // Allle Farben aus
        digitalWrite(5, HIGH);
        digitalWrite(7, HIGH);
        digitalWrite(8, HIGH);
        break;
    case 1: // Rot
        digitalWrite(5, HIGH);
        digitalWrite(7, HIGH);
        digitalWrite(8, LOW);
        break;
    case 2: // Gruen
        digitalWrite(5, HIGH);
        digitalWrite(7, LOW);
        digitalWrite(8, HIGH);
        break;
    case 3: // Blau
        digitalWrite(5, LOW);
        digitalWrite(7, HIGH);
        digitalWrite(8, HIGH);
        break;
    }

    // Setze die gemeinsame Kathode auf HIGH und schalte die LED somit an
    digitalWrite(11, HIGH);

    // Warte eine Millisekunde
    delay(1);

    // Schalte die LED aus
    digitalWrite(11, LOW);

    AllesAus();
}

// Diese Funktion gibt den Inhalt des Arrays Spielfeld auf der LED Matrix aus.
void AusgabeLEDs()
{
    // Das wird für alle 3 Zeilen widerholt
    for (int i = 0; i < 3; i++) {
        // Prüfe die LEDs 0, 3 und 6
        switch (Spielfeld[3 * i]) {
        case 0: // alle Farben aus
            digitalWrite(2, HIGH);
            digitalWrite(3, HIGH);
            digitalWrite(4, HIGH);
            break;
        case 1: // Rot
            digitalWrite(2, LOW);
            digitalWrite(3, HIGH);
            digitalWrite(4, HIGH);
            break;
        case 2: // Gruen
            digitalWrite(2, HIGH);
            digitalWrite(3, LOW);
            digitalWrite(4, HIGH);
            break;
        case 3: // Blau
            digitalWrite(2, HIGH);
            digitalWrite(3, HIGH);
            digitalWrite(4, LOW);
            break;
        }

        // Pruefe die LEDs 1, 4 und 7
        switch (Spielfeld[3 * i + 1]) {
        case 0: // Allle Farben aus
            digitalWrite(5, HIGH);
            digitalWrite(7, HIGH);
            digitalWrite(8, HIGH);
            break;
        case 1: // Rot
            digitalWrite(5, HIGH);
            digitalWrite(7, HIGH);
            digitalWrite(8, LOW);
            break;
        case 2: // Gruen
            digitalWrite(5, HIGH);
            digitalWrite(7, LOW);
            digitalWrite(8, HIGH);
            break;
        case 3: // Blau
            digitalWrite(5, LOW);
            digitalWrite(7, HIGH);
            digitalWrite(8, HIGH);
            break;
        }

        // Pruefe die LEDs 2, 5 und 8
        switch (Spielfeld[3 * i + 2]) {
        case 0: // alle Farben aus
            digitalWrite(12, HIGH);
            digitalWrite(13, HIGH);
            digitalWrite(14, HIGH);
            break;
        case 1: // Rot
            digitalWrite(12, LOW);
            digitalWrite(13, HIGH);
            digitalWrite(14, HIGH);
            break;
        case 2: // Gruen
            digitalWrite(12, HIGH);
            digitalWrite(13, HIGH);
            digitalWrite(14, LOW);
            break;
        case 3: // Blau
            digitalWrite(12, HIGH);
            digitalWrite(13, LOW);
            digitalWrite(14, HIGH);
            break;
        }

        // Schalte die dazugehörigr gemeinsame Kathode an.
        // Die Zustände an den Kathoden werden durch den Transistor invertiert
        switch (i) {
        case 0:
            digitalWrite(10, LOW);
            break;
        case 1:
            digitalWrite(9, LOW);
            break;
        case 2:
            digitalWrite(6, LOW);
            break;
        }

        // warte eine Millisekunde
        delay(1);

        // Schalte die LED Matrix wieder aus
        AllesAus();
    }
}

// Nach jedem Spielzug muss geprüft werden ob es einen Gewinner gibt
// Dazu werden einfach die Werte in dem Array Spielfeld zeilenweise uns spaltenweise multipliziert
void PruefeGewinner(void)
{
    int Produkt = 1;

    // Pruefe zuerst, ob es ein Unentschieden gibt
    for (int i = 0; i < 9; i++) {
        Produkt *= Spielfeld[i];
    }
    if (Produkt != 0) {
        Gewinner = -1;
    }

    // Pruefe alle 3 Zeilen und Spalten
    for (int i = 0; i < 3; i++) {
        Produkt = Spielfeld[0 + i] * Spielfeld[3 + i] * Spielfeld[6 + i];
        if (Produkt == 1) {
            Gewinner = 1;
        }
        if (Produkt == 8) {
            Gewinner = 2;
        }

        Produkt = Spielfeld[0 + i * 3] * Spielfeld[1 + 3 * i] * Spielfeld[2 + 3 * i];
        if (Produkt == 1) {
            Gewinner = 1;
        }
        if (Produkt == 8) {
            Gewinner = 2;
        }
    }

    // Pruefe die 2 Diagonalen
    Produkt = Spielfeld[0] * Spielfeld[4] * Spielfeld[8];
    if (Produkt == 1) {
        Gewinner = 1;
    }
    if (Produkt == 8) {
        Gewinner = 2;
    }

    Produkt = Spielfeld[2] * Spielfeld[4] * Spielfeld[6];
    if (Produkt == 1) {
        Gewinner = 1;
    }
    if (Produkt == 8) {
        Gewinner = 2;
    }
}

// Falls das Spiel gewonnen wurde, zeige eine dazugehörige Animation
void SpielGewonnen(void)
{
    bool neuesSpiel = false;

    // Zuerst bleibt die Anzeig für 2 Sekunden auf dem aktuellen Spielstand
    unsigned long Counter = millis();
    while ((millis() - 2000) < Counter) {
        AusgabeLEDs();
    }

    // Danach wird noch zwischengespeichert, welcher Spieler als nächstes an der Reihe wäre
    // Das ist notwendig, weil später dieser Wert verloren geht
    int naechsterSpieler = Gewinner + 1;
    if (naechsterSpieler > 2) {
        naechsterSpieler = 1;
    }

    // Solange keine Taste gedrückt wird, wird nun diese Animation angezeigt
    do {
        // Teil 1
        for (int i = 0; i < 9; i++) {
            Spielfeld[i] = Gewinner;
        }
        Spielfeld[4] = 0;

        Counter = millis();
        while ((millis() - 1000) < Counter) {
            AusgabeLEDs();

            if (TasteGedrueckt() != 10) {
                neuesSpiel = true;
            }
        }

        // Teil 2
        for (int i = 0; i < 9; i++) {
            Spielfeld[i] = 0;
        }
        Spielfeld[4] = Gewinner;

        Counter = millis();
        while ((millis() - 1000) < Counter) {
            AusgabeLEDs();

            if (TasteGedrueckt() != 10) {
                neuesSpiel = true;
            }
        }
    } while (!neuesSpiel);

    // eine Taste wurde gedrückt. Das Spiel kann weitergehen
    InitVariablen();
    Spieler = naechsterSpieler;
}

// Falls das Spiel mit unentschieden endet, zeige eine dazugehörige Animation
void SpielUnentschieden(void)
{
    bool neuesSpiel = false;

    // Zuerst bleibt die Anzeig für 2 Sekunden auf dem aktuellen Spielstand
    unsigned long Counter = millis();
    while ((millis() - 2000) < Counter) {
        AusgabeLEDs();
    }

    // Danach wird noch zwischengespeichert, welcher Spieler als nächstes an der Reihe wäre
    // Das ist notwendig, weil später dieser Wert verloren geht
    int naechsterSpieler = Spieler;

    // Solange keine Taste gedrückt wird, wird nun diese Animation angezeigt
    do {
        // Teil 1
        Spielfeld[0] = 1;
        Spielfeld[2] = 1;
        Spielfeld[6] = 1;
        Spielfeld[8] = 1;
        Spielfeld[4] = 0;
        Spielfeld[1] = 2;
        Spielfeld[3] = 2;
        Spielfeld[5] = 2;
        Spielfeld[7] = 2;

        Counter = millis();
        while ((millis() - 1000) < Counter) {
            AusgabeLEDs();

            if (TasteGedrueckt() != 10) {
                neuesSpiel = true;
            }
        }

        // Teil 2
        Spielfeld[0] = 2;
        Spielfeld[2] = 2;
        Spielfeld[6] = 2;
        Spielfeld[8] = 2;
        Spielfeld[4] = 0;
        Spielfeld[1] = 1;
        Spielfeld[3] = 1;
        Spielfeld[5] = 1;
        Spielfeld[7] = 1;

        Counter = millis();
        while ((millis() - 1000) < Counter) {
            AusgabeLEDs();

            if (TasteGedrueckt() != 10) {
                neuesSpiel = true;
            }
        }
    } while (!neuesSpiel);

    InitVariablen();
    Spieler = naechsterSpieler;
}

// Die Setup-Funktion wird beim ersten Start aufgerufen
void setup()
{
    Init();
    AllesAus();
    InitVariablen();

    //Serial.begin(9600);
}

// Die Loop Funktion wird endlos durchlaufen
void loop()
{

    // ermittle, ob eine Tastee geddrückt wurde
    int Taste = TasteGedrueckt();

    // Falls eine Taste gedrückt wurde und dieses Feld noch unbelegt ist, markiere es mit dem aktuellen Spieler
    if ((Taste != 10) && (Spielfeld[Taste] == 0)) {
        Spielfeld[Taste] = Spieler;
        Spieler++;
    }

    // Nach jedem Spielzug ist der neue Spieler an der Reihe. Dafür wird der Spieler zuerst inkrementiert und danach ein Übrelauf geprüft
    if (Spieler > 2) {
        Spieler = 1;
    }

    // An der Status-LED wird gezeigt, welcher Spieler gerade an der Reihe ist
    AusgabeStatusLED();

    // Der aktuelle Spielstand wird auf der LED Matrix ausgegeben
    AusgabeLEDs();

    // Es wird geprüft, ob es schon einen Gewinner gibt
    PruefeGewinner();

    //Serial.println(Gewinner);

    // Falle es einen Gewinner gibt, wird die dazugehörige Animation aktiviert
    if (Gewinner > 0) {
        SpielGewonnen();
    }
    // Falls das Spiel unentscheiden steht, wird die dazugrhörige Animation aktiviert
    if (Gewinner == -1) {
        SpielUnentschieden();
    }

    /*
    for(int i = 0; i < 9; i++)
    {
        Serial.print(Spielfeld[i]);
        Serial.print("; ");
    }
    Serial.print("   ");
    Serial.print(analogRead(A7));
    Serial.print("   ");
    Serial.print(Taste);
    Serial.print("   ");
    Serial.print(Spieler);
    Serial.println(" ");
    */
}