Tic-Tac-Toe KI



  • Hallo,

    Ich hab jetzt vor ein paar Stunden ein Tic-Tac-Toe für 2 Spieler programmiert. Nun würde ich gerne eine KI hinzufügen für einen Spieler. Jetzt hab ich ein paar Fragen. Mein erster Gedanke zu der KI war, wieder ungeheuer viele if-Abfragen zu machen. Wo Ich positioniert habe, ob in der Mitte, wenn nicht, platziere in der Mitte, ob ich irgendwo bereits 2 hab, ob er bereits irgendwo 2 hat usw. Gibts da noch eine andere gescheite Lösung zb. einen Algorithmus oder so? Wenn nicht, wie kann ich das am besten mit if lösen ohne dutzende Abfragen zu haben? Oder geh ich da völlig falsch ran und sollte mir nochmal darüber Gedanken machen?
    Und was könnte man noch an meinem Code ohne KI optimieren?

    Anbei noch mein Code:

    TicTacToe.cpp

    #include <iostream>
    //#include <Konsolenfenster.h>
    #include "extras.h"
    #include <conio.h>
    #include <string>
    #include <vector>
    
    bool zeigeSpielerPosition(char Taste, //das X, bzw. das O zeichnen
    						  bool &Spieler,
    						  std::string &zahlen) {
    	if(Taste < '1' || Taste > '9') return false; //liegt die Taste im Bereich?
    	if(Taste == '1') {
    		 if(zahlen[0] != '1') return false; //wenn es noch nicht besetzt ist..
    		 else { extra::gotoxy(25, 4); zahlen[0] = 'X'; }  } //dann gehe zur position und markiere das feld im string
    	else if(Taste == '2') {
    		if(zahlen[1] != '2') return false; //usw.
    		else { extra::gotoxy(29, 4); zahlen[1] = 'X'; } }
    	else if(Taste == '3') {
    		if(zahlen[2] != '3') return false;
    		else { extra::gotoxy(33, 4); zahlen[2] = 'X'; } }
    	else if(Taste == '4') {
    		if(zahlen[3] != '4') return false;
    		else { extra::gotoxy(25, 3); zahlen[3] = 'X'; } }
    	else if(Taste == '5') {
    		if(zahlen[4] != '5') return false;
    		else { extra::gotoxy(29, 3); zahlen[4] = 'X'; } }
    	else if(Taste == '6') {
    		if(zahlen[5] != '6') return false;
    		else { extra::gotoxy(33, 3); zahlen[5] = 'X'; } }
    	else if(Taste == '7') {
    		if(zahlen[6] != '7') return false;
    		else { extra::gotoxy(25, 2); zahlen[6] = 'X'; } }
    	else if(Taste == '8') {
    		if(zahlen[7] != '8') return false;
    		else { extra::gotoxy(29, 2); zahlen[7] = 'X'; } }
    	else if(Taste == '9') {
    		if(zahlen[8] != '9') return false;
    		else { extra::gotoxy(33, 2); zahlen[8] = 'X'; } }
    
    	if(Spieler) { extra::color_cout(extra::GRUEN, "X"); Spieler = false; } //spieler 1 ist dran oder..
    	else 		{ extra::color_cout(extra::ROT, "O"); Spieler = true; } //spieler 2 und wechsle dann den spieler
    	return true;
    }
    
    bool gewonnen(const std::vector<int> &Feld) {
    	if(  (Feld[0] == 1 && Feld[1] == 1 && Feld[2] == 1) //die entsprechenden reihen überpruefen
    	  || (Feld[0] == 1 && Feld[3] == 1 && Feld[6] == 1) //immer wenn 1 drinsteht war's auch spieler 1
    	  || (Feld[6] == 1 && Feld[7] == 1 && Feld[8] == 1)
    	  || (Feld[2] == 1 && Feld[5] == 1 && Feld[8] == 1)
    	  || (Feld[0] == 1 && Feld[4] == 1 && Feld[8] == 1)
    	  || (Feld[2] == 1 && Feld[4] == 1 && Feld[6] == 1)
    	  || (Feld[1] == 1 && Feld[4] == 1 && Feld[7] == 1)
    	  || (Feld[3] == 1 && Feld[4] == 1 && Feld[5] == 1) ) {
    		extra::color_cout(20, 10, extra::GRUEN, "Spieler 1 hat gewonnen!");
    		return true; //gewonnen
    	}
    	else if(  (Feld[0] == 2 && Feld[1] == 2 && Feld[2] == 2) //alles nochmal für spieler 2
    		   || (Feld[0] == 2 && Feld[3] == 2 && Feld[6] == 2)
    		   || (Feld[6] == 2 && Feld[7] == 2 && Feld[8] == 2)
    		   || (Feld[2] == 2 && Feld[5] == 2 && Feld[8] == 2)
    		   || (Feld[0] == 2 && Feld[4] == 2 && Feld[8] == 2)
    		   || (Feld[2] == 2 && Feld[4] == 2 && Feld[6] == 2)
    		   || (Feld[1] == 2 && Feld[4] == 2 && Feld[7] == 2)
    		   || (Feld[3] == 2 && Feld[4] == 2 && Feld[5] == 2) ) {
    		extra::color_cout(20, 10, extra::ROT, "Spieler 2 hat gewonnen!");
    		return true; //gewonnen
    	}
    	return false; //(noch) nicht gewonnen
    }
    
    int main() {
    	using namespace std;
    	//kons::mache_fenster_gross(); aus Konsolenfenster.h..
    	extra::color(extra::WEISS); //konsolenfarbe auf ein helles weiss aendenr
    
    	while(1) {
    		extra::cls();
    		string zahlen = "123456789"; //verfuegbare zahlen
    		bool Spieler = true; //1 = Spieler 1, 0 = Spieler 2
    		char Taste;
    		vector<int> Feld(9); //zum merken der Spieler pro feld, 1 = Spieler 1, 2 = Spieler 2
    		unsigned int Runden = 0; //bei 9 ist das spiel so oder so vorbei
    		cout << "\n\t\t\tTic-Tac-Toe\n"
    		     << "\t\t\t-----------\n\n"
    		     << "\t\t\t1. Spielen\n"
    		     << "\t\t\t2. Beenden\n\n"
    		     << "\t\t\t3. Infos\n\n"
    		     << "\t\t\tDeine Wahl: ";
    		Taste = getch();
    		switch(Taste) {
    			case '1':
    			extra::cls(); //konsole leeren
    			cout << "\n\t\t\t7 | 8 | 9" //das spielfeld
    				 << "\n\t\t\t4 | 5 | 6"
    				 << "\n\t\t\t1 | 2 | 3\n\n";
    			do {
    				extra::gotoxy(1, 7);
    				cout << "\t\tGebe deine Position an, Spieler ";
    				if(Spieler) cout << "1: ";
    				else    	cout << "2: ";
    				Taste = getch();
    				if((Taste >= '1' || Taste <= '9') && Feld[(Taste - '0')-1] == 0) { //bereich überprüfen
    					if(Spieler) Feld[(Taste - '0')-1] = 1; //Taste - '0' -> Zahl zwischen 1 und 9
    					else        Feld[(Taste - '0')-1] = 2;
    				}
    				if(zeigeSpielerPosition(Taste, Spieler, zahlen)) ++Runden; //wenn feld noch nicht besetzt runden erhöhen
    				if(gewonnen(Feld)) break; //wenn jmd gewonnen hat dann spiel beenen
    			} while( Runden < 9 ); //solange die rundenanzahl korrekt ist
    			extra::gotoxy(30, 10);
    			if(!gewonnen(Feld)) cout << "\n\t\tUnentschieden..\n";
    			cout << "\n\nDr" << extra::ue << "cke eine beliebige Taste um zum Menu zur"
    						 << extra::ue << "ckzukehren...\n";
    			getch();
    			break;
    
    			case '2': return 0; break;
    
    			case '3':
    			extra::cls();
    			cout << "Dieses Spiel ist f" << extra::ue << "r den Ziffernblock deiner "
    			     << "Tastatur ausgelegt.\nDamit spielt es sich leichter.\n\n"
    			     << "\n\nDr" << extra::ue << "cke eine beliebige Taste um zum Menu zur"
    				 << extra::ue << "ckzukehren...\n";
    			getch();
    			break;
    
    			default: ;
    		}
    	}
    }
    

    extras.h (nur ein Auszug mit den benötigten Funktionen)

    #ifndef KONSOLENFENSTER_H_INCLUDED
    #define KONSOLENFENSTER_H_INCLUDED
    
    #include <windows.h>
    
    /*hier sind größtenteils extrafunktionen für die konsole. Die sind zum größten Teil einfach
      aus dem internet abkopiert und mir ist es auch (vorerst) egal wie sie funktionieren ;)*/
    
    namespace extra {
    	const char ae = 132;
        const char oe = 148;
        const char ue = 129;
    
    	enum {
    		SCHWARZ,
    		DUNKELBLAU,
    		DUNKELGRUEN,
    		BLAUGRUEN,
    		DUNKELROT,
    		LILA,
    		OCKER,
    		HELLGRAU,
    		DUNKELGRAU,
    		BLAU,
    		GRUEN,
    		HELLBLAU,
    		ROT,
    		MAGENTA,
    		GELB,
    		WEISS
        };
    
    	void gotoxy(int X, int Y) {
    		HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
    		COORD pos;
    		pos.X = X - 1;
    		pos.Y = Y - 1;
    		SetConsoleCursorPosition(hCon, pos);
    	}
    
    	void color(int FARBE) {
    		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FARBE);
    	}
    
    	void color_cout(int Xpos, int Ypos, const int FARBE, const std::string TEXT) {
    		gotoxy(Xpos, Ypos);
    		color(FARBE);
    		std::cout << TEXT;
    		color(WEISS);
    	}
    
    	void color_cout(const int FARBE, const std::string TEXT) {
    	  color(FARBE);
    	  std::cout << TEXT;
    	  color(WEISS);
    	}
    
    	void cls() {
    		HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
    		COORD pos;
    		DWORD lpNumberOfCharsWritten;
    
    		pos.X = 0;
    		pos.Y = 0;
    
    		FillConsoleOutputCharacter(hCon, ' ', 80*25, pos, &lpNumberOfCharsWritten); //Konsole löschen
    		SetConsoleCursorPosition(hCon, pos);
    	}
    
    }
    
    #endif
    

    Danke schonmal im Voraus 🙂



  • Incocnito schrieb:

    Gibts da noch eine andere gescheite Lösung zb. einen Algorithmus oder so?

    Ja.
    http://de.wikipedia.org/wiki/Minimax-Algorithmus



  • ich mach zufälligerweise gerade genau das glaiche und wollte auch einen computergegener machen.
    hier steht einiges leider kein algorithmus oder so, aber möglichkeiten

    zu deinem quellcode ich würde noch eine abfrage machen, ob das spiel nicht unentschieden ist.

    mfg


  • Mod

    Du kannst auch eine Datenbank aller Züge machen. Dann kannst du entweder die Antwort des Computergegners automatisiert suchen lassen indem du die KI gegen sich selbst trainierst (Dies ist die interessante Methode) oder du kannst die perfekte Strategie von vornherein einprogrammieren (langweilig). Die nötige Datenbank ist sehr klein:
    http://xkcd.com/832/



  • SeppJ schrieb:

    Du kannst auch eine Datenbank aller Züge machen. Dann kannst du entweder die Antwort des Computergegners automatisiert suchen lassen indem du die KI gegen sich selbst trainierst (Dies ist die interessante Methode)

    Wenn ich es richtig verstanden habe, soll man anfangs den computer zufällige züge gegen einen "anderen" computer gegener machen. dabei lernt einer / beide (?) wie man es besser machen kann und macht später keine zufälligen züge mehr.
    Bitte korrigiert mich wenn ich etwas falsch verstanden habe.
    meine frage ist jetzt, wie diese datenbank aufgebaut sein sollte, bzw. diese werte gespeichert werden sollen.


  • Mod

    Gucky schrieb:

    SeppJ schrieb:

    Du kannst auch eine Datenbank aller Züge machen. Dann kannst du entweder die Antwort des Computergegners automatisiert suchen lassen indem du die KI gegen sich selbst trainierst (Dies ist die interessante Methode)

    Wenn ich es richtig verstanden habe, soll man anfangs den computer zufällige züge gegen einen "anderen" computer gegener machen. dabei lernt einer / beide (?) wie man es besser machen kann und macht später keine zufälligen züge mehr.

    Ja, das ist der Plan. Du markierst ob ein fraglicher Zug zu Sieg Niederlage oder Unentschieden führt und speicherst diese Information ab.

    meine frage ist jetzt, wie diese datenbank aufgebaut sein sollte, bzw. diese werte gespeichert werden sollen.

    So viel Programmierkenntnisse solltest du schon haben, dass dir dafür eine Möglichkeit einfällt.



  • Das mit der Datenbank ist leider keine Option für mich, dafür kann ich noch nicht gut genug programmieren (ich hatte noch keine Netzwerkprogrammierung oder etwas in der Richtung). Und ob unentschieden ist wird in Zeile 111 überprüft.


  • Mod

    Incocnito schrieb:

    Das mit der Datenbank ist leider keine Option für mich, dafür kann ich noch nicht gut genug programmieren (ich hatte noch keine Netzwerkprogrammierung oder etwas in der Richtung).

    😮 😕 Was hat denn das miteinander zu tun? Ich dachte eher an einen kleinen vector oder ähnliches.



  • Upps 😃
    Hmm wenn ich Datenbank höre denke ich immer an irgendwas mit Internet, hab das noch nie im Zusammenhang mit nem Array oder so gehört.



  • TTT ist ein Spiel, dass sich für klassiches Backtracking eignet. Schau mal da nach. Ist zwar nicht wirklich KI aber sehr breit anwendbar und gut, um ggf. Rkursion zu lernen.


Anmelden zum Antworten