Poker-Spiel

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
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Hi.
Da ich noch wenig Ahnung von OOP hab, hab ich mir gedacht, ich mach mal irgendwas objektorientiertes. Ich hab einfach mal Poker genommen.
Ich hab 2 grundlegende Klassen, eine für einzelne Karten, eine für mehrere Karten (als Liste zusammengefasst), die erstmal noch nichts mit Poker zu tun haben.
Dann hab ich eine Klasse erstellt, die von der obigen Klasse erbt aber diese noch um eine Vergleichsfunktion nach Pokerregeln erweitern soll. Also diese Vergleichsfunktion sollte Pokerkombinationen erkennen und diese vergleichen können.
Da ist auch im Moment mein Problem, die Ideen die mir da kommen, scheinen mir viel zu umständlich.

Ich poste vielleicht erstmal, was ich habe, damit ihr euch mal ein allgemeines Bild davon machen könnt.

Code: Alles auswählen

# -*- coding: cp1252 -*-
import random

class Card(object):
    """
    Stellt einen Datentyp dar, der die Eigenschaften einer Spielkarte (Farbe, Rang) beinhaltet, sowie eine Vergleichsfunktion
    """
    colorsList = ["Kreuz", "Pik", "Herz", "Karo"]
    ranksList = ["As", "König", "Dame", "Bube",
                 "10", "9", "8", "7", "6", "5", "4", "3", "2"]

    def __init__(self, color=0, rank=0):
        self.color = color
        self.rank = rank

    def __str__(self):
        return self.colorsList[self.color] + " " + self.ranksList[self.rank]

    def __cmp__(self, other):
        """
        Nur Rang wird verglichen
        """
        if self.rank == other.rank:
            return 0
        elif self.rank < other.rank:
            return 1
        else:
            return -1
        

class CardPackage(object):
    """
    Stellt einen Datentyp dar, der mehrere Card-Objekte zusammenfasst
    """
    def __init__(self, none=1):
        """
        Erstellt, falls none == 0 ist, eine Liste mit 52 Karten
        """
        self.package = []
        if none == 0:
            for x in range(len(Card.colorsList)):
                for y in range(len(Card.ranksList)):
                    self.package.append(Card(x, y))

    def __str__(self):
        output = ""
        for i in xrange(len(self.package)):
            output += str(self.package[i]) + "\n"
        return output

    def __add__(self, other):
        newcardpackage = CardPackage()
        newcardpackage.package = self.package + other.package
        return newcardpackage

    def GiveCard(self):
        """
        Löscht eine Karte aus self.package und gibt ihren Wert zurück
        """
        return self.package.pop()
    
    def GetCard(self, card):
        """
        Fügt eine Karte vom Kartenstapel package (vorgesehen als Instanz von CardPackage()) zu self.cards hinzu.
        """
        return self.package.append(card)

    def mix(self):
        """
        Mischt die Karten, die sich im self.package befinden
        """
        return random.shuffle(self.package) 
    
    def sort(self):
        """
        Sortiert die Karten nach der __cmp__ Methode der Klasse Card nach Rang und anschließend zweitrangig nach Farbe
        """
        self.package.sort(Card.__cmp__)
        for j in xrange(3):
            for i in xrange(len(self.package)-1):
                if self.package[i].rank == self.package[i+1].rank:
                    if self.package[i].color < self.package[i+1].color:
                        self.package[i], self.package[i+1] = self.package[i+1], self.package[i]

                
class PokerHand(CardPackage):
    """
    Das Selbe, wie CardPackage mit Vergleichs-Funktionen nach Poker Texas Hold'em Regeln.
    """
    def __init__(self):
        CardPackage.__init__(self)

    def __add__(self, other):
        newpokerhand = PokerHand()
        newpokerhand.package = self.package + other.package
        newpokerhand.sort()
        return newpokerhand

    def __cmp__(self, other):
        pass
        
class PokerPlayer(object):
    def __init__(self, name):
        self.name = name
        self.cards = PokerHand()

    def __str__(self):
        return "Blatt von " + self.name + "\n\n" + self.cards.__str__()
Mir ist bewusst, dass der Code bei weitem nicht perfekt ist, zum Beispiel die Sortierfunktion ist nicht optimiert und so weiter, aber ich glaube bei heutiger Rechenleistung kann ich mir sowas leisten.

Ihr könnt mir sicher 1000e Kleinigkeiten nennen aber worauf's mir gerade am meisten ankommt: Was haltet ihr von der Klassenaufteilung? Mir kommt das teilweise (dafür dass es nur so ein einfaches (halbes!) Poker ist) viel zu umständlich vor. Zum Beispiel so eine Zeile, wenn ich einem Spieler eine Karte vom Stapel geben will:

Code: Alles auswählen

player.cards.GetCard(stapel.GiveCard())
Dann hab ich eben wie gesagt noch das Problem, dass ich nicht genau weiß, wie ich Pokerkombinationen (Full House, etc) erkennen soll. Ich könnte das ganz umständlich irgendwie hinkriegen aber ich glaub das geht sicher auch relativ einfach. Wäre nett wenn ihr mir ein paar kleine Tipps geben könntet ;)

Also mir kommt es eigentlich hauptsächlich auf die Umsetzung als objektorientiertes Spiel an, das ganze rein prozedual zu schreiben wäre für mich sicherlich kein Problem, ich wollte einfach mal ein wenig praktische Erfahrung im Bereich OOP sammeln und würde jetzt gerne hören, was ihr dazu sagt (negative Kritiken erwünscht :p)
Zuletzt geändert von Nocta am Donnerstag 27. Dezember 2007, 23:45, insgesamt 1-mal geändert.
BlackJack

Für die `Card.__cmp__()`-Methode schau Dir mal die `cmp()`-Funktion an. Und für `CardPackage.__str__()` die `join()`-Methode auf Zeichenketten. Wenn man ``for i in xrange(len(obj))`` schreibt gibt's in 99% der Fälle einen einfacheren Weg ohne den Index.

`GiveCard()` und `GetCard()` sind für mein Verständnis genau falsch herum benannt. Das Objekt im Mittelpunkt ist hier das `CardPackage` wenn ich darauf `GetCard` aufrufe, erwarte ich eine Karte als Rückgabewert von dem Objekt und nicht das ich dem Objekt eine Karte übergeben muss. Insgesamt ist die Namensgebung etwas gewöhnungebedürftig. Eine Karte ziehen heisst auf Englisch `draw()` und mischen heisst `shuffle()`, wie die Funktion in `random`. `mix()` impliziert IMHO mehrere "Zutaten" die vermischt werden und nicht eine, die gemischt wird. Auch das hinzufügen einer Karte könnte man nach der zugrunde liegenden Methode `append()` nennen.

Beim Sortieren muss die `Card.__cmp__` nicht explizit angegeben werden. Und es ist wirklich ein wenig umständlich. Die "zweite" Sortierung gehört auch mit in die `Card.__cmp__()`. Dort am besten die beiden "Einzelteile" Rang und Farbe in ein Tupel stecken und dann vergleichen.

Methoden mit doppelten führenden und abschliessenden Unterstrichen sollte man nicht direkt aufrufen, wenn es eine Alternative gibt: ``foo.__str__()`` schreibt man besser als ``str(foo)``. Für die `__str__()`-Methoden solltest Du auch mal einen Blick auf Zeichenkettenformatierung mittels ``%``-Operator werfen.

Der Aufruf um eine Karte zu ziehen würde man so wahrscheinlich auch nicht machen. Bei OOP geht es um Objektbeziehungen und Verantwortlichkeiten. Man würde eher dem `player` bei der Initialisierung den Kartenstapel bekannt machen und ihm eine `draw()`-Methode verpassen, die dann eine Karte vom Stapel zieht und der eigenen Hand hinzufügt. Der Aufruf währe dann ``player.draw()`` und die Methode würde ``self.hand.append(self.deck.draw())`` ausführen.
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Hi, danke erstmal für die Antwort ;)
Für die `Card.__cmp__()`-Methode schau Dir mal die `cmp()`-Funktion an.

Code: Alles auswählen

>>> help(cmp)
Help on built-in function cmp in module __builtin__:

cmp(...)
    cmp(x, y) -> integer
    
    Return negative if x<y, zero if x==y, positive if x>y.
Was willst du mir damit sagen? So hab ich das doch.
Und für `CardPackage.__str__()` die `join()`-Methode auf Zeichenketten.
Okay, das vereinfacht die sache wohl, das werd ich einbauen, danke ;)
Wenn man ``for i in xrange(len(obj))`` schreibt gibt's in 99% der Fälle einen einfacheren Weg ohne den Index.
Hm, das stimmt wohl.
Ich hab gerade mal

Code: Alles auswählen

for i in list: print i
ausprobiert, das ist wirklich einfacher als den Index zu nehmen :p Ich bin eben PHP-Arrays gewohnt (jaja, da gibt's ne foreach-Schleife aber indizierte Arrays durchläuft man da auch meistens mit ner "normalen" Schleife)
`GiveCard()` und `GetCard()` sind für mein Verständnis genau falsch herum benannt. Das Objekt im Mittelpunkt ist hier das `CardPackage` wenn ich darauf `GetCard` aufrufe, erwarte ich eine Karte als Rückgabewert von dem Objekt und nicht das ich dem Objekt eine Karte übergeben muss. Insgesamt ist die Namensgebung etwas gewöhnungebedürftig. Eine Karte ziehen heisst auf Englisch `draw()` und mischen heisst `shuffle()`, wie die Funktion in `random`. `mix()` impliziert IMHO mehrere "Zutaten" die vermischt werden und nicht eine, die gemischt wird. Auch das hinzufügen einer Karte könnte man nach der zugrunde liegenden Methode `append()` nennen.
Ja, die Namensgebung ist insgesamt etwas schiefgelaufen.
Was `GiveCard()` und `GetCard()` angeht, war ich einfach nicht weitsichtig genug um von vornherein zu sehen, dass eine Methode nicht ausreicht und dann habe ich den Namen einfach unverändert gelassen. Ich hab auch nicht wirklich auf die Namensgebung geachtet, obwohl ich einsehe, dass das ein wichtiger Punkt ist, wenn man den Überblick behalten will. Aber daran lässt sich arbeiten.
Beim Sortieren muss die `Card.__cmp__` nicht explizit angegeben werden. Und es ist wirklich ein wenig umständlich. Die "zweite" Sortierung gehört auch mit in die `Card.__cmp__()`. Dort am besten die beiden "Einzelteile" Rang und Farbe in ein Tupel stecken und dann vergleichen.
Macht es denn einen Unterschied, ob ich die Funktion explizit angebe oder nicht?
Natürlich hast du Recht, die gesamte Sortierung wollte ich auch in die `Card.__cmp()__` stecken aber wenn ich den Vergleich nach beiden Merkmalen (Rang UND Farbe) ausgerichtet habe, hat die Sortierfunktion ziemlich komisch sortiert. Jedenfalls war's nicht wirklich geordneter als vorher :p
Also hab ich mir gedacht ich sortier das halt erstmal nach dem Rang und sortier den Rest mit so einem Vertauschsystem. Dass ich das nicht optimiert habe, habe ich glaub ich schonmal erwähnt.
Aber über einen Tipp, wie ich das jetzt letzendlich in der `__cmp()__`-Methode lösen könnte, wäre ich dankbar ;)

Zu der Methode hätte ich aber auch noch eine Frage:
Was, wenn ich einmal 2 Karten vergleichen will um rauszukriegen, welche den höheren Rang hat und einmal 2 Karten vergleichen will, um rauszukriegen, ob sie Identisch sind?
In beiden Fällen würde die Methode nur den Rang vergleichen ich wüsste immer noch nicht, ob die Karten identisch sind. Wahrscheinlich werd ich das nicht brauchen aber mir geht's ums Prinzip. Ich könnte zwar noch ein Argument einbauen, das bestimmt, ob die Kartenränge oder die gesamte Karte verglichen werden soll, aber das wäre irgendwie umständlich.
Gibt es in Python einen === (exakt gleich) Operator oder Ähnliches?
Methoden mit doppelten führenden und abschliessenden Unterstrichen sollte man nicht direkt aufrufen, wenn es eine Alternative gibt: ``foo.__str__()`` schreibt man besser als ``str(foo)``. Für die `__str__()`-Methoden solltest Du auch mal einen Blick auf Zeichenkettenformatierung mittels ``%``-Operator werfen.
Ich werd's mir merken. Aber aus irgendeinem Grund HASSE ich Zeichenkettenformatierungen, sieht irgendwie unschön aus :p
Der Aufruf um eine Karte zu ziehen würde man so wahrscheinlich auch nicht machen. Bei OOP geht es um Objektbeziehungen und Verantwortlichkeiten. Man würde eher dem `player` bei der Initialisierung den Kartenstapel bekannt machen und ihm eine `draw()`-Methode verpassen, die dann eine Karte vom Stapel zieht und der eigenen Hand hinzufügt. Der Aufruf währe dann ``player.draw()`` und die Methode würde ``self.hand.append(self.deck.draw())`` ausführen.
Hört sich nach einer vernünftigen Idee an :)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Nocta hat geschrieben:Hi, danke erstmal für die Antwort ;)
Für die `Card.__cmp__()`-Methode schau Dir mal die `cmp()`-Funktion an.

Code: Alles auswählen

>>> help(cmp)
Help on built-in function cmp in module __builtin__:

cmp(...)
    cmp(x, y) -> integer
    
    Return negative if x<y, zero if x==y, positive if x>y.
Was willst du mir damit sagen? So hab ich das doch.
Ja eben, du hast ``cmp()`` implementiert. Du kannst einfach ``def __cmp__ (self, other)\n return cmp(self.rank, other.rank)`` sagen und bist schon fertig.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Leonidas hat geschrieben:
Nocta hat geschrieben:Hi, danke erstmal für die Antwort ;)
Für die `Card.__cmp__()`-Methode schau Dir mal die `cmp()`-Funktion an.

Code: Alles auswählen

>>> help(cmp)
Help on built-in function cmp in module __builtin__:

cmp(...)
    cmp(x, y) -> integer
    
    Return negative if x<y, zero if x==y, positive if x>y.
Was willst du mir damit sagen? So hab ich das doch.
Ja eben, du hast ``cmp()`` implementiert. Du kannst einfach ``def __cmp__ (self, other)\n return cmp(self.rank, other.rank)`` sagen und bist schon fertig.
Okay, jetzt versteh ich das ;)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Nocta, du wolltest Tipps zur objektorientierten Analyse.

Prinzipiell ist es ein guter Ansatz, die beteiligten Dinge und wichtige Konzepte zu Objekten zu machen und ihr Verhalten zu Methoden. Objekte sollten immer aktiv sein, keine passiven Datenhalter. So könnte die "Hand" des Spielers wissen, was sie hält - repräsentiert durch ein weiteres Objekt möglicherweise und dieses dann, ob es mehr Wert ist als das, was eine andere Hand hält.

Namen sind ebenfalls wichtig denn es gibt ja die uralte Regel, dass, wer den wahren Namen von Etwas kennt, Macht über dieses Etwas erhält. So ist es auch bei Objekten ;) Ich meine, im Englischen wird Herz, Kreuz usw. als "Suit" bezeichnet, nicht als "Color". Der Kartenpack ist eher ein "Deck" oder "Stack". Mischen ist "shuffle", nicht "mix". Statt "give" spricht mal wohl vom "dealen" einer Karte.

Setze Vererbung sparsam ein. Häufig wird sie für eine "is-a"-Relation benutzt, d.h. für das Bilden einer Typhierarchie (Ein Volvo ist ein Auto, ein Auto ist ein Fahrzeug). Sie sollte nicht verwendet werden, um erst ein bisschen und dann in einer Unterklasse ein bisschen mehr Funktionalität hinzuzufügen. Vor allen Dingen, mache nicht den Fehler, auf Vorrat zu generalisieren. Damit machst du dir das Leben nur schwerer.

Ich würde wohl Poker (vom den ich leider nur weiß, was ich bei Stefan Raabs Pokernacht aufgeschnappt habe) mit Card, Deck, Hand, Table und vielleicht einem Player modellieren. Karten wissen, welche Farbe und welchen Wert sie haben und ob sie mehr Wert als eine andere Karte sind. Der Stapel kann sich mischen und Karten ausgeben. Die Hand hält die Karten des Spielers und weiß, wieviel sie zusammen mit den offenen Karten (die implizit auch gehalten werden) Wert ist. Auf dem Tisch liegen die community cards und außerdem hält dieses Objekt Stapel und Hände zusammen. Gibt es einen Spieler, diesem würde ich Einsatz und Geld als Eigenschaften zuordnen, etwas das man theoretisch auch der Hand mitgeben könnte, daher bin ich hier unsschlüssig, dann hält dieser die Hand und der Tisch kennt die Spieler. Außerdem kennt der Spieler den Startspieler (bzw. die Starthand) und muss diese Sache mit dem Blind und Button verwalten.

Stefan
BlackJack

Zwei kleine Einschränkungen würde ich machen: Es kann durchaus Sinn machen auch reine Datenobjekte zu haben und es muss nicht alles in Klassen stecken. Wenn man unbedingt dem heiligen OOP folgen will, immer dran denken, dass Module auch Objekte sind. Sozusagen eine spezielle Singleton-Klasse deren Instanz mit einem ``import`` erzeugt wird.
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

sma hat geschrieben:Nocta, du wolltest Tipps zur objektorientierten Analyse.

Prinzipiell ist es ein guter Ansatz, die beteiligten Dinge und wichtige Konzepte zu Objekten zu machen und ihr Verhalten zu Methoden. Objekte sollten immer aktiv sein, keine passiven Datenhalter. So könnte die "Hand" des Spielers wissen, was sie hält - repräsentiert durch ein weiteres Objekt möglicherweise und dieses dann, ob es mehr Wert ist als das, was eine andere Hand hält.

Namen sind ebenfalls wichtig denn es gibt ja die uralte Regel, dass, wer den wahren Namen von Etwas kennt, Macht über dieses Etwas erhält. So ist es auch bei Objekten ;) Ich meine, im Englischen wird Herz, Kreuz usw. als "Suit" bezeichnet, nicht als "Color". Der Kartenpack ist eher ein "Deck" oder "Stack". Mischen ist "shuffle", nicht "mix". Statt "give" spricht mal wohl vom "dealen" einer Karte.

Setze Vererbung sparsam ein. Häufig wird sie für eine "is-a"-Relation benutzt, d.h. für das Bilden einer Typhierarchie (Ein Volvo ist ein Auto, ein Auto ist ein Fahrzeug). Sie sollte nicht verwendet werden, um erst ein bisschen und dann in einer Unterklasse ein bisschen mehr Funktionalität hinzuzufügen. Vor allen Dingen, mache nicht den Fehler, auf Vorrat zu generalisieren. Damit machst du dir das Leben nur schwerer.

Ich würde wohl Poker (vom den ich leider nur weiß, was ich bei Stefan Raabs Pokernacht aufgeschnappt habe) mit Card, Deck, Hand, Table und vielleicht einem Player modellieren. Karten wissen, welche Farbe und welchen Wert sie haben und ob sie mehr Wert als eine andere Karte sind. Der Stapel kann sich mischen und Karten ausgeben. Die Hand hält die Karten des Spielers und weiß, wieviel sie zusammen mit den offenen Karten (die implizit auch gehalten werden) Wert ist. Auf dem Tisch liegen die community cards und außerdem hält dieses Objekt Stapel und Hände zusammen. Gibt es einen Spieler, diesem würde ich Einsatz und Geld als Eigenschaften zuordnen, etwas das man theoretisch auch der Hand mitgeben könnte, daher bin ich hier unsschlüssig, dann hält dieser die Hand und der Tisch kennt die Spieler. Außerdem kennt der Spieler den Startspieler (bzw. die Starthand) und muss diese Sache mit dem Blind und Button verwalten.

Stefan
Dass die Namen total für'n Arsch sind, hab ich langsam begriffen :D
Die Vererbung fand ich in dem Fall hier schon sinnvoll, da eine Pokerhand im Prinzip nichts anderes ist als ein Untertyp von einem Kartenset.
Deine Vorschläge zur Einteilung des Spieles in Klassen find ich sinnvoll und so hab ich das im Großem und Ganzen auch gemacht.
Das ganze Zeug wie Blinds, Geld, usw, also der gesamte Spielablauf, wollte ich in eine extra Klasse/Funktion (da bin ich mir nicht sicher) packen , die alle Informationen hat und den Spielablauf regelt.
Darin hätte ich die ganzen einzelnen Datentypen eben zu einem Spiel zusammengefügt.

Tut mir leid, dass ich so lange nicht geantwortet hab, aber sind halt Ferien ;)
Ich hab schon einige Änderungen, die ihr vorgeschlagen habt eingebaut und versuche auch in Zukunft an eure Ratschläge zu denken. Aber mir ist an diesem Projekt (also das Pokerspiel) irgendwie die Lust vergangen. Es war ja nie etwas, was ich später als funktionierendes Programm benutzen wollte sondern nur etwas, um ein bisschen Praxiserfahrung zu sammeln.
Also ist nicht so, dass ich bei jedem Projekt, mit dem ich euch in nächster Zeit konfrontieren werde, bei der hälfte die Lust verliere. Aber mit einem Pokerspiel kann ich eigentlich nicht's anfangen :p Ich glaub ihr wisst was ich meine. Den Zweck, nämlich, dass ich etwas draus gelernt habe, hat's ja trotzdem noch erfüllt, weil ich ja jetzt weiß, auf was ich achten muss usw.
Ich hab jetzt vor einen IRC-Client zu schreiben und denke, dass ich das durchziehen werde, sofern ich nicht auf irgendwas stoße, was ich nicht bewältigen kann.
Und da ich gut erzogen bin bedank ich mich mal für eure ganzen Antworten die mir sehr geholfen haben ;)
fme
User
Beiträge: 34
Registriert: Sonntag 1. April 2007, 18:58
Wohnort: Bremen

Ich durfte gerade die Pokervariante "Texas Hold’em" mit Netzwerkfunktionalität für ein Softwaretechnik-Projekt in Java programmieren.
Alleine die Klasse zum auswerten der Karten hat 931 Zeilen. Ist, wie du jetzt ja auch festgestellt hast, kein gutes Projekt für den Anfang.
Ich habe mich auch gewundert, wieviel Spiel-Logik man da einbauen muss.
Vor allem die Sachen, an die man zuerst gar nicht denkt ...

Solltest du trotzdem noch Lust finden das Projekt weiter zu entwickeln, dann kann ich dir evtl. hier und da ein klein wenig helfen.
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Ja, ich hab das ganze wohl unterschätzt. Ich hab mir zu erst gedacht, das wird schon nicht so schwer (okay "schwer" im Sinne von Komplex ist's ja eigentlich auch nicht), aber für den Anfang war mir das dann doch schon zu viel weil ich immer wieder irgendwo irgend ein Problem hatte usw.
Solltest du trotzdem noch Lust finden das Projekt weiter zu entwickeln, dann kann ich dir evtl. hier und da ein klein wenig helfen.
Danke für das Angebot aber im Moment hab ich erstmal genug davon ;)
Antworten