Warum ist Objekt befüllt?

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.
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

Hallo,

ich bin Python Anfänger und würde gerne ein Kartenspiel programmieren.

Ich bin gerade erst mal dabei, dass Deck zu erstellen und den Spielern die ersten 5 Karten aus dem Deck zu geben.

Leider habe ich folgendes Problem. Ich habe zwei Objekte der Klasse Spieler. Dies beinhaltet die Attribute, Name und Kartenhand. Spieler 1 zieht 5 Karten aus dem Stapel, das funktioniert alles soweit. Spieler 2 hat aber leider auch immer die Karten von Spieler 1, woran kann das liegen? Wieso ist die Liste von SPieler 2 nicht Leer?

Hier der Code

Code: Alles auswählen

import random
Farben = ["Kreuz", "Pik", "Herz", "Karo"]
Werte = ["7", "8", "9", "10", "Bube", "Dame", "Koenig", "Ass"]
Skat = [(sFarbe, sWert) for sWert in Werte for sFarbe in Farben]

class Spieler:
    name = ""
    handKarten = []

    def ziehe_Karte(self, karte):
        self.handKarten.append(karte)

    def lege_Karte(self, nummer):
        if not self.handKarten:
            return()
        self.handKarten.pop(nummer)
        return()

    def zeige_Handkarten(self):
        for i in range(0, len(self.handKarten)):
            print(self.handKarten[i])
        return()

def draw_card(lKarten):
    if not lKarten:
        print("Keine Karte mehr im Deck")
        return ()
    karte = lKarten.pop(0)
    print("gezogene Karte %s %s" % karte)
    return karte

def start_game(spieler, deck):
    for i in range(5):
        spieler.ziehe_Karte(draw_card(deck))
    return ()

print ("Das Spiel beginnt mische das Deck")
random.shuffle(Skat)
print ("Deck gemischt kann losgehen")

p1 = Spieler()
p2 = Spieler()

p1.name = input("Spieler 1, wie lautet dein Name? ")
p2.name = input("Spieler 2, wie lautet Dein Name? ")

start_game(p1, Skat)
print("Spieler 1 Deine Karten")
p1.zeige_Handkarten()
print("Spieler 2 Deine karten")
p2.zeige_Handkarten()
Folgende Ausgabe:
as Spiel beginnt mische das Deck
Deck gemischt kann losgehen
Spieler 1, wie lautet dein Name? Markus
Spieler 2, wie lautet Dein Name? Steffi
gezogene Karte Herz 7
gezogene Karte Karo Koenig
gezogene Karte Karo 9
gezogene Karte Pik 7
gezogene Karte Pik 8
Spieler 1 Deine Karten
('Herz', '7')
('Karo', 'Koenig')
('Karo', '9')
('Pik', '7')
('Pik', '8')
Spieler 2 Deine karten
('Herz', '7')
('Karo', 'Koenig')
('Karo', '9')
('Pik', '7')
('Pik', '8')
Was übersehe ich? Wieso hat der Spieler 2, dieselben Karten wie Spieler 1?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@betzi1985: Du hast `name` und `handKarten` auf der *Klasse* definiert. Was dort definiert wird, existiert *einmal*, eben auf der Klasse. Beide Attribute haben dort nichts zu suchen und Dir fehlt eine `__init__()`-Methode die das *Objekt* initialisiert und dort für jedes Exemplar eine eigene Liste anlegt. Und der Name sollte dort auch gleich übergeben und gesetzt werden und nicht erst eine leere Zeichenkette als Dummywert erhalten.

Weitere Anmerkungen:

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

``return`` ist keine Funktion und sollte auch nicht so geschrieben werden. Insbesondere bedeutet ``return ()`` oder auch ``return()`` das da ein Wert zurückgegeben wird, nämlich ein leeres Tupel:

Code: Alles auswählen

In [568]: def f():
     ...:     return()
     ...: 

In [569]: f()
Out[569]: ()

In [570]: type(f())
Out[570]: tuple
Und auch ohne das Tupel würde ein ”nacktes” ``return`` am Ende einer Funktion oder Methode keinen Sinn machen, denn da ist die Funktion/Methode auch ohne ``return`` am Ende.

Was soll das `l` bei `lKarten` bedeuten? Falls das der Versuch ist Typnamen in Namen zu packen: das macht man nicht. Irgendwann reicht einem eine Liste nicht mehr aus, und man schreibt eine `Karten`-Klasse und schon hat man lauter falsche, irreführende Namen im Programm, oder muss eben die Namen überall anpassen, was unnötig Arbeit macht. Gleiches bei `sWert` und `sFarbe`. Gewöhn Dir so einen Unsinn gar nicht erst an.

Deutsch/Englisch gemischt ist keine gute Idee. Mal heisst es `ziehe_Karte()` und mal `draw_card()`. Wonach wird denn hier entscheiden wann es Englisch und wann es Deutsch benannt wird? Das verwirrt nur.

Das wenn keine Karten vorhanden sind entweder die Aktion einfach ignoriert wird, oder ein leeres Tupel statt einer Karte geliefert wird, ist nicht sinnvoll.

``for i in range(0, len(sequence)):`` ist in Python ein „anti pattern“. Man kann direkt über die Elemente von Sequenzen iterieren, ohne den unnötigen Umweg über einen Laufindex.

Man sollte keine Benutzerein- und Ausgaben mit Programmlogik vermischen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@betzi1985: `ziehe_Karte()` zieht überhaupt gar keine Karte, das fügt eine zur Hand hinzu.

In `draw_card()` würde man `pop()` ohne Argument benutzen und vom Ende ziehen, statt vom Anfang, was ineffizient ist, weil dort alle verbleibenden Karten um eine Position nach vorne gerückt werden müssen.

Zeichenkettenformatierung mit ``%`` hat man im Grunde in Python 2 schon nicht mehr gemacht. Es gibt Zeichenkettenformatierung mit der `format()`-Methode und in f-Zeichenkettenliterale.

Aus dem anomymen Kartentupel könnte man auch ein `collections.namedtuple` machen und damit dann den Code lesbarer.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import random
from collections import namedtuple

SUITS = ["Kreuz", "Pik", "Herz", "Karo"]
VALUES = ["7", "8", "9", "10", "Bube", "Dame", "Koenig", "Ass"]

Card = namedtuple("Card", "suite value")


class Player:
    def __init__(self, name, cards=None):
        if cards is None:
            cards = []
        self.name = name
        self.cards = cards

    def add_card(self, card):
        self.cards.append(card)

    def get_card(self, index):
        return self.cards.pop(index)

    def show_cards(self):
        for card in self.cards:
            print(card)


def draw_card(deck):
    card = deck.pop()
    print(f"gezogene Karte {card.suite} {card.value}")
    return card


def start_game(player, deck):
    for _ in range(5):
        player.add_card(draw_card(deck))


def main():
    deck = [Card(suite, value) for value in VALUES for suite in SUITS]
    print("Das Spiel beginnt mische das Deck")
    random.shuffle(deck)
    print("Deck gemischt kann losgehen")

    player_a = Player(input("Spieler 1, wie lautet dein Name? "))
    player_b = Player(input("Spieler 2, wie lautet Dein Name? "))

    start_game(player_a, deck)
    print("Spieler 1 Deine Karten")
    player_a.show_cards()
    print("Spieler 2 Deine karten")
    player_b.show_cards()


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

Danke Euch erstmal für Eure Antworten, ich muss mir das alles Schritt für Schritt durchlesen, was ihr da so geschrieben habt.

namedtuple kenne ich jetzt noch nicht. Aber wenn ich Mau, Mau, Programmieren möchte, wir vergleiche ich dann, ob ich die Karte auf die entsprechende Karte legen kann? ALso Entweder SUITS = SUITS oder VALUES = VALUES ? geht das mit Nametuples?

Hier mein angepasster Code

Code: Alles auswählen

import random

Farben = ["Kreuz", "Pik", "Herz", "Karo"]
Werte = ["7", "8", "9", "10", "Bube", "Dame", "Koenig", "Ass"]
Skat = [(sFarbe, sWert) for sWert in Werte for sFarbe in Farben]


class Spieler:

    def __init__(self):
        self.name = None
        self.handKarten = []

    def ziehe_Karte(self, karte):
        self.handKarten.append(karte)

    def lege_Karte(self, nummer):
        if not self.handKarten:
            return ()
        karte = self.handKarten.pop(nummer)
        return karte

    def zeige_Handkarten(self):
        for i in range(0, len(self.handKarten)):
            print(self.handKarten[i])
        return ()

#Wenn keine Karten mehr zum Ziehen vorhanden sind, sollen die Karten auf dem Spielfeld gemischtt und als neues Deck genutzt werden. Ausnahme ist die letzt Karte auf dem Spielfeld, die bleibt im Spiel 
def neue_Spiefeldkarten(lKarten, spielfeldKarten):
    karte = spielfeldKarten.pop(len(spielfeldKarten)-1)
    lKarten = spielfeldKarten
    random.shuffle(lKarten)
    spielfeldKarten.clear()
    spielfeldKarten.append(karte)


def draw_card(lKarten, spielfeldKarten):
    if not lKarten:
        print("Keine Karte mehr im Deck")
        neue_Spiefeldkarten(lKarten, spielfeldKarten)
    karte = lKarten.pop(0)
    print("gezogene Karte %s %s" % karte)
    return karte


def start_game(spieler, deck, spielfeldkarten):
    for i in range(5):
        spieler.ziehe_Karte(draw_card(deck, spielfeldkarten))
    return ()


spielfeldkarten = []
print("Das Spiel beginnt mische das Deck")
random.shuffle(Skat)
print("Deck gemischt kann losgehen")

p1 = Spieler()
p2 = Spieler()

p1.name = input("Spieler 1, wie lautet dein Name? ")
p2.name = input("Spieler 2, wie lautet Dein Name? ")

start_game(p1, Skat, spielfeldkarten)
print("Spieler 1 Deine Karten")
p1.zeige_Handkarten()
spielfeldkarten.append(p1.lege_Karte(0))
spielfeldkarten.append(p1.lege_Karte(0))
neue_Spiefeldkarten(Skat, spielfeldkarten)
print("Zeige Spielfeldkarte")
for i in range(0, len(spielfeldkarten)):
    print(spielfeldkarten[i])
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`neue_Spiefeldkarten()` funktioniert so nicht. Python kopiert nie von sich aus Werte bei einer einfachen Zuweisung (oder bei Übergabe als Argument an eine Funktion oder Methode). In der Regel verändert man Listen nicht. Also insbesondere kein `clear()`. Das macht nur ganz selten Sinn. Wenn man mit einer leeren Liste starten möchte, dann macht man normalerweise genau das: man erstellt eine neue leere Liste. Wenn Funktionen oder Methoden Listen verändern, sollte man das deutlich dokumentieren, denn das ist unüblich und überraschend für den Leser.

Ich würde hier eine Klasse für den Zustand des Spiels schreiben. Oder zumindest für den „Tisch“ wo das Kartendeck und der aufgedeckte Stapel den Zustand ausmachen.

In `neue_Spielfeldkarten()` braucht der `pop()`-Aufruf übrigens kein Argument. Da wird per Default das letzte Element entfernt.

Edit: Wenn es eine Regel gibt nach der Karten zusammenpassen, macht es vielleicht Sinn die Karte nicht nur von Tupel auf `collections.namedtuple()` umzustellen, sondern gleiche eine Klasse zu schreiben die eine `matches()`-Methode hat, die sagt ob die Karten passen oder nicht. So dass man beispielsweise so etwas schreiben könnte:

Code: Alles auswählen

    if not card.matches(table.top_card):
        print(f"{card} passt nicht auf {table.top_card}")
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

__blackjack__ hat geschrieben: Dienstag 29. November 2022, 12:52 mit einer leeren Liste starten möchte, dann macht man normalerweise genau das: man erstellt eine neue leere Liste. Wenn Funktionen oder Methoden Listen verändern, sollte man das deutlich dokumentieren, denn das ist unüblich und überraschend für den Leser.

Ich will nicht mit einer leeren Liste starten. Die Liste ist voll. Ich möchte alle Elemente außer das Letzte. Wieder zurück ins Deck packen und durschmischen. Also wenn beim MAUMAU keine Karten zum Ziehen mehr da sind, wird ja auch der Spielstabel durchgemischt, außer die Letzte Zahl. Daher muss ich die Liste doch mit .clear leeren, funktioniert auch eigentlich soweit.

PS: Mit der Klasse Karte, habe ich auch erst gedacht, aber irgendwie bekomme ich das nicht wirklich hin :(.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@betzi1985: Doch du willst mit einer leeren Liste starten, sonst würdest Du ja nicht `clear()` aufrufen, denn das leert ja die Liste. Und dann packst Du da wieder eine Karte rein. Genau so gut könnte man auch eine neue Liste mit genau dieser einen Karte erstellen.

Und das funktioniert in Deiner Funktion gerade *nicht*. Denn die Karten sind dann einfach weg. Insbesondere sind die nicht im Kartendeck, denn das Argument `lKarten` wird in der Funktion überhaupt nicht verwendet. Der Wert wird ignoriert und es wird zu einem Synonym von `spielfeldKarten` gemacht. Test:

Code: Alles auswählen

In [622]: deck = []

In [623]: spielfeldkarten = [1, 2, 3, 4, 5]

In [624]: neue_Spiefeldkarten(deck, spielfeldkarten)

In [625]: deck
Out[625]: []

In [626]: spielfeldkarten
Out[626]: [5]
`deck` sollte danach nicht leer sein. Wenn Du schon Listen veränderst, musst Du das auch konsequent bei allen machen. Besser, sauberer, weniger fehleranfällig wäre hier aber der funktionale Weg, also neue Werte erstellen, statt welche zu verändern. Dann braucht man `lKarten` auch gar nicht übergeben, denn das ist an der Stelle ja auf jeden Fall eine leere Liste.

Beispiel:

Code: Alles auswählen

In [627]: def neue_spiefeldkarten(spielfeldkarten):
     ...:     karte = spielfeldkarten[-1]
     ...:     karten = spielfeldkarten[:-1]
     ...:     random.shuffle(karten)
     ...:     return karten, [karte]
     ...: 

In [628]: spielfeldkarten = [1, 2, 3, 4, 5]

In [629]: neue_spiefeldkarten(spielfeldkarten)
Out[629]: ([2, 4, 1, 3], [5])
Gibt das neue verdeckte Kartendeck und den neuen aufgedeckten Kartenstapel zurück. Verändert ausserhalb der Funktion keinen Zustand → keine komischen Überraschungen.

Funktions- und Methodennamen beschreiben üblicherweise die Tätigkeit, die sie ausführen, damit der Leser weiss was die Funktion macht, und damit man die von eher passiven Werten leichter unterscheiden kann. `neue_spielfeldkarten` wäre beispielsweise ein guter Name für neue Spielfeldkarten (wenn es denn auch alte gibt).
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

@__blackjack__ ich muss mir das nochmal in Ruhe ansehen, ich verstehe immer noch nicht ganz das Problem. was ich habe wenn ich .clear() aufrufe. Mein Code tut zumindest das was es soll.
Doch du willst mit einer leeren Liste starten

Nein, wieso will ich mit einer leeren Liste starten? Die Liste ist voll, ich will diese Liste in eine andere Liste schieben und sie danach leeren, wie denn bitte Sonst, außer mit clear?


Nochmal zur Erklärung.

Ich habe einen Stapel "Skat" und einmal "Spielfeldkarten". Der Spieler zieht immer vom "Skat" und legt Karten auf den "Spielfeldkarten". Ist der "Skat" leer, wird die oberste Karte des "Spielfeldkarten" mit Pop entfernt und zwischengespeichert. Anschließend werden die restlichen Spielfeldkarten in den "Skat" verschoben und durchgemischt. Also Skat = Spielekarten --> schiebt die Spielfeldkarten in den Skat zurück. Und Spielfeldkarten.Clear(), leert den Spielfeldstapel. Und mit Spielfeldkarten.append(karte). Lege ich die gemerkte Karte, wieder zurück auf das Spielfeld.

Wahrscheinlich etwas umständlich programmiert, aber funktionieren tut es.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und mit

Code: Alles auswählen

spielfeldkarten = [karte]
erledigst du gleich beides auf einmal. In Python ist es eher ungewoehnlich, Listen und andere dynamische Datenstrukturen grossartig zu "recyceln". Ja, sowas wie die oberste Karte vom Stapel nehem ist mit einem pop gut erledigt. Aber erst Liste loeschen, und dann einfach eine Karte anhaengen - macht man nicht. Sondern siehe oben. Sich so zu denken anzugewoehnen verschont einen vor schwer zu debuggenden Fehlern, bei denen irgendwelche Datenstrukturen unklaren Zustand haben. Es ist einfach wohl-definiert.
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

Ich bekomme aber bei karte = spielfeldkarten[-1] einen Fehler, was soll das überhaupt sein -1?

Wenn ich

Code: Alles auswählen

Spielfeldkarten = [karte]
mache , tut sich bei mir garnichts, es bleiben genau alle Karten im Array wie vorher auch, da wird nichts neu angelegt.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na man muss das schon zuweisen, also zurueckgeben aus der Funktion, statt die Liste in-place zu veraendern. Weil eben genau das ueberraschend ist. Das hat __blackjack__ ja schon vorgemacht.

Und liste[-1] gibt einem das letzte Element einer Liste (die mindestens ein Element haben muss). Das ist Python Grundlage.
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

Ok, ich muss aber genau 2 Listen verändern in einer Funktion? Ich gebe im Return 2 Listen zurück und wie genau soll ich dann diese dann entsprechenden Listen zuweisen?

Ah habe es gefunden, danke. Ok, muss mich an solche Symtax wohl erstmal gewöhnen.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@betzi1985: Eine Ausnahme kann man bei ``spielfeldkarten[-1]`` nur bekommen wenn die Liste leer ist. Dann hätte man aber auch eine Ausnahme bei `pop()` bekommen. In beiden Fällen einen `IndexError`:

Code: Alles auswählen

In [1]: spielfeldkarten = []

In [2]: spielfeldkarten[-1]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In [2], line 1
----> 1 spielfeldkarten[-1]

IndexError: list index out of range

In [3]: spielfeldkarten.pop()
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In [3], line 1
----> 1 spielfeldkarten.pop()

IndexError: pop from empty list
Zuweisung an an mehrere Namen geht mit ”tuple unpacking”, wobei diese Bezeichnung alt und etwas irreführend ist, weil man nicht nur den Inhalt von Tupeln auf mehrere Ziele verteilen kann, sondern beliebige iterierbare Objekte die entsprechend viele Werte liefern.

Code: Alles auswählen

deck, spielfeldkarten = neue_spiefeldkarten(spielfeldkarten)
Ich würde empfehlen bei Gelegenheit mal das Grundlagentutorial in der Python-Dokumentation durchzuarbeiten. Da bekommt man einen ganz guten Überblick über die Grundlagen der Sprache.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

Ok, werde ich machen, ich finde es aber komisch, dass ich manchmal eine Liste verändern kann, ohne das ich sie über Return, zurückgebe, und manchmal muss ich return machen, weil sonst die Veränderung nicht greift, echt komisch.

Anders ausgedrückt, habe manchmal das Gefühl, es kommt CallByValue raus und manchmal CallByReference, obwohl es sich immer um Listen handelt. In dem Beispiel oben, wenn ich Spielkarten =[karte] mache, ist dies Liste nur innerhalb der Funktion gültig, wenn ich aber Spielkarten.pop() mache, ist diese Liste auch außerhalb der Funktion geändert. Oder liegt es da dran, dass, Spielkarten = [karte] ein "neues" Objekt und somit eine neue Adresse anlegt?
Zuletzt geändert von betzi1985 am Dienstag 29. November 2022, 16:50, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist nicht komisch, sondern die Art, wie Python Parameter behandelt.

Wenn du sowas machst

Code: Alles auswählen

variable = 1000
def funktion(argument):
    argument = 2
funktion(variable)
print(variable)
dann bleibt die Aenderung in der Funktion lokal. Das ist auch richtig so, Python kennt keine Uebergabe "per Referenz" (hier ewig lange Diskussion einfuegen, ob es denn so heissen soll, oder ob es nicht einen anderen, viel weniger bekannten, aber praeziseren Namen hat..).

Was du aber machst ist, eine Datenstruktur, die uebergeben wurde, zu *veraendern*. Nicht den Namen, den du benutzt irgendwo, an ein neues Objekt zu binden. Und das ist der Unterschied. Weil das aber ueberraschend sein kann, vermeidet man es wenn moeglich. Sonder berechnet neue Datenstrukturen, und weist die explizit dem alten Namen zu, wenn's denn so sein soll.

Code: Alles auswählen

numbers = list(range(10))

def remove_odds(numbers):
    return [i for i in numbers if i % 2 == 0] # komplett neue Liste!

numbers = remove_odds(numbers) 
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

__deets__ hat geschrieben: Dienstag 29. November 2022, 16:50 Das ist nicht komisch, sondern die Art, wie Python Parameter behandelt.

Wenn du sowas machst

Code: Alles auswählen

variable = 1000
def funktion(argument):
    argument = 2
funktion(variable)
print(variable)
dann bleibt die Aenderung in der Funktion lokal. Das ist auch richtig so, Python kennt keine Uebergabe "per Referenz" (hier ewig lange Diskussion einfuegen, ob es denn so heissen soll, oder ob es nicht einen anderen, viel weniger bekannten, aber praeziseren Namen hat..).

Was du aber machst ist, eine Datenstruktur, die uebergeben wurde, zu *veraendern*. Nicht den Namen, den du benutzt irgendwo, an ein neues Objekt zu binden. Und das ist der Unterschied. Weil das aber ueberraschend sein kann, vermeidet man es wenn moeglich. Sonder berechnet neue Datenstrukturen, und weist die explizit dem alten Namen zu, wenn's denn so sein soll.

Code: Alles auswählen

numbers = list(range(10))

def remove_odds(numbers):
    return [i for i in numbers if i % 2 == 0] # komplett neue Liste!

numbers = remove_odds(numbers) 

Anders ausgedrückt, habe manchmal das Gefühl, es kommt CallByValue raus und manchmal CallByReference, obwohl es sich immer um Listen handelt. In dem Beispiel oben, wenn ich Spielkarten =[karte] mache, ist dies Liste nur innerhalb der Funktion gültig, wenn ich aber Spielkarten.pop() mache, ist diese Liste auch außerhalb der Funktion geändert. Oder liegt es da dran, dass, Spielkarten = [karte] ein "neues" Objekt und somit eine neue Adresse anlegt?

Ok, also beim Verändern von Listen besser mit Return arbeiten, verstehen.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

[karte] ist ein neues Objekt. Und das lokal (!) einer Variablen zuzuweisen (und global wollen wir aus guten Gruenden nicht, die im Grunde die gleichen sind - Ueberraschung) aendert irgendein anderes Objekt nicht.

pop hingegen modifiziert eine Liste, und damit wirkt sich das auch auf einen uebergebene Liste aus. Was auch zeigt: es ist nie CallByValue. Es ist immer ein Zeiger auf ein Objekt, es gibt eben Objekte wie zB Zahlen und Strings, die schlicht nicht modifizierbar sind. Das ist der einzige Unterschied. Die Uebergabe-Semantik ist die gleiche.
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

So, habe das Spiel in der ersten Version fertig, es läuft so weit. Ich weiß, es ist ein schrecklicher Code, aber für mein erstes Programm, bin ich schon mega Stolz, vielleicht könnt ihr ja konsturktives Feedback geben.

Code: Alles auswählen

import random
import os
from enum import Enum

class bcolors :
    OK = '\033[92m' #GREEN
    WARNING = '\033[93m' #YELLOW
    FAIL = '\033[91m' #RED
    RESET = '\033[0m' #RESET COLOR


class Kartenwert (Enum):
    SIEBEN = 0
    ACHT = 1
    NEUN = 2
    ZEHN = 3
    BUBE = 4
    DAME = 5
    KÖNIG = 6
    ASS = 8


class Sonderfunktion (Enum):
    NONE = 0
    SIEBEN = 1
    ACHT = 2
    BUBE = 3

class Kartenfarbe (Enum):
    NONE = 0
    KARO = 1
    HERZ = 2
    PIK = 3
    KREUZ = 4



class Spieler:

    def __init__(self, nummer = None, name= None, handKarten = None, aktiv = False):
        self.spielernummer = nummer
        self.name = name
        if handKarten is None:
            handKarten = []
        self.handKarten = handKarten
        self.aktiv = aktiv

    def get_Karte(self, karte):
        self.handKarten.append(karte)

    def lege_Karte(self, nummer):
        if not self.handKarten:
            return ()
        return self.handKarten.pop(nummer)

    def spieler_hat_sieben(self):
        return [wort for wort in self.handKarten if Kartenwert.SIEBEN in wort]

    def is_spieler_aktiv(self):
        return self.aktiv

class Spiel:

    def __init__(self, startspieler = None, farben = None, werte = None, spielkarten = None, karte = None, wunschfarbe = Kartenfarbe.NONE, sonderfunktion = None, spielanzahl = 0, maumauSpieler=[]):
        self.aktiverspieler = startspieler
        self.farben = farben
        self.werte = werte
        self.skat = [(sFarbe, sWert) for sWert in werte for sFarbe in farben]
        self.spielkarten = spielkarten
        self.karte =  karte
        self.sonderfunktion = sonderfunktion
        self.wunschfarbe = wunschfarbe
        self.spielanzahl = spielanzahl
        self.siebene = 0
        self.spieler = maumauSpieler
        self.karte_gezogen = False

    def bildschirm_leeren(self):
        self.loesche_console()
        text = bcolors.OK + "Als nächstes ist " + self.aktiverspieler.name + " dran, bitte Taste drücken" + bcolors.RESET
        input(text)
        self.loesche_console()

    def wechsel_spieler(self):
        kartengezogentext = None
        if self.sonderfunktion == Sonderfunktion.ACHT:
            self.sonderfunktion = Sonderfunktion.NONE
         #   self.bildschirm_leeren()
            #self.zeige_spieler_handkarten()
            return
        if(self.spieler[0].aktiv):
            self.spieler[0].aktiv = False
            self.aktiverspieler = self.spieler[1]
            self.spieler[1].aktiv = True
        else:
            self.spieler[1].aktiv = False
            self.aktiverspieler = self.spieler[0]
            self.spieler[0].aktiv = True
        if self.sonderfunktion == Sonderfunktion.SIEBEN and not self.aktiverspieler.spieler_hat_sieben():
            anzahlgezogen = self.siebene * 2
            kartengezogentext= bcolors.FAIL + ""+ str(anzahlgezogen)  +" Karten gezogen"+ bcolors.RESET
            for zahl in range(self.siebene):
                self.karte_ziehen()
                self.karte_ziehen()
                self.siebene = 0
            self.sonderfunktion= Sonderfunktion.NONE
        self.bildschirm_leeren()
        if kartengezogentext is not None:
            print(kartengezogentext)
        self.zeige_spieler_handkarten()

    def spielkarten_zurueckfuehren(self):
        karte = self.spielkarten[-1]
        self.skat = self.spielkarten[:-1]
        random.shuffle(self.skat)
        random.shuffle(self.skat)
        random.shuffle(self.skat)
        self.spielkarten.clear()
        self.spielkarten.append(karte)

    def karte_ziehen(self):
        if len(self.skat) == 0:
            self.spielkarten_zurueckfuehren()
        self.aktiverspieler.get_Karte(self.skat.pop())

    def alles_aufräumen(self):
        self.spieler[0].aktiv = False
        self.spieler[1].aktiv = False
        self.sonderfunktion = Sonderfunktion.NONE
        self.skat = [(sFarbe, sWert) for sWert in self.werte for sFarbe in self.farben]
        self.spielkarten=[]
        self.spieler[0].handKarten=[]
        self.spieler[1].handKarten=[]


    def spiel_starten(self):
        random.shuffle(self.skat)
        random.shuffle(self.skat)
        random.shuffle(self.skat)
        random.shuffle(self.skat)
        self.aktiverspieler = self.spieler[0]
        for i in range(5):
            self.karte_ziehen()
        self.aktiverspieler = self.spieler[1]
        for i in range(5):
            self.karte_ziehen()

    def erlaubter_zug(self):
        aktuelle_spielkarte = self.gib_letzte_Spielkarte()
        if self.sonderfunktion == Sonderfunktion.BUBE:
            if self.karte[0] == self.wunschfarbe and not self.karte[1] == Kartenwert.BUBE:
                self.sonderfunktion = Sonderfunktion.NONE
                return True
            else:
                return False
        else:
            if self.karte[0] == aktuelle_spielkarte[0] or self.karte[1] == aktuelle_spielkarte[1] or \
                    (self.karte[1] == Kartenwert.BUBE and aktuelle_spielkarte[1] != Kartenwert.BUBE):
                return True
            else:
                return False

    def lege_karte (self, index):
        index -= 1

        if index == (len(self.aktiverspieler.handKarten)) and self.karte_gezogen == False:
            self.karte_gezogen = True
            self.karte_ziehen()
            self.loesche_console()
            ziehtext = bcolors.OK + self.aktiverspieler.handKarten[-1][0].name + " " + self.aktiverspieler.handKarten[-1][1].name + " gezogen"+ bcolors.RESET
            input(ziehtext)
            self.zeige_spieler_handkarten()
        elif index == (len(self.aktiverspieler.handKarten) + 1) and self.karte_gezogen:
            spiel.karte_gezogen = False
            self.wechsel_spieler()
        elif index == (len(self.aktiverspieler.handKarten) + 1) and not self.karte_gezogen:
            print(bcolors.FAIL, "Sie müssen erst eine Karte ziehen oder ablegen um den Spieler zu wechseln", bcolors.RESET)
            self.zeige_spieler_handkarten()
        elif index == (len(self.aktiverspieler.handKarten)) and self.karte_gezogen == True:
            print(bcolors.FAIL, "Sie dürfen nur eine Karte ziehen", bcolors.RESET,)
            self.zeige_spieler_handkarten()
        else:
            self.karte = self.aktiverspieler.handKarten[index]
            if self.erlaubter_zug():
                self.karte_gezogen= False
                self.sonderfunktion= Sonderfunktion.NONE
                self.spielkarten.append(self.aktiverspieler.handKarten.pop(index))
                self.ueberpruefe_sonderfunktion()
                self.wunschfarbe= Kartenfarbe.NONE
                if self.sonderfunktion == Sonderfunktion.BUBE:
                   self.waehle_farbe()
                if len(self.aktiverspieler.handKarten) == 0:
                    return
                self.wechsel_spieler()
            else:
                print(bcolors.FAIL,"Unerlaubter Zug",bcolors.RESET)
                self.zeige_spieler_handkarten()


    def gib_letzte_Spielkarte(self):
        return (self.spielkarten[-1])

    def ueberpruefe_sonderfunktion(self):
        if self.gib_letzte_Spielkarte()[1] == Kartenwert.SIEBEN:
            self.sonderfunktion = Sonderfunktion.SIEBEN
            self.siebene += 1
        elif self.gib_letzte_Spielkarte()[1] == Kartenwert.ACHT:
            self.sonderfunktion = Sonderfunktion.ACHT
            if(self.aktiverspieler.spielernummer==1):
                self.bildschirm_leeren()
                print(bcolors.FAIL, self.spieler[1].name, "muss leider aussetzen", bcolors.RESET)
                text = bcolors.OK + self.aktiverspieler.name + " ist nochmal dran" + bcolors.RESET
                input(text)
                self.zeige_spieler_handkarten()
            else:
                self.bildschirm_leeren()
                print(bcolors.FAIL, self.spieler[0].name, "muss leider aussetzen", bcolors.RESET)
                text = bcolors.OK + self.aktiverspieler.name + " ist nochmal dran" + bcolors.RESET
                input(text)
                self.zeige_spieler_handkarten()
        elif self.gib_letzte_Spielkarte()[1] == Kartenwert.BUBE:
            self.sonderfunktion = Sonderfunktion.BUBE
        else:
            self.sonderfunktion = Sonderfunktion.NONE

    def zeige_spieler_handkarten(self):
        karte =self.gib_letzte_Spielkarte()
        if not self.spieler[0].aktiv:
            if len(self.spieler[0].handKarten) == 1:
                print(bcolors.FAIL, "ACHTUNG: ", self.spieler[0].name, " hat letzte Karte", bcolors.RESET)
            else:
                print(self.spieler[0].name, "hat ", len(self.spieler[0].handKarten), " Karten")
        else:
            if len(self.spieler[1].handKarten) == 1:
                print(bcolors.FAIL,"ACHTUNG: ", self.spieler[1].name, " hat letzte Karte", bcolors.RESET)
            else:
                print(self.spieler[1].name, "hat ", len(self.spieler[1].handKarten), " Karten")
        print(bcolors.OK,"Aktuelle Spielkarte: ", karte[0].name, karte[1].name, bcolors.RESET)
        if not(self.wunschfarbe == Kartenfarbe.NONE):
            print(bcolors.WARNING,"Gewünscht Farbe:", self.wunschfarbe.name,bcolors.RESET)
        print(self.aktiverspieler.name, "wähle aktion:")
        for i in range(0, len(self.aktiverspieler.handKarten)):
            print("(", i + 1, ")", " ", self.aktiverspieler.handKarten[i][0].name, self.aktiverspieler.handKarten[i][1].name, " legen")
        print("(", len(self.aktiverspieler.handKarten) + 1, ")", " Karte ziehen")
        print("(", len(self.aktiverspieler.handKarten) + 2, ")", " nächster Spieler")

    def waehle_farbe(self):
        print ("Wählen sie eine Kartenfarbe!")
        print ("1 Karo")
        print("2 Herz")
        print("3 Pik")
        print("4 Kreuz")
        auswahl = int(input("Auswahl"))
        self.wunschfarbe = Kartenfarbe(auswahl)

    def ermittle_nicht_aktiven_spieler(self):
        return ([spieler for spieler in self.spieler if spieler.aktiv==True])

    def ermittle_aktiven_spieler(self):
        return ([spieler for spieler in self.spieler if spieler.aktiv==False])

    def loesche_console(self):
        clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
        clearConsole()


print("==================TEXT MAU MAU============================================")
p1 = Spieler(nummer=1,name=input("Spieler1 bitte Deinen Namen eingeben! "))
p2 = Spieler(nummer=2,name=input("Spieler2 bitte Deinen Namen eingeben! "))


spiel = Spiel(startspieler=None, farben = [Kartenfarbe.KARO, Kartenfarbe.PIK, Kartenfarbe.HERZ, Kartenfarbe.KREUZ], werte= [Kartenwert.SIEBEN, Kartenwert.ACHT, Kartenwert.NEUN, Kartenwert.ZEHN,Kartenwert.BUBE, Kartenwert.DAME,Kartenwert.KÖNIG, Kartenwert.ASS],
              spielkarten= [], wunschfarbe= Kartenfarbe.NONE, sonderfunktion= None, spielanzahl=0, maumauSpieler=[p1,p2])
neues_spiel =True
while(neues_spiel):
    spiel.alles_aufräumen()
    spiel.spielanzahl +=1

    if spiel.spielanzahl%2 == 1:
        spiel.spieler[1].aktiv=True
        spiel.aktiverspieler = spiel.spieler[1]
    else:
        spiel.spieler[0].aktiv = True
        spiel.aktiverspieler = spiel.spieler[0]

    spiel.alles_aufräumen()
    spiel.spiel_starten()

    spiel.spielkarten.append(spiel.skat.pop())
    startkarte = spiel.gib_letzte_Spielkarte()
    print(bcolors.OK,"Erste Karte im Spiel: ", startkarte[0].name, " ",startkarte[1].name,  bcolors.RESET)
    input("Los geht es!")
    spiel.ueberpruefe_sonderfunktion()
    if spiel.sonderfunktion == Sonderfunktion.BUBE:
        spiel.loesche_console()
        spiel.zeige_spieler_handkarten()
        spiel.waehle_farbe()
    else:
        spiel.wechsel_spieler()
    spiel.loesche_console()
    spiel.zeige_spieler_handkarten()
    while(len(spiel.spieler[0].handKarten)>0 and len(spiel.spieler[1].handKarten)>0):
        spiel.lege_karte(int(input("Auswahl")))


    clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
    clearConsole()
    print (bcolors.OK, spiel.aktiverspieler.name, "hat das Spiel gewonnen",bcolors.RESET)
    neues_spiel=input('Neues Spiel? (y/n): ').lower().strip() == 'y'
print ("Danke fürs Spielen bis zum nächsten mal")

Was ist jetzt noch zu tun:

Ich fange noch keine Falscheingaben ab, das Programm würde aktuell noch abstürzen.

Nice 2 Have ist, dass dem Spieler mögliche Züge, farblich dargestellt werden, damit er schneller die passende Karte zum Legen findet.

Wenn es soweit fertig ist, muss ich noch herausfinden, wie genau die Struktur sein muss und was ich tun muss, um es in GitHub so zu veröffentlichen, dass es sowohl in Windows als auch Linux ohne Probleme läuft, vielleicht kann man mir da mal Tips geben??


Gruß betzi1985
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@betzi1985: Was so beim drüberschauen aufgefallen ist: Das Hauptprogramm ist nicht in einer Fuktion und damit gibt es globale Variablen was nicht sein sollte. Das verdeckt im Moment einen Fehler weil eine der Methoden in `Spiel` auf eine globale Variable zugreift.

Defaultwerte für Argumente die gar nicht Optional sind ist falsch.

Man muss auch nicht für jedes Attribut ein Argument haben. Und beim Aufrufen muss man auch nicht alle Argumente per Schlüsselwort angeben.

`alles_aufräumen()` sollte es nicht geben. Hier haben wir wieder das Thema irgendwas leeren/aufräumen, statt einfach das alte Objekt wegzuwerfen und mit einem neuen anzufangen. Es ist einfach fehleranfälliger zu versuchen etwas wieder in einen sauberen Anfangszustand zu versetzen, als einfach reinen Tisch zu machen und einfach mit einem neuen Anfangszustand zu beginnen.

`Spiel` übernimmt Aufgaben die nicht zur Spiellogik gehören. Die Benutzerinteraktion würde man davon trennen.

Nur eine der Methoden von `Spiel` bekommt ein Argument übergeben. Das ist ein riecht komisch, als wenn ein Programm mit globalen Variablen einfach in eine Klasse verschoben wurde. Damit sind dann zwar formal die globalen Variablen weg, aber das Problem, dass das alles ein bisschen unübersichtlich ist, und damit fehleranfälliger, bleibt ja. Ich denke zum Beispiel nicht das `karte` ein Attribut von der Klasse sein sollte. Das gehört zum Vorgang der den Zustand des Spiels in den nächsten Zustand überführt, aber nicht zum Zustand selbst.

`karte_gezogen` sieht auch komisch aus. Wird das tatsächlich über Spielzüge hinweg verwendet? Ich glaube nicht.

`erlaubter_zug()` prüft ob der Zug erlaubt ist *und* ändert den Zustand des Spiels, und zwar so das man die Methode in dem Fall nicht mehr als einmal aufrufen kann! Das ist überraschend und damit fehleranfällig.

Es gibt da auch Redundanzen. Man braucht eigenltich nicht die Informationen „Sonderfunktion Bube“ wenn es gleichzeitig ein Attribut `wunschfarbe` gibt. Wenn das einen anderen Wert als `None`/`Kartenfarbe.NONE` hat, dann ist das ja auch ein Zeichen das die „Sonderfunktion Bube“ aktiv ist.

`NONE` ist keine Kartenfarbe von Skatkarten. „Keine Farbe“ würde ich hier als `None` modellieren. Dann muss man die Farben die nicht `NONE` sind, nicht noch mal extra als Liste erstellen und herum reichen.

Die Sonderfunktionen sollten keine Kartenwertnamen haben, sondern einen Namen an dem man die Sonderfunktion erkennen kann. Also `AUSSETZEN`, `KARTEN_ZIEHEN`, und so weiter.

`Spieler.spielernummer` sollte nur `Spieler.nummer` heissen. Das es die Nummer eines Spielers ist, wird ja schon durch den Datentyp `Spieler` klar. Wobei das Attribut komplett weg kann. Genau wie das `aktiv`-Attribut. Wenn es nur zwei Spieler gibt, sollte die `Spiel`-Klasse einfach zwei Attribute `aktiver_spieler` und `inaktivier_spieler` haben. Das macht den Code an vielen Stellen einfacher. Da wird dann auch einiges an Code verschwinden wo in zwei Zweigen immer fast das gleiche steht, und sich im Grunde nur ein Indexwert für den jeweiligen Spieler unterscheidet.

`Spieler.lege_Karte()` gibt immer noch ein leeres Tupel oder eine Karte zurück. Die Methode wird aber auch nirgends aufgerufen!

In `loesche_console()` wird eine anonyme Funktion an einen Namen gebunden und dann auch gleich in der nächsten Zeile aufgerufen. Das macht beides keinen Sinn. *Und* der Inhalt der Funktion ist dann auch noch fragwürdig. Es werden Farben per ANSI-Escapes ausgegeben, dann kann man auch die Konsole mit der entsprechenden ANSI-Escape-Sequenz löschen und muss dafür dann nicht plötzlich nach Betriebssystem unterschiedliche externe Programme aufrufen.

`random.shuffle()` mehrfach hintereinander aufzurufen ist Zeitverschwendung. Das wird dadurch nicht ”zufälliger” als wenn man nur einmal mischt.

Die Schreibweise von Namen entspricht noch nicht überall den Konventionen. Und auch inhaltlich sind die Namen manchmal nicht gut bis falsch. `Spieler.get_Karte()` beispielsweise: Methoden die mit `get_*` anfangen holen einen Wert *von* dem Objekt auf dem sie Aufgerufen werden, und übergeben da keinen Wert der dann dem internen Zustand *hinzugefügt* wird. Das sollte `fuege_karte_hinzu()` heisssen. Ich würde an der Stelle auch ganz gerne für Englisch plädieren, weil dort einiges weniger sperrig klingt (`add_card()`) und weil im Englischen der Plural nur sehr selten wie die Einzahl eines Begriffs geschrieben wird. Im Englischen ``for player in players:`` im Deutschen ``for spieler in spieler:`` → Problem! Und das tritt halt recht häufig auf.

Man macht keine Vergleiche mit literalen Wahrheitswerten. Bei dem Vergleich kommt doch nur wieder ein Wahrheitswert bei heraus. Entweder der, den man sowieso schon hatte; dann kann man den auch gleich nehmen. Oder das Gegenteil davon; dafür gibt es ``not``.

Dieses Zusammenstückeln von Zeichenketten, teilweise mit `str()` für Werte, ist unübersichtlich und eher BASIC denn Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode oder f-Zeichenkettenliteralen.

`neues_spiel` kann man sich sparen. Einfach eine ”Endlosschleife” (``while True:``), die an entsprechender Stelle dann mit ``break`` verlassen wird.

Ich hatte ja schon geschrieben das die Karten keine einfachen Tupel sein sollten, denn das die Zahlen 0 und 1 ab und zu mal Kartenwert oder -farbe bedeuten ist total undurchsichtig.

Zeilenlängen: Ich bin da ja konvervativ und bleibe bei maximal 80 Zeichen pro Zeile wo der Style Guide mittlerweile bis 120 geht, aber 264 Zeichen ist echt zu lang.

Python kann zwar mit Umlauten in Namen umgehen, aber nicht alle anderen Werkzeuge die mit Python-Quelltext oder allgemein Quelltext arbeiten. Das würde ich deshalb auf ASCII beschränken.

Sooo das reicht erst mal. 😇
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
betzi1985
User
Beiträge: 13
Registriert: Mittwoch 1. Juni 2022, 14:00

Guten Morgen,

danke, dass Du Dir die Mühe gemacht hast, meinen Code zu lesen.

Ich werde versuchen Punkt für Punkt durchgehen und versuchen umzusetzen.

Aber eine Frage schon mal vorab, warum ist es wichtig, dass man Methoden immer Parameter mitgibt? Ich arbeite ja immer nur mit den Eigenschaften meiner Klasse Spiel, sodass ich dort eigentlich keine Übergabeparameter benötige?

Ok habe verstanden, dass es Probleme geben kann, wenn man Attribute verändert, wenn sie nicht übergeben werden, aber ist das nicht genau der Sinn und Zweck von Methoden, dass ich meine Attribute verändere?

Was ist jetzt schon mal gemacht habe, die Klassen in eigene Dateien ausgelagert und die __init__.py hat jetzt eine Methode Main und wirt gestartet mit

Code: Alles auswählen

 if __name__=="__main__":
    main()
Ich hoffe, das ist so besser?

Gruß

Betzi
Antworten