# Tic Tac Toe - GUI Linien zeichnen



## TheAnfänger (22. Feb 2017)

Hallo Leute,
Ich bin noch ein Java-Anfänger, habe aber zuvor schon etwas Erfahrung mit HTML, CSS und JavaScript gesammelt. Momentan probiere ich ein Tic Tac Toe Spiel zu programmieren, scheitere aber an der GUI. Kann mir da jemand helfen?
Das Hauptproblem ist, das Spielfeld (oder überhaupt etwas) zu zeichnen.
(Ich habe mir gedacht ich fang mal mit der GUI an, darum ist von der Logik noch nichts enthalten)
Ich benutze Eclipse und das ist der Code:


```
public class TicTacToe {
    static WerBeginnt anf;
   
    public static void main(String[] args) {
        anf = new WerBeginnt();
        anf.setVisible(true);
    }
   
    public static void spielbeginnt(int beg, WerBeginnt wb) { // beg == 0 Spieler beginnt    beg == 1 KI beginnt
        wb.dispose();
        new GUI();
    }
}
```


```
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class GUI extends JFrame {
    private static final long serialVersionUID = 6411499808530678723L; // Eclipse generated
   
    JLabel text;
    JPanel jp1, jp2;
    Zeichnen z;
    Object line1, line2, line3, line4;
   
    GUI() {
        super("Tic Tac Toe");
        setSize(500, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
       
        jp1 = new JPanel();
        jp2 = new JPanel();
        text = new JLabel("Text");
       
        jp1.add(text);
        this.add(jp1, BorderLayout.NORTH);
       
        z = new Zeichnen();
       
        jp2.add(z);
        this.add(jp2, BorderLayout.CENTER);
       
        this.setVisible(true);
    }
}
```


```
import java.awt.*;
import javax.swing.*;

public class Zeichnen extends JComponent {
    private static final long serialVersionUID = 8485890844039483149L;
   
    private Color c = Color.BLACK; //Standardmässig schwarz
   
    public void paint(Graphics g) {
        super.paint(g);
        g.setColor(c);
        g.drawLine(1, 1, 100, 100);
    }
    public void setColor(Color c) {
        this.c = c;
    }
}
```
Ich habe noch eine Klasse WerBeginnt, aber dort funktioniert eigentlich alles.

Wenn irgendwelche Infos fehlen einfach fragen


----------



## @SupressWarnings() (22. Feb 2017)

Vielleicht solltest du als Java-Anfänger erst einmal die Logik programmieren und später dann von einem Text UserInterface in ein graphisches umsteigen. Zu deinem spezifischen Problem kann ich dir nicht helfen, da ich in JavaFX meine GUIs programmiere.


----------



## Java20134 (23. Feb 2017)

Warum nutzt du keine Buttons? Damit hast du automatisch die Linien und du hast die Eingabe geklärt. Dadurch fehlt nur noch die Abfrage von welcher Person die Eingabe gemacht wurde, wenn es ein 1 gegen 1 Spiel ist.


----------



## TheAnfänger (23. Feb 2017)

@SupressWarnings() hat gesagt.:


> Vielleicht solltest du als Java-Anfänger erst einmal die Logik programmieren und später dann von einem Text UserInterface in ein graphisches umsteigen. Zu deinem spezifischen Problem kann ich dir nicht helfen, da ich in JavaFX meine GUIs programmiere.


Naja, die Logik halte ich jetzt nicht für besonders schwer (vielleicht irre ich mich auch) und eigentlich möchte ich von der Konsole wegkommen. (Habe dort auch schon ein (sehr) kleines Spiel programmiert.)



Java20134 hat gesagt.:


> Warum nutzt du keine Buttons? Damit hast du automatisch die Linien und du hast die Eingabe geklärt. Dadurch fehlt nur noch die Abfrage von welcher Person die Eingabe gemacht wurde, wenn es ein 1 gegen 1 Spiel ist.


Das probiere ich dann mal umzusetzen 

Aber wenn ich jetzt sonst irgendwie in einem anderen Projekt was mit zeichnen umzusetzen versuche, wie kann ich es dort lösen?


----------



## Robat (23. Feb 2017)

(1) was genau funktioniert denn nicht???
(2) rufst du `spielbeginnt()` denn auch irgendwo auf?
(3) du gibst deinem JFrame nirgends einen LayoutManager mit. Daher bringt es auch nix mit `BorderLayout.NORTH` zu arbeiten. Du musst schon explizit sagen `setLayout(new BorderLayout());`.

Kleine Tipps am Rande:
- vermeide es von JFrame zu erben solange du nicht die Funktionalität erweitern willst. Lege lieber ein Jframe Objekt an
- Gib deinen Variablen aussagekräftig Namen. 

Gruß Robert


----------



## Harry Kane (23. Feb 2017)

TheAnfänger hat gesagt.:


> Aber wenn ich jetzt sonst irgendwie in einem anderen Projekt was mit zeichnen umzusetzen versuche, wie kann ich es dort lösen?


1. paintComponent(Graphics g) überschreiben und nicht paint(Graphics g). Da paint intern paintComponent, paintBorder und paintChildren aufruft, ist der Unterschied aber nicht sooo groß. Du darfst dich nur nicht wundern, wenn du später eine Border setzen möchtest und die nicht erscheint.
2. Wenn irgendwas nicht auf dem Bildschirm zu sehen ist, solltest du als erstes überprüfen, ob beim Layouten bzw. bei der Größenberechnung was schief gelaufen ist. Der Platz, der einer JComponent zur Verfügung gestellt wird, kann entweder von Hand bestimmt werden (beim Null-Layout, nicht empfehlenswert) oder vom Layoutmanager berechnet werden. Leider ist das Layouten in Java (IMHO) nicht wirklich intuitiv.
Wenn ich mir den Aufbau deiner Komponentenhierarchie anschaue, ergibt sich folgendes Bild:
Zeichnen liegt in jp2. jp2 hat keine LayoutManager gesetzt bekommen und verwendet deshalb den Default (ein FlowLayout)
jp2 liegt im CENTER der Gui, die ein BorderLayout verwendet (BorderLayout ist bei JFrames bzw. bei deren ContentPanes der Default).
Beim layouten passiert folgendes: jp2 bekommt den größten Teil des Platzes von Gui. Zeichnen hat aber keine Kindkomponenten und hat auch keine preferredSize gesetzt bekommen. Deshalb sollte Zeichnen eine preferredSize von 0,0 zurückgeben. Das von jp2 benutzte FlowLayout weist aber jeder Komponente eine Größe zu, die aus der preferredSize berechnet wird, in dem Fall 0,0. Deshalb ist Zeichnen unsichtbar. Lösung: setze bei Zeichnen die preferredSize von Hand.


----------



## TheAnfänger (23. Feb 2017)

Robat hat gesagt.:


> (1) was genau funktioniert denn nicht???


Das etwas gezeichnet wird (Problem inzwischen gelöst)


Robat hat gesagt.:


> (2) rufst du `spielbeginnt()` denn auch irgendwo auf?


Ja, zuerst erstelle ich in der main() ein Objekt der Klasse WerBeginnt. Dieses Objekt erstellt ein Fenster, in dem man auswählen kann wer beginnt (KI oder Spieler). Nachdem man ausgewählt hat wird die Funktion spielbeginnt() u. a. mit dem Objekt selbst als Parameter. Das Fenster wird dann noch geschlossen und die eigentliche GUI wird gestartet.


Robat hat gesagt.:


> (3) du gibst deinem JFrame nirgends einen LayoutManager mit. Daher bringt es auch nix mit `BorderLayout.NORTH` zu arbeiten. Du musst schon explizit sagen `setLayout(new BorderLayout());`.


BorderLayout ist doch Standard, oder nicht? Gab zumindest keine Probleme.


Robat hat gesagt.:


> Kleine Tipps am Rande:
> - vermeide es von JFrame zu erben solange du nicht die Funktionalität erweitern willst. Lege lieber ein Jframe Objekt an
> - Gib deinen Variablen aussagekräftig Namen.
> 
> Gruß Robert


Bis jetzt wurde in jedem Tutorial das ich mir angesehen habe eine Klasse erstellt, die von JFrame erbt.
Ich finde meine Namen aussagekräftig genug. 


Harry Kane hat gesagt.:


> 1. paintComponent(Graphics g) überschreiben und nicht paint(Graphics g). Da paint intern paintComponent, paintBorder und paintChildren aufruft, ist der Unterschied aber nicht sooo groß. Du darfst dich nur nicht wundern, wenn du später eine Border setzen möchtest und die nicht erscheint.


Hab ich jetzt mal geändert.


Harry Kane hat gesagt.:


> 2. Wenn irgendwas nicht auf dem Bildschirm zu sehen ist, solltest du als erstes überprüfen, ob beim Layouten bzw. bei der Größenberechnung was schief gelaufen ist. Der Platz, der einer JComponent zur Verfügung gestellt wird, kann entweder von Hand bestimmt werden (beim Null-Layout, nicht empfehlenswert) oder vom Layoutmanager berechnet werden. Leider ist das Layouten in Java (IMHO) nicht wirklich intuitiv.
> Wenn ich mir den Aufbau deiner Komponentenhierarchie anschaue, ergibt sich folgendes Bild:
> Zeichnen liegt in jp2. jp2 hat keine LayoutManager gesetzt bekommen und verwendet deshalb den Default (ein FlowLayout)
> jp2 liegt im CENTER der Gui, die ein BorderLayout verwendet (BorderLayout ist bei JFrames bzw. bei deren ContentPanes der Default).
> Beim layouten passiert folgendes: jp2 bekommt den größten Teil des Platzes von Gui. Zeichnen hat aber keine Kindkomponenten und hat auch keine preferredSize gesetzt bekommen. Deshalb sollte Zeichnen eine preferredSize von 0,0 zurückgeben. Das von jp2 benutzte FlowLayout weist aber jeder Komponente eine Größe zu, die aus der preferredSize berechnet wird, in dem Fall 0,0. Deshalb ist Zeichnen unsichtbar. Lösung: setze bei Zeichnen die preferredSize von Hand.


preferredSize() DAS war es! Danke!


So, hab es jetzt geschafft ein TicTacToe-Feld anzuzeigen.
Aber eine andere Frage:
Die X und Os werden ja erst nachträglich hinzugefügt und kann ich somit noch nicht in der paintComponent() angeben. Wie soll ich das lösen?


----------



## Harry Kane (24. Feb 2017)

Hier der Link zu einem Beispiel, wie man eine JComponent mit variablen Inhalten zeichnen kann:
Klick.
Das ganze ist etwas dirty, weil die Daten (in dem Fall die zu zeichnenden Shapes) in derselben Klasse gespeichert, die auch für das Zeichnen zuständig ist (also die View). 
Bei größeren Anwendungen sollte man auf jeden Fall die Daten (das Model) von der View trennen, so dass ein und dasselbe Model auf verschiedenen Arten gezeichnet werden kann.


----------



## TheAnfänger (24. Feb 2017)

Harry Kane hat gesagt.:


> Hier der Link zu einem Beispiel, wie man eine JComponent mit variablen Inhalten zeichnen kann:
> Klick.
> Das ganze ist etwas dirty, weil die Daten (in dem Fall die zu zeichnenden Shapes) in derselben Klasse gespeichert, die auch für das Zeichnen zuständig ist (also die View).
> Bei größeren Anwendungen sollte man auf jeden Fall die Daten (das Model) von der View trennen, so dass ein und dasselbe Model auf verschiedenen Arten gezeichnet werden kann.


Ich habe das inzwischen mit einem if-else Konstrukt gelöst, das einfach den Zustand der Felder abfragt. (Ja, ich weiss das das auch nicht schön ist aber
Dein Code sieht mir im ersten Augenblick auch relativ komliziert aus. 
Gibts den auch in kurz?

Inzwischen habe ich ein völlig anderes Problem:
Ich habe die Buttons in das Feld gesetzt und jetzt soll mein Programm pausieren, bis diese gedrückt werden:

```
public class TicTacToe {
    static WerBeginnt anf;
    static GUI gui = null;
    // Grosses Array Zeilen Kleines Array Spalten
    static int[][] ticTacToeFeld = new int[3][3]; // Zustände: 0 == nichts gesetzt, 1 == Kreuz, 2 == Kreis
   
    public static void main(String[] args) {
        for (int i = 0; i < ticTacToeFeld.length; i++) {
            for (int j = 0; j < ticTacToeFeld[i].length; j++) {
                ticTacToeFeld[i][j] = 0;
            }
        }
        anf = new WerBeginnt();
        anf.setVisible(true);
    }
   
    public static void spielbeginnt(int beg, WerBeginnt wb) { // beg == 0 Spieler beginnt    beg == 1 KI beginnt
        wb.dispose();
        gui = new GUI();
        for(int i = 9; i > 0; i--) {
            if(i % 2 == 1 && beg == 0) {
                spielerZug();
            } else if(i % 2 == 0 && beg == 1) {
                spielerZug();
            } else {
                kiZug();
            }
        }
    }
   
    public static void spielerZug() {
        gui.setText("Du bist am Zug");
        //
        // Hier soll das Programm pausieren
        //
    }
   
    public static void kiZug() {
       
    }
}
```


```
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class GUI extends JFrame implements ActionListener {
    private static final long serialVersionUID = 6411499808530678723L; // Eclipse generated
   
    private JLabel text;
    private JPanel jp1, jp2;
    private Zeichnen z;
    private JButton b1, b2, b3, b4, b5, b6, b7, b8, b9;
   
    GUI() {
        super("Tic Tac Toe");
        setSize(300, 361);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setLayout(new BorderLayout());
       
        jp1 = new JPanel();
        jp2 = new JPanel();
        text = new JLabel("Text");
       
        jp1.add(text);
        this.add(jp1, BorderLayout.NORTH);
       
        z = new Zeichnen();
        Dimension dimension = new Dimension(300, 300);
        z.setPreferredSize(dimension);
       
        /* 
         * Hier kommt jetzt etwas viel Code um die Buttons richtig einzufügen 
         */
       
        ...
       
        /*
         * Buttons sind endlich drin
         */
       
        jp2.add(z);
        this.add(jp2, BorderLayout.CENTER);
       
        this.setVisible(true);
    }
    void setText(String text) {
        this.text.setText(text);
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if(e.getActionCommand().equals(("b" + i) + j)) {
                    // Befehl einfügen
                }
            }
        }
    }
}
```


----------



## Harry Kane (24. Feb 2017)

Du könntest einfach auf eine Benutzereingabe warten anstatt das Programm "pausieren" zu lassen.
Sobald der menschliche Spieler einen Zug macht (was z. B. durch einen Mausklick ausgelöst werden könnte), macht die KI auch einen Zug. Nach jedem Zug (egal ob Mensch oder KI) wird geprüft, ob einer schon gewonnen hat.


----------



## TheAnfänger (24. Feb 2017)

Und wie setze ich das um?


----------



## Harry Kane (24. Feb 2017)

Bezieht sich die Frage darauf, wie man auf eine Benutzereingabe reagiert? Dazu enthält der oben verlinkte Post zwei Beispiele (Eingabe per Buttonklick oder Mausklick)?
Oder brauchst du eine Anregung zur Ablauflogik? Dazu fällt mir sowas ein:
1. Beim drücken des Start-Buttons wird das Feld geleert. Wenn die KI den ersten Zug hat, macht die KI einen Zug.
2. Es wird auf eine Benutzereingabe gewartet. Sobald die erfolgt, wird diese validiert (z. B. ob das Feld auf das geklickt wurde, noch frei ist, oder ob der Benutzer ev. zweimal hintereinander geklickt hat), und wenn sie gültig ist, auf dem Spielfeld eingetragen. Dann ist wieder die KI am Zug.
3. Nach jedem Zug wird geprüft, ob es einen Sieger gibt. Wenn ja, ist die Runde beendet. Eine neue Runde wird bei Drücken des Start-Buttons begonnen.


----------



## TheAnfänger (25. Feb 2017)

Harry Kane hat gesagt.:


> Bezieht sich die Frage darauf, wie man auf eine Benutzereingabe reagiert? Dazu enthält der oben verlinkte Post zwei Beispiele (Eingabe per Buttonklick oder Mausklick)?
> Oder brauchst du eine Anregung zur Ablauflogik? Dazu fällt mir sowas ein:
> 1. Beim drücken des Start-Buttons wird das Feld geleert. Wenn die KI den ersten Zug hat, macht die KI einen Zug.
> 2. Es wird auf eine Benutzereingabe gewartet. Sobald die erfolgt, wird diese validiert (z. B. ob das Feld auf das geklickt wurde, noch frei ist, oder ob der Benutzer ev. zweimal hintereinander geklickt hat), und wenn sie gültig ist, auf dem Spielfeld eingetragen. Dann ist wieder die KI am Zug.
> 3. Nach jedem Zug wird geprüft, ob es einen Sieger gibt. Wenn ja, ist die Runde beendet. Eine neue Runde wird bei Drücken des Start-Buttons begonnen.


Das Problem besteht eigentlich darin, dass ich in einer Methode (spielerZug()) in der Klasse TicTacToe auf den Buttonklick aus GUI warten möchte.
Oder geht das wirklich nur in ActionPerformed()?


----------



## Harry Kane (25. Feb 2017)

Mir fällt keine andere saubere Methode ein. Ich werde auch weder groß recherchieren noch nachgrübeln, wie das gehen könnte. Du kannst meinen Ratschlag annehmen und dein Programm entsprechend ändern, oder du suchst weiter nach dem magischen Zellenblock, der dir deinen Ansatz mit "auf Buttonklick in Methodenkörper warten" ermöglicht.


----------



## TheAnfänger (25. Feb 2017)

Ich habe das Programm jetz so umgeschrieben, dass spielerZug() in ActionPerformed() aufgerufen wird.
Inzwischen habe ich auch noch eine kleine KI hinzugefügt (ist aber NOCH nicht unbesiegbar (aber schon schwieriger)).

Bin soweit eigentlich fertig mit meinem TicTacToe und möchte allen danken, die mir geholfen haben!
Falls jemand am Code interessiert ist (aus welchem Grund auch immer) kann man mich anschreiben.
Falls jemand doch noch Verbesserungsvorschläge hat, darf man sie ruhig sagen/schreiben

Hab das Spiel noch beigefügt, falls es sich jemand anschauen will.


----------

