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?
-
ich mach zufälligerweise gerade genau das glaiche und wollte auch einen computergegener machen.
hier steht einiges leider kein algorithmus oder so, aber möglichkeitenzu deinem quellcode ich würde noch eine abfrage machen, ob das spiel nicht unentschieden ist.
mfg
-
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.
-
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.
-
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.