TicTacToe

Hallo,
ich bin ein Neueinsteiger in Java und nach dem Lesen von 2 Büchern habe ich es geschafft, mit Anleitungen aus einem der Bücher, und viel Improvisation ein TicTacToe Spiel zu programmieren. Das Spiel funktioniert, aber die künstliche Intelligenz ist erstaunlich dumm. Die Züge des Computers basieren auf dem Bruteforce-Algorithmus und einer Bewertungsfunktion, eigentlich sollte der Computer in der Lage sein gegen jeden beliebigen Menschen unentschieden zu spielen, doch das tut er nicht. :confused: Weiß jemand, wie ich ihn dazu bringen kann?

Der Quellcode liegt im Anhang
 

Anhänge

  • quellcode.txt
    4,7 KB · Aufrufe: 168

DosCoder

Erfahrenes Mitglied
Hi,
seit ner halben Stunde versuche ich nun schon, deinen Code zu verstehen. Ergebnislos. Und zwar aus folgenden Gründen:
1. gesamter Code ist unformatiert( gut, das habe ich mit Netbeans wieder hingebogen)
2. aussagelose Methoden und Variablen (z.B.: "k", "anzuege")
3. keine Kommentare außer denen von Netbeans

zu 1): Wenn man deine Datei mit den Editor öffnet, wird man erst mal von unformatierten Code erschlagen. Also muss man die txt-Datei umbennen und in eine IDE einfügen. Warum stellst du nicht gleich die *.java-Datei zu Verfügung?
zu 2) Wie schon in den Beispielen: Was soll game.k darstellen oder was macht value()? Irgendendeinen Wert zurückgeben? So wie ich das verstanden habe, prüft sie, ob jemand gewonnen hat. Ist das richtig oder habe ich deinen Code falsch interpretiert?
zu 3) Wenn du schon aussagelose Methoden hast, dann verwende doch bitte Kommentare.

Also, wenn du deinen Code nochmal überarbeitest, wird man dir auch bereitwilliger helfen.

Ciao
DosCoder
 

DosCoder

Erfahrenes Mitglied
Hi,
irgendwie ist es entwedern zu heiß, zu spät oder ich bin einfach zu doof, aber selbst nach 2.5 Stunden habe ich das Problem nicht gepackt :(. Ich wüde es ja morgen nochmal versuchen, aber da fahre ich 2 Wochen in Urlaub (zwar mit Notebook, aber ohne Netz). Sorry.
Ich muss aber dazu sagen, dass dein Quellcode ziehmlich vermurkst ist:
Beipsiel:
Java:
int wert=0; [...]
wert=value(); //das geht doch kürzer!
[...]
if(wert!=0){return wert;} 
wert=0; //ist das notwendig, nach der Abfrage?
Zudem erschwert die fehlende OOP das ganze. Aber wie gesagt, es muss auch irgendwie an mir liegen. Tut mir echt leicht.
*Sache an den nächsten weitergeb*

Ciao
DosCoder
 
Hallo,

also ich denke, es geht hauptsächlich um diese mysteriöse Funktion namens x:
Java:
	public int x(int tiefe) {
		int wert = 0;
		int wertneu = 0;
		int anzzuege = 0;
		wert = value();
		if (tiefe == 0) {
			return wert;
		}
		if (wert != 0) {
			return wert;
		}
		wert = 0;
		getzuege();
		for (int i = 0; i < 9; i++) {
			anzzuege += zuege[i];
		}
		if (anzzuege == 0) {
			return 0;
		}
		for (int n = 0; n < 9; n++) {
			if (zuege[n] == 1) {
				buttons.get(n).setText("X");
				wertneu = o(tiefe - 1);
				if (wertneu >= wert) {
					wert = wertneu;
					k = n;
				}
				buttons.get(n).setText(" ");
			}
		}
		return wert;
	}
(Einrückungen sind von mir bzw. Eclipse - ansonsten kann man den Code ja nicht vernünftig lesen. Gewöhn dir am besten möglichst schnell an, deinen Code einzurücken.)

Meine Anmerkungen/Tipps hierzu (manche Sachen wurden schon erwähnt, aber ich will sie trotzdem nochmal nennen):
  • Deklariere lokale Variablen dort, wo du sie brauchst. Die Variable wertneu deklarierst du beispielsweise ganz am Anfang, und man hat keine Ahnung a) welchen Sinn die Variable erfüllt und b) warum sie auf 0 gesetzt wird. Wenn die Variable dann ziemlich zum Schluss der Funktion hin benutzt wird, muss man mit dem Blick erst wieder ganz nach oben wandern, um rauszufinden, wie sie denn jetzt initialisiert wurde.
  • Ergebnisse einer Funktion "hintenrum", also über das Setzen von Membervariablen, zurückzugeben, ist auch erst mal verwirrend. Das kann in bestimmten Fällen schon Sinn ergeben, aber dann nennt man die Methode nicht getzuege, sondern eher updatezuege o.ä. (noch besser: updateMoves, ein Gemisch aus verschiedenen Sprachen bei der Benennung lenkt nur ab.)
  • Womit wir schon bei der Benennung wären: die Benennung einer Funktion/Variable macht idealerweise Kommentare zu ihrem Verwendungszweck überflüssig. Namen wie x, o, k oder value sind daher tabu! Eine Ausnahme stellen höchstens Laufvariablen in Schleifen dar, aber nur wenn aus dem Kontext unmittelbar klar ist, wozu sie verwendet wird.
  • Die Funktionen x und o sind bis auf 2 Zeilen absolut identisch. Code, der an mehreren Stellen steht, ist schlecht, da man bei einer Änderung an einer Stelle auch die andere Stelle ändern muss (und wehe man vergisst das mal...). Überleg dir mal, ob du die beiden Funktionen nicht zusammenfassen könntest.
  • Trenne die grafische Oberfläche (View) von den Daten (Model) deines Programms! Die ständige Aktualisierung von zuege aus den Werten der Buttons ist durch die Brust ins Auge. Und ganz nebenbei auch der Grund, warum dein Algorithmus nicht funktioniert. Du setzt nämlich nach einem ausprobierten Zug und dem rekursiven Aufruf den jeweiligen Button wieder zurück, aber das Array zuege ist immer noch auf dem alten Stand. Mein Vorschlag: ersetze das Array zuege durch
Java:
int spielfeld[9];
Dieses Array ist dann dein "Modell" des Spielfeldes. Eine 0 im Array steht für ein freies Feld, eine 1 für ein "X" und eine -1 für ein "O". Verwende dieses Array und nur dieses Array für alle Abfragen, die den Status des Spielfeldes betreffen (also z.B. auch zur Ermittlung, ob jemand gewonnen hat). Setze die Textwerte der Buttons nur dann, wenn tatsächlich ein Zug erfolgt, den der Benutzer auch mitbekommen soll. Wenn du dich daran hältst, sollte ein a) viel kürzerer b) leichter verständlicher und wartbarer und c) vor allem funktionierender Quellcode rauskommen.
Und jetzt viel Erfolg bei der Umsetzung :)

Grüße, Matthias
 
Hallo,

das sieht doch schon mal viel besser aus! So lässt sich der Quellcode gleich besser verstehen. Noch ein paar Anmerkungen zum Programmierstil:
  • Das hier:
Java:
int wert=0;
wert=bewerten();
ist unschön, da die Initialisierung mit 0 überhaupt keinen Sinn ergibt. Kürzer und klarer ist
Java:
int wert=bewerten();
  • Mehrere Anweisungen in einer Zeile tragen nicht zur Lesbarkeit des Quellcodes bei. Zeilenumbrüche kosten nichts, ebenso wie Leerzeichen hie und da.
  • Deine Art der Einrückung ist etwas unkonventionell. Benutzt du eine IDE wie Eclipse oder Netbeans? Diese können dir die Formatierungsarbeit größtenteils abnehmen. In Eclipse z.B. mit Strg+Umschalt+F.
Unter Berücksichtigung dieser Punkte könnte deine KI-Funktion so aussehen:
Java:
public int computer() {
	int wert = bewerten();
	if (wert != 0) return wert;
	if (zählefreieFelder() == 0) return wert;
	int wertneu = 0;
	for (int i = 0; i < 9; i++) {
		if (spielfeld[i] == 0) {
			spielfeld[i] = 1;
			wertneu = anwender();
			if (wertneu >= wert) {
				errechneterZug = i;
				wert = wertneu;
				if (wert == 1) {
					spielfeld[i] = 0;
					// Es gibt nur -1,0,1-> 1 ist perfekt->sofort abbrechen
					// und Rechenzeit sparen
					return wert;
				}
			}
			spielfeld[i] = 0;
		}
	}
	return 0;
}
Der Fehler ist nun folgender: überleg dir mal, was passiert, wenn sich der Computer in einer auswegslosen Situation befindet. anwender() gibt also bei jedem Schleifendurchlauf eine -1 zurück (egal wo der Computer sein X setzt, er verliert zwangsläufig in der Situation). Dann wird
  1. errechneterZug nie gesetzt
  2. am Ende eine 0 zurückgegeben, obwohl eigentlich -1 richtig wäre (es soll ja der bestmögliche Ausgang ermittelt werden, und der ist hier nun mal leider -1)
Beide Fehler könntest du dadurch beheben, dass du wert am Anfang auf -1 initialisierst und am Schluss wert zurückgibst statt 0 (in anwender() natürlich entsprechend!).

Dann ist mir grade noch ein Fehler aufgefallen. Angenommen der Computer kann bestenfalls ein Unentschieden erzielen, und zwar indem er auf Position 8 setzt. Dann wird errechneterZug auf 8 gesetzt und probiert dann noch aus, was passiert, wenn er auf Position 9 setzt. Angenommen er würde dann verlieren, wenn er das macht. Dann kann es vorkommen, dass beim Abarbeiten aller möglichen Spielausgänge errechneterZug zwischendrin auf einen ganz anderen Wert gesetzt wird. Dadurch ergibt sich am Schluss ein möglicherweise falscher Zug. Du darfst also errechneterZug nur setzen, wenn der Computer den Zug ermittelt hat, den er tatsächlich als nächstes ausführen soll.

Grüße, Matthias
 
Ich könnte natürlich noch zahlefreiefelder in bestimmefelder umbenennen,
und versuchen errechneterZug nicht immer zu setzen. Aber ich glaube, dass würde keinen Unterschied machen. zahlefreiefelder tut ihre aufgabe und im Bezug auf den Wert von errechneterZug denke ich, ist die Hauptsache doch, dass er ganz am Ende korrekt gesetzt wird. Mein Problem liegt irgendwo im Algorithmus. Das Programm muss kein Musterbeispiel an Eleganz sein, aber es sollte seine Arbeit tun. Ich glaube nicht, dass diese Verbesserungen den Fehler zeigen würden. Weiß jemand, was an der Theorie falsch ist?
 

Neue Beiträge