# TicTacToe: Spieler kann nicht gewinnen



## Reality (2. Okt 2004)

Hi,
ich programmiere gerade ein grafisches TicTacToe.
Leider kann man nur bedingt gewinnen. Es kann nur manchmal Kreis gewinnen und wenn ich abfrage, ob Kreuz gewonnen hat, kommt eine NullPointerException.


```
import java.awt.*;

public class Spielfeld{
  Player playerCircel;
  Player playerCross;

  static String playedToken = "";
  static boolean field[] = new boolean[9];  //Felder besetzt?
  static String playerOnField[] = new String[9]; // Wer hat welches Feld besetzt?
  static private int lastPlaced;  //Wo wurde ein Zeichen zuletzt gesetzt?

  private int pos_x;  //X-Position der Maus
  private int pos_y; //Y-Position der Maus

  Spielfeld(){
    playerCircel = new Player();
    playerCross = new Player();

    for(int i=0; i<field.length; i++)
      field[i] = false;

    for(int i=0; i<playerOnField.length; i++){
      playerOnField[i] = "";
    }
  }

  private void drawCross(Graphics g){
    //Ein 'X' malen
    g.setColor(Color.RED);
    if(pos_x != 0 && pos_y !=0 && !occupyField(pos_x, pos_y)){
      g.drawLine(pos_x, pos_y, pos_x + 20, pos_y + 20);
      g.drawLine(pos_x - 1, pos_y, pos_x + 19, pos_y + 20);
      g.drawLine(pos_x + 20, pos_y, pos_x, pos_y + 20);
      g.drawLine(pos_x + 19, pos_y, pos_x - 1, pos_y + 20);

      pos_x = 0;
      pos_y = 0;
    }
  }

  private void drawCircel(Graphics g){
    //Ein Kreis malen
    g.setColor(Color.RED);

    //Wenn Feld im gültigen Bereich ist und es noch nicht belegt ist,
    // dann zeichne...
    if(pos_x != 0 && pos_y !=0 && !occupyField(pos_x, pos_y)){
      g.drawOval(pos_x, pos_y, 25, 25);
      g.drawOval(pos_x - 1, pos_y, 25, 25);

      pos_x = 0;
      pos_y = 0;
    }
  }

  void drawField(Graphics g){
    //Spielfeld zeichnen
    g.fillRect(60, 120, 150, 2);
    g.fillRect(60, 170, 150, 2);
    g.fillRect(110, 70, 2, 150);
    g.fillRect(160, 70, 2, 150);
  }

  void drawToken(Graphics g, int pos_x, int pos_y, String token){

    if(placeX(pos_x) && placeY(pos_y)){  // Wenn in ein gültiges Feld geklickt wurde...
      if (token.equals(Main.cross)) {
        drawCross(g);
        playedToken = Main.cross;
        playerOnField[lastPlaced] = Main.cross;
        playerCross.hasWon(Main.cross, "Kreuz");
      }

      else if(token.equals(Main.circel)){
        drawCircel(g);
        playedToken = Main.circel;
        playerOnField[lastPlaced] = Main.circel;
        playerCircel.hasWon(Main.circel, "Kreis");
      }
    }
  }

  // Wurde in einem gültigem Bereich geklickt? Falls ja, platziere das Zeichen
  // an der richten Stelle.
  private boolean placeX(int pos_x){
    if(pos_x <=110 && pos_x >= 60){
      this.pos_x = 75;
      return true;
    }

    else if(pos_x <= 160 && pos_x >= 110){
      this.pos_x = 125;
      return true;
    }

    else if(pos_x <= 210 && pos_x >= 110){
      this.pos_x = 175;
      return true;
    }

    //Falls an falscher Stelle geklickt, dann soll auch nicht gezeichnet werden
    else return false;
  }

  private boolean placeY(int pos_y){
    if(pos_y <= 120 && pos_y >= 70){
      this.pos_y = 85;
      return true;
    }

    else if(pos_y <= 170 && pos_y >= 120){
      this.pos_y = 135;
      return true;
    }

    else if(pos_y <= 220 && pos_y >= 170){
      this.pos_y = 185;
      return true;
    }

    else return false;

  }


  //Ist ein Feld schon besetzt? Falls ja, darf nicht gezeichnet werden
  boolean occupyField(int pos_x, int pos_y){

    if(pos_x <=110 && pos_x >= 60 && pos_y <= 120 && pos_y >= 70 && !field[0]){
      field[0] = true;  //Als besetzt markieren
      lastPlaced = 0;  //Speichern wo der Spieler gesetzt hat
      return false; //Feld nicht besetzt => Es kann gezeichnet werden
    }

    else if(pos_x <= 160 && pos_x >= 110 && pos_y <= 120 && pos_y >= 70 && !field[1]){
      field[1] = true;
      lastPlaced = 1;
      return false;
    }

    else if(pos_x <= 210 && pos_x >= 160 && pos_y <= 120 && pos_y >= 70 && !field[2]){
     field[2] = true;
     lastPlaced = 2;
     return false;
   }

   else if(pos_x <= 110 && pos_x >= 60 && pos_y <= 170 && pos_y >= 120 && !field[3]){
     field[3] = true;
     lastPlaced = 3;
     return false;
   }

   else if(pos_x <= 160 && pos_x >= 110 && pos_y <= 170 && pos_y >= 120 && !field[4]){
     field[4] = true;
     lastPlaced = 4;
     return false;
   }

   else if(pos_x <= 210 && pos_x >= 160 && pos_y <= 170 && pos_y >= 120 && !field[5]){
     field[5] = true;
     lastPlaced = 5;
     return false;
   }

   else if(pos_x <= 110 && pos_x >= 60 && pos_y <= 220 && pos_y >= 170 && !field[6]){
     field[6] = true;
     lastPlaced = 6;
     return false;
   }

   else if(pos_x <= 160 && pos_x >= 110 && pos_y <= 220 && pos_y >= 170 && !field[7]){
     field[7] = true;
     lastPlaced = 7;
     return false;
   }

   else if(pos_x <= 210 && pos_x >= 160 && pos_y <= 220 && pos_y >= 170 && !field[8]){
     field[8] = true;
     lastPlaced = 8;
     return false;
   }

    else return true;  //Feld schon belegt oder nicht vorhanden
  }
}
```

Alle Möglichkeiten zum Gewinnen, habe ich noch nicht abgefragt, aber die die gehen sollten, gehen nur teilweise.


```
import javax.swing.*;

public class Player {

  void hasWon(String player, String playerName){      
    for(int i=1; i <= Spielfeld.playerOnField.length/3; i+=3){
      System.out.println(i+"\n"+
                         i+3+"\n"+
                         i+6);
      if(Spielfeld.playerOnField[i].equals(player) &&
         Spielfeld.playerOnField[i+3].equals(player) &&
         Spielfeld.playerOnField[i+6].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
      }
    }

    for(int i=0; i < Spielfeld.playerOnField.length/3; i+=3){
      if(Spielfeld.playerOnField[i].equals(player) &&
         Spielfeld.playerOnField[i+1].equals(player) &&
         Spielfeld.playerOnField[i+2].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
      }
    }
  }
}
```

Der komplette Code:
http://mitglied.lycos.de/masterchan/TicTacToeV2.zip

Liebe Grüße
Reality


----------



## Reality (2. Okt 2004)

OK, die NullpointerException kam, weil ich PlayerCross nicht initialisiert habe. :roll: 
Aber trotzdem erkennt mein Programm immernoch nicht richtig, wenn man gewonnen hat.

Ich habe jetzt übrigens nur noch eine Klasse Player und mache davon Instanzen playerCross und playerCircel und übergebe per Parameter der Methode hasWon(String player) den Namen ("Kreuz" und "Kreis") der Player.

Die Änderungen könnt ihr euch runterladen.

Liebe Grüße
Reality


----------



## Griffin (3. Okt 2004)

Nur zur Information:
Wenn ich das Spiel starte (TicTacToeV2.zip) kommt  am anfang eine NullPointerException, wie bei dir. Aber dann kann ich nur als Kreuz spielen! Wenn ich dann so meine kreuze mache, erkennt er eigentlich nur in der ersten Reihe den Sieg. Nicht aber in den anderen Reihen. Ausserdem gewinnt immer Kreis, obwohl ich ständig als Kreuz spiele.
Bei der Meldung "Kreis hat gewonnen" kommen fast unendlich viele Meldungen in der Console und ich muss den Dialog fast 10 mal wegklicken.


----------



## Reality (3. Okt 2004)

Hi,
beim erneuten Uploud, wo ich mein Spiel geupdatet habe, muss ein Fehler unterlaufen sein.
Lade es dir bitte nocheinmal runter.

Das nicht richtig gewonnen werden kann. Ist ja mein Problem.

Liebe Grüße
Reality


----------



## Reality (3. Okt 2004)

Hier ein Beispiel, wo nichts getan wird:
Ausgabe der ersten Schleife, wo kontrolliert wird, ob jemand gewonnen hat.:


> cross
> 
> 
> circel
> ...



Liebe Grüße
Reality


----------



## Griffin (3. Okt 2004)

Also ich hab mir das ganze nochmals von 


> Der komplette Code:
> http://mitglied.lycos.de/masterchan/TicTacToeV2.zip


runtergeladen. Aber ich hab immer noch die selben Probleme. Entweder liegt auf dem Server immer noch eine alte Version, oder da ist immer noch was anderes falsch!?!


----------



## Reality (3. Okt 2004)

Hi,
ich habe diesmal die Datei nicht überschrieben, sondern gelöscht und dann neu übertragen. Ich habe es mir auch runtergeladen und mich vergewissert.

OK, nun zum eigentlichen Problem.
In hasWon() stimmt etwas mit den Schleifen nicht, denn wenn ich die Schleifen durch explizite if-Anweisungen ersetze, funktioniert es wunderbar! (nur wäre das umständlicher, schlechter lesbar und mehr Code.)


```
if(Spielfeld.playerOnField[0].equals(player)
       && Spielfeld.playerOnField[1].equals(player)
       && Spielfeld.playerOnField[2].equals(player)){
      JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
    }
    
    if(Spielfeld.playerOnField[3].equals(player)
       && Spielfeld.playerOnField[4].equals(player)
       && Spielfeld.playerOnField[5].equals(player)){
      JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
    }
    
    if(Spielfeld.playerOnField[6].equals(player)
       && Spielfeld.playerOnField[7].equals(player)
       && Spielfeld.playerOnField[8].equals(player)){
      JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
    }
```

Das hier sind natürlich nur drei Möglichkeiten zu gewinnen.

Diese Schleife müsste eigentlich dasselbe tun!

```
for(int i=0; i < Spielfeld.playerOnField.length/3; i+=3){
      if(Spielfeld.playerOnField[i].equals(player) &&
         Spielfeld.playerOnField[i+1].equals(player) &&
         Spielfeld.playerOnField[i+2].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
      }
    }
```

Liebe Grüße
Reality


----------



## Griffin (3. Okt 2004)

Jetzt "funktioniert" es und auch mal der schnelle Blick auf die Schleife sagt, dass alles korrekt ist. Ich schau mir das mal bischen genauer an!


----------



## Griffin (3. Okt 2004)

So ich hab den ersten Fehler gefunden. So wie der Code jetzt bei dir steht wird die Schleife nur einmal und für die erste Reihe ausgeführt. Du musst folgendes schreiben:

```
for(int i=0; i < Spielfeld.playerOnField.length; i+=3) // nicht durch 3 teilen!!!
```
Dein durch 3 teilen und dein i+=3  mögen sich nämlich gar nicht 


```
for(int i=0; i < Spielfeld.playerOnField.length; i+=3){
      System.out.println("ist das hier dreimal?");
      if(Spielfeld.playerOnField[i].equals(player) &&
         Spielfeld.playerOnField[i+3].equals(player) &&
         Spielfeld.playerOnField[i+6].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
      }
    }
```
Jedoch kommt hier die Gewinnmeldung viel zu oft bei mir...ich such noch nach dem Fehler. Wollt dir erstmal meinen Fortschritt sagen


----------



## Griffin (3. Okt 2004)

Ok, hab auch das andere Problem in den Griff bekommen. Auch hier war die Schleife mit falschen Daten gefüttert!.
*waagerecht Suchen*

```
for(int i=0; i < Spielfeld.playerOnField.length; i+=3){
      if(Spielfeld.playerOnField[i].equals(player) &&
         Spielfeld.playerOnField[i+1].equals(player) &&
         Spielfeld.playerOnField[i+2].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
      }
    }
```
*senkrecht Suchen*

```
for(int i=0; i < 3; i++){
      if(Spielfeld.playerOnField[i].equals(player) &&
         Spielfeld.playerOnField[i+3].equals(player) &&
         Spielfeld.playerOnField[i+6].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
      }
    }
```

EDIT:
Wenn du noch die zwei IF-Abfragen einfügst findet er auch die gewinne bei Diagonalen dreiern!!

```
// von links oben nach rechts unten
if(Spielfeld.playerOnField[0].equals(player) &&
         Spielfeld.playerOnField[4].equals(player) &&
         Spielfeld.playerOnField[8].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
}
// von rechts oben nach links unten
if(Spielfeld.playerOnField[2].equals(player) &&
         Spielfeld.playerOnField[4].equals(player) &&
         Spielfeld.playerOnField[6].equals(player)){
        JOptionPane.showMessageDialog(Main.frame, playerName + " hat gewonnen");
}
```


----------



## Reality (3. Okt 2004)

Hey, danke! 

Ich bekomme jedoch einen Stackoverflow, wenn ich das hier spiele:






Grund: Es können beide Spieler in dieser Situation gewinnen, aber normalerweise dürften nicht beide Player-Instanzen "gleichzeitig" aufgerufen werden, da eine if-Anweisung dies verhindern (müsste).


```
void drawToken(Graphics g, int pos_x, int pos_y, String token){

    if(placeX(pos_x) && placeY(pos_y)){  // Wenn in ein gültiges Feld geklickt wurde...
      if (token.equals(Main.cross)) {
        drawCross(g);
        playedToken = Main.cross;
        playerOnField[lastPlaced] = Main.cross;
        playerCross.hasWon(Main.cross, "Kreuz"); //Hat Kreuz gewonnen?
      }

      else if(token.equals(Main.circel)){
        drawCircel(g);
        playedToken = Main.circel;
        playerOnField[lastPlaced] = Main.circel;
        playerCircel.hasWon(Main.circel, "Kreis"); //Hat Kreis gewonnen?
      }
    }
  }
```

Liebe Grüße
Reality


----------



## Reality (3. Okt 2004)

Öhm, hat sich irgendwie erledigt.

Liebe Grüße
Reality


----------



## Griffin (4. Okt 2004)

Also bei mir besteht das Problem immer noch. Er kann sich nicht entscheiden wer gewonnen hat. Zumindest gibt er mit zig Meldungen aus. Und das passiert auch bei anderen Situtation... meistens wenn das Spielfeld schon voll ist!


----------



## Reality (4. Okt 2004)

Ich habe das Problem zufällig gelöst und weiß immernoch nicht so genau, warum das Problem verschwunden ist.

Ich habe den aktualisierten Quellcode hochgeladen. Die Änderungen sind schnell zu sehen, wenn man sich die Klasse Player anschaut.

Liebe Grüße
Reality


----------



## Griffin (4. Okt 2004)

Super, funzt einwandfrei. Jetzt kann man nur noch auf zusätzliche Funktionen warten. 

P.S. Deine hasWon Methode in Player braucht den zweiten Parameter nicht mehr, da du das in der Methode victory abwickelst.


----------



## Reality (4. Okt 2004)

Griffin hat gesagt.:
			
		

> Super, funzt einwandfrei. Jetzt kann man nur noch auf zusätzliche Funktionen warten.


Hmm, mal sehen. Ich wollte eigentlich nur das Spielprinzip von TicTacToe verstehen, was ich jetzt auch verstanden habe. Eine "KI" oder Netzwerkfunktion einzubauen wäre noch interessant. Aber alles andere wäre eigentlich nur der Feinschliff und für mich somit Zeitverschwendung, wenn das Spiel nicht für die Öffentlichkeit gedacht ist.


> P.S. Deine hasWon Methode in Player braucht den zweiten Parameter nicht mehr, da du das in der Methode victory abwickelst.


Ahja, danke. 

Liebe Grüße
Reality

PS: Du darfst natürlich auch zusätzliche Funktionen einbauen, wenn du willst.


----------

