@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:
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.