Tic Tac Toe mit etwas verbuggtem Bot

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
classic
User
Beiträge: 23
Registriert: Mittwoch 3. September 2008, 15:37
Wohnort: Erfurt bzw Lauchröden

Hallo,
ich habe auch mal ein Tic Tac Toe-Spiel programmieren wollen, und hab es eben gemacht.
Ob der Code sehr schön und ansehnlich ist, bezweifle ich.
Ich weiß auch, dass "global" eine "böse" :wink: Anweisung ist, das muss mir keiner sagen, ich weiß nur nicht wie ich diese rausbekommen soll.
Ich glaube auch das der Bug an den globals liegt, nur wie ich ihn beheben soll, weiß ich eben nicht.
Das Spiel an sich ist funktionsfähig, bei 2 Spielern funktioniert es auch. (Änderbar recht weit unten bei "playernumber=1")

Und hier ist eben der Code (für konstruktive Kritik zur Verbesserung bin ich dankbar):

Code: Alles auswählen

import Tkinter as tk
class tictactoe_field():
    def __init__(self,zahl,anzahl):
        self.zahl=zahl
        self.but=0
        self.anzahl=int(anzahl**0.5)
    def create(self):
        self.but=tk.Button(root,text='',width=10,height=5)
        self.but.grid(row=(self.zahl-1)/self.anzahl,column=(self.zahl+(self.anzahl-1))%self.anzahl)
    def change(self):
        global activeplayer,playernumber
        if fields[self.zahl].but["text"]=='':
            fields[self.zahl].but.config(text=activeplayer)
            fields[self.zahl].but["state"]='disabled'
            winner(winlist,anzahl)
            if activeplayer==True:
                activeplayer=False
                if playernumber==1:
                    opponent(winlist,opplist)
            else:
                activeplayer=True
def winner(winlist,anzahl):
    finished=0
    all_disabled=1
    for i in winlist:
        if fields[i[0]].but["text"]!='' and fields[i[0]].but["text"]==fields[i[1]].but["text"] and fields[i[0]].but["text"]==fields[i[2]].but["text"]:
            winner=int(fields[i[0]].but["text"])
            tk.Label.config(winlabel,text='Spieler '+str(winner)+' hat gewonnen')
            for k in range(1,anzahl+1):
                if fields[k].but["state"]=='normal':
                    fields[k].but["state"]='disabled'
            finished=1
            break
    for k in range(1,anzahl+1):
        if fields[k].but["state"]=='normal':
            all_disabled=0
            break
    if finished==0 and all_disabled==1:
        tk.Label.config(winlabel,text='Niemand hat gewonnen')
def opponent(winlist,opplist):
    global activeplayer
    print activeplayer
    was_set=0
    for i in winlist:
        for k in range(len(i)):
            l=(k+1)%3
            m=(k+2)%3
            if fields[i[k]].but["text"]!='' and fields[i[k]].but["text"]==fields[i[l]].but["text"] :
                tictactoe_field.change(fields[i[m]])
                was_set=1
                break
    if was_set==0:
        for i in opplist:
            if fields[i].but["text"]=='':
                tictactoe_field.change(fields[i])
                break
root=tk.Tk()
root.title('Tic Tac Toe')
playernumber=1
fields=dict()
anzahl=9
activeplayer=True
winlist=[[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
opplist=[5,1,3,7,9,2,4,6,8]
for i in range(1,anzahl+1):
    fields[i]=tictactoe_field(i,anzahl)
    tictactoe_field.create(fields[i])
    fields[i].but.config(command=lambda arg=fields[i]:tictactoe_field.change(arg))
winlabel=tk.Label(root,text='')
winlabel.grid(row=3,column=0,columnspan=3)
root.mainloop()
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

classic hat geschrieben:Und hier ist eben der Code (für konstruktive Kritik zur Verbesserung bin ich dankbar):
Nur mal ein paar Aspekte für den Anfang:
- Hin und wieder eine eingefügte Leerzeile ist enorm hilfreich.
- Du musst die Logik von der Darstellung trennen, also nicht Darstellungstext der Buttons abfragen
- Du benutzt einen Mix aus objektorientierten und prozedural/imperativen Elementen; das ist keine gute Idee.
- Die Verwendung von global (du benutzt es auch dann, wenn es gar nicht nötig ist), wird sich erledigt haben, wenn du konsequent auf OOP setzt.
- Allgemein kann man sagen, lässt sich global bei Verzicht auf OOP vermeiden, indem Werte eine Funktion als Argument betreten und als Rückgabewert verlassen.
- Für meinen Geschmack arbeitest du zu viel über Indizes.
Benutzeravatar
classic
User
Beiträge: 23
Registriert: Mittwoch 3. September 2008, 15:37
Wohnort: Erfurt bzw Lauchröden

numerix hat geschrieben:Nur mal ein paar Aspekte für den Anfang:
- Hin und wieder eine eingefügte Leerzeile ist enorm hilfreich.
- OK, ich werde versuchen, die Leerzeilen zu berücksichtigen
numerix hat geschrieben:- Du musst die Logik von der Darstellung trennen, also nicht Darstellungstext der Buttons abfragen
- Hättest du auch eventuell eine Idee parat, wie ich sonst prüfen kann, ob das Feld belegt ist oder nicht?
Mir fällt nämlich momentan keine ein :( (Vllt. eine Liste in der die freien Felder aufgelistet sind?)
numerix hat geschrieben:- Du benutzt einen Mix aus objektorientierten und prozedural/imperativen Elementen; das ist keine gute Idee.
- Ja, das meine OOP nicht sehr toll ist weiß ich, benutze/beherrsche die auch erst seit einem sehr kurzen Zeitraum. Aber was sind "prozedural/imperative Elemente"? Etwa Funktionen??
numerix hat geschrieben:- Die Verwendung von global (du benutzt es auch dann, wenn es gar nicht nötig ist), wird sich erledigt haben, wenn du konsequent auf OOP setzt.
- Allgemein kann man sagen, lässt sich global bei Verzicht auf OOP vermeiden, indem Werte eine Funktion als Argument betreten und als Rückgabewert verlassen.
Das versteh ich nun nicht, schließt OOP global aus, oder ein?
numerix hat geschrieben:- Für meinen Geschmack arbeitest du zu viel über Indizes.
-Der Begriff "Indizes" ist mir leider unbekannt...

MfG
classic
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Trennung von Logik und Darstellung:
Du müsstest eine interne Datenstruktur schaffen - Liste oder Dictionary -, die die Zustände der einzelnen Felder speichert. Die Buttons, die nur der Darstellung dienen, entnehmen ihre Beschriftung dann aus dieser Datenstruktur.

Prozedural/imperativ ist ein Programmierparadigma, bei dem mit Funktionen (in manchen Programmiersprachen heißen sie Prozeduren bzw. unterscheidet man zwischen Prozeduren und Funktionen), aber nicht mit Klassen und Objekten gearbeitet wird. Günstig ist es meist, wenn man sich für einen Weg in einem Programm entscheidet.

OOP schließt global weder aus noch ein. Durch die Verwendung von Klassen wird es aber überflüssig.

Indizes ist der Plural von "Index". In der Anweisung

Code: Alles auswählen

liste[5] = "fünf"
ist 5 ein Index der Liste.
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

OOP heißt aber nicht unbedingt, einfach alles in Klassen zu stecken.

http://www.python-forum.de/topic-12791.html
Benutzeravatar
classic
User
Beiträge: 23
Registriert: Mittwoch 3. September 2008, 15:37
Wohnort: Erfurt bzw Lauchröden

So, habe einige Vorschläge berücksichtigt, ich benutze nun keine globals mehr, Logik und Darstellung sind getrennt, ein paar Leerzeilen wurden eingefügt.
Allerdings nutze ich immer noch einen Klassen-Funktionen-Mix und benutze nun noch mehr Indizes.
Auch spinnt meine Funktion die den Gewinner bestimmt...
Habe mir durch prints immer die Werte ausgeben lassen durch die bestimmt wird, ob es einen Gewinner gibt.
Dadurch ist mir aufgefallen, dass das 8te Feld "ignoriert" wird.
Ich sehe keinen Fehler, vielleicht seht ihr ihn ja.

Hier der Code:

Code: Alles auswählen

import Tkinter as tk

class tictactoe_field():
    def __init__(self,zahl,anzahl):
        self.zahl=zahl
        self.but=0
        self.anzahl=int(anzahl**0.5)
        self.free=''
    def create(self):
        self.but=tk.Button(root,width=10,height=5)
        self.but.grid(row=(self.zahl-1)/self.anzahl,column=(self.zahl+(self.anzahl-1))%self.anzahl)
    def change(self):
        if self.free=='':
            self.but.config(text=allvars[1])
            self.but["state"]='disabled'
            if winner(winlist,anzahl):
                allvars[2]=False
            self.free=allvars[1]
            if allvars[1]=='X':
                allvars[1]='O'
                if allvars[0]==1 and allvars[2]==True:
                    opponent(winlist,opplist)
            else:
                allvars[1]='X'
    def opp_can(self):
        allvars[2]=True
        tictactoe_field.change(self)

def winner(winlist,anzahl):
    finished=0
    all_disabled=1
    for i in winlist:
        if fields[i[0]].free!='' and fields[i[0]].free==fields[i[1]].free and fields[i[0]].free==fields[i[2]].free:
            tk.Label.config(winlabel,text='Spieler '+fields[i[0]].free+' hat gewonnen')
            for k in range(1,anzahl+1):
                if fields[k].free=='':
                    fields[k].but["state"]='disabled'
            finished=1
            return True
            break
    for k in range(1,anzahl+1):
        if fields[k].free=='':
            all_disabled=0
            break
    if finished==0 and all_disabled==1:
        tk.Label.config(winlabel,text='Unentschieden')
        return True

def opponent(winlist,opplist):
    allvars[2]=False
    was_set=0
    for i in range(len(winlist)):
        for k in range(len(winlist[i])):
            l=(k+1)%3
            m=(k+2)%3
            if fields[winlist[i][k]].free!='' and fields[winlist[i][m]].free=='' and fields[winlist[i][k]].free==fields[winlist[i][l]].free:
                tictactoe_field.change(fields[winlist[i][m]])
                was_set=1
                break
        if was_set==1:
            break
    if was_set==0:
        for i in opplist:
            if fields[i].free=='':
                tictactoe_field.change(fields[i])
                break

root=tk.Tk()
root.title('Tic Tac Toe')

playernumber=2
fields=dict()
anzahl=9

allvars=dict()
allvars[0]=playernumber
allvars[1]='X'
allvars[2]=False

winlist=[[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
opplist=[5,1,3,7,9,2,4,6,8]

for i in range(1,anzahl+1):
    fields[i]=tictactoe_field(i,anzahl)
    tictactoe_field.create(fields[i])
    fields[i].but.config(command=lambda arg=fields[i]:tictactoe_field.opp_can(arg))

winlabel=tk.Label(root)
winlabel.grid(row=3,column=0,columnspan=3)
root.mainloop()
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Vom Regen in die Traufe...

"allvars" ist noch viel schlimme ;)
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Verzichte bitte auf magic numbers und modulglobalen code (Code, der beim importieren eines Moduls ausgeführt wird). Wenn du Dictionaries numerierst, ist das ein Zeichen dafür, dass du eine Liste nehmen möchtest. Den Code von Zeile 71-86 verstehe ich nicht. Wenn du selbsterklärende Namen benutzt, können Andere den Code besser / schneller verstehen (Was bedeutet i?). Die Variable anzahl in Zeile 73 sagt fast gar nichts aus. Eine Anzahl von was? Gewinnern, Verlierern, Feldern, Spielen? Wenn du iterierst, empfiehlt es sich, statt range() xrange() zu benutzen, weil range eine Liste zurückgibt und xrange einen Generator. Klassennamen werden (wenn man auf PEP8 hört) in CamelCase geschrieben (also TictactoeField). Deine Klassendeklaration sieht etwas seltsam aus, weil sie von nichts erbt (old-style?), aber trotzdem Klammern am Ende vorkommen. Wenn du Python < 3.0 hast, dann kann es Vorteile haben, New-Style-Klassen einzusetzen. Dies kannst du dadurch erreichen, dass du deine Klasse von object erben lässt. Ab Python 3.0 sind diese Standard, sodass Klassen syntaktisch gesehen wieder wie vor 2.5 deklariert werden.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

@derdon: Wenn er Python 3.0 *hätte*, dann könnte er aber nicht xrange() statt range() verwenden ... :wink:
Er hat aber nicht Python 3.0 verwendet, wie man am import von Tkinter sehen kann.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Wenn du Python < 3.0 hast, dann kann es Vorteile haben, New-Style-Klassen einzusetzen.
Ich habe nicht behauptet, dass ich wisse (Konjunktiv lässt grüßen), wie die Python-Version von classic lautet. Ich gebe zu, dass man die letzten Sätze falsch verstehen kann, weil sie nicht eindeutig formuliert wurden.
Benutzeravatar
classic
User
Beiträge: 23
Registriert: Mittwoch 3. September 2008, 15:37
Wohnort: Erfurt bzw Lauchröden

So, habe versucht, das Script weiterhin zu verbessern.
Habe aber eben noch nicht alles umgesetzt, teilweise weil ich es nicht kann, teilweise weil ich keine Idee habe wie.

xrange muss ich mir morgen mal anschauen, genauso wie "new"/"old"-style Klassen (wir hatten Klassen nur ganz kurz im Infounterricht)

Der Code wird morgen weiterverbessert, momentan funktioniert er:

Code: Alles auswählen

# -*- coding: cp1252 -*-
import Tkinter as tk

class TictactoeField():
    def __init__(self,zahl,felderanzahl):
        self.zahl=zahl
        self.but=0
        self.felderanzahl=int(felderanzahl**0.5)
        self.free=''
    def create(self):
        self.but=tk.Button(root,width=10,height=5)
        self.but.grid(row=(self.zahl-1)/self.felderanzahl,column=(self.zahl+(self.felderanzahl-1))%self.felderanzahl)
    def change(self):
        self.but.config(text=allvars[1])
        self.but["state"]='disabled'
        self.free=allvars[1]
        if allvars[1]=='X':
            allvars[1]='O'
            if allvars[0]==1:
                bot(winlist,botlist)
        else:
            allvars[1]='X'
        winner(winlist,felderanzahl)

def winner(winlist,felderanzahl):
    finished=0
    all_disabled=1
    for i in winlist:
        if fields[i[0]].free!='' and fields[i[0]].free==fields[i[1]].free and fields[i[0]].free==fields[i[2]].free:
            winlabel.config(text='Spieler '+fields[i[0]].free+' hat gewonnen')
            for k in range(1,felderanzahl+1):
                if fields[k].free=='':
                    fields[k].but["state"]='disabled'
            finished=1
            break
    for k in range(1,felderanzahl+1):
        if fields[k].free=='':
            all_disabled=0
            break
    if finished==0 and all_disabled==1:
        tk.Label.config(winlabel,text='Unentschieden')

def bot(winlist,botlist):
    was_set=0
    for i in range(len(winlist)):
        for k in range(len(winlist[i])):
            l=(k+1)%3
            m=(k+2)%3
            if fields[winlist[i][k]].free!='' and fields[winlist[i][m]].free=='' and fields[winlist[i][k]].free==fields[winlist[i][l]].free:
                TictactoeField.change(fields[winlist[i][m]])
                was_set=1
                break
        if was_set==1:
            break
    if was_set==0:
        for i in botlist:
            if fields[i].free=='':
                TictactoeField.change(fields[i])
                break

root=tk.Tk()
root.title('Tic Tac Toe')
winlabel=tk.Label(root)#Gibt Sieger aus
winlabel.grid(row=3,column=0,columnspan=3)

players=1
fields=dict()
felderanzahl=9

allvars=[players,'X']
#Spielernummer; Zeichen des aktiven Spielers

winlist=[[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
#Felder die einheitlich sein müssen, damit jmd. gewinnt( X o. O)
botlist=[5,1,3,7,9,2,4,6,8]
#Setzliste für den Bot

for i in range(1,felderanzahl+1):#Erstellung der 9 Buttons
    fields[i]=TictactoeField(i,felderanzahl)
    TictactoeField.create(fields[i])
    fields[i].but.config(command=lambda arg=fields[i]:TictactoeField.change(arg))

root.mainloop()
Antworten