Am einfachsten: Die Zufallsmethode
Wahrscheinlich hast du dir unter künstlicher Intelligenz etwas anderes vorgestellt, aber die einfachste Methode, wie das Programm seinen Zug machen kann, ist es, ein zufälliges Kästchen auszuwählen, das noch frei ist, und dort das Symbol hineinzusetzen.
Anschließend muss nur noch ausgewertet werden, ob das Spiel gewonnen ist oder ob der Spieler wieder dran ist.
Wie kann man das umsetzen?
Wir brauchen dazu vor allem eine neue Methode für unsere Spielverwaltung. Nennen wir sie zufallsZug(). Hier wird zunächst ermittelt, welche Felder frei sind. Aus diesen wird dann per Zufallsfunktion eines ausgewählt, und das Symbol wird dort platziert. Wir benötigen für unser Programm also auch die Bibliothek random(). Danach prüft die Funktion mit den vorhandenen Methoden, ob der Computer gewonnen hat. Das Objekt Spielverwaltung muss also gar nicht verändert werden, sondern bekommt nur eine Methode hinzu. Der Aufruf der Methoden in der Mausklick-Abfrage verändert sich ein wenig, da jetzt ja nicht mehr zwei Spieler, sondern ein Spieler und ein Computer beteiligt sind.
Die Methode »zufallsZug()«
Wie wählt das Programm nun zufällig ein leeres Feld aus? Am besten ist es, du erstellst eine Liste mit dem Namen leer, in der alle Positionen der leeren Felder aufgelistet sind. Aus dieser Liste kannst du ein zufälliges Element auswählen. Du musst jedes Element der Liste spielstand einmal durchgehen und für jede 0, die du darin findest, die Position der 0 an die Liste leer anhängen.
Erstellung der Liste leer:
leer = []
for x in range(0,9):
if self.spielstand[x] == 0:
leer.append(x)
Die Liste leer enthält nun alle Kästchen, in denen noch kein Symbol ist. Aus dieser Liste wird zufällig ein Element ausgewählt. Für die Auswahl muss das Programm wissen, wie viele Elemente die Liste leer besitzt. Das geht mit len(leer) (len steht für length, also Länge).
anzahl = len(leer)
zufall = randint(0,anzahl-1)
zelle_nr = leer[zufall]
return zelle_nr
Das war jetzt die lange Version der Funktion. Natürlich kann man sie auch weiter vereinfachen:
Zum Beispiel so:
zelle_nr = leer[randint(0,anzahl-1)]
return zelle_nr
Und von da aus geht es natürlich noch einfacher, alles in einer Zeile:
return leer[randint(0,anzahl-1)]
Und es geht noch einfacher, wenn man eine Funktion verwendet, die im Modul random schon enthalten ist und die eine zufällige Zahl aus einer Liste auswählt:
Programmieren bedeutet auch Vereinfachen – je einfacher der Code, desto effizienter das Programm – solange es noch verständlich bleibt.
[ ! ] Achtung
Um die randint()- oder choice()-Funktion zu nutzen, muss am Anfang des gesamten Programms natürlich wie immer die Zeile from random import * eingefügt werden.
In zelle_nr steht also am Schluss die Nummer der Zelle (eine von 0 bis 8), in die der Zug des Computers gesetzt werden soll. Diese Nummer gibt die Funktion zurück.
Unsere ganze Zufallsauswahlfunktion sieht jetzt so aus; sie kommt in die Klassendefinition der Spielverwaltung:
def zufallsZug(self):
leer = []
for x in range(0,9):
if self.spielstand[x] == 0:
leer.append(x)
return choice(leer)
Anschließend soll das Symbol auch dorthin gesetzt werden – aber unsere Methode setzen erwartet eine x,y-Koordinate der Zelle. Die Zellennummer muss also in eine x,y-Koordinate umgerechnet werden, bevor wir setzen aufrufen können. Wie machen wir das? 0 muss in 0,0 gewandelt werden, 1 in 0,1 – 2 in 0,2 – 3 in 1,1 usw. …
Die Formel ist einfach:
y = nr // 3
x = nr % 3
Die Reihe (y-Position) ist das Ganzzahlergebnis der Nummer geteilt durch 3, die x-Position der Rest dieser Division. Dann passt es.
Dafür schreiben wir noch eine kleine zusätzliche Methode zur Spielverwaltung hinzu:
def nrSetzen(self,pos,spieler_nr):
y = pos // 3
x = pos % 3
self.setzen(x,y,spieler_nr)
Jetzt haben wir sowohl eine Methode für das Setzen eines Symbols auf x,y-Position als auch auf eine Nummernposition. Damit sind wir für alles gerüstet.
Anschließend muss nur noch die mausKlick()-Funktion auf die neue Situation angepasst werden. Dann sollte es bereits funktionieren:
def mausKlick(ereignis):
# geklickte Zelle ermitteln:
zellenPos = feld.toLocationInGrid(ereignis.getX(),ereignis.getY())
zx = zellenPos.getX()
zy = zellenPos.getY()
# Wenn Zelle leer, dann weiter:
if spiel.zellePruefen(zx,zy) == 0:
spiel.setzen(zx,zy,spiel.aktiver_spieler)
if spiel.gewonnen(spiel.aktiver_spieler):
msgDlg("Spieler "+str(spiel.aktiver_spieler)+" hat gewonnen!")
elif (spiel.brettVoll()):
msgDlg("Das Spiel ist beendet: Unentschieden!")
else:
spiel.naechsterSpieler()
spiel.nrSetzen(spiel.zufallsZug(),spiel.aktiver_spieler)
if spiel.gewonnen(spiel.aktiver_spieler):
msgDlg("Der Computer hat gewonnen!")
elif (spiel.brettVoll()):
msgDlg("Das Spiel ist beendet: Unentschieden!")
else:
spiel.naechsterSpieler()
Das Spiel geht jetzt davon aus, dass der menschliche Spieler immer Spieler 1 ist (Kreis) und das Spiel beginnt, während der Computer immer Spieler 2 (Kreuz) und danach dran ist. Nach jedem Mausklick wird zuerst der Spielerzug gemacht und geprüft, ob das Spiel beendet ist. Wenn nicht, wird automatisch der Computerzug ausgeführt, und danach wird ebenfalls überprüft, ob er das Spiel gewonnen hat oder das Spiel zu Ende ist. Wenn nicht, dann wird der aktive Spieler wieder auf 1 gesetzt, und der Spieler ist wieder dran. Beim nächsten Mausklick des Spielers geht es dann weiter.
Puristen könnten einwenden, dass der letzte Code weiter vereinfacht werden könnte. Schließlich wird die Abfrage, ob der Spieler gewonnen hat oder das Spiel zu Ende ist, zweimal nacheinander in genau derselben Weise gemacht. Daraus könnte man natürlich auch eine Funktion machen, die dann einfach zwei Mal aufgerufen wird. Ja, könnte man. Du darfst das gerne machen, es ist eine gute Übung. Es gibt beim Programmieren in der Regel viele Wege zum Ziel; und der kürzeste ist oft – aber nicht immer – der beste. Wir belassen das Programm aber jetzt erst einmal so, denn es funktioniert ja gut und ist klar zu verstehen.
Spiele mal ein paar Spiele gegen den Computer. Dir wird auffallen, dass der Computer nur die »Intelligenz« eines zweijährigen Kindes besitzt. Er kann gerade mal erkennen, welche Felder frei sind, und setzt sein Zeichen dann einfach irgendwo hin. Gegen einen Spieler, der auch nur ein bisschen aufpasst oder halbwegs überlegt spielt, wird er nicht gewinnen – außer durch reinen Zufall. Diesen Gegner kann man nicht wirklich ernst nehmen.
Wir wollen den Gegenspieler jetzt eine Stufe klüger machen, damit man auch richtig gegen ihn spielen kann.