TKinter Tic Tac Toe

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
Sconine
User
Beiträge: 49
Registriert: Montag 1. Juni 2009, 11:00

Hallo zusammen,

diesmal mussten wir ein Spiel programmieren mit Tkinter. Es scheint zu funktionieren, aber ich glaube man kann bestimmt wieder einiges "vereinfacht" bzw. "besser" scheiben.

Code: Alles auswählen

import Tkinter

from random import randint

#stores the current player 
global player   

player = 'x'

def who_wins(board):

    """
    Determines winner of a supplied tictactoe board.
    Expects a 9 element list as representation for the board
    with "x" and "o" representing the players.
    Returns "x" when x has won and "o" when o has won.
    Returns nothing when no one has won
    """ 

    # the 3 horizontal 3 vertical and 2 diagonal winning possibilities

    rows = [(0,1,2), (3,4,5), (6,7,8), (0,3,6), (1,4,7), (2,5,8), 
            (0,4,8), (2,4,6),]

    for a, b, c in rows:
        if board[a] + board[b] + board[c] == 'xxx':
            return 'x'
        elif board[a] + board[b] + board[c] == 'ooo':
            return 'o'
    #return

def put(board, symbol, position):

    """
    Places a symbol on a tictactoe board.
    Expects a 9 element list representing a board, a symbol
    and the position where to place the symbol in the list.
    If the list contains a "." where the symbol should go 
    it is placed there and a 0 returned.
    If the list doesn't contain a "." where the symbol should go
    the function returns 1
    """

    show(board)
    if board[position] != ".":
        return 1
    if board[position] == ".":
        board[position] = symbol
        next_round()
    return 0

def show(board):

    """shows the board and status messages"""

    for i in range(9):
        if board[i] == 'o':
            ttt_button[i].config(image=ocell)
        elif board[i] == 'x':
            ttt_button[i].config(image=xcell)



    if (board.count(".") == 0) and (who_wins(board) != ("x" or "o")):
        label1.config(text="It's a Draw!")
        game_over()

    if who_wins(board) == ("x"):
        label1.config(text="X wins!")
        game_over()

    if who_wins(board) == ("o"):
        label1.config(text="O wins!")
        game_over()

def computer_play():

    """place an 'o' on a random free space"""

    while board.count(".") > 0:
        RandomPosition = randint(0,8)
        #try to put o on random position
        if not put(board, "o", RandomPosition):    
            #The position was free, break the while loop    
            break    

def next_round():
    
    """show board and change player"""

    show(board)
    global player
    if player == 'o':
        player = 'x'
    elif player == 'x':
        player = 'o'
        computer_play() 

def game_over():

    """disables all buttons"""

    for b in ttt_button:
        b.config(state = "disabled")
        
root=Tkinter.Tk()

#supplied .png images have been converted to gif
bcell = Tkinter.PhotoImage(file='bcell.gif')
ocell = Tkinter.PhotoImage(file='ocell.gif')
xcell = Tkinter.PhotoImage(file='xcell.gif')
board = [".",".",".",".",".",".",".",".","."]

#create list with 9 button widgets
ttt_button = []
for i in range(9):
    b = Tkinter.Button(image=bcell, command=lambda i=i: put(board, player, i))
    # the lambda construct is necessary or we end up passing the result of put
    ttt_button.append(b)

# arrange widgets on 3x3 grid
for row in range(3):
    for column in range(3):
        ttt_button[row*3+column].grid(row=row, column=column)        

label1 = Tkinter.Label(text="playing Tic Tac Toe")
label1.grid(row=3, column=0, columnspan = 3)

root.mainloop()
Es wäre nett, wenn ihr mir die Gründe für die Verbesserungsvorschläge erklären könntet.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hihi, du kriegst am Ende bestimmt eine Eins für deinen Kurs, weil du soviel Eigenleistung erbracht (also Threads erstellt) hast. :)

Hast du das eigentlich mal getestet? Ich glaube kaum, dass es die gewünschten Ergebnisse liefern wird.

Code: Alles auswählen

    rows = [(0,1,2), (3,4,5), (6,7,8), (0,3,6), (1,4,7), (2,5,8),
            (0,4,8), (2,4,6),]

    for a, b, c in rows:
        if board[a] + board[b] + board[c] == 'xxx':
            return 'x'
        elif board[a] + board[b] + board[c] == 'ooo':
            return 'o' 
BlackJack

@Sconine: Die Funktionsaufrufe, die wie Sprünge benutzt werden, sind verwirrend. `put()` ruft `next_round()` auf, das `computer_play()` aufruft, welches wiederum `put()` aufruft. Das muss erst wieder `next_round()` aufrufen wo dann entschieden wird, dass es keine Endlosrekursion wird. Das ist etwas zuviel des Guten und schon nahe an "Spaghetti-Code". Insbesondere mit der ``while``-Schleife in `computer_play()` habe ich total den Überblick verloren, ob das überhaupt so funktioniert.

``global`` ist nicht nur Böse™, sondern auf Modulebene auch noch komplett sinnfrei.

Auf Modulebene sollten sowieso keine Objekte liegen, die im Code neu gebunden werden. Wenn man keine Klassen verwenden möchte, muss man solche Objekte halt als Argumente herumreichen.

Die Leerzeile zwischen Funktionssignatur und DocString würde ich weglassen, dafür aber zwei zwischen jeweils zwei Funktionsdefinitionen lassen. Das mach deutlicher was zusammengehört und was nicht.

Die vielen literalen 'x' und 'o' und auch '.' sollte man duch Konstanten ersetzen. So fallen Fehler durch einen `NameError` auf und man kann die interne Repräsentation einfacher verändern.

Bei `who_wins()` würde ich ein explizites ``return None`` ans Ende setzen. Das macht die Absicht deutlicher.

`put()` missbraucht 0 und 1 als Wahrheitswerte. Python hat nun mittlerweile seit mehreren Jahren `True` und `False` für so etwas.

Die zweite Bedingung in Zeile 65 macht nicht das was Du denkst. Der Ausdruck ``("x" or "o")`` ergibt *immer* ``'x'``.

Diese drei ``if``-Abfragen kann man aber sowieso etwas geschickter anordnen, so dass man nur zwei benötigt und die zweite Bedingung von der ersten Abfrage überflüssig wird.

In Zeile 110 ist `player` in der ``lambda``-Funktion potentiell gefährlich. Der Name wird erst zur Laufzeit der Funktion im Modul nachgeschlagen und sollte zu *dem* Zeitpunkt besser den richtigen Wert haben. Sonst kann es passieren, dass der menschliche Spieler das Symbol des Computerspielers setzt. Da sollte man zur Sicherheit besser eine Konstante verwenden.

Die Schleife lässt sich durch eine "list comprehension" ersetzen und `ttt_button` sollte besser `ttt_buttons` heissen.

Man könnte den Quelltext etwas PEP 8 konformer gestalten. Leerzeichen nach Kommata, Schreibweise von Namen, usw.

@snafu: Der zitierte Quelltext sieht IMHO auf den ersten Blick okay aus!? Vielleicht fehlt irgendwo ein DocString in dem genauer erklärt wird, wie `board` aufgebaut ist.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ist er auch. Ich habe total verdrängt, dass `a, b, c` natürlich Unpacking macht. In dem Moment dachte ich, die Konstruktion bindet die ersten 3 Tuple. :oops:
Sconine
User
Beiträge: 49
Registriert: Montag 1. Juni 2009, 11:00

Also ich gewinne immer bei dem Spiel. Dann ist es doch richtig programmiert. :D

Sobald jemand gewonnen hat und ich das Spiel beenden möchte, werde ich immer gefragt, ob ich das Spiel wirklich beenden möchte, das es noch läuft (Wenn ich Python Shell schließen möchte).

Ansonsten läuft es.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Naja, wenn die Intelligenz deines Gegners ein stinknormales random ist, wundert das ja nicht wirklich. ;)
Antworten