Seite 1 von 2
Finde meinen Fehler einfach nicht
Verfasst: Sonntag 3. Januar 2010, 22:33
von Matty265
Hallo,
ich habe ein kleines Terminal-RPG geschrieben, bzw bin dabei, und finde einfach meinen Fehler zum verecken nicht.
Ich wusste nicht wo ich mich sonst hätte melden können, hoffe ich bin hier richtig
Code: Alles auswählen
# -*- coding: iso-8859-1 -*-
from random import *
def fusssoldat():
geg_leben = randint(20,25)
return geg_leben
def schlag():
cache1 = randint(1,10)
if cache1 == 2:
haerte = 0
return haerte
else:
haerte = randint(15, 20)
return haerte
def kritisch():
cache1 = randint(1, 5)
if cache1 == 2:
haerte = randint(20,26)
return haerte
else:
haerte = 0
return haerte
def runde():
print "Ein Fußsoldat!!!"
print "Lebenspunkte: ", cache
x = int(raw_input("1: Normaler Schlag, 2: Kritischen Schlag versuchen "))
if x == 1:
staerke = schlag()
cache = seinleben-staerke
print "Übrige Lebenspunkte: ", cache
elif x == 2:
staerke = kritisch()
cache = seinleben-staerke
print "Übrige Lebenspunkte: ", cache
eigenes_leben = 100
hilfe = 0
cache = fusssoldat()
while cache > 0:
runde()
Hier die Fehlernachricht:

Verfasst: Sonntag 3. Januar 2010, 22:35
von tordmor
Woher kommt seinleben in Zeile 30?
Verfasst: Sonntag 3. Januar 2010, 22:40
von Matty265
Ist gefixt, da sollte auch "cache" hin.
Trotzdem immernoch der gleiche Fehler
Verfasst: Sonntag 3. Januar 2010, 22:45
von start_with_python
Ganz einfach: Fehlermeldung lesen und verstehen!
"UnboundError: local variable 'cache' referenced before assignment"

Variable 'cache' benutzt obwohl sie noch nicht existiert.
Zudem sagt die Fehlermelung, dass das ganze in Zeile 26 in der Funktion "runde".
Verfasst: Sonntag 3. Januar 2010, 22:46
von Matty265
Schon klar, aber sie wird doch mit "cache = fusssoldat()" generiert oder nicht?
Verfasst: Sonntag 3. Januar 2010, 22:50
von numerix
Matty265 hat geschrieben:Schon klar, aber sie wird doch mit "cache = fusssoldat()" generiert oder nicht?
Ja, ABER: Da du in der Funktion runde() im weiteren Verlauf eine Zuweisung an einen Bezeichner namens cache vornimmst, wird eine solche Variable in den lokalen Namensraum eingeführt, die den globalen Bezeichner "cache" überschattet. Somit versuchst du, auf die lokale Variable cache zuzugreifen, bevor sie definiert wurde ...
Verfasst: Sonntag 3. Januar 2010, 22:52
von start_with_python
Jau, aber nur lokal. Du musst die Daten weitergeben. So bswp:
Code: Alles auswählen
def runde(deinedaten):
cache = deinedaten
cache=fusssoldat()
runde(cache)
[/code]
Verfasst: Sonntag 3. Januar 2010, 22:54
von Dauerbaustelle
Das ist in der Tat ein seltsames Verhalten. In Zeile 30 bzw 34 erzeugst du eine lokale Variable "cache". Wenn der Interpreter in Zeile 26 angekommen ist, wurde diese allerdings noch nicht erzeugt (lokal). Sie existiert aber global. Das erwartete Verhalten wäre imho, dass er die globale Variable nimmt. Tut er aber nicht.
Es scheint, als würde der Parser beim Interpretieren einer Zeile schon von den nächsten Zeilen Bescheid wissen. Weird.
Verfasst: Sonntag 3. Januar 2010, 22:57
von Matty265
Ah, ok alles klar.
Nur wie könnte ich das Problem lösen?
Das Problem ist ja, das auf cache die aktuellen Lebensdaten gespeichert sind, welche ja immer weiter benutzt werden sollen. Und da die Funktion in einer Schleife ist, sollten diese Daten ja ausserhalb der Funktion definiert werden, da ja sonst die Daten immerwieder resettet werden
Verfasst: Sonntag 3. Januar 2010, 23:16
von jbs
Code: Alles auswählen
# -*- coding: iso-8859-1 -*-
from random import randint
fusssoldat = lambda: randint(20,25)
def schlag():
#chance von 1 zu 10 nicht zu treffen
if not randint(0,9):
return 0
else:
return randint(15, 20)
def kritisch():
#chance von 1 zu 5 zu treffen
if not randint(0,4):
return randint(20,26)
return 0
def runde(f_leben):
print "Ein Fußsoldat!!!"
print "Lebenspunkte: ", f_leben
x = int(raw_input("1: Normaler Schlag, 2: Kritischen Schlag versuchen "))
if x == 1:
staerke = schlag()
elif x == 2:
staerke = kritisch()
f_leben = f_leben-staerke
print "Übrige Lebenspunkte: ", f_leben
return f_leben
def game():
eigenes_leben = 100
hilfe = 0
f_leben = fusssoldat()
while f_leben > 0:
f_leben = runde(f_leben)
game()
edit: Hab mal angefangen das neu aufzuziehen:
http://paste.pocoo.org/show/161762/
Verfasst: Sonntag 3. Januar 2010, 23:28
von Hyperion
@OP: Du solltest Dir mal PEP8 durchlesen (s. auch Sig von jbs

). Die Einrückung sollte eigentlich 4 Spaces sein - bei Dir sieht es nach 8 aus.
Außerdem solltest Du Dir mehr Gedanken über die Bennenung von Objekten und Bezeichnern machen. "schlag" würde eher auf eine Klasse als auf eine Funktion hinweisen! Gleiches gilt für "fussoldat" - wieso nicht "create_footsoldier()"? Gleiches gilt für "schlag" und "kritisch". Auch da würde sich ein "do_xyz" besser machen oder eben ein richtiges passendes Verb. Das mag Dir im Moment nicht so wichtig vorkommen, aber bei größeren Programmen und nach einiger Zeit wieder angefasster Software macht sich so etwas bezahlt!
Zudem solltest Du auch auf Dokumentation achten. Was macht eine Funktion? Was liefert sie zurück? Was für Parameter erwartet sie? (String, Iterable, Dictionary, File-like-Object, ...)
Verfasst: Montag 4. Januar 2010, 00:27
von sma
Erstens, man kann auch aus einer DOS-Konsole kopieren oder den Fehler einfach abschreiben. Dabei fällt einem dann vielleicht ja auch auf, dass der Python-Interpreter genau auf die fehlerhafte Zeile zeigt und sogar (auf Englisch) den konkreten Fehler meldet. Aber das haben ja schon andere angesprochen.
Die Regel, das eine Variable als lokal zählt, wenn *irgendwo* in einer Funktion eine Zuweisung an diese Variable steht, ist nun mal, wie Python (Ruby übrigens auch) funktioniert. Seltsam ist das nicht.
Auf die Frage, wie man das Problem löst: Am besten ohne veränderbare globale Variablen. Ich habe auch mal etwas gebaut, das man unter
http://paste.pocoo.org/show/161775/ findet. Der Zufall will, dass ich gerade an so etwas ähnlichem bastle.
Ein paar Erklärungen: Ich ziehe `randrange` dem `randint` vor, weil sich dies so wie andere Range-Funktionen verhält und man sich nicht merken muss, wann denn nun das Ende Teil des Intervalls ist und wann nicht. Geschmackssache. Die Funktion `d` würfelt 1W6 (also einen 6-seitigen Würfel). Die Hilfsfunktion `critical` entscheidet, ob ein Wurf von 3W6 als kritischer Treffer gewertet wird, was passiert, wenn ein Pasch dabei ist. Die Klasse `Character` repräsentiert die Kämpfer. Man könnte sich diverse Attribute vorstellen, doch ich habe mich auf den Namen, einen Wert für die Verteidigung (was eine Rüstung repräsentieren könnte), die Waffe (wovon es nur eine gibt) und Lebenspunkte entschieden. Waffen repräsentiere ich mit der Klasse `Weapon`. Sie haben einen Namen und eine Funktion, die den zufälligen Schaden bestimmt. Ein Speer habe den Schaden 1W6+3, ein Breitschwert macht 2W6 Punkte Schaden. Die Methode `Character.attack` lässt den Charakter einen anderen angreifen. Ein kritischer Treffer verdopple einfach den Schaden. Hier könnte man (wie bei meinem Regelvorbild, dem Dragon Age RPG, natürlich auch andere Dinge machen, um den Kampf etwas abwechslungsreicher zu gestalten). Ein Charakter ist tot, wenn die Lebenspunkte 0 erreichen. Die Klasse `Combat` hat eine Methode `do` (was ein total schlechter Name ist), die einen Kampf bis zum Tod einer der beiden beteiligten Parteien durchführt. Das müsste jetzt nicht unbedingt eine Klasse sein, oder hier würde auch eine Funktion ausreichen, aber will man von 2 Parteien auf N gehen oder das ganze noch stärker parametrisieren, zeigt die Erfahrung, dass auch hier eine Klasse sinnvoll ist.
Thoma hat übrigens gute Chancen gegen zwei Fußsoldaten, verliert aber regelmäßig gegen drei.
Stefan
Verfasst: Montag 4. Januar 2010, 00:36
von snafu
@Hyperion: Ob überall "make_", "create_", "get_", "do_", ... davorsteht, spielt IMHO eine eher untergeordnete Rolle, wenn die Namen nicht das beschreiben, was die Funktion tut, bzw das Konzept eher schlecht aufgebaut ist. jbs hat da schon einige Ansätze in seinem Edit geliefert, auch wenn ich persönlich noch ein paar Dinge anders machen würde.
Verfasst: Montag 4. Januar 2010, 00:38
von jbs
Ich hatte `randrange` total vergessen

.
@sma: Wie hast du denn vor die Typen der Feinde zu verwalten?
Verfasst: Montag 4. Januar 2010, 01:00
von BlackJack
@Dauerbaustelle: Der Parser interpretiert nichts sondern ist noch vor dem Compiler, den er mit den geparsten Token aus dem Quelltext füttert. Und bevor ein Modul ausgeführt wird, werden erst einmal alle enthaltenden Klassen, Funktionen, und Methoden in Bytecode übersetzt. Und bei *diesem* Schritt wird auch entschieden was lokal und was "global" ist. Wenn in einer Funktion eine Zuweisung an einen Namen passiert, dann ist der Name lokal, es sei denn er wurde als ``global`` deklariert (oder ``nonlocal`` bei Python 3).
Letztendlich sind beide Verhalten verwirrend. Denn wenn `cache` in einer Funktion zweimal vorkommt, aber in einem Fall auf einen anderen Namensraum zugegriffen wird, als in dem anderen, dann ist das sicher auch nicht besonders durchschaubar. Noch interessanter wird es, wenn dann noch bedingte Ausführung oder Schleifen ins Spiel kommen. Beispiele:
Code: Alles auswählen
a, b = 47, 11
def spam():
if blah:
a = 42
print a
for i in xrange(23):
print b
b = ham(i)
Ob die ``print``-Anweisungen auf die Modulebene oder lokal zugreifen, würde jetzt von einer Bedingung bzw. von der Nummer der Schleifeniteration abhängen. Wäre IMHO auch nicht besser.
Die jetzige Implementierung hat den Vorteil, dass die Namen und die Anzahl der lokalen Namen dem Compiler schon bekannt sind, und dass er deshalb Bytecode erzeugen kann, bei dem nicht mehr über den Namen in einem Dictionary nach den Objekten gesucht werden muss, sondern dass die Objekte zu den Namen in einem Array abgelegt werden, und der Zugriff über feste Indices passiert. Das ist schneller.
Verfasst: Montag 4. Januar 2010, 03:32
von Dauerbaustelle
BlackJack hat geschrieben:@Dauerbaustelle: Der Parser interpretiert nichts sondern ist noch vor dem Compiler, den er mit den geparsten Token aus dem Quelltext füttert.
Ich weiß doch, ich weiß doch, das war nur ein Wortmismatch :-D
Verfasst: Montag 4. Januar 2010, 06:57
von Hyperion
snafu hat geschrieben:@Hyperion: Ob überall "make_", "create_", "get_", "do_", ... davorsteht, spielt IMHO eine eher untergeordnete Rolle, wenn die Namen nicht das beschreiben, was die Funktion tut,...
Sicher. Nur gibt es eben oftmals kein explizites Verb, was die "Funktion" einer Funktion beschreibt. Daher kann man sich in diesen Fällen gut mit der Kombi aus Verb und Infinitiv helfen. Ich hatte das ja anhand des Beispiels versucht zu erläutern. Ich gebe zu, dass der explizite Hinweis auf die Sinnhaftigkeit der Bennenung bei mir fehlt - aber den lieferst Du ja nun nach

Verfasst: Montag 4. Januar 2010, 12:45
von sma
In der Regel ist es eine gute Idee, Methoden mit Verben zu beschreiben. Ausnahmen bestätigen die Regel. In Smalltalk beispielsweise schreibt man Getter- und Setter-Methoden wie in Python die Attribute direkt ohne Verb. Dafür benutzt man dort für Boolsche Attribute gerne ein "is" oder "can" während das in Ruby (oder Scheme) meist durch ein "?" am Ende angedeutet wird. In Java wiederum hat sich "get" und "set" für Accessor-Methoden durchgesetzt.
Aber wenn ich z.B. eine Klasse "CharacterCreator" hätte, die Methoden hat, mit denen ich initialisierte Objekte wie z.B. Fußsoldaten erzeugen kann, dann würde ich dort dennoch "create_foot_soldier" und nicht einfach nur "foot_soldier" als Methodennamen wählen, selbst wenn das dadurch ein bisschen redundant wird. Doch das "create" deutet an, dass jeder Aufruf ein neues Objekt erzeugt und somit die Methode (im Gegensatz zu einem Getter) einen Seiteneffekt hat.
Nicht 100% passend aber Lesenswert in diesem Fall auch
http://steve-yegge.blogspot.com/2006/03 ... nouns.html
Stefan
Verfasst: Montag 4. Januar 2010, 12:47
von sma
jbs hat geschrieben:@sma: Wie hast du denn vor die Typen der Feinde zu verwalten?
Was meinst du mit Typen? Die Klassen? Oder verschiedene Arten wie Goblin-Bogenschützen, Elfenreiter oder Orkwache?
Stefan
Verfasst: Montag 4. Januar 2010, 12:54
von jbs
Die verschiedenen Arten.