Java Klasse

Hauptmenü

Die Java Klasse des Hauptmenüs braucht wie im ersten Projekt eine Funktion, die beim drücken des Buttons ausgelöst wird.

public void spielStarten(View view){
}

Der Button soll die zweite Activity, das Spiel, ausführen. Solche Aktionen werden in Android mit Intents durchgeführt.Ein Intent ist die Beschreibung einer Aktion, die Ausgeführt werden soll, was nicht nur das öffnen einer Activity ist, sondern auch z.B. das Öffnen einer Webseite oder das wählen einer Nummer. Zuerst muss ein neuer Intent erstellt werden. Die Argumente des Konstruktors sind der Kontext, in diesem Fall die bereits geöffnete, aktuelle, Activity, und die zu öffnende Activity.

Intent spielStart = new Intent(MainActivity.this, SpielActivity.class);

Der Intent kann mit startActivity() ausgeführt werden. Danach sollte die aktuelle Activity mit finish() beendet werden.

public void spielStarten(View view){
        Intent spielStart = new Intent(MainActivity.this, SpielActivity.class); //Neuer Intent um SpielActivity zu starten
        startActivity(spielStart);   //Intent ausführen, neue Activity SpielActivity starten
        finish();   //Aktuelle Activity beenden
    }

Spiel

Beim Spiel muss nicht für jeden Button eine einzelne Methode erstellt werden, sondern nur eine, die durch jeden Button aufgerufen wird.

public void game_click(View view) {     //Wird aufgerufen, sobald ein Feld angeklickt wird
}

Wenn ein Button gedrückt wird, wird die angegebene Methode mit einem Parameter vom Typ View aufgerufen. Ein View stellt ein Element der Benutzeroberfläche dar, in unserem Fall ist das "View view" (Name des Objektes: "view", Typ: "View"). Aus diesem View kann nun ein Objekt vom Typ Button erstellt werden, dass den gedrückten Button repräsentiert.

public void game_click(View view) {     //Wird aufgerufen, sobald ein Feld angeklickt wird
     Button button = (Button) view;  //"button" ist der aktuell gedrückte Button
}

Das "(Button)" gibt hierbei an, dass es sich bei dem View view um ein Button, also eine Unterklasse von View ist.

Alles folgende soll nur ausgeführt werden wenn der geklickte Button leer ist:

if (button.getText().equals("")) {  //Nur ausführen wenn ein leerer Button angeklickt wurde
}

Wenn ein Button geklickt wird, soll der Text und die Textfarbe dem aktuellen Spieler entsprechen.

switch (aktiverSpieler) {
    case 0: //Spieler X/0 am Zug
        button.setText("X");    //Feld auf X setzen
        button.setTextColor(ContextCompat.getColor(this, R.color.spieler0));    //Farbe für Spieler 0 aus den Ressourcen laden, auf button anwenden
        break;
    case 1: //Spieler O/1 am Zug
        button.setText("O");    //Feld auf O setzen
        button.setTextColor(ContextCompat.getColor(this, R.color.spieler1));    //Farbe für Spieler 1 aus den Ressourcen laden, auf button anwenden
        break;
}

Das Meiste in diesem Abschnitt sollte bereits bekannt sein, neu ist jedoch die folgende Zeile:

ContextCompat.getColor(this, R.color.spieler0)

Ressourcen

Wenn innerhalb einer App Daten wie Texte, Bezeichnungen, Textgrößen, Farben usw. oft verwendet werden, ist es sinnvoll sie als Ressource zu speichern. Damit werden diese Werte nur ein Mal gespeichert, und wenn der Wert im Layout oder Code benötigt wird, kann er von dort geladen werden. Dies hat den Vorteil, dass man oft vorkommende Werte einfach ändern kann, oder auf verschiedenen Geräten unterschiedliche Werte verwenden kann (z.B. wenn die App in viele Sprachen übersetzt wird). Diese "Values ressource files" findet man unter res/values. Falls dort die Datei "colors.xml" noch nicht existiert, kann sie mittels Rechtsklick auf "values" erstellt werden. Je nach Android version enthält diese Datei schon Farben, die das Design wie die Farbe der Benachrichtigungsleiste bestimmen. Folgende Werte kommen neu hinzu:

<color name="spieler0">#F44336</color>
<color name="spieler1">#4CAF50</color>

Tipp: Ein Klick auf die Vorschau der Farbe links vom Code ruft ein Auswahlfeld für Farben auf!

Somit wird von

ContextCompat.getColor(this, R.color.spieler0)

ein Integer mit dem Wert 0xF44336 zurückgegeben.

Nachdem ein Spieler seinen Zug abgeschlossen hat, muss geprüft werden, ob er das Spiel gewonnen oder zum Gleichstand gebracht hat. Dafür wird die Methoden spielGewonnen() geschrieben.

Gewinnfunktion

Die Gewinnfunktion prüft, ob einer der Spieler das Spiel gewonnen hat. Dafür müssen 4 Möglichkeiten geprüft werden: Horizontal, Vertikal, Diagonal, Umgekehrt Diagonal

Bei Horizontal und Vertikal genügt es, die Zeile/Spalte zu prüfen, in der gerade ein Feld eingenommen wurde, da nur dort das Spiel gewonnen werden kann.

boolean spielGewonnen(int[][] spielfeld, int x, int y) {
    for (int i = 0; i < spielfeld.length; i++) {    //Alle Felder der aktuellen Zeile
        if (spielfeld[i][y] != aktiverSpieler)      //Abbrechen sobald ein Feld der Reihe nicht dem aktuellen Spieler gehört
            break;
        if (i == spielfeld.length - 1)              //Spiel ist gewonnen da alle Felder geprüft wurden und jedes dem aktuellen Spieler gehören
            return true;
    }
}

Mittels einer for-Schleife werden alle vorhandenen Felder geprüft, int y ist hierbei die y Koordinate des aktuellen Feldes. Diese bleibt konstant, somit werden alle Felder in Zeile y geprüft. Diefor-Schleife wird mittels break; abgebrochen sobald eines der Felder nicht dem aktuellen Spieler gehört. Wenn alle Felder überprüft wurden ohne dass die Schleife abbricht gehört jedes Feld dem aktuellen Spieler, also gibt die Funktion truezurück.

Die Schleife zur Prüfung der Vertikalen verfährt nach gleichem Prinzip:

for (int i = 0; i < spielfeld[0].length; i++) { //Alle Felder der aktuellen Spalte
    if (spielfeld[x][i] != aktiverSpieler)      //Abbrechen sobald ein Feld der Reihe nicht dem aktuellen Spieler gehört
        break;
    if (i == spielfeld.length - 1)              //Spiel ist gewonnen da alle Felder geprüft wurden und jedes dem aktuellen Spieler gehören
        return true;
}

Für die erste Diagonale müssen in jedem Durchlauf sowohl die x als auch y Koordinate erhöht werden. Außerdem muss dieser Test nur durchgeführt werden wenn x = y ist, also ein Feld auf der Diagonalen eingenommen wurde.

if (x == y) {                                   //Auf Diagonalen
    for (int i = 0; i < spielfeld.length; i++) {//Alle Felder der Diagonalen
        if (spielfeld[i][i] != aktiverSpieler)  //Abbrechen sobald ein Feld der Reihe nicht dem aktuellen Spieler gehört
            break;
        if (i == spielfeld.length - 1)          //Spiel ist gewonnen da alle Felder geprüft wurden und jedes dem aktuellen Spieler gehören
            return true;
    }
}

Für die zweite Diagonale muss nur die y Koordinate invertiert werden.

 for (int i = 0; i < spielfeld.length; i++) {    //Alle Felder der 2. Diagonalen
    if (spielfeld[i][spielfeld.length - 1 - i] != aktiverSpieler)   //Abbrechen sobald ein Feld der Reihe nicht dem aktuellen Spieler gehört
        break;
    if (i == spielfeld.length - 1)              //Spiel ist gewonnen da alle Felder geprüft wurden und jedes dem aktuellen Spieler gehören
        return true;
}

Ans Ende der Gewinnfunktion muss noch ein return false; da das Spiel nicht gewonnen ist wenn keiner der Tests erfolgreich war.

In game_click() muss natürlich nach jedem Spielzug überprüft werden, ob das Spiel mit dem Zug gewonnen wurde, oder ob gerade der 9. (und letzte) Zug gespielt wurde, ohne dass das Spiel gewonnen wurde.

//Auf Spiel Ende testen
if (spielGewonnen(spielfeld, getXCoordinate(button), getYCoordinate(button))) { //Testen ob das Spiel gewonnen ist
    Intent spielEnde = new Intent(SpielActivity.this, SpielEndeActivity.class); //Neuer Intent um SpielEndeActivity zu starten
    Bundle optionen = new Bundle(); //Neue Optionen
    optionen.putInt("gewinner", aktiverSpieler); //Wert "gewinner" in Optionen auf aktuellen Spieler festlegen
    spielEnde.putExtras(optionen);  //Optionen an Intent anhängen
    startActivity(spielEnde);   //Intent ausführen, neue Activity SpielEndeActivity starten
    finish();   //Aktuelle Activity beenden
    return;
}

if (anzahlSpielzuege >= 8) {    //Spiel wurde mit dem 9. Zug nicht beendet, also ist das Spiel unentschieden
    Intent spielEnde = new Intent(SpielActivity.this, SpielEndeActivity.class); //Neuer Intent um SpielEndeActivity zu starten
    Bundle optionen = new Bundle(); //Neue Optionen
    optionen.putInt("gewinner", -1); //Wert "gewinner" in Optionen auf -1 (kein Gewinner, Gleichstand)festlegen
    spielEnde.putExtras(optionen);  //Optionen an Intent anhängen
    startActivity(spielEnde);   //Intent ausführen, neue Activity SpielEndeActivity starten
    finish();   //Aktuelle Activity beenden
    return;
}

Außerdem müssen noch der Zähler für die Spielzüge erhöht werden, die Anzeige des aktuellen Spielers aktualisiert und die Farbe angepasst werden:

aktiverSpieler = (aktiverSpieler + 1) % 2; //Nächster Spieler
textViewSpieler.setText("Spieler " + aktiverSpieler); //Anzeige für aktuellen Spieler aktualisieren
if (aktiverSpieler == 0)
    textViewSpieler.setTextColor(ContextCompat.getColor(this, R.color.spieler0));   //Farbe der Anzeige festlegen
else
    textViewSpieler.setTextColor(ContextCompat.getColor(this, R.color.spieler1));

anzahlSpielzuege++; //Aktueller Spielzug fertig

Aufgabe: Lade den Code herunter und erweitere die App, sodass auf dem Endbildschirm die benötigte Zeit pro Spieler angezeigt wird.

Tipp: mit System.currentTimeMillis(); kann die aktuelle Zeit in Sekunden abgerufen werden

Last updated