# TicTacToe bauen



## lisali (24. Juni 2010)

Hallo,

ich wollte meine Java Kenntnisse erweitern und TicTacToe nachbauen.

Jetzt hänge ich fest, dass ich mit einer Schleife alle 9 Buttons erstellt habe, aber nicht weiß wie ich das actionPerformed hineinkriege:


```
JPanel p = new JPanel();
	    p.setLayout(new GridLayout(3,3));
	    
	    // adding buttons to play
	    JButton[] b = new JButton[9];
	    for (int i=0; i<9;i++)
	    {
	    b[i] = new JButton("");     
	    p.add(b[i]);
	    

	    }
```

Wie mache ich das? Ich möchte nämlich, dass ein Button nach dem Klick disabled wird.


----------



## Cromon (25. Juni 2010)

Ohne etwas mit Java am Hut zu haben würde ich so rein intuitiv den ActionListener und die Methode addActionListener verwenden.


----------



## timestamp (25. Juni 2010)

Diese solltest du natürlich in der Schleife hineinfügen 
Um den Button nach einem Klick zu disablen, musst du im Listener die entsprechende Funktion aufrufen (hab ich grad nicht mehr im Kopf, aber google hilft da ja bestimmt  )


----------



## lisali (25. Juni 2010)

Ja, das ist mir klar... habe ich ja auch probiert, aber das geht nicht so einfach. Ich habe jetzt nur den Code geposted, der keine Fehler meldet. Ich kriege dieses actionPerformed eben nicht in die Schleife rein.

Und ich hatte schon gegoogelt und bin nicht fündig geworden, was das Ganze bezogen auf eine Array-Schleife angeht.


----------



## Kai008 (25. Juni 2010)

Mal rein inuitiv würde ich es so versuchen:


```
JPanel p = new JPanel();
p.setLayout(new GridLayout(3,3));
	    
// adding buttons to play
JButton[] b = new JButton[9];
for (int i=0; i<9;i++)
{
	b[i] = new JButton("");
	b[i].addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent arg0)
		{
			System.out.println("Button gedrückt");
		}
	};
	p.add(b[i]);
}
```

Ich würde aber eher ein 2D-Array verwenden.


----------



## lisali (25. Juni 2010)

Wieso ein zweidim. Array und wie würde sowas in dem Fall aussehen?

Ich weiß ehrlich gesagt nicht mal wie ich den Code fortführen soll. Eigentlich wollte ich alles so laufen lassen, dass ich eine Klasse nur für's GUI schreibe und dann noch eine für die Spielfunktion an sich und die Methoden in einem Interface definiere. Aber so rein logisch bin ich da noch nicht hinterhergekommen...


----------



## timestamp (25. Juni 2010)

Ein 2D-Array bietet sich an, da TicTacToe ja ein 2-D spiel ist 

Würde dann so aussehen:

```
|0,0|0,1|0,2|
|1,0|1,1|1,2|
|2,0|2,1|2,2|
```

So kannst du dann auch leicht überprüfen was n einer Reihe liegt, da du jetzt die Spielfeldkoordinaten als Arrayindex verwendest


----------



## BloodyNewbie (25. Juni 2010)

evtl. eine kleine Hilfe:
ich bin ein Freund von Auslagern der ActionListener...aber jeder so wie er/sie es mag:
Für die GUI würde ich es ungefähr so implementieren:


```
import java.awt.GridLayout;
import java.awt.Toolkit;
import javax.swing.JButton;
import javax.swing.JFrame;

public class TicTacToe {

	private JFrame frame;
	private JButton[] buttons;
	private boolean xOrO;

	public static void main(String[] args) {
		new TicTacToe();
	}

	public TicTacToe() {
		xOrO = true;
		initFrame();
		initButtons();

	}

	private void initButtons() {
		buttons = new JButton[9];
		int i;
		for (i = 0; i < 9; i++) {
			buttons[i]= new JButton();
			frame.add(buttons[i]);
			buttons[i].addActionListener(new TicTacToeListener(this ,buttons[i]));

		}

	}

	private void initFrame() {
		frame = new JFrame("Tic Tac Toe");

		frame.setSize(250, 250);
		frame.setLayout(new GridLayout(3, 3));
		frame.setLocation(
				(Toolkit.getDefaultToolkit().getScreenSize().width - 250) / 2,
				(Toolkit.getDefaultToolkit().getScreenSize().height - 250) / 2);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}



	public boolean isxOrO() {
		return xOrO;
	}

	public void setxOrO(boolean xOrO) {
		this.xOrO = xOrO;
	}

}
```

und der ActionListener, wobei dann noch die Logik des Spiels fehlt!


```
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;

public class TicTacToeListener implements ActionListener {

	private TicTacToe ticTacToe;
	private JButton button;

	public TicTacToeListener(TicTacToe ticTacToe, JButton button) {
		this.ticTacToe = ticTacToe;
		this.button = button;
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == button){
			if (ticTacToe.isxOrO()){
				button.setBackground(Color.RED);
			}else{
				button.setBackground(Color.BLUE);
			}
			button.setEnabled(false);
			changeXorO();
		}

	}

	private void changeXorO() {
		if (ticTacToe.isxOrO()) {
			ticTacToe.setxOrO(false);
		} else {
			ticTacToe.setxOrO(true);
		}
	}
}
```


----------



## Matt297 (25. Juni 2010)

Um mal deine eigentliche Frage zu beantworten, ohne sonst irgendwie auf die Logik des Spiels einzugehen, wenn du einen Button der gedrückt wird deaktivieren willst, würd ich das folgendermaßen machen:


```
JPanel p = new JPanel();
p.setLayout(new GridLayout(3,3));

// adding buttons to play
JButton[] b = new JButton[9];
for (int i=0; i<9;i++)
{
  b[i] = new JButton("");     
  b[i].addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    e.getSource().setEnabled(false);
    //Hier evtl noch weitere Code, z.B. Funktionsaufruf
   });
}
p.add(b[i]);
```


----------



## lisali (26. Juni 2010)

Hallo,

ich bräuchte eigentlich nur noch eine Rekursion für die KI. Könnte mir da jemand behilflich sein?


----------



## Kai008 (26. Juni 2010)

Zeig mal was du hast oder willst, dann können wir dir sicher weiterhelfen.


----------



## lisali (26. Juni 2010)

Was ich habe ist sehr viel. Für meinen Fall jetzt habe ich das:


```
public void pcStep()
	{
		for(int i = 0; i < 9; i++)
		{
			if(buttonField[i] == -1)
			{
				gui.performClickAction(i); break;
			}
		}
		
	}
```

Das ist die Methode, wo der PC dran ist zu "spielen". Und es ist halt eine sehr dumme Künstliche Intelligenz, weil er hieri mmer nur das nächstfreie Feld nimmt und es belegt.


----------



## Kai008 (26. Juni 2010)

Ich würde es so machen:

Setzt der Spieler im ersten Zug ins mittlere Feld setzt er seine Figur in eine zufällige Ecke. Wenn nicht setzt er in die Mitte.
Danach hohlt er sich aus Konstanten die Startposition und eine Richtung zum testen, geht sie durch, schaut ob sie 2 feindliche Figuren in einer Reihe sind und setzt seine Figur ins freie Feld. Wenn er keins findet setzt ers irgendwo hin.

Die Startpositionen/Richtungen wären dann:


```
0:0 Rechts
0:0 Unten
0:0 Diagional
1:0 Unten
2:0 Unten
2:0 Diagional
0:1 Rechts
0:2 Rechts
```

Wenn ich mich jetzt nicht vertan habe.


----------



## lisali (26. Juni 2010)

Okay. Ich soll das rekursiv programmieren... wie stelle ich das an?


----------



## Kai008 (26. Juni 2010)

Du schreibst eine Methode, die sich immer selbst aufruft, und die Parameter so ändert, dass das nächste Feld getestet wird.


----------



## lisali (26. Juni 2010)

Muss ich da nicht total viele if-Bedinungen verwenden?


----------



## Kai008 (26. Juni 2010)

Nein.
Indem du Konstanten festlegst und die Methodenaufrufsketten in einer Schleife startest.


----------



## lisali (26. Juni 2010)

Könntest du mir bitte ein Beispiel dafür geben?


----------



## Akeshihiro (26. Juni 2010)

Eine Rekursion ist eigentlich nix interessantes, kannst du dir wie eine Schleife vorstellen. Du brauchst halt Daten,  mit denen du arbeitest, eine Abbruchbedingung und eine Rückgabe. Die Inputdaten verarbeitest du halt und wenn die Abbruchbedingung erfüllt ist, dann wird eine entsprechende Rückgabe getätigt, andernfalls wird das Ergebnis eines weiteren Aufrufs der Funktion selbst zurückgegeben. Wenn die Funktion sich selbst aufruft, müssen die Inputdaten natürlich geändert werden, sonst hast du eine Abbruchbedingung, die nie erfüllt ist und somit den Effekt einer Endlosschleife.

Ich habe zwar kein Beispiel für deinen Fall, aber vielleicht hilft es dir beim Verständnis, ist eigentlich ziemlich einfach:

```
public class RekursionTest {
	public static int indexOfChar(String str, char c, int index) {
		if(str.charAt(index) == c) {
			return index;
		} else {
			return indexOfChar(str, c, ++index);
		}
	}

	public static void main(String[] args) {
		System.out.println(indexOfChar("hallo du da, was geht ab?", 'd', 0));
	}
}
```
Ausgabe:


> 6


----------



## timestamp (26. Juni 2010)

Zum Beispiel so 


```
void RekursiveFunktion(int x){
  if( x < 9 )
    RekursiveFunktion(x+1);
}

RekursiveFunktion(0);
```


----------



## Kai008 (26. Juni 2010)

Ich bin zwar schon den ganzen Tag müde, aber versuche es trotzdem. Ist aber rein VHIDT und basiert nicht auf deinen bisherigen Sources.


```
public class ComputerStep
{
	private static final byte DIRECTORY_EAST = 0;
	private static final byte DIRECTORY_SOUTH = 1;
	private static final byte DIRECTORY_DIA_SOUTH_EAST = 2;
	private static final byte DIRECTORY_DIA_SOUTH_WEST = 3;

	private static final Dimension[] DIRECTORY_STEPS = {
		new Dimension(1, 0),
		new Dimension(0, 1),
		new Dimension(1, 1),
		new Dimension(-1, 1);
	};

	private static final Step[] STEPS = {
		new Step(new Point(0, 0), DIRECTORY_EAST),
		new Step(new Point(0, 0), DIRECTORY_SOUTH),
		new Step(new Point(0, 0), DIRECTORY_DIE_SOUTH_EAST),
		new Step(new Point(1, 0), DIRECTORY_SOUTH),
		new Step(new Point(2, 0), DIRECTORY_SOUTH),
		new Step(new Point(0, 1), DIRECTORY_EAST),
		new Step(new Point(0, 2), DIRECTORY_EAST),
	};

	private GameButton[][] buttons;
	private Point foundetFreeField;

	public static void start()
	{
		boolean bool = false;
		for(byte b = 0; b < STEPS.lenght && !bool; b++)
		{
			foundetFreeField = null;
			Step step = STEPS[b];
			bool = computing(step.getPoint(), step.getDirectory(), 1, 0, 0);
		}
	}
	private static boolean computing(Point point, byte dir, byte stepCount, byte foundetFree, byte foundetEnemy)
	{
		boolean b = false;
		if(stepCount != 3)
		{
			GameButton gameButton = buttons[point.x][point.y];
			if(gameButton.getPlayerStone() == null)
			{
				foundetFree++;
				foundetFreeField = point;
			}
			else if(gameButton.getPlayerStone().getPlayer == Stone.HUMAN)
				foundetEnemy++;

			Dimension dirSteps = DIRECTORY_STEPS[dir];
			b = computing(new Point(point.x + dirSteps.width, point.y + dirSteps.height), dir, stepCounter + 1, foundetFree, foundetEnemy);
		}
		else
		{
			if(foundetFree == 1 && foundetEnemy == 2)
				b = true;
		}
		return(b);
	}

	private static class Step
	{
		private Point p;
		private byte dir;

		private Step(Point p, byte dir)
		{
			this.p = p;
			this.dir = dir;
		}
		private Point getPoint()
		{
			return(p);
		}
		private Point getDirectory()
		{
			return(dir);
		}
}
```


----------



## lisali (27. Juni 2010)

Okay, die Rekursion ist mir jetzt klar, aber deine Info war auch ziemlich hilfreich.

Wenn Mitte als erster Zug nicht belegt, dann Mitte belegen. Sonst zufällig belegen.

Hmm.. und jetzt versuche ich mich an den Rest, aber da werde ich mir schon wieder unsicher. Weil... man muss doch für jede Richtung und jeden Fall eine IF-Abfrage machen. Ich verstehe den Sinn der Rekursion irgendwie nicht, weil ich doch einfach eine For-Schleife benutzen kann?


----------



## Kai008 (27. Juni 2010)

Nein, das wäre Iterativ (wiederholend) und nicht rekursiv (selbstbezüglich).
Warum brauchst du ifs? Zwar bezweifel ich, dass das was ich geschrieben habe wirklich so funktioniert (wäre das Erste mal, das eine Klasse von mir auf Anhieb funktioniert), aber ich habe ja auch nur 2 ifs. Eine prüft ob das geprüfte Feld eine Menschlichen oder keinen Spielstein (bzw. auch indirekt ob es ein Computer-Stein) enthält, und eine prüft, ob er das Ende des Spielfeldes und damit der Rekursion erreicht wurde.


----------



## lisali (27. Juni 2010)

Na brauche ich nicht IFs, um zu gucken welches Feld belegt werden soll? Ich muss doch die Strategie des PC-Gegners festlegen.


----------



## Kai008 (27. Juni 2010)

Warum? Du willst doch hoffentlich nicht jede mögliche Kombination einzeln eintragen.
Du musst auf jeden Fall einen Zufallsaspekt einbauen. Sonst wird der Computer zu berechenbar, was es langweilig werden lässt. (Gut, bei Tic Tac Toe ist da nichts zu verlieren, aber allgemein).
Andererseits muss der Rechner natürlich mögliche Kombinationen blocken. Also kannst du durch die Rekursion mögliche 3er-Reihen blocken, und wenn es keine gibt zufällig platzieren, damit bei den gleichen menschlichen Zügen nicht immer das genau gleiche Endergebnis rauskommt.


----------



## lisali (27. Juni 2010)

Nee, "wollen" möchte ich bestimmt nicht, aber ich kann mir gerade einfach nicht ausmalen wie es in Java aussehen würde mal so einen Fall zu definieren, dass er bei der und der Belegung, so und so entscheidet.
Und von deinem (riesigen) Code wurde ich ehrlich gesagt nicht so wirklich schlauer. Tut mir Leid. Das ist nicht böse gemeint. Ich will es ja verstehen, aber es fällt mir so schwer. 
Wenn ich einfach wüsste wie ich das Code-technisch anstelle, dass er z.B. bei 0 dann 2 belegt, usw.

Ich denke da irgendwie nur an die IF-Bedingungen... aber das liegt auch daran, dass ich eben noch eine Anfängerin bin in Java und bisher eher nur PHP kann und selbst das nicht so super...


----------



## Kai008 (27. Juni 2010)

Die 81 Zeilen findest du riesig? (34 für die eigendlichen Methoden). oo"
Beginne bei der start() und gehe einfach zeilenweiße durch. Dann wirst du sicher schnell verstehen, wie es funktioniert. (Oder von mir zumindest geplant war).


----------



## HonniCilest (28. Juni 2010)

Hey mir ist aufgefallen, dass es hier noch Probleme zu geben scheint, was den Spielalgorithmus für die KI angeht.
Es gibt einen "Allgemeinen Spielalgorithmus" (so hat ihn jedenfalls mein früherer Lehrer genannt) der rekursiv arbeitet. Dieser ist mit folgender Anleitung beschrieben:
1*. Bestimme ein Maß für die Qualität der aktuellen Spielstellung aus der Sicht des Spielers
--> In der Regel int als "Bewertungsnote"
2*. Teste für den Spieler, der gerade am Zug ist, alle möglichen Züge. Für jeden dieser Züge teste rekursiv alle möglichen Reaktionen des Gegners, für jeden dieser Reaktionen teste rekursiv alle möglichen Gegnerreaktionen usw.
--> Zusätzlich ist es hier möglich eine Maximalanzahl für die Rekursionsebenen anzugeben, wodurch die KI-Stärke bestimmt werden kann. Es gibt maximal 9 Ebenen, wodurch es auch zu keinen Problemen mit Rekursion kommen sollte.
3*. Wähle als besten Zug denjenigen, der bei entsprechender Reaktion des Gegners im Endeffekt zur größten Verbesserung der eigenen Stellung führt.
--> Der Zug mit der höchsten "Bewertungsnote".




*zitiert aus "Algorithmen kompakt und verständlich" von Markus von Rimscha


----------



## Kai008 (28. Juni 2010)

Ja, aber


```
³ Wir stellen bei dieser Gelegenheit auch fest, dass Tic-Tac-Toe tatsächlich nicht zu
gewinnen ist, wenn der Gegner keinen Fehler begeht.
```

Ein Computer könnte heutzutage leicht die "paar" Möglichkeiten, die Tic Tac Toe hat durchzurechnen, und würde zu den Entschluss kommen, dass er, wenn der Gegner in die Mitte setzt, er niemals in die Mitte einer Kante setzen darf.

Beispiel:


```
---
-x-
---

---
-xo
---

x--
-xo
---

x--
-xo
--o

x-x
-xo
--o
```

Falls er das macht, wird das Spiel sogar noch einseitiger, und er wird nicht mehr schlagbar.
Und ich denke nicht, das irgendjemand CnC lange gespielt hätte, wenn der Gegner vom Beginn an eine Basis gebaut hätte (wenn er in den Missionen keine vorgegebene hätte), die der Spieler mit dem zu ihm zur Verfügung gestellten Geldmitteln nicht durchdringen könnte.

Deshalb bin ich der Meinung, man darf ein Spiel nicht so ohne weiteres durchrechnen lassen, denn wenn man das macht hat der Spieler gegen einen Rechner mit heutzutage min. 12 GFlops nicht den Hauch einer Chance.


----------

