Textadv "FinalF"

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Servus zusammen,

seit längerem (ca 3 Monate) werkel ich nun schon an diesem Projekt. Ziel war es ein einfaches Textadventure zu programmieren welches sich einfach erweitern/modden lässt und ich dabei Python lerne.

Item implementation
Versch. Charakter zustände
Highscorelist welche auf einem Server liegt (damit man Netzwerkprogrammierung auch mal angeschnitten hätte ;))

Und was weiss ich...

Ich hab zig mal den Code umgebaut, ich habe in der Woche vl. 1-3 Stunden investieren können und Ehrlich gesagt macht das Spiel derzeit weder Spass noch fesselt einen die Story ;)

Was ich gerne wissen würde ob der Code einigermassen in Ordnung ist, oder total crap und am besten alles vewerfen.

Danke schonmal falls sich jemand die Zeit nimmt und mal meinen Code anschaut :)

https://github.com/kevindecker/textadv

Greetz Kev
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

kevind hat geschrieben:Was ich gerne wissen würde ob der Code einigermassen in Ordnung ist, oder total crap und am besten alles vewerfen.
Ich habe jetzt nur mal in die main.py hineingeschaut. Du hast dort Funktionen bis zum geht nicht mehr geschachtelt. Gibt es dafür einen konkreten Grund?

Edit: Jetzt habe ich auch noch einen kurzen Blick in die Datei character.py geworfen. Warum ist improve keine Methode der Character-Klasse? Die Funktion schreit doch geradezu danach.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Der main Loop hat 4 Funktionen (okay press_enter is da etwas blöd plaziert).

Innerhalb dieser Funktionen habe ich Funktionen geschrieben um gewisse Programmabläufe wiederzuverwenden bzw. die Logik etwas vom Programmablauf zu trennen. Keine gute Idee ?
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

kevind hat geschrieben:Innerhalb dieser Funktionen habe ich Funktionen geschrieben um gewisse Programmabläufe wiederzuverwenden bzw. die Logik etwas vom Programmablauf zu trennen.
Funktionen sind prima. Sie sollten aber möglichst unabhängig voneinander sein um Flexibilität zu ermöglichen.

Deine Funktionen sind nicht unabhängig. Du hast da ungefähr so etwas:

Code: Alles auswählen

def foo():
    i = 1
    def bar():
        global j
        j = 2
        def baz(c):
            return c * i + j
        k = 3
        l = baz(k)
        return l + 2
    j = 3
    print bar()
foo()
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

´global` kommt genau 1 mal vor, lässt sich bestimmt auch auf eine andere Weise lösen aber ich wollte das nun mal zu einem "fertigen" Punkt bringen, deswegen der schnelle Ausweg ;) (ich weiss dass es nicht schön ist)

Bzgl. der Unabhängigkeit meiner Funktionen. Ich wüsste nicht wie ich das von der Struktur her großartig anders machen könnte. In der Battle Funktion werden ein haufen Sachen abgefragt da komme ich nicht um ein paar Funktionen herum.

Könntest du mir ein konkretes beispiel geben.


Danke dir schonmal für das Feedback.
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

kevind hat geschrieben:Bzgl. der Unabhängigkeit meiner Funktionen. Ich wüsste nicht wie ich das von der Struktur her großartig anders machen könnte. In der Battle Funktion werden ein haufen Sachen abgefragt da komme ich nicht um ein paar Funktionen herum.
Wenn Battle ein so mächtiges eigenständiges Ding mit diversen Daten ist auf denen Funktionen arbeiten, dann sieht das für mich nach einem Kandidaten für eine Klasse aus. Du baust dir mit den verschachtelten Funktionen ohnehin etwas, das versucht eigene Blöcke und Namensräume zu schaffen.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Ich erklär mal grob was die Funktion battle macht.

Der Funktion battle werden 2 objekte der Klasse character übergeben.

Also player und opponent. Die Objekte der Klasse character (player, opponent) enthalten Attribute wie vitalität, defense, damage, und ne liste mit attacken (die Attacken in der Liste sind ebenfalls Objekte einer Klasse), etc.

Die Funktion Battle soll die Kampfsequenz des Spiels darstellen, Player attackiert Opponent, Opponent attackiert Player. Bis einer von beiden die Bedingung "Vitalität <= 0" erfüllt.

Damit alles geregelt abläuft brauch ich aber ein paar "Hilfsfunktionen"

character_status // gibt die werte eines character objektes aus (also player oder opponent)
character_attacks_status // gibt mir an welche attacken bereit sind oder sich im "cooldown" befinden
check_defeated // prüft ob ein character objekt besiegt wurde
character_attack // eine Funktion innerhalb von battle welche wiederum ein paar aufgaben ausführt:

Prüft ob die getätigte Eingabe gültig ist
Prüft Cooldowns
Berechnet den Schaden
Entzieht dem Gegner Vitalität
Setzt die Cooldowns neu
Gibt für die Textausgabe den Damage wert zurück.

Die Funktionen werden jede Runde neu angwendet, ebenfalls beim Computergegner.

Ich dachte mir also: Ich verwende die Daten der 2 Objekte und verarbeite sie in der Funktion battle.

Werde aber Battle als Klasse mal in Betracht ziehen.

Ansonsten würde mich natürlich noch über ein Paar andere Punkte freuen :)
BlackJack

@kevind: Ein paar Punkte neben den verschachtelten Funktionen die man dringend entwirren sollte:

Das `main`-Modul enthält zwei nicht benötigte Importe und in der `main()`-Funktion ein paar definierte aber unbenutze Namen.

Die `random.seed()`-Aufrufe sind unnötig. Es gibt sogar Situationen wo der Aufruf dieser Funktion kontraproduktiv sein kann. Ich habe übrigens noch nie einen Aufruf mit `None` als Argument gesehen. Das ist ja sowieso der Default-Wert.

Davon abgesehen, dass die ganzen Funktionsdefinitionen innerhalb der `main()`-Funktion nicht sein sollten, steckt auch noch alles innerhalb der Hauptschleife. Dadurch werden bei jedem Schleifendurchlauf neue Funktionsobjekte erstellt, und die aus dem vorherigen Druchlauf verworfen, obwohl es in der Regel gleiche Objekte sein werden. Also völlig unnötiger Aufwand der da betrieben wird. Das trifft auch auf `prompt` zu, was soweit ich das sehe immer wieder den selben Wert zugewiesen bekommt. Das könnte man auch *einmal* *vor* der Schleife erledigen.

`main_menue()` macht als Funktion zu wenig. Das ist ja letztendlich nur ein `print()` und nicht eine Funktion welche das Hauptmenü abwickelt.

Es gibt etliche Quelltextzeilen die länger als 80 Zeichen sind. Bei den umgebrochenen gibt es welche bei denen die Struktur nicht ersichtlich ist. Zum Beispiel `dialog.msg()`-Aufrufe bei denen die umgebrochenen Zeilen links auf gleicher Tiefe wie der Funktionsaufruf liegen und damit nicht sofort ersichtlich ist, dass es sich nicht um einen neuen Ausdruck handelt, sondern immer noch um den gleichen.

Die `str()`-Funktion mit einer Zeichenkette aufzurufen gibt nur genau dieselbe Zeichenkette wieder zurück — so ein Aufruf ist also sinnfrei.

Die Ausnahmebehandlung in `newgame()` macht keinen Sinn. Unter welchen Umständen hättest Du denn hier einen `ValueError` erwartet? Und die Behandlung aller anderen Ausnahmen durch die Ausgabe einer absolut nichtssagenden Ausgabe für den Benutzer ist eine sehr schlechte Idee. So kommt man Programmierfehlern nur sehr schwer auf die Spur, weil jede Ausnahme die zur Aufklärung beitragen könnte, durch im Grunde *keine* Information ersetzt wird.

Kommentare werden üblicherweise *vor* den kommentierten Quelltext geschrieben und nicht in der Zeile danach. DocStrings dagegen als Zeichenkettenliteral nach der entsprechenden Signatur die man Dokumentieren möchte. Wenn man Kommentare ans Ende von Zeilen anhängt, dann erhöht sich die Gefahr, dass man die 80-Zeichen-Grenze sprengt, ausserdem sind solche Inline-Kommentare nicht so flüssig zu lesen, weil Code und Kommentar vermischt werden. Inline-Kommentare eignen sich eher für kurze Ergänzungen, denn für ausführlichere Kommentare. Ein Beispiel wären Einheiten zu Zahlenangeben wie in:

Code: Alles auswählen

    weight = 42  # in kg.
    distance = 23  # in cm.
Besonders unschön sind falsche Kommentare, also Fälle in denen sich Kommentar und Quelltext offensichtlich widersprechen. Da weiss der Leser dann nämlich nicht so ohne weiteres ob nun der Kommentar oder der Code falsch ist und wem er nun glauben soll. Bei der `newgame()`-Funktion steht zum Beispiel „call battle function”, aber das tut die Funktion überhaupt nicht. Ist da nun der Kommentar falsch, oder die Funktion unvollständig?

Wenn man doppelte Anführungszeichen in einem Zeichenkettenliteral unterbringen möchte, dann ist es IMHO einfacher die Zeichenkette selbst in einfachen Anführungszeichen einzufassen statt die doppelten Anführungszeichen innerhalb der Zeichenkette zu escapen.

Wenn die `battle`-Funktion, die ihrerseits wieder viel zu lang ist und zu viele Funktionsdefinitionen enthält, den Spielzustand als Argument entgegennehmen und den möglicherweise veränderten Zustand zurückgeben würde, dann müsste `game_state` nicht ``global`` auf Modulebene existieren.

`characters_status()` ist ungünstig formatiert, denn zusammen mit dem falschen Kommentar, dass die Funktion eine Zeichenkette zurück gibt, ist nicht wirklich leicht ersichtlich, das hier eine Tupel mit zwei Zeichenketten zurück gegeben wird. In der Funktion wiederholt sich der Code zweimal, was darauf hindeutet, dass die Aufteilung ungünstig ist. Und eigentlich würde man den Code auf dem `Charakter`-Objekt erwarten. Da gibt es noch andere Code-Abschnitte die eigentlich in die Zuständigkeit von `Charakter` gehören.

Einen Modulglobalen Namen `character` (das Modul) und in Funktionen dann den Namen `character` für etwas anderes zu verwenden kann verwirrend werden.

`check_defeated()` könnte gleich das Ergebnis des Vergleichs zurück geben statt so asymmetrisch `True` und `None` zurück zu geben.

In `character_attack()` werden Wahrheitswerte explizit mit literalen Wahrheitswerten verglichen. Dabei kommt nur wieder ein Wahrheitswert heraus, also hätte man gleich den ursprünglichen Wert verwenden können. Ausserdem ist dort ein ``if`` was genau das Gegenteil des vorhergehenden ``if`` überprüft. Das ist eigentlich ein Fall für ein ``else``. Mit ``is`` sollte man Wahrheitswerte schon gar nicht prüfen.

Die `damage_calc()`-Funktion wird dort soweit ich das sehe zu oft aufgerufen. Man könnte sich den Wert in zwei Fällen merken und später wiederverwenden, statt die Funktion noch einmal aufrufen.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Danke BlackJack das du dir soviel Mühe gamcht hast.

Ich bin dabei das ganze jetzt nochmal mehr Objektorientiert zu verbessern, dazu versuche ich das Ganze auch sauberer zu machen. Ich denke durch das "rumgebastel" habe ich vergessen die Kommentare stellenweise zu ergänzen oder zu entfernen.

Ehrlich gesagt hätte ich nicht gedacht das so etwas "einfaches" so eine Größe und (für meine Verhältnisse) Komplexität annimmt.

Gerne zeige ich euch dann die verbesserte Version :)

Danke soweit! Gruss, Kev
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

kevind hat geschrieben:Ehrlich gesagt hätte ich nicht gedacht das so etwas "einfaches" so eine Größe und (für meine Verhältnisse) Komplexität annimmt.
Du bekommst möglicherweise mehr Codezeilen, aber eine verringerte Komplexität.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Heyho!

Ich habe nun die main.py und character.py nochmal überarbeitet (hauptsächlich main.py).

Dieses mal ist es mir ziemlich leicht gefallen, habe flüssig durch programmiert und es hat eigentlich auf anhieb so funktioniert wie ich es wollte.
Für die Kommentare brauche ich vl. noch etwas praxis zu wissen was sinnvoll ist und was nicht. Zb ab Zeile 120 da kommt die Kampfsequenz. Wenn ich da keine Kommentare einfüge sieht es so unübersichtlich aus, obwohl ich finde das die Namen schon aussagekräftig sind. Was meint ihr ?

Ausnahmen muss ich noch genauer anschauen..

Ansonsten, wie findet ihr es nun ?

@BlackJack wo siehst du das problem von random.seed() ? Ist das als Initiator nicht notwendig ?

@All...
Würde ich absehen eine GUI darüber zu legen, müsste ich eingabe/ausgabe noch mehr trennen ?

Code: Alles auswählen

        def attack_selection(self):
            while True:
                while True:
                    try:
                        selection = int(input("Attack:"))
                        break
                    except ValueError:
                        print("Wrong input, enter a number") # würde ich so etwas im falle einer GUI als Rückgabewert verwenden?
                if self.player.select_attack(selection) is True:
                    return selection
                else:
                    continue
Wobei ich glaube da müsste die ganze Funktion anders aussehen...

Gruss, Kev
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@kevind: ich schau mir grad Deine character.py an.
- random.seed wurde glaube ich schon oben behandelt.
- set_attacks wird innerhalb von __init__ definiert?
-- es setzt self.attacks, gibt self.attacks zurück und wird unten wieder self.attacks zugewiesen. Das ist verwirrend. Außerdem ein dict das durchnummerierte Indizes hat ist nur eine umständliche Liste.
- in select_attacks wird »in« auf keys() und nicht direkt auf das Dictionary angewendet, was gleich doppelt ungeschickt ist. Erstens der unnötige Aufruf einer weiteren Funktion und zweitens wird der Vorteil, dass bei Dictionaries mit Hashs verglichen wird durch die viel langsamere Iteration und dem einzelnen Vergleichen jedes Elements ersetzt. Statt im »if« True bzw. False zurückzuliefern würde ein »return« der Bedingung genügen.
- random_name hat schon wieder so eine komische innere Funktion, die in diesem Fall so kurz ist, dass man sie auch gleich weglassen könnte.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Hey Sirius3,

erstmal danke das du dir die Mühe mit meinem Code machst :!:

- random.seed zu vermeiden wurde erwähnt, habe mir gerade die Dokumentation dazu angeschaut.
If x is omitted or None, current system time is used; current system time is also used to initialize the generator when the module is first imported.
Soll bedeuten das es schon beim Import initialisiert wird somit random.seed() eigentlich unnötig ist (wenn man kein hash übergibt) verstehe ich das richtig ?

- set_attacks habe ich innerhalb der __init__ definiert da ich sonst beim zuweisen der self.attacks nicht auf die Funktion zugreifen kann :) Ich werde das ganze nochmal überdenken... mir gefällt das selber nicht wirklich (fand es aber total ausgefuchst als ich es eingebaut habe :mrgreen: )

- bzgl den Dict: Ich verwende zur auswahl der Attacken eben "nummern eingabe" und mir schien es auf diese weise am einfachsten. Hättest du einen besseren Vorschlag ?

- "in select_attacks "in"" Ich verstehe da nicht genau was du meinst. (Zudem woher soll ich wissen was schneller oder langsamer ist und muss ich mich wirklich (in meinen Python anfängen) mit solchen Details auseinandersetzen ? Ich hab ja mit anderen Basics zu kämpfen wie du siehst :) ) Warum ein "return" ? Ich möchte doch nur wissen ob die ausgewählte Attacke (die eingegebene Zahl) überhaupt im besitz des Characters ist und sich diese nicht im "cooldown" befindet.

- random_name stimmt, totaler quatsch war da wohl im "Funktionsrausch" :evil: .


Danke dir vielmals !
BlackJack

@kevind: Du verschachtelst immer noch zu viel in Funktionen. Definitionen von Funktionen gehören nur sehr selten in andere Funktionen. In der Regel nur dann, wenn die innere Funktion *sehr* eng mit der äusseren Verbunden ist und nicht alleine existieren kann. Das ist bei einer Funktion die den Bildschirm beziehungsweise das Textterminal leert sicher nicht der Fall. *Klassen* habe ich noch nie innerhalb einer Funktion definiert.

Funktionen sollten kurz sein. In der Regel nicht mehr als 25 bis 50 Zeilen. Mann sollte einfach und schnell erkennen können was eine Funktion macht. Es gibt Ausnahmen bei der Länge, denn wenn die Funktion einfach gestrickt ist, kann sie auch lang sein, aber ich sehe nicht warum die `main()` so verdammt lang sein muss.

Was soll der `New`-Präfix bei den Klassennamen bedeuten?

Die schlechte Einrückung gibt es teilweise immer noch. In der `Game.__init__()` sollte bei der Zuweisung der Attribute `player` und `opponent` das `False` beziehungsweise `True` nicht auf gleicher Ebene wie das `self` in der Zeile davor stehen. Du hast deswegen ja wahrscheinlich die Leerzeilen eingefügt, aber das ist IMHO nicht genug beziehungsweise der falsche Weg um deutlich zu machen, dass die Zeilen jeweils zu *einer* Anweisung gehören.

Die Kommentare direkt über den `Game`-Methoden gehören eigentlich als DocStrings in die Methode. Wenn die Klasse nicht in der `main()`-Funktion versteckt wird, dann könnte man daraus zum Beispiel auch Dokumentation generieren lassen oder die Information in einer Python-Shell mit `help()` abfragen.

Die Werte für den Spielstatus würde ich als Konstanten definieren, dann kann man die Werte austauschen und die Gefahr von unbemerkten Fipptehlern verringert sich, weil ein falsch geschriebener Konstanten-Name zu einem `NameError` führt, beziehungsweise zu einem `AttributeError` wenn man die Konstanten auf der Klasse definiert.

Zu ermitteln ob ein Spieler geschlagen ist, würde ich der `Character`-Klasse überlassen. Wenn man über mehrere Punktoperatoren geht, sollte man sich überlegen ob die Funktion oder Methode nicht ihre Kompetenzen überschreitet und zu viel über das Innenleben von den Objekten weiss, auf die sie da zugreift. Vielleicht möchte man später ja mal ändern was es bedeutet das ein Spieler geschlagen ist und mehr als nur den `vitality`-Wert in diesen Test einbeziehen oder man hat verschiedene `Character`-Klassen die diese Frage unterschiedlich lösen wollen.

Die Methode `check_defeated()` gibt entweder `True` oder `None` zurück. Das ist eine ungünstige Kombination, weil man eigentlich `True` und `False` erwarten würde.

Das Wiederverwenden vom `opponent`-Objekt für jeden Gegner finde ich zu magisch. Neuer Gegner gleich neues Objekt wäre sauberer. So schleppt man vielleicht irgendwann Zustand von einem Gegner zum nächsten der gar nicht beabsichtigt war. Und verschiedene Implementierungen von Gegnern sind auch nicht möglich.

`Game.print_attack_status()` ist keine Methode. Wenn man `self` nicht benutzt, dann braucht man eine Begründung warum die Funktion als Methode auf einer Klasse definiert wurde. Den Grund sollte man dokumentieren und eine `staticmethod()` daraus machen, damit dem Leser klar wird, dass es keine Methode ist. In diesem Fall greift der Code auch wieder viel zu tief durch. Er muss wissen wie ein `Character` Angriffe verwaltet und wie diese Angriffsobjekte aussehen. Die Funktion sollte man als Methode auf `Character` verschieben und dort vielleicht auch noch mal darüber nachdenken was davon in `Attack` gehört.

`random_attack()` ist ebenfalls an der falschen Stelle und weiss viel zu viel über die Interna. Man sollte einen `Character` nach den verfügbaren Angriffsmethoden fragen können und `Attack`-Objekte danach ob sie verwendbar sind oder nicht. Und einen `Character` sollte man nach einer zufällig gewählten, ihm zur Verfügung stehenden Angriffsmethode fragen können. Soweit ich das sehe hat die Methode auch ein Problem wenn dem Gegner überhaupt keine Angriffsmethoden zur Verfügung stehen.

Wenn ``continue`` grundsätzlich als letzte Anweisung in einer Schleife ausgeführt wird, dann ist es unnötig, denn dann würde ja auch ohne ``continue`` als nächstes der nächste Schleifendurchlauf ausgeführt werden. Noch sinnfreier ist ein ``else: pass``.

``continue`` würde ich grundsätzlich versuchen zu vermeiden, weil das ein unbedingter Sprung irgendwo aus einer Verschachtelung ist, den man der Struktur des Quelltextes nicht so leicht ansehen kann. Ausserdem kann es schwierig werden, wenn man ans Ende eines Schleifendurchlaufes später noch Code hinzufügen möchte, der auf jeden Fall vor jedem erneuten Schleifenbeginns ausgeführt werden soll. Der würde durch ein ``continue`` dann übergangen.

Wahrheitswerte sollte man weder mit ``is`` noch mit ``==`` oder ``!=`` testen sondern einfach als Bedingung verwenden, denn es sind ja schon Wahrheitswerte! Bei so einem Vergleich käme nur *wieder* ein Wahrheitswert heraus. Wenn man auf das Gegenteil eines Wahrheitswertes testen möchte, gibt es den ``not``-Operator.

`attack_selection()` finde ich benutzerunfreundlich. So eine Methode sollte dem Benutzer die möglichen Angriffe auflisten und eine Nummer zwischen 1 und der Anzahl der gelisteten Angriffe erfragen. Dieses Auflisten und erfragen kann man in einer für verschiedene Auswahlen wiederverwendbaren Funktion erledigen. Zum Beispiel für die Hauptmenü-Auswahl. Wo die Eingabe eines Zeichens was nicht zu einer Zahl gehört mit einem Programmabbruch quittiert wird.

`set_attack_cooldown()` ist keine Funktion und gehört wieder entsprechend auf `Character` und `Attack` verteilt.

Die `battle()`-Funktion operiert auf `game`, könnte also eine Methode auf `Game` sein. Und sie weiss wieder viel zu viel. Bei einer Zeile wie ``game.player.attacks[selection].cooldown_counter -= game.player.attacks[selection].cooldown`` weiss die Funktion Interna über das `Game`-Objekt, wie das `Character`-Objekt Angriffe verwaltet, und wie `Attack`-Objekte aufgebaut sind und manipuliert das auch noch von aussen.

Die Funktion ist auch etwas lang und so beim drüberschauhen sieht es so aus, als wenn fast der gleiche Code zweimal dort steht, einmal für den Spielerangriff und einmal für den Gegnerangriff.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Danke BlackJack...

und ich dachte nun dass der code garnicht so übel ist :P Dann habe ich ja wieder was zu tun, könnte wohl etwas dauern ;)

Danke und Gruss !

EDIT:
Ich gehe nun die Punkte vorne zu durch,

Wo erstelle ich den am besten funktionen wie "clear" welche die Konsole (unabhängig vom betriebssystem) leert?

Wie darf man "die Klasse" weiss zuviel verstehen ? Geht es hierbei darum dass die Klasse zu umfangreich wird und es besser wäre sie aufzuteilen ? (Also wie in meinem Fall Game/Character und evtl. noch "Battle" hinzufügen?)

Den präfix "New" habe ich verwendet weil es sich bei der Objektzuweisung schöner lesen lässt... x = newgame (so denkt man sich beim lesen "aha da wird eine neue Gameinstanz erstellt) Aber könnte man sich auch so denken... ist eigentlich logisch.

Wie sollte es von der Einrückung her sein wenn es über 3 Zeilen geht ? Wäre das Okay so ? Weitere Zeilen einrücken würde ja irgendwann dazu führen das kein Platz mehr ist um was zu schreiben.

Code: Alles auswählen

        def info(self):
            print("----------------------------------------------------------")
            print("Round: {0}, Game state: {1}, Player: {2}, Opponent: {3}"
                .format(self.round, self.state, self.player.name,
                self.opponent.name))
            print("----------------------------------------------------------")
BlackJack

@kevind: So eine `clear()`-Funktion erstellt man am besten zumindest mal auf Modulebene. :-)

Mit „die Klasse weiss zuviel” ist gemeint, dass die Methoden tief zu anderen Objekten durchgreifen und deren Zustand direkt verwenden oder gar manipulieren. Die `Game`-Klasse sollte weder wissen wie `Charakter` *intern* die Angriffsobjekte verwaltet, noch wie ein Angriff seinen „Cooldown”-Schritt umsetzt. Denn dann kann man weder diese Interna der anderen Objekte ändern ohne auch in der `Game`-Klasse Änderungen vorzunehmen, noch könnte man `Charakter`- oder `Attack`-Klassen verwenden, die diese Sachen anders lösen als die `Game`-Klasse das erwartet. Das man das machen kann — wenn man die Zuständigkeiten richtig verteilt — ist ja einer der Vorteile von objektorientierter Programmierung.

Bei *der* Einrückung sieht man nun zumindest deutlich, dass die beiden Zeilen noch zum `print()` gehören. Ich persönlich Versuche auch die Struktur innerhalb eines Ausdrucks kenntlich zu machen wenn er über mehrere Zeilen geht. Wenn die Einrückung dabei zu tief werden sollte, ist der Ausdruck vielleicht zu komplex und man könnte Teilausdrücke heraus ziehen und an Namen binden.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

BlackJack hat geschrieben:...Denn dann kann man weder diese Interna der anderen Objekte ändern ohne auch in der `Game`-Klasse Änderungen vorzunehmen, noch könnte man `Charakter`- oder `Attack`-Klassen verwenden, die diese Sachen anders lösen als die `Game`-Klasse das erwartet. Das man das machen kann — wenn man die Zuständigkeiten richtig verteilt — ist ja einer der Vorteile von objektorientierter Programmierung.
Den rest habe ich verstanden aber ich glaube das Zitierte habe ich nicht wirklich verstanden. (Könnte es zumindest nicht erklären ;))

Wer weiß denn nun zuviel ? Die Klasse "Game" über "Character" weil innerhalb der "Game" Klasse "player" und "opponent" aus der "Character" Klasse erstellt werden ?

Habe auf Github eine aktuellere Fassung gestellt. Methoden von "Game" zu "Character" verschoben wo sie mehr sinn machen, "Game" Klasse der übersicht halber ausgelagert. Ebenso erfreu ich mich über die Save/load funktion welche nun funktioniert :)

Danke schonmal !
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@kevind: Stell dir Klassen wie Mitarbeiter - oder noch besser: wie Dienstleister - vor: Der Verwender der Klasse ist wie das Unternehmen (bzw der Kunde), welches eine Dienstleistung in Anspruch nimmt. Der Dienstleister bietet also ein bestimmtes Repertoire an Leistungen an, die ein Aufrufer entsprechend nutzen kann. Derjenige, der die Dienstleistung in Anspruch nimmt, weiß aber nicht, wie der Dienstleister die Erbringung der Leistung intern umsetzt. Wenn du jetzt aber in den Arbeiten des Dienstleisters herumpfuschst, indem du irgendwelche Attribute der Dienstleisterklasse beschreibst oder sonstwie das Wissen über dessen Interna verwendest, dann machst du das Prinzip gewissermaßen kaputt. Es wird dann total unklar, wer jetzt überhaupt welche Kompetenzen hat - der Zuständigkeitsbereich verschwimmt. Letztlich endet dies in unstrukturiertem Code und ist daher nicht unbedingt das, was man unter sauberer Programmierung verstehen würde.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

@snafu

Das heißt also das meine Game Klasse zuviel über Character weiß und man innerhalb von Klassen keine andere Klassen Instanz aufrufen sollte. (Denn dann weiß Sie ja zuviel).
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Doch, man kann andere Klassen "aufrufen" (denke mal, du meintest das Erstellen neuer Klasseninstanzen). Es geht einfach nur um eine saubere Trennung bzw Beachtung von Schnittstellen. In den meisten Fällen bschränkt sich die Schnittstelle auf die von der benutzten Klasse definierten Methoden. Alles andere "sieht" der Aufrufer überhaupt nicht. Du musst versuchen, mehr aus der Sicht des jeweiligen Aufrufers zu denken und weniger aus der Sicht des allwissenden Programmierers. Es gibt meistens ein Hierarchie-Prinzip. Dabei dürfte dein `Game` weit oben stehen, deine Charaktere darunter. Und einzelne Aktionen/Handlungen sind wiederum die Werkzeuge der Charaktere. Daher stehen sie offensichtlich recht weit unten in der Hierarchie. Der Untenstehende weiß nichts konkretes über seinen Aufrufer - er führt nur dessen Befehle aus. Der Aufrufer hingegen kennt nur den Befehlssatz des "Untergebenen" (quasi den Dienstleistungskatalog), weiß aber nicht (und soll es auch nicht wissen), wie der Untergebene die Befehle umsetzt.
Antworten