Seite 1 von 1

Tic Tac Toe mit etwas verbuggtem Bot

Verfasst: Sonntag 11. Januar 2009, 17:54
von classic
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()

Re: Tic Tac Toe mit etwas verbuggtem Bot

Verfasst: Sonntag 11. Januar 2009, 19:42
von numerix
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.

Re: Tic Tac Toe mit etwas verbuggtem Bot

Verfasst: Sonntag 11. Januar 2009, 20:06
von classic
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

Verfasst: Sonntag 11. Januar 2009, 20:33
von numerix
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.

Verfasst: Sonntag 11. Januar 2009, 20:46
von audax
OOP heißt aber nicht unbedingt, einfach alles in Klassen zu stecken.

http://www.python-forum.de/topic-12791.html

Verfasst: Montag 12. Januar 2009, 22:20
von classic
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()

Verfasst: Montag 12. Januar 2009, 22:51
von audax
Vom Regen in die Traufe...

"allvars" ist noch viel schlimme ;)

Verfasst: Montag 12. Januar 2009, 23:19
von derdon
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.

Verfasst: Dienstag 13. Januar 2009, 07:51
von numerix
@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.

Verfasst: Dienstag 13. Januar 2009, 19:54
von derdon
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.

Verfasst: Dienstag 13. Januar 2009, 22:37
von classic
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()