Objekt wird mehreren statt einem Objekt(en) einer anderen Klasse zugeordnet

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
inno16
User
Beiträge: 4
Registriert: Samstag 30. September 2023, 19:58

Hi, ich habe vor kurzem das Programmieren angefangen und erstelle mir aktuell ein kleines Kartenspiel, womit ich auch schon zu meinem aktuellen Problem komme:

Code: Alles auswählen

for i in range(anzahl):
            print(10)
            for self.name in self.spieler_liste.values():
                print(self.name)
                karte = self.kartendeck.pop(0)
                print(f"Test: {karte}")
                self.name.hand.append(karte)
                print(self.name)
Erklärung: ich möchte mit dieser Schleife jedem Spieler (3), anzahl(4) Karten aus dem Kartendeck mit 54 Karten geben.
Die Karten sind Objekte der Klasse Karte oder Sonderkarte und sind alle individuell.
Problem: ich möchte dem aktuellen Spieler die oberste Karte des Kartendecks geben, jedoch wird diese Karte jedem Spieler zugewiesen.

self.spieler_list ist ein dictionary das die Klasse Spieler enthält:

Code: Alles auswählen

class Spieler:
    def __init__(
        self,
        name: str,
        handkarten: list[Karte, Sonderkarte] = [],
        nachziehkarte: (Karte, Sonderkarte) = None,
        geklopft: bool = False,
    ) -> None:
        self.name = name
        self.hand = handkarten
        self.nachziehkarte = nachziehkarte
        self.geklopft = geklopft
        logging.info(f"Spieler: Neuer Spieler:: name:{self.name}")

    def __str__(self) -> str:
        return f"Spieler(name: {self.name}, handkarten: {self.hand}, nachziehkarte: {self.nachziehkarte})"

    def __repr__(self) -> str:
        return f"Spieler(name: {self.name}, handkarten: {self.hand}, nachziehkarte: {self.nachziehkarte})"
Klasse Karte:

Code: Alles auswählen

def __init__(
        self,
        name: str,
        wertigkeit: int,
    ) -> None:
        self.name = name
        self.wert = wertigkeit
Subklasse Sonderkarte:

Code: Alles auswählen

class Sonderkarte(Karte):
    def __init__(
        self, name: str, wertigkeit: int, sonderfunktion: int, beschreibung: str = ""
    ) -> None:
        super().__init__(name, wertigkeit)

        specialkarten = {
            1: "Karte vom Nachziehstapel an Mitspieler weiter geben",
            2: "Eigene Karte an Mitspieler weiter geben",
            3: "Karte mit einem Mitspieler tauschen",
            4: "Karte von einem Mitspieler anschauen",
            5: "Eigene Karte anschauen",
        }

        self.sonder = sonderfunktion
        self.beschreibung = specialkarten[sonderfunktion]
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Der entscheidende Code fehlt: das Hauptprogramm.
Für die Laufvariable einer for-Schleife ein Attribut zu verwenden, ist ziemlich unsinnig. Attribute sind dazu da, Zustand über das Ende einer Methode hinaus zu speichern, in einer for-Schleife wird der Wert aber ständig geändert.
Einen Spieler `name` zu nennen ist verwirrend. Ausdrücke wie "name.hand" machen das nochmal deutlich. Warum sollte ein Name eine Hand haben?
Dass __init__ keinen Rückgabewert hat, oder __str__ einen String zurückliefert, ist klar, die Typannotation also überflüssig und kann weg.
Dass geklopft ein bool ist, wird allein schon am Defaultwert klar.
`(Karte, Sonderkarte)` ist keine gültige Typannotation, Du meintest wohl Union[Karte, Sonderkarte], wobei Sonderkarte bereits eine Karte ist, von der Union also wenig überigbleibt. Und da das Argument auch None sein kann, willst Du eigentlich Optional[Karte].
`handkarten` hat als Defaultwert eine Liste, aber veränderliche Objekte dürfen niemals Defaultargumente sein, weil das Objekt nur einmal existiert, für alle Instanzen ist es also das selbe Objekt. Typischerweise benutzt man None als Defaultwert und prüft darauf in __init__.
`specialkarten` sollte eine (Klassen)-Konstante sein, sonderfunktion wäre besser ein Enum statt ein int (dann ist sepcialkarten auch unnötig). Das Argument `beschreibung` wird gar nicht benutzt.
Eine Karte hat einen Namen?
Ob das mit der Klasse Sonderkarte so eine gute Idee ist, kann man jetzt noch nicht sagen, aber ich bezweifle es. Wie wird die Sonderfunktion im Programm umgesetzt?
inno16
User
Beiträge: 4
Registriert: Samstag 30. September 2023, 19:58

Vielen Dank, der Tipp mit der Liste als Defaultwert hat mein Problem gelöst.
An ein Enum hab ich garnicht gedacht, das werde ich jetzt auch umsetzten.
Was meinst du mit
Eine Karte hat einen Namen?
Die Sonderfunktion wird später folgendermaßen umgesetzt:

Code: Alles auswählen

if isinstance(nachziehkarte, Sonderkarte):
            falsche_eingabe: bool = True
            while falsche_eingabe:
                logging.info(
                    "Spielfunktionen: karte_ziehen_aktion:: if isinstance(nachziehkarte, Sonderkarten):"
                )
                auswahl: int = input(
                    f"Sonderfunktion({nachziehkarte.beschreibung}) durchführen = 1, Karte mit Handkarte tauschen = 2\n"
                )
                logging.info(
                    f"Spielfunktionen: karte_ziehen_aktion:: Spieler Input: {auswahl}"
                )
                try:
                    if int(auswahl) == 1 or int(auswahl) == 2:
                        break
                    else:
                        logging.info(
                            f"Spielfunktionen: karte__ziehen_aktion:: Spieler Input: {auswahl} steht nicht zur Wahl (1, 2)"
                        )
                        print(f"{auswahl} ist keine Wahlmöglichkeit!")

                except ValueError:
                    logging.warning(
                        "Spielfunktionen: karte_ziehen_aktion:: ValueError -> auswahl: int = input('Karte ablegen: 1, Karte austauschen: 2') -> kein Int"
                    )
                    print(f"{auswahl} ist keine Wahlmöglichkeit!")

            if int(auswahl) == 1:
                match nachziehkarte.sonder:
                    case 1:  # Karte vom Nachziehstapel an Mitspieler geben
                        logging.info("Spielfunktionen: karte_ziehen_aktion:: case 1")
                        self.karte_ablegen(spieler_name, spieler.nachziehkarte)
                        mitspieler_auswahl1: str = self.mitspieler_auswahl(spieler_name)
                        self.karte_ziehen(mitspieler_auswahl1, 1)
                    case 2:  # Eigene Karte an Mitspieler geben
                        logging.info("Spielfunktionen: karte_ziehen_aktion:: case 2")
                        self.karte_ablegen(spieler_name, spieler.nachziehkarte)
                        x = self.handkarte_auswaehlen(spieler_name)
                        x = x - 1
                        karte: (Karte, Sonderkarte) = spieler.hand[x]
                        mitspieler_auswahl2: str = self.mitspieler_auswahl(spieler_name)
                        mitspieler: Spieler = self.spieler_liste[mitspieler_auswahl2]
                        logging.info(
                            f"Spielfunktionen: karte_ziehen_aktion, case 2:: Ausgewählte Karte bei {spieler_name} = {x}"
                        )
                        spieler.hand.pop(x)
                        mitspieler.hand.append(karte)
                    case 3:  # Eigene Karte mit Karte von Mitspieler Tauschen
                        logging.info("Spielfunktionen: karte_ziehen_aktion:: case 3")
                        self.karte_ablegen(spieler_name, spieler.nachziehkarte)
                        x = self.handkarte_auswaehlen(spieler_name)
                        x = x - 1
                        karte: (Karte, Sonderkarte) = spieler.hand[x]
                        mitspieler_auswahl3: str = self.mitspieler_auswahl(spieler_name)
                        mitspieler: Spieler = self.spieler_liste[mitspieler_auswahl3]
                        x = self.handkarte_auswaehlen(mitspieler_auswahl3)
                        x = x - 1
                        karte_mitspieler: (Karte, Sonderkarte) = mitspieler.hand[x]
                        mitspieler.hand[karte_mitspieler] = karte
                        spieler.hand[karte] = karte_mitspieler
                        logging.info(
                            f"{spieler_name} und {mitspieler_auswahl3} haben {karte} mit {karte_mitspieler} getauscht"
                        )

                    case 4:  # Karte eines Mitspielers anschauen
                        logging.info("Spielfunktionen: karte_ziehen_aktion:: case 4")
                        mitspieler_auswahl4 = self.mitspieler_auswahl(spieler_name)
                        karten_auswahl = self.handkarte_auswaehlen(mitspieler_auswahl4)
                        karten_auswahl = int(karten_auswahl) - 1
                        karten_auswahl = self.spieler_liste[mitspieler_auswahl4].hand[
                            karten_auswahl
                        ]
                        print(f"{karten_auswahl}")
                        self.karte_ablegen(spieler_name, spieler.nachziehkarte)
                    case 5:  # Eigene Karte anschauen
                        logging.info("Spielfunktionen: karte_ziehen_aktion:: case 5")
            elif int(auswahl) == 2:
                handkarten_auswahl: int = self.handkarte_auswaehlen(spieler_name)
                self.karte_austauschen(spieler_name, nachziehkarte, handkarten_auswahl)
                logging.info(
                    f"Spielfunktionen: karte_ziehen_aktion:: Spieler: {spieler_name} Nachziehkarte({nachziehkarte}) austauschen mit Handkarte an Stelle {handkarten_auswahl}"
                )

        elif isinstance(nachziehkarte, Karte):
            falsche_eingabe: bool = True
            while falsche_eingabe:
                logging.info(
                    "Spielfunktionen: karte_ziehen_aktion:: elif isinstance(nachziehkarte, Karten):"
                )
                auswahl: int = input(f"Karte ablegen = 1, Karte austauschen = 2\n")
                logging.info(
                    f"Spielfunktionen: karte_ziehen_aktion:: Input Spielerwahl: {auswahl}"
                )
                try:
                    if int(auswahl) == 1 or int(auswahl) == 2:
                        break
                    else:
                        logging.info(
                            f"Spielfunktionen: karte_ziehen_aktion:: Spieler Input: {auswahl} steht nicht zur Wahl (1, 2)"
                        )
                        print(f"{auswahl} ist keine Wahlmöglichkeit!")

                except ValueError:
                    logging.warning(
                        "Spielfunktionen: karte_ziehen_aktion:: ValueError -> auswahl: int = input('Karte ablegen: 1, Karte austauschen: 2') -> kein Int"
                    )
                    print(f"{auswahl} ist keine Wahlmöglichkeit!")

                except Exception as e:
                    error_msg = e
                    logging.critical(f"Spielfunktionen: karte_ziehen_aktion:: {e}")
                    print(e)

            if int(auswahl) == 1:
                self.karte_ablegen(spieler_name, nachziehkarte)
            elif int(auswahl) == 2:
                handkarten_auswahl: int = self.handkarte_auswaehlen(spieler_name)
                self.karte_austauschen(spieler_name, nachziehkarte, handkarten_auswahl)
                logging.info(
                    f"Spielfunktionen: karte_ziehen_aktion:: Spieler: {spieler_name} Nachziehkarte({nachziehkarte}) austauschen mit Handkarte an Stelle {handkarten_auswahl}"
                )
        elif nachziehkarte == None:
            logging.error(
                "Sielfunktionen: karte_ziehen_aktion:: nachziehkarte nicht vorhanden!"
            )

        else:
            logging.critical(
                "Spielfunktionen: karte_ziehen_aktion: nachziehkarte ist nicht Klasse Karte oder Sonderkarte!"
            )
            raise Exception
            
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Genau das hatte ich befürchtet. Du benutzt Klassen nicht richtig, wenn Du im Code per if den Typ prüfst. Dann kannst Du gleich eine Klasse benutzen und per `if sonderfunktion != Normalekarte` prüfen, ob Du eine Sonderkarte hast.
Dass `falsche_eingabe` ein bool ist, sieht man daran, dass Du der Variable den Wert False zuweist. Die Typannotation ist überflüssig und sollte gelöscht werden. Flags benutzt man aber nicht, wenn man auch einfach eine while-True schleife schreiben könnte, was Du ja effektiv hier auch machst.
Bei auswahl ist dann die Typannotation auch noch falsch. Bitte keine Annotationen einsetzen, wenn man sie nicht verstanden hat und nicht braucht!
`logging.info` benutzt man nicht direkt, sondern erzeugt erst einen logger. Und wo man sich gerade befindet, weiß Logging selbst, das muß man nicht in jedem geloggten String händisch angeben.
Die `auswahl` sollte nur einmal in int umgewandelt werden und nicht bei jeder Verwendung.
Statt des riesig langen match-Konstrukts würde man dann wirklich Vererbung, ein Wörterbuch oder ähnliches einsetzen.
Auf None prüft man mit is. Man sollte ein Exception-Exemplar erzeugen und dem auch einen Parameter mitgeben.

Die ganze Funktion sähe dann so aus:

Code: Alles auswählen

if aktion_durchfuehren(AKTIONSBESCHREIBUNG[nachziehkarte.typ]):
    self.aktion[self.nachziehkarte.typ]()
else:
    handkarten_auswahl = self.handkarte_auswaehlen(spieler_name)
    self.karte_austauschen(spieler_name, nachziehkarte, handkarten_auswahl)
    logger.info("Spieler: %s %s austauschen mit Handkarte an Stelle %s", spieler_name, nachziehkarte, handkarten_auswahl)
Ob die Aktionsmethode einer normalen Karte aufgerufen wird, oder einer Sonderkarte ist dem Code egal.
inno16
User
Beiträge: 4
Registriert: Samstag 30. September 2023, 19:58

Inzwischen habe ich die Klasse Sonderkarte entfernt und ein Enum für die Sonderfunktionen eingebaut.
Ich weiß du hast ein Problem mit meinen Annotationen, jedoch sind diese für mich sehr hilfreich und möchte sie erstmal eingebaut lassen.
Das mit der while True schleife hat mir ein Kumpel gesagt, das ich dafür eine Variable benutzen soll.
Das ganze mit dem logger weiß ich nicht was du damit meinst, wäre schön, wenn du mir das erklärst.
Die vermehrte Umwandlung der 'auswahl' werde ich jetzt ändern.
Das match-Konstrukt würde ich erst mal so lassen, bis ich alles so laufen habe, wie ich es möchte. Danach werde ich mich daran setzten, mir ist es auch ein kleines Dorn im Auge.
Warum überprüft man auf None mit is? Funktioniert doch so auch.
inno16
User
Beiträge: 4
Registriert: Samstag 30. September 2023, 19:58

Ich erkläre an der Stelle kurz die Regeln des Kartenspiels:
Das Spiel läuft mit einem Deck Pokerkarten + 2 Joker.
Jeder bekommt x Karten, die äußeren Beiden werden zu beginn angeschaut. Danach bleiben alle Karten verdeckt liegen.
Das Ziel ist durch Karten tauschen und entfernen, 4 <= Wert aller Handkarten zu bekommen.
Wenn man denkt man hat das Ziel erreicht, kann man klopfen, jeder Spieler außer der, der geklopft hat, darf 2 Züge machen.
Der geklopft-Status darf jederzeit zurück genommen werden. (ich werde das einfach in dem Zug des entsprechenden Spielers abfragen).
Nach den 2 Zügen werden die Karten aufgedeckt, die Wertigkeit der Handkarten zusammengerechnet und der mit dem niedrigsten Wert gewinnt.
Wenn jemand alle Karten losgeworden ist, gewinnt dieser sofort.
Sonderkarten:
König - Karte vom Nachziehstapel an Mitspieler
Dame - Eigene Karte an Mitspieler
Bube - Eigene Karte mit Karte Mitspieler tauschen
10 - Karte von Mitspieler anschauen
9 - Eigene Karte anschauen
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@inno16: Wie können Typannotationen für Dich nützlich sein wenn Du *falsche* Typannotationen hast und das nicht merkst? Zum Umgang mit Typannotationen gehört auch, dass man lernt wann man sie *nicht* verwendet. Insbesondere in Fällen die sowohl für menschliche Leser als auch die Software die diese Annotationen überprüft sonnenklar ist was da für ein Typ vorliegt, schaden die mehr als sie nützen, weil sie vom wesentlichen ablenken.

Womit prüfst Du die Typannotationen denn? Und schon beim schreiben oder mindestens bei jedem speichern? Falls das nämlich nicht der Fall sein sollte, ist das ebenfalls ein Grund die dringend weg zu lassen, oder aber eine automatisierte, sehr regelmässige Prüfung in den Arbeitsablauf einbauen, deren Ergebnis man gut sichtbar in der Quelltextansicht hat.

Das ``match``-Konstrukt ist Missbrauch von ``match``. Das sollte einfach eine ``if``/``elif``-Kaskade sein. Die Aufgabe von ``match``/``case`` ist es Namen Werte zuzuweisen in dem Ausdrücke strukturell gematcht werden. Passiert das nicht, dann braucht man kein ``match``/``case``.

Auf Singletons, also Objekte die es genau einmal gibt, prüft man mit ``is``. `None` ist so ein Wert.

`Spieler.__str__()` kann weg, das macht doch das gleiche wie `Spieler.__repr__()` und `object.__str__()` ruft einfach `self.__repr__()` auf.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten