Skip to main content
gamedev.net
🔒 Locked

[Python] Help needed again

Started by RottenTree Sep 6, 2009 at 3:13 PM 8 replies 1.4k views
Original Post
RottenTree
RottenTree
http://www.gamedev.net/community/forums/topic.asp?topic_id=542869

class Sheep:
    def __init__(self):
        self.alive = True
        self.afraid = False

    def die(self): self.alive = False

    def fear(self): self.afraid = True

    def __str__(self):
        if (!self.alive): return "a dead sheep"
        # it doesn't make sense to ask about whether a dead sheep is frightened!
        if (self.afraid): return "a scared sheep"
        return "an ordinary sheep"

class Wolf:
    def __init__(self, action):
        self.action = action
        # Oluseyi is correct. Nothing to see here, move along ;)

    # Because of the naming, we no longer actually have to implement
    # an 'action' method. We instead store a function as data, and call it.
    # The power of Python :)

sheep = Sheep()
print "The sheep is", sheep
wolves = [Wolf(sheep.fear), Wolf(sheep.die)]
for wolf in wolves:
    wolf.action()
    print "The sheep is", sheep

So this is the corrected code you wrote for me I wanted to take it further but I ran into a problem which pretty much demotivated but now I'm trying to get back :) What I wanted to do is to make a reusable method for example:

#inside sheep class
def set_to_false(self,bool_var):
    bool_var = False

def die(self):
    self.set_to_false(self.alive)
#but this isn't working the problem is that I don't know how to tell it that
#I want to change the variable passed to it and not bool_var

I know this isn't very useful with this current code but I think with a more complex project it could be quite useful and it's seriously annoying me that I don't know how to solve it >:(
Zahlman
Zahlman
Here's what actually happens: when you call the function, the name 'bool_var' is set to refer to 'self.alive'. Then, when 'bool_var = False' is executed, the name 'bool_var' is re-set to refer to 'False'. The variable changes (though not the one you want), but the value doesn't.

In Python, an assignment always causes the variable to refer to a different value. It doesn't affect the previous value represented by the variable. Meanwhile, most built-in value types are 'immutable' (can't be changed; that's an ordinary English word, not just a programming term). You can cause a variable that was once False to be True, but you can't cause False, itself to become True.

(Actually, that's a little inaccurate, because 'False' isn't really the value, but a pre-defined, global variable named 'False', which refers to that value. But there is an underlying value, that can't change.)

But what you can do is design a value type to be mutable (either by using a list or a dict, which are mutable, or by creating a class) and then having the function mutate whatever is passed in. Then, the Python semantics are working to your advantage: because the function's parameter refers to the same value as the one that is passed in, the calling context will "see" the change.

In your case, this could be as simple as using the class you already have - passing it in, and having the function change the named data member.

# No particular reason this needs to be inside a class.def kill(target):  target.alive = False  # See what happens here? 'target' refers to the same value as the caller  # knows about, so the caller sees the change. Meanwhile, the 'alive' member  # stored *within* the 'target' stops referring to whatever it used to refer  # to, and starts referring to what False refers to - i.e. the primitive  # false value. (Unless you changed False to refer to something else. But  # for obvious reasons this is generally considered an extremely bad idea :) )# Then, inside the Sheep class, you could have:def die(self):    kill(self)
RottenTree
RottenTree
@_fastcall

I was told(in the thread I mentioned) that passing by reference is not truly the object orientated way. Besides it looks ugly and confusing for me :P but thanks.

@Zahlman
I might be dumb but I still don't get it :| I mean I think I understood what you wrote but:

What if I wanted to change the members of the same object?(for example: sheep.alive, sheep.hungry)
Not the same(as in name) member of objects.
How would that look?
Captain P
Captain P
Quote:
Original post by Zahlman
Here's what actually happens: when you call the function, the name 'bool_var' is set to refer to 'self.alive'.

Shouldn't that be: 'bool_var is set to refer to what self.alive refers to'?


True and False are the (only) two instances of the 'bool' class. 'self.alive = True' means that self.alive now refers to that True instance. When you pass self.alive to a function, it gets a copy of the reference, so that copy (bool_var) will refer to the same object (True), but the original reference (self.alive) can not be accessed or modified from within that function.
RottenTree
RottenTree
So I'm out of luck :( ?

I guess I will have to cook up something or just do it the simple way once I have finished it I think I will post it to get it reviewed/criticised :)
Captain P
Captain P
For booleans, yes, you're out of luck if you're looking for a simple way. But wait, you're not: 'self.alive = False' is simple, and does exactly what you want. ;) Writing a function for that is not only overkill, it's also obfuscating your code.

What Zahlman suggested, a 'kill' function that sets it's arguments 'alive' member to False, would be more useful, as it performs a sensible operation (killing an object). Perhaps you'll want to set a killed objects hitpoints to 0 as well, or inform other objects (hey hunter, your prey has died!), those are reasons why it would be useful to have such a function. But if the operation is 'set a bool to False'... then just do what you want to do directly and set that bool to False. ;)


So:
class Sheep:   # Other methods go here   def die(self):      self.alive = False      self.hitpoints = 0
or:
def kill(sheep):   sheep.alive = False   sheep.hitpoints = 0
but not:
def set_to_false(bool_var):   bool_var = False
RottenTree
RottenTree
I think I sort of cleaned up it I made it with pygame(I don't know how many of you know it) but I'm sure you will find plenty of mistakes in it :)

So here it is (I will mention just in case that it's a menu and that the comments are for myself):

import pygamefrom pygame.locals import *pygame.font.init()wscreen=800hscreen=600screen = pygame.display.set_mode((wscreen,hscreen))fsize = 50font = pygame.font.SysFont("Times New Roman",fsize)clock = pygame.time.Clock()class Base:    def __init__(self):        self.running = True        self.menu_y = 0        self.menu_z = 0        self.n_menus = []#navigate        self.s_menus = []#switch        self.sl_menus = []#switchlist        self.sh_menus = []#shutdown        self.every_menu = []        self.curr_menu = []        self.curr_section = None        self.opt_music = True        self.opt_quality = ('High','Medium','Low')            def down(self):        self.menu_y += 1        print 'down'            def up(self):        self.menu_y -= 1        print 'up'    def create_menu(self,ilk,name,y,z,target=None,do=None):        temp = ilk(name,y,z,target,do)                self.every_menu += [temp]        if ilk == Menu:            self.n_menus += [temp]        if ilk == Shutdown:            self.sh_menus += [temp]        if ilk == Switch:            self.s_menus += [temp]        if ilk == SwitchList:            self.sl_menus += [temp]                def end(self):        base.running = False        pygame.quit()    def go_to(self,target):        self.menu_z = target        self.menu_y = 0    def change_music(self):        if self.opt_music:            self.opt_music = False        else:            self.opt_music = True    def change_quality(self,setting):        self.opt_quality = setting        def load_curr(self):        self.curr_menu = []        for section in self.every_menu:            if self.menu_z == section.z:                self.curr_menu += [section]                    def draw_menus(self):                count = 0        screen.fill((0,0,0))                for section in self.curr_menu:            if self.menu_y < 0:# so you don't go 'above' the menu                self.menu_y = len(self.curr_menu)-1            if self.menu_y >= len(self.curr_menu):# same but 'below'                self.menu_y = 0                        if section.y == self.menu_y:                section.active = True                self.curr_section = section            else:                section.active = False                                        if section.active:                screen.blit(section.title_a,(wscreen/2.5,(hscreen/3)+50*count))                                else:                screen.blit(section.title,(wscreen/2.5,(hscreen/3)+50*count))            if section in self.s_menus:                if section.state:                    screen.blit(section.on,(wscreen/2.5+section.width+20,(hscreen/3)+50*count))                else:                    screen.blit(section.off,(wscreen/2.5+section.width+20,(hscreen/3)+50*count))                            if section in self.sl_menus:                screen.blit(section.state,(wscreen/2.5+section.width+20,(hscreen/3)+50*count))                        count += 1                    pygame.display.update()class Menu:    def __init__(self,name,y,z,target,do):        self.name = name                self.y = y        self.z = z        self.do = do                self.target = target        self.white = (240,240,240)        self.yellow = (244,215,0)        self.title = font.render(self.name,True,self.white)        self.title_a = font.render(self.name,True,self.yellow)                self.active = False    def action(self):        base.go_to(self.target)        base.load_curr()        class Switch(Menu):    def __init__(self,name,y,z,target,do):        Menu.__init__(self,name,y,z,target,do)        self.width = self.title.get_width()        self.on = font.render('On',True,self.white)        self.off = font.render('Off',True,self.white)                self.active = False        self.state = True    def action(self):        self.target()        if self.state:            self.state = False        else:            self.state = Trueclass SwitchList(Menu):    def __init__(self,name,y,z,target,do):        Menu.__init__(self,name,y,z,target,do)                self.width = self.title.get_width()                self.active = False        self.counter = 0        self.choice = self.target[self.counter]        self.state = font.render(self.target[self.counter],True,self.white)                self.switch = False    def action(self):        self.counter -= 1        if self.counter < 0:            self.counter = len(self.target)-1        self.choice = self.target[self.counter]        self.state = font.render(self.choice,True,self.white)        self.do(self.choice)class Shutdown(Menu):    def __init__(self,name,y,z,target,do):        Menu.__init__(self,name,y,z,target,do)                self.white = (240,240,240)        self.yellow = (244,215,0)        self.title = font.render(self.name,True,self.white)        self.title_a = font.render(self.name,True,self.yellow)                self.active = False            def action(self):        base.end()base = Base()base.create_menu(Menu,'New Game',0,0,0)#placeholderbase.create_menu(Menu,'Options',1,0,1)base.create_menu(Shutdown,'Exit Game',2,0)base.create_menu(Switch,'Music:',0,1,base.change_music)base.create_menu(SwitchList,'Quality:',1,1,base.opt_quality,base.change_quality)base.create_menu(Menu,'Back',2,1,0)base.load_curr()base.draw_menus()while base.running:   clock.tick(30)   for event in pygame.event.get():        if event.type == pygame.QUIT:            running = False            pygame.quit()        if event.type == pygame.KEYDOWN:            if event.key == K_UP:                base.up()            if event.key == K_DOWN:                base.down()            if event.key == K_RETURN:                print 'enter'                base.curr_section.action()            base.draw_menus()
Zahlman
Zahlman
Quote:
Original post by RottenTree
What if I wanted to change the members of the same object?(for example: sheep.alive, sheep.hungry)
Not the same(as in name) member of objects.
How would that look?


You can use any of a variety of reflective techniques. For example:

def set_member_false(instance, member_name):  # 'setattr' is a built-in Python function.  # Python objects behave like dictionaries, so their members can actually  # be looked up in the same way - through a wrapper such as this.  setattr(instance, member_name, False)  # Another technique is:  # instance.__dict__[member_name] = Falseclass Sheep:  def die(self):    set_member_false(self, 'alive') # notice the quotes!


However, now you are doing even more work for no real benefit.

@Captain P: You are correct, of course. The problem is that it's too easy to speak informally when formality is required to be clear. :)
RottenTree
RottenTree
Thanks that's quite handy!
Maybe it's not useful with my current code but I had to know how it's done because once I got the idea I couldn't get it out of my head and because I failed to implement it a couple of times it got pretty annoying.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.