Textadv "FinalF"

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
BlackJack

@kevind: Ich hatte doch Beispiele gegeben wo Funktionalität und damit Wissen in der falschen Klasse (oder Funktion) stecken. Zum Beispiel beim „Cooldown”. Das Spiel sollte nur wissen dass es so etwas gibt und den `Character` bitten das durchzuführen, der `Character` sollte diese Bitte an die einzelnen `Attack`-Objekte weitergeben. Ähnlich beim ermitteln einer zufälligen Attacke. Hier mal skizziert wie man das aufteilen kann:

Code: Alles auswählen

class Attack(object):
    # ...
    def cooldown(self):
        self.cooldown_counter -= self.cooldown

    def is_usable(self):
        return self.cooldown_counter <= self.cooldown
    # ...


class Character(object):
    # ...
    def cooldown(self):
        for attack in self.attacks:
            attack.cooldown()

    def get_usable_attacks(self):
        return [attack for attack in self.attacks if attack.is_usable()]

    def get_random_attack(self):
        return random.choice(self.get_usable_attacks())
    # ...


class Game(object):
    # ...
    def battle(self, opponent):
        # ...
        self.player.cooldown()
        # ...
        attack = opponent.get_random_attack()
        # ...
    # ...
Jetzt kann man in der Attacke ändern wie „Cooldown” funktioniert oder was benutzbar genau bedeutet ohne dass man in `Character` oder `Game` etwas ändern muss. Genau so kann man in `Character` die Angriffe in einer Liste, in einem Wörterbuch, oder in einem eigenen Datentyp verwalten, ohne das `Game` wissen muss, wie das genau gemacht wird.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

@BlackJack

ich bin jetzt wieder ein stück weiter...

Wie stellst du dir das mit den Konstanten vor ? Zahlen ? Steh grad (oder schon länger seit ich darüber nachdenke ;) ) aufm Schlauch

Das heisst, das object "opponent" wenn es besiegt wurde "löschen" und dann neu in einer besseren variante instanzieren ?

Die unterschiede zu "is" und "==" werde ich noch recherchieren...

Ich glaube jetzt habe ich auch das mit dem "Wissen" der Klassen gut im Griff. Vl. könntest du mir dazu noch ein kurzes feedback geben.

Danke vielmals !
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo kevind,

Du hast noch viel Aufräumarbeiten vor Dir:

Code: Alles auswählen

if abc is True:
->
if abc:

Code: Alles auswählen

if abc is False:
->
if not abc:

Code: Alles auswählen

if a>b:
    return True
else:
    return False
->
return a>b
Funktionen sollten entweder einen Rückgabewert haben oder nicht, und nicht je nachdem ob eine Bedingung erfüllt ist oder nicht (siehe »battle_end«).

»character_menu« weiß noch zu viel von »player«.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Ich brauche aber das return True wie du in der "battle sequence" siehst. Wäre folgendes exemplar besser?

Code: Alles auswählen

    def battle_end(self):
        if self.player.defeated() is True:
            print("{0} has been defeated by {1}".format(self.player.name,
                self.opponent.name))
            self.state = "over"
            return True

        elif self.opponent.defeated() is True:
            self.round += 1

            print("{0} has been defeated by {1}, prepare for round {2}"
                .format(self.opponent.name, self.player.name, self.round))

            self.player.improve()
            self.opponent.improve()
            self.opponent.name = character.random_name()
            self.state = "newround"
            return True

        else:
            return False


Das character_menu weiß deshalb noch zuviel da ich mit der ganzen Menüführung unzufrieden bin... ich versuche das auch etwas Objektorientiert zu lösen aber wollte mir bisher noch nicht recht gelingen.

Danke für dein Feedback Sirius3
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@kevind: Nein, du kannst einfach `if self.player.defeated():` schreiben (ohne die explizite Angabe des Wahrheitswertes). Außerdem vergleicht man Werte mit `==` und nicht mit `is`. Letzteres testet auf Objekt*identität* - also darauf, ob es sich um ein und dasselbe Objekt handelt.

Identität ist etwas anderes als Gleichheit. Alle identischen Dinge sind zwar gleich, aber alle gleichen Dinge sind nicht unbedingt identisch. Wenn ich 2 Autos mit exakt derselben Ausstattung (bzw "Konfiguration") habe, dann sind diese zwar völlig gleich, jedoch nicht identisch.

`is` braucht man in Python eigentlich ziemlich selten. Ab besten merkst du dir für den Anfang erstmal, dass du es überhaupt nicht brauchst. Es gibt nur einen Sonderfall: Wenn man wissen will, ob etwas `None` ist, dann benutzt man `if xyz is None:`.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

@snafu

Okay das war wohl etwas ungeschickt, den nicht überarbeiteten Code nochmal zu posten.

So wie ich es verstanden habe bemängelte Sirius3 dass ich bei der Funktion "battle_end" nur True zurückgebe und kein False. Deswegen habe ich nun als letzte möglichkeit (das "else") ein False zurückgegeben.

Aber danke, du hast mir nun das Googeln bzgl. den verschiedenen Operatoren abgenommen :mrgreen:
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Ich habe das ganze nochmal etwas überarbeitet.

Habe das Menü nun auch in eine Klasse gepackt, mach ich das vom handling her richtig ? Funktionieren tut es mal :P

Macht langsam echt fun mit Klassen zu arbeiten :)
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Was mich ein bisschen irritiert sind diese "ungarisch anmutenden" Variablennamen mit Typpräfix (m_ / m). Vor allem:

Code: Alles auswählen

    def __init__(self, mID, mlabel, mtype): # dass diese drei Dinge was mit dem Menü zu tun haben, ist offensichtlich.
is_main und is_battle finde ich unnötig, warum nicht menu.type == "x"? Ansonsten ist "Menu" ein unglücklicher Bezeichner, weil das gar kein Menü ist, sondern ein Menüeintrag! Und die Vermischung von dem, was außerhalb der Klasse definiert wird, und dem, was innerhalb der Klasse steht, finde ich auch unsauber, macht die Klasse zumindest schwer wiederverwertbar (if self.mID == ..., obwohl mID außerhalb angegeben werden kann / intransparentes Verhalten).

Edit:

Code: Alles auswählen

def pause():
    input("Hit any key to continue")
ist auch lustig. Ist Enter "any key"? ;)
Zuletzt geändert von nomnom am Mittwoch 17. April 2013, 22:33, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@kevind: Boah, bitte schreibe in die Commit-Messages doch nicht immer nur "Update xyz.py"... :roll:

Dass du Dateien erneuerst, ist ziemlich üblich für nen Commit. Du sollst eher beschreiben, was genau du *am Code* verändert hast. Und jetzt auch nicht immer nur "Update function `foo()`" (auch wenn man das manchmal vielleicht machen kann), sondern lieber etwas wirklich Aussagekräftiges, sodass man die Commits auf einem Blick unterscheiden kann und idealerweise möglichst einfach nachvollziehen kann, was wann geändert wurde. Du hast den Sinn dahinter scheinbar noch nicht so ganz verstanden. ;)
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Das steht wohl atomatisch da... Eigentlich nutze ich das nur um nicht dauernd bei irgendwelche pastbins voll zu spamen... ;)

Aber könnte ich machen..
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Du hast zwei "clear"-Funktionen. Einmal main.clear() und dialog.cls(). Da wäre auch ein Modul mit allen diesen Werkzeugen, die du in mehreren Modulen brauchst, sinnvoll.
Ansonsten schreibt man Konstanten normalerweise GROSS. Gibt auch ein paar Tippfehler im Code. So heißt es "successful" und "wrapped", nicht "successfull" und "wraped".
dialog.msg()
Auch hast du in dialog.msg (warum abgekürzt? message ist besser, und bedeutet mit Code-Completion sogar gefühlt kürzer) in allen Zweigen der if-Abfrage als erstes "box_width = 50".
Typen vergleicht man mit "is_instance":

Code: Alles auswählen

>>> is_instance("foo", str) # besser als  type("foo") == str
True
...

Code: Alles auswählen

elif type(obj) == tuple or list:
:shock: Die Zeile liefert immer True!!!! Sie wird nämlich so ausgewertet:

Code: Alles auswählen

elif (type(obj) == tuple) or (list):
Und da "list" immer True ist, ... Zudem wiederholst du den Code:

Code: Alles auswählen

                    for string in wraped_text:
                        print("{0} {1}".format(box_symbol_side, string))
dialog.line()
Das fänd' ich schöner:

Code: Alles auswählen

print({"small": ...,
       "normal": ...,
       ...}[argument])
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Immer noch vieles Unschönes drin.

Zum Beispiel in `main.py` neben der schon erwähnten schlechten Namensvergabe fällt mir noch auf, dass du `print()` anscheinend nicht richtig verstanden hast. Aus dem ``print("{0} {1}".format(self.mID, self.mlabel))`` in `Menu.print_menu_entry()` lässt sich nämlich viel einfacher ein ``print(self.mID, self.mlabel)`` machen. Die `print`-Funktion sorgt da schon selbst für die nötigen Leerzeichen zwischen den Argumenten, da es die Default-Einstellung für den Trenner ist. Nebenbei ist auch dies ein schlechter Name, denn eine Methode der `Menu`-Klasse muss nicht nochmal erwähnen, dass sie sich auf das Menü bezieht. Hier würde `Menu.print_entry()` besser klingen. Abgesehen davon ist es ja - wie auch schon gesagt wurde - eher ein `MenuEntry`. Und im Grunde ist überhaupt keine `.print()`-Methode nötig. Du könntest besser `__str__()` implementieren und dort die von dir genutzte Formatierung als String zurückliefern. Ein Benutzer der Klasse kann dann (mehr oder weniger implizit) auf die String-Repräsentation zurückgreifen, wenn er die Menüeinträge anzeigen will. Ich denke da an eine `Menu`-Klasse, die eine Liste von `MenuEntry`s als Instanzattribut beherbergt und zum "Rendern" ein ``'\n'.join(self.entries)`` benutzt. Das ist aber wahrscheinlich alles noch ein bißchen zu hoch für dich und von daher sei dir verziehen. ;)

`Menu.is_main()` und `Menu.is_battle()` sind auch nicht schön. Du brauchst dort keinen expliziten Wahrheitswert anzugeben. Es würde reichen, jeweils ``return self.mtype == 'main'`` bzw ``return self.mtype == 'battle'`` anzugeben.

Das mit der `mID` ist viel zu undurchsichtig. Hier solltest du besser Konstanten definieren.

So, und jetzt hab ich keine Lust mehr, zu nörgeln und geh ins Bett... :)
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Oh mann da denkt man jedesmal "jetzt ist es uper" aber dann wird hier einem schnell wieder die Augen geöffnet ;)

Was versteht ihr genau unter Konstanten ? Ich kenne den "variablen typ" konstante, oder eine konstante in form von "123" zb. Wie soll ich das in verbindung mit mID bringen ?
nomnom hat geschrieben:...Und die Vermischung von dem, was außerhalb der Klasse definiert wird, und dem, was innerhalb der Klasse steht, finde ich auch unsauber...
Meinst du das ich auf Methoden von "Game" innerhalb des Menüs zugreiffe ?

Mir ist auch aufgefallen das es eigentlich kein Menü ist... Die einzelnen Instanzen der Klasse Menü sind eigentlich wie erwähnt Menü Einträge wobei jeder Eintrag alle Funktionen des ganzen "Menüs" enthält. Finde ich nicht so schön. Mir ist bisher leider kein besserer Weg eingefallen. (Für Ideen bin ich offen :))

@snafu

Naja ich verwende "print" hab es aber nicht studiert... der Grund warum ich print mit {} vewendet habe ist damit alle print gleich sind. (Zugegeben sind meine Ideen auch nicht immer die besten ;))

Bzgl. der Menü Klasse mit den Entrys, meinst du dass beim erstellen eines "MenüEntrys" sich dieser automatisch in ein "Menü Klassenobjekt" hinzufügt ?

Danke für deine aufbauenden Worte :roll:
Zuletzt geändert von kevind am Freitag 19. April 2013, 08:02, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo kevind,

Deine neue »Attribute«-Klasse enthält lauter Getter die völlig unnötig sind. In Python greift man direkt auf Attribute zu und benutzt im Gegenteil Properties, falls sich doch noch mehr dahinter versteckt.

»vitality« und »max_vitality« scheinen mir eine einziges Attribut mit Maximal-Wert zu sein. Dieses besondere Verhalten solltest Du nicht in Deiner Character-Klasse abbilden sondern dafür eine neue Attributklasse erzeugen.

In CreateGame.damage_calc gibt es den Zweig »not npc«, »npc« und was soll dann noch das dritte sein?
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Hey Sirius3,

du meinst hier "name, value, ID" ?

Wie ich das mit Vitality noch anders lösen könnte werde ich überlegen.

Ja du hast recht das ist quatsch. Da würde eigentlich "if, else" reichen.

Danke soweit !
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@kevind: ja genau
Dann wird »Attribute« einfach:

Code: Alles auswählen

class Attribute():
    def __init__(self, atr_value, atr_name):
        self.value = atr_value
        self.name = atr_name

    def reduce(self, amount=5):
        self.value -= amount

    def improve(self, amount=5):
        self.value += amount

    def improve_for_newround(self):
        self.value += random_value() + random_value()

    def __str__(self):
        return "{}: {}".format(self.name, self.value)

class AttributeWithMaxValue(Attribute):
    def __init__(self, atr_value, atr_name, max_value):
        Attribute.__init__(self, atr_value, atr_name)
        self.max_value = max_value

    def improve_for_newround(self):
        self.max_value += random_value() + random_value()
        self.value = self.max_value

    def __str__(self):
        return "{}: {}/{}".format(self.name, self.value,self.max_value)
ich habe noch die ID entfernt, weil überflüssig, eine Attribut mit Maximalwert eingeführt und eine __str__-Methode hinzugefügt, z.B. für:

Code: Alles auswählen

    def details(self, game_round):
        print("\n\tName: {0} \t\t Level: {1}".format(self.name, self.level))
        print("\tExp: {0}/{1} \t\t Round:".format(self.exp,
            self.exp_levelup, game_round))
        print("\n{}\t{}\n{}\t{}\n".format(self.strength, self.vitality, self.dextery, self.defense)
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Genial, danke!

Ich hab mir in der zwischenzeit auch das __str__ statement angeschaut. Dies ist aber auch nur anwendbar wenn die Methode 1 String zurückgeben soll oder?

Wenn ich jetzt das Menü neu aufbaue möchte. Müsste ich dann die Game Instanz von Menüpunkt zu Menüpunkt übergeben oder wie stell ich sowas ordentlich an ?

Kann mir da jemand ein bischen "Starthilfe" geben :) ?

Gruss, Kev
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Servus,

meine Character Klasse arbeitet mit den Werten der Attribute Klasse. Nun möchte ich vermeiden das ich innerhalb der Character Klasse direkt auf die Attribute zugreife. (Klassen sollen ja nichts über das Innenleben einer anderen Klasse wissen oder ?)

Nun würde ich gerne zb folgendes machen:

Code: Alles auswählen

damage = character.atk - character.defense
um dieses hier zu vermeiden:

Code: Alles auswählen

damage = character.atk.value - character.defense.value
Ich dachte ich könnte das irgendwie mit der __str__ methode lösen aber das funktioniert nicht.

Die Attribut Klasse sieht momentan so aus:

Code: Alles auswählen

class Attribute():
    def __init__(self, atr_ID, atr_value, atr_name):
        self.ID = atr_ID
        self.value = atr_value
        self.name = atr_name

    def __str__(self):
        return(self.value)

    def reduce(self, amount):
        self.value -= amount

    def improve(self):
        self.value += 5

    def improve_for_newround(self):
        self.value += random_value() + random_value()

    def is_ID(self, possible_id):
        if self.ID == possible_id:
            return True
        else:
            return False
Danke für eure Ratschläge.

ps: Ja das mit der ID muss ich noch regeln... Eins nach dem Anderen ;)
BlackJack

@kevind: Was hat denn `__str__()` damit zu tun? Das soll eine Zeichenkette zurück geben. Und man müsste dazu dann auch `str()` mit dem Exemplar aufrufen. Zeichenketten kann man schlecht voneinander abziehen.
Benutzeravatar
kevind
User
Beiträge: 71
Registriert: Montag 22. Oktober 2012, 20:23
Wohnort: /dev/null

Ich dachte vl. lässt sich das noch in INT umwandeln. Aber vergessen wir das mal.

Was würde sich für diese Situation am besten eignen ?
Antworten