Tic Tac Toe, wer gewinnt

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
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Ich habe gerade ein Tic Tac Toe programmiert ( Konsole , Spieler gegen Spieler ) , aber meine whowins funktion hat wahrscheinlich ein Fehler drin, die Funktion erkennt nicht wenn jemand gewinnt.

Code: Alles auswählen

#0=nothing on the field
#1= X on the field
#4= O on the field

def whowins(board):
    b=board
    winningpositions= map(sum,[[b[0],b[1],b[2]],[b[3],b[4],b[5]],
                               [b[6],b[7],b[8]],[b[0],b[3],b[6]],
                               [b[1],b[4],b[7]],[b[2],b[5],b[8]],
                               [b[0],b[4],b[8]],[b[2],b[4],b[6]]])
    
    if 12 in winningpositions:
        return 4
    elif 3 in winningpositions:
        return 1
    else:
        return 0

def whosnext(now):
    return 4 if now==1 else 1

def newboard(board,position,turn):
    board=board[::]
    board[position-1]=turn
    return board

def display(board):
    counter=1
    for x in board:
        if x==1:
            print(" X","|" if counter%3 else "\n",end="")
        elif x==4:
            print(" O","|" if counter%3 else "\n",end="")
        else:
            print("",counter,"|" if counter%3 else "\n",end="")
        counter +=1


def main():
    board=[0]*9
    turn=4
    while True:
        display(board)
        while True:
            field = int(input("Welches Feld wollen Sie besetzen? "))
            if board[field-1]==0:
                break
            else:
                print("Dieses Feld ist schon besetzt")
        turn = whosnext(turn)
        board = newboard(board,field,turn)

        if whowins(board)==4:
            print("Spieler 'O' gewinnt")
            break
        elif whowins(board)==1:
            print("Spieler 'X' gewinnt")
            break


if __name__=="__main__":
    main()
    input()
        
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Was genau funktioniert denn nicht? Was gibst du ein, was kommt als Ergebnis und was erwartest du?

Als erstes solltest du deinen Code mal ein wenig aufräumen und die doppelten Codeteile entfernen. Auzßerdem kann man mit Slicing das testen vereinfachen. Auch die ganzen magischen Zahlen (1, 3, 4, 12) solltest du durch Konstanten ersetzen. Wenn du statt 1 und 4 die Werte 1 und -1 nimmst, dann wird das Problem symmetrisch und ist etwas leichter zu lösen.

367 Bytes:

Code: Alles auswählen

C,b,p,m="O X",[0]*9,1,0
while 1:
 for i in range(3):print"".join(C[j+1]for j in b[i*3:][:3])
 w=[x for x in map(sum,[b[:3],b[3:6],b[6:9],b[::3],b[1::3],b[2::3],[b[0],b[4],b[8]],[b[2],b[4],b[6]]])if x**2>8]
 if w:print"%s wins!"%C[w[0]/3+1];break
 elif m==9:print"draw!";break
 i=raw_input("%s:"%C[p+1])
 if i.isdigit():i=int(i)
 if 0<=i<9and not b[i]:b[i]=p;p=-p;m+=1
Das Leben ist wie ein Tennisball.
anogayales
User
Beiträge: 456
Registriert: Mittwoch 15. April 2009, 14:11

@EyDu: und deine Variante soll jetzt genau was "darstellen"?. Besonder leserfreundlich ist sie ja gerade nicht und PEP8 konform auch nicht.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

anogayales hat geschrieben:und deine Variante soll jetzt genau was "darstellen"?
Eine Herausforderung.
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@fail: um einen counter in einer for-Schleife mitzählen zu lassen gibt es enumerate:

Code: Alles auswählen

def display(board):
    print(' {} | {} | {}\n'*3).format(*(
        {1:'X', 4:'O'}.get(x, str(counter))
        for counter, x in enumerate(board,1)
    ))
Aber wo soll der Fehler sein?

@EyDu: Du benutzt Python 2.x und außerdem 86 Bytes zu viel.
Im übrigen ist X=2 und O=0 die einfachste Art, X und O zu codieren :D

Code: Alles auswählen

C="O X"
b=[1]*9
p=2
while 1in b:
 try:i=input(C[p]+':');(b[i]-1or b)[i]=p;p=2-p
 except:1
 k=b[:3],b[3:6],b[6:],b[::3],b[1::3],b[2::3],b[::4],b[2:7:2]
 for i in k[:3]:print"|".join(C[j]for j in i)
 w=map(sum,k)
 if 0in w or 6in w:print"%s wins!"%C[2*(6in w)];break
else:print"draw!"
Edit: jetzt mit 282 Bytes.
Zuletzt geändert von Sirius3 am Samstag 23. März 2013, 22:42, insgesamt 3-mal geändert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Eigentlich wollte ich ja kein except, da Strg+D dann nicht mehr funktioniert. Aber so bekomme ich es auf 310:

Code: Alles auswählen

C,b,p,m="O X",[0]*9,1,9
while m:
 for i in 0,3,6:print"".join(C[j+1]for j in b[i:i+3])
 w=[x for x in map(sum,[b[:3],b[3:6],b[6:9],b[::3],b[1::3],b[2::3],[b[0],b[4],b[8]],[b[2],b[4],b[6]]])if x*x>8]
 if w:print"%s wins!"%C[w[0]/3+1];break
 try:i=input(C[p+1]+":");b[i]=p;p=-p;m-=1
 except:1
else:
 print"draw!"
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@EyDu: und Du kannst die X vom Gegner überschreiben ;-)
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Er erkennt nicht wenn jemand gewinnt
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@fail: dann läßt Du Dir am besten mal winningpositions ausgeben und prüfst von Hand, ob die Zahlen stimmen.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

@Sirius3 Danke jetzt funktioniert es.
Verbesserungsvorschläge?

Code: Alles auswählen

import sys

Nothing=0
X=1
O=4

def whowins(board):
    b=board
    winningpos = (b[:3],b[3:6],b[6:9],b[:7:3],b[1:8:3],b[2:9:3],b[:9:4],b[2:7:2])
    winningpos = list(map(sum,winningpos))

    return O if (3*O) in winningpos else (X if (3*X) in winningpos else Nothing)
    


def whosnext(now):
    return O if now==X else X


def newboard(board,position,turn):
    board=board[:]
    board[position-1]=turn
    return board

def display(board):
    counter=1
    for x in board:
        if x==X:
            print(" X","|" if counter%3 else "\n",end="")
        elif x==O:
            print(" O","|" if counter%3 else "\n",end="")
        else:
            print("",counter,"|" if counter%3 else "\n",end="")
        counter+=1


def main():
    board=[Nothing]*9
    turn=O
    for i in range(9,0,-1):
        display(board)
        while True:
            try:
                field = int(input("Welches Feld wollen Sie besetzen? "))
            except ValueError:
                print("Zahl eingeben")
                continue
            if board[field-1]==Nothing:
                break
            else:
                print("Dieses Feld ist schon besetzt")
        turn = whosnext(turn)
        board = newboard(board,field,turn)

        if whowins(board)==O:
            print("Spieler 'O' gewinnt")
            input()
            sys.exit()
        elif whowins(board)==X:
            print("Spieler 'X' gewinnt")
            input()
            sys.exit()

    print("Unentschieden")
    input()
    sys.exit()
    
if __name__=="__main__":
    main()
    input()
        
BlackJack

Verschachtelte bedingte Ausdrücke sind nicht so leicht zu lesen. Da hätte ich wahrscheinlich ``if``/``elif``/``else`` gewählt. Und das `b` als Abkürzung für `board` hätte ich mir auch gespart. Das bringt dem Leser IMHO nichts.

`newboard()` ist zwar hübsch funktional umgesetzt, aber es macht keinen wirklichen Sinn hier die Datenstruktur zu kopieren. Das könnte man durch eine `put_symbol()` ersetzen. Die dann allerdings so simpel ist, dass man sich fragen muss, ob man sie braucht.

Den `counter` in der `display()`-Funktion kann man mit `enumerate()` erzeugen. Das `x` auch für `O` stehen kann ist ein bisschen verwirrend. Man könnte es zum Beispiel `symbol` nennen. Die `print()`-Argumente wiederholen sich fast identisch. Das könnte man heraus ziehen und in den Zweigen nur den Wert bestimmen der sich verändert.

In der `main()` ist `turn` IMHO ein falscher Name. Daran ist ja kein Spielzug gebunden sondern ein Symbol, beziehungsweise der numerische Wert, der das Symbol repräsentiert.

Da `i` in der Schleife nicht verwendet wird, ist es nicht nötig, dass diese Schleife rückwärts „zählt”. Ein einfaches ``range(9)`` dürfte für die meisten Leser leichter zu verstehen sein. Selbst wenn man rückwärts von 9 bis 1 zählen will, fände ich ``reversed(range(1, 10))`` leichter zu lesen als ``range(9, 0, -1)``, wo zumindest ich immer erst überlegen muss von wo bis wo das nun genau geht. Noch besser wäre es die 9 gar nicht hart zu kodieren, sondern die Länge vom `board` zu verwenden. Damit hat man eine magische Zahl weniger im Quelltext.

Von ``continue`` bin ich kein grosser Fan, weil das ein wenig wie GOTO ist. Es wird von irgendwo mitten in der Schleife an den Anfang gesprungen und dieser Programmfluss spiegelt sich nicht in der Quelltextstruktur wieder. Man kann auch schlechter nachträglich Code am Ende des Schleifenkörpers einfügen, der grundsätzlich vor dem nächsten Durchlauf ausgeführt werden soll, wenn weiter oben ein ``continue`` sozusagen die Abkürzung zum Schleifenanfang nimmt. In konkreten Fall könnte man zum Beispiel einen ``else``-Zweig zur Ausnahmebehandlung hinzufügen.

Statt an mehreren Stellen im Quelltext eins von der Eingabe der Feldnummer abzuziehen, sollte man das gleich bei der Eingabe erledigen. Wenn man interene Werte nicht gleich nach der Benutzereingabe und kurz vor der Ausgabe umrechnet, bekommt man bei komplexeren Programmen schnell das Problem, dass man nicht mehr so genau weiss was wann verwendet wird und man sich intern in Umrechnungen verheddert.

*Vor* dem Zug das Spielersymbol zu wechseln ist verwirrend, denn das entspricht nicht der Vorgehensreihenfolge bei einem „echten” Spiel. Da legt man am Anfang fest wer anfängt, der macht einen Zug, und *dann* wechselt der Spieler.

`who_wins()` wird zweimal mit dem gleichen `board` als Argument aufgerufen. Das ergibt beide male das gleiche Ergebnis, ist also unnötiger Aufwand. Die beiden Zweige sind auch nahezu identisch. Die ganzen leeren `input()`-Aufrufe sollten aus dem Programm verschwinden. So ein unnötiges Bestätigen müssen nervt spätestens nach einer Weile ziemlich.

`sys.exit()` und damit auch das `sys`-Modul kann man sich mit ``return``-Anweisungen sparen.

Die Schreibweise von `Nothing` und das (nicht) setzen von Leerzeichen folgen nicht dem Style Guide for Python Code.

Ungetestet:

Code: Alles auswählen

NOTHING = 0
X = 1
O = 4


def who_wins(board):
    winningpos = list(
        map(
            sum,
            [
                board[:3], board[3:6], board[6:9], board[:7:3],
                board[1:8:3], board[2:9:3], board[:9:4], board[2:7:2]
            ]
        )
    )
    if 3 * O in winningpos:
        return O
    elif 3 * X in winningpos:
        return X
    else:
        return NOTHING


def whos_next(player_symbol):
    return O if player_symbol == X else X


def put_symbol(board, position, symbol):
    board[position] = symbol


def display(board):
    for counter, symbol in enumerate(board, 1):
        if symbol == X:
            value = ' X'
        elif symbol == O:
            value = ' O'
        else:
            value = ' ' + str(counter)
        print(value, '|' if counter % 3 else '\n', end='')


def main():
    board = [NOTHING] * 9
    symbol = X
    for _ in range(len(board)):
        display(board)
        while True:
            try:
                field = int(input('Welches Feld wollen Sie besetzen? ')) - 1
            except ValueError:
                print('Zahl eingeben')
            else:
                if board[field] == NOTHING:
                    break
                else:
                    print('Dieses Feld ist schon besetzt')
        put_symbol(board, field, symbol)
        winner = who_wins(board)
        if winner == O:
            print("Spieler 'O' gewinnt")
            return
        elif winner == X:
            print("Spieler 'X' gewinnt")
            return
        symbol = whos_next(symbol)

    print('Unentschieden')

    
if __name__ == '__main__':
    main()
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Ja dein Programm ist irgendwie klarer.
Denkst du das Haskell mich zu sehr funktionalisiert hat? Ist der funktionale stil in python nicht gut?
BlackJack

@fail: Davon abgesehen, dass Python eine deutlich mehr objektorientierte Programmiersprache ist, also Objekte und veränderliche Zustände unterstützt, gibt es Sprachkonstrukte und Module in der Standardbibliothek, die auch funktionale Programmierung ermöglichen. Da man also die Wahl hat, muss man schauen ob das jeweilige Mittel sinnvoll ist. Und in diesem Fall möchte man ja einen Zustand verändern. Das Kopieren des Board-Wertes macht keinen Sinn. In einer rein funktionalen Programmiersprache kann man auch darauf hoffen, dass das der Compiler erkennt und wegoptimiert.
Antworten