Tic Tac Toe

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
patchef
User
Beiträge: 3
Registriert: Montag 1. März 2021, 10:34

Montag 1. März 2021, 11:15

Guten Tag!
Ich lerne seit einigen Tagen Python und habe mich an einer Übungsaufgabe zu TicTacToe versucht... Da ich mich selbst testen wollte, habe ich keinerlei Hilfe/Tipps verwendet und bin meiner Meinung nach zu einem zufriedenstellenden Ergebnis gekommen. Natürlich war die Musterlösung (wie ich erwartet habe) wesentlich anspruchsvoller und das Problem wurde bspw. mit Klassen gelöst, was soweit ich weiß für einen guten Programmierstil spricht. (Dafür ist mein script immerhin deutlich kürzer)

Dennoch wollte ich mal meine Lösung von etwas Fortgeschrittenen Usern bewerten lassen... Was haltet ihr von meinem Programmierstil? Ist es so, dass man mich dafür auslachen würde oder ist es gar nicht so schlimm wie ich befürchte? Das Ergebnis an sich gefällt mir sehr gut. Man kann das Programm starten/beenden, kann unendlich oft weiterspielen, und auch die Fehlermeldungen bei unzulässigen Zügen funktionieren einwandfrei....

PS: Wie anspruchsvoll wäre es, eine KI für das Programm zu implementieren? Habe gehört das soll gar nich so schwer sein, aber macht es als blutiger Anfänger überhaupt Sinn, sich da schon reinzudenken oder soll ich erst die Basics allesamt lernen?

Hier mein Code (Durch das Forum ist die Formatierung etwas zerstört)... Ich freue mich über jeden Kommentar dazu :)
(Vllt. könnte ich auch noch einige Funktionen einbauen wie z.B. den Spielstand, aber dafür bin ich aktuell zu faul...)

Code: Alles auswählen

global list1
list1 = [" ", " ", " ", " ", " ", " ", " ", " ", " "]


def spielfeld():
    print(" ", list1[0], " | ", list1[1], " | ", list1[2], "\n ", list1[3], " | ", list1[4], " | ", list1[5], "\n ", list1[6], " | ", list1[7], " | ", list1[8], " ")


def zug_x():
    x = 0
    while x == 0:
        z = int(input("Spieler mit X: Auf welchem Feld machst du deinen ersten Zug? [1-9]"))
        z -= 1
        if list1[z] == " ":
            list1[z] = "X"
            x = 1
        else:
            print("\nHier wurde bereits gesetzt!!!")
    spielfeld()


def zug_o():
    y = 0
    while y == 0:
        s = int(input("Spieler mit O: Auf welchem Feld machst du deinen ersten Zug? [1-9]"))
        s -= 1
        if list1[s] == " ":
            list1[s] = "O"
            y = 1
        else:
            print("\nHier wurde bereits gesetzt!!!")
    spielfeld()


def play():
    print("Das Spielfeld sieht wie folgt aus:\n 1 | 2 | 3 \n 4 | 5 | 6 \n 7 | 8 | 9 ")
    while 1:
        zug_x()
        if ((list1[0] != " ") & (list1[0] == list1[3] == list1[6])) | (
                (list1[0] != " ") & (list1[0] == list1[1] == list1[2])) | (
                (list1[0] != " ") & (list1[0] == list1[4] == list1[8])):
            print("\nDer Sieger ist der Spieler mit: ", list1[0])
            break
        elif ((list1[2] != " ") & (list1[2] == list1[5] == list1[8])) | (
                (list1[2] != " ") & (list1[2] == list1[4] == list1[6])):
            print("\nDer Sieger ist der Spieler mit: ", list1[2])
            break
        elif (list1[6] != " ") & (list1[6] == list1[7] == list1[8]):
            print("\nDer Sieger ist der Spieler mit: ", list1[6])
            break
        elif ((list1[4] != " ") & (list1[4] == list1[1] == list1[7])) | (
                (list1[4] != " ") & (list1[4] == list1[3] == list1[5])):
            print("\nDer Sieger ist der Spieler mit: ", list1[4])
            break
        elif (list1[0] != " ") & (list1[1] != " ") & (list1[2] != " ") & (list1[3] != " ") & (list1[4] != " ") & (
                list1[5] != " ") & (list1[6] != " ") & (list1[7] != " ") & (list1[8] != " "):
            print("\nDas Spiel ging unentschieden aus!")
            break
        zug_o()
        if ((list1[0] != " ") & (list1[0] == list1[3] == list1[6])) | (
                (list1[0] != " ") & (list1[0] == list1[1] == list1[2])) | (
                (list1[0] != " ") & (list1[0] == list1[4] == list1[8])):
            print("\nDer Sieger ist der Spieler mit: ", list1[0])
            break
        elif ((list1[2] != " ") & (list1[2] == list1[5] == list1[8])) | (
                (list1[2] != " ") & (list1[2] == list1[4] == list1[6])):
            print("\nDer Sieger ist der Spieler mit: ", list1[2])
            break
        elif (list1[6] != " ") & (list1[6] == list1[7] == list1[8]):
            print("\nDer Sieger ist der Spieler mit: ", list1[6])
            break
        elif ((list1[4] != " ") & (list1[4] == list1[1] == list1[7])) | (
                (list1[4] != " ") & (list1[4] == list1[3] == list1[5])):
            print("\nDer Sieger ist der Spieler mit: ", list1[4])
            break
        elif (list1[0] != " ") & (list1[1] != " ") & (list1[2] != " ") & (list1[3] != " ") & (list1[4] != " ") & (
                list1[5] != " ") & (list1[6] != " ") & (list1[7] != " ") & (list1[8] != " "):
            print("\nDas Spiel ging unentschieden aus!")
            break


repeat = input("Gebe 1 ein, wenn ihr anfangen wollt, Tic-Tac-Toe zu spielen! :)")
while repeat == "1":
    list1 = [" ", " ", " ", " ", " ", " ", " ", " ", " "]
    play()
    repeat = input("Gebe 1 ein, wenn ihr nochmal spielen wollt.")
else:
    print("Bis zum nächsten Mal! :)")
Benutzeravatar
sparrow
User
Beiträge: 2745
Registriert: Freitag 17. April 2009, 10:28

Montag 1. März 2021, 12:57

Ich fange mal von oben an:

Verwende niemals global. Verwende niemals globale Variablen.

list1 ist ein schlechter Name für etwas. Namen sollten sprechend sein und erklären, was sie bedeuten. Und das Durchnummerieren von Namen ist in der Regel ein Zeichen dafür, dass man eine Datenstruktur verwenden sollte. In deinem Fall ist das Nummer-Suffix aber völlig überflüssig. Der Name sollte eher "spielfeld" heißen.
Und statt einer Liste mit 9 Werten würde sich eine verschachtelte Liste mit 3x3 Werten anbieten. Das kommt dem natürlichen Vorbild (also dem Spielfeld) viel näher und macht die Adressierung im Spiel in meinen Augen schöner.

Funktionsnamen sollten Tätigkeiten beschreiben. Die Funktion "spielfeld()" sollte also eher "zeige_spielfeld()" heißen. Und das Spielfeld, das gezeigt werden soll, sollte diese Funktion als Parameter bekommen.
Verwende doch eine Schleife um von dieser sehr langen Zeichenkette weg zu kommen.
Und wenn nicht: Verwende doch eine f-Zeichenketten um von diesem Zusammenstückeln von weg zu kommen.

zug_x und zug_o sind sich so ähnlich: sie solten eine Funktion sein.
Die Benennung ist hier in den Funktionen wieder unschön.
Du verwendest Zahlen als boolsche Werte. Python kennt True und False dafür. Das kann man sich aber hier komplett sparen: du baust hier kompliziert eine while True schleife nach, die man mit break verlassen könnte, statt ovrher einen Wert zu verwenden, den man in der Schleife ändert - und der nur dazu dient die Schleife zu verlassen.
Was machst du, wenn der Benutzer sich vertippt und einen Buchstaben eingibt? Was machst du, wenn der Spieler einen zu hohen Wert eingibt?-> Fehlerbehandlung einbauen!

play habe ich mir nicht näher angeschaut, denn: Du verwechselst hier logische Operatoren ("and" und "or") mit bitweisen Operatoren ("&" und "|"). Erstere sind korrekt.
Und das sieht anch viel copy&paste aus. Da drängt es sich auf, das mit Schleifen deutlich zu verkürzen.
Edit: Oh, ich sehe gerade, dass da die "Prüfung" tatsächlich komplett kopiert wurde. Dafür sind Funktionen da, um solche Codedoppelungen überflüssig zu machen.

Und grundsätzlich gilt: Funktionen erhalten alles, was sie brauchen, als Parameter und geben ihr Ergebnis mit return zurück. Keine globalen Variablen!
Sirius3
User
Beiträge: 14826
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 1. März 2021, 13:22

Alles was eine Funktion braucht, muß sie über ihre Argumente bekommen. Vergiss gleich wieder, dass es das Schlüsselwort `global` überhaupt gibt, so wie Du es eingesetzt hast, ist es eh unsinnig.
Funktionen sollten nach Tätigkeiten benannt sein, spielfeld -> display_field. Apropos, willst Du Englisch oder Deutsch benutzen? play aber zug, spielfeld aber list1? Ich empfehle immer Englisch Namen, da dann der Lesefluß mit den englischen Keywords besser passt.
Variablennamen sollten aussagekräftig sein, list1 ist das nicht. Was soll überhaupt die 1, ich sehe nirgends eine list2!
zug_x und zug_o sind (fast) identisch, das ist also nur eine Funktion, mit passendem Parameter.
Die Variable `x` wird normalerweise für Kommazahlen benutzt, nicht für Flags. Wobei Flags den Wert True und False annehmen können, nicht 1 oder 0. Das Flag ist aber gar nicht nötig, weil man die Schleife per `break` verlassen kann.
Du prüfst gar nicht, ob die Eingaben korrekt sind.

Ebenso hast Du in `play` zweimal den selben Code kopiert.
& ist eine bitweise Verknüpfung, für logische Bedingungen wird `and` benutzt.
Die erste Zuweisung an list1 wird gar nicht benutzt.
Für eine zweidimensionales Feld wäre eine verschachtelte Liste besser.

Auch die letzten paar Zeilen sollten in eine Funktion wandern, um nicht aus Versehen globale Variablen zu erzeugen.
Ein else-Block bei einem while ohne break macht keinen Sinn.

Code: Alles auswählen

def display_field(field):
    for row in field:
        print(f" {' | '.join(row)}")

def move(field, player):
    while True:
        index = int(input("Spieler mit X: Auf welchem Feld machst du deinen ersten Zug? [1-9]"))
        row, column = divmod(index - 1, 3)
        if field[row][column] == " ":
            break
        print("\nHier wurde bereits gesetzt!!!")
    field[row][column] = player


def get_winner(field):
    for i in range(3):
        if field[i][0] == field[i][1] == field[i][2] != " ":
            return field[i][0]
        if field[0][i] == field[1][i] == field[2][i] != " ":
            return field[0][i]
    if (field[0][0] == field[1][1] == field[2][2] != " " or
        field[0][2] == field[1][1] == field[2][0] != " "):
        return field[1][1]
    if all(cell != " " for row in field for cell in row):
        return "U"
    return None


def play(field):
    print("Das Spielfeld sieht wie folgt aus:\n 1 | 2 | 3 \n 4 | 5 | 6 \n 7 | 8 | 9 ")
    player = "X"
    display_field(field)
    while True:
        move(field, player)
        display_field(field)
        winner = get_winner(field)
        if winner:
            break
        player = "O" if player == "X" else "X"

    if winner == "U":
        print("\nDas Spiel ging unentschieden aus!")
    else:
        print(f"\nDer Sieger ist der Spieler mit: {winner}")


def main():
    repeat = input("Gebe 1 ein, wenn ihr anfangen wollt, Tic-Tac-Toe zu spielen! :)")
    while repeat == "1":
        field = [[" "] * 3 for _ in range(3)]
        play(field)
        repeat = input("Gebe 1 ein, wenn ihr nochmal spielen wollt.")
    print("Bis zum nächsten Mal! :)")
    

if __name__ == "__main__":
    main()
patchef
User
Beiträge: 3
Registriert: Montag 1. März 2021, 10:34

Montag 1. März 2021, 15:35

Okay super, vielen Dank euch!
Für die Benennung der Variablen muss ich mich wohl entschuldigen, denn das war wirklich dumm und da habe ich wohl unnötig eure Zeit verschwendet... Sorry!
Die restlichen Verbesserungen (v.a. Parameter statt Variablen) haben mir sehr weitergeholfen!!! In vielen Tutorials wird das zwar anders praktiziert, aber ich finde es deutlich besser, wenn man schon als Anfänger auf seinen Stil achtet...

Ein paar Fragen zum verbesserten script hätte ich dann dennoch kurz :)

1) Wie ist der Aufbau von field zu verstehen?

Code: Alles auswählen

field = [[" "] * 3 for _ in range(3)]
def display_field(field):
	for row in field:
		print(f" {' | '.join(row)}")
Also ich habe das noch nie so gesehen. Das Ergebnis ist mir klar, aber was bedeutet der unterstrich in Zeile 1? Und sind row/cell Schlüsselwörter? Was bedeutet das f in Zeile 4? Als Antwort würde mir ein Schlagwort reichen nachdem ich googeln kann, um den Aufbau solcher mehrdimensionalen Listen (???) zu verstehen.

2) Wenn ich in einer print-funktion in geschweiften Klammern schreibe {}, bedeutet das dann folgendes...? Und was bedeutet hier wieder das f?

print(f"Der Sieger ist der Spieler mit {winner}")
ist das selbe wie: print("Der Sieger ist der Spieler mit", winner)
Benutzeravatar
sparrow
User
Beiträge: 2745
Registriert: Freitag 17. April 2009, 10:28

Montag 1. März 2021, 16:04

Der Unterstrich bedeutet in Python laut Konvention "der Wert der an diesen Namen gebunden wurde, ist nicht relevant.
range gibt eine Ganzzahl zurück, die ist aber nicht wichtig - um das zu zeigen wird er an _ gebunden.

row und cell sind keine Schlüsselwörter - es sind gut benannte Variablen.

Eine mehrdimensionale Liste ist eine Liste mit Listen.

Code: Alles auswählen

[
    [1, 2, 3],
    [4, 5, 6],
]

Das f vor einer Zeichenkette kannst du hier nachlesen. Das sorgt dafür, dass du in der Zeichenkette den Wert eines Namens einfügen kannst, indem du den Namen in geschweifte Klammern schreibst.
marichcel
User
Beiträge: 3
Registriert: Mittwoch 27. Januar 2021, 11:13

Freitag 30. April 2021, 18:44

patchef hat geschrieben:
Montag 1. März 2021, 11:15
Guten Tag!
Ich lerne seit einigen Tagen Python
Wer's glaubt wird selig... ich lerne seit 2,3 Jahren python und habe nich kein anständiges Programm auf die Beine gebracht :o :roll:
Antworten