Search code examples
pythonobjectpygametile

"Nonetype object has no attribute 'take turn' in Pygame. Hung up on a single line


I am creating a rogue-like game with python 2.7, using pygame and libtcodpy libraries. It is a tile based game, and when I tried to implement a function called 'take turn' that would move the enemy one space to the left every time the player moved - it applied the same function to the player and the result was extremely buggy and made "PLAYER" move left one space as well. The error occurs in line 101. I was seeing if somebody could point out the flaw. I've tried to solve it using this advice, but can't figure out where he wants me to put the line of code:

"Even after I put self.ai = ai outside of the if-statement I still get the error that NoneType doesn't have a function called take_turn.

I suspect an error around anything related to the 'ai' and also 'take_turn' functions.

I solved it by creating a new ai called ai_Player and in the take_turn function I just put return, so it does nothing. Works like a charm. (Don't forget to add the AI to the player)"

my code as follows (stars indicate where I removed code and made the game run again):

class obj_Actor:
    def __init__(self, x, y, name_object, sprite, creature = None, ai = None):
        self.x = x #map address
        self.y = y #map address
        self.sprite = sprite

        self.creature = creature
        if creature:
            creature.owner = self

        **self.ai = ai
        if ai:
            ai.owner = self**

    def draw(self):
        SURFACE_MAIN.blit(self.sprite, (self.x*constants.CELL_WIDTH, self.y*constants.CELL_HEIGHT))

    def move(self, dx, dy):
        if GAME_MAP[self.x + dx][self.y + dy].block_path == False:
            self.x += dx
            self.y += dy

**class ai_Test:
    def take_turn(self):
        self.owner.move(-1, 0)**

def game_main_loop():

    game_quit = False

    #player action definition
    player_action = 'no-action'

    while not game_quit:

        #handle player input
        player_action = game_handle_keys()

        if player_action == 'QUIT':
            game_quit == True

        **if player_action != 'no-action':
            for obj in GAME_OBJECTS:
                obj.ai.take_turn()**
        #TODO draw the game
        draw_game()

    #quit the game
    pygame.quit()
    exit()

def game_initialize():
    #This function, initializes main window, and pygame

    global SURFACE_MAIN, GAME_MAP, PLAYER, ENEMY, GAME_OBJECTS

    #initialize pygame
    pygame.init()

    SURFACE_MAIN = pygame.display.set_mode( (constants.GAME_WIDTH,constants.GAME_HEIGHT) )

    GAME_MAP = map_create()

    creature_com1 = com_Creature('Greg')
    PLAYER = obj_Actor(0, 0, "Human", constants.S_PLAYER, creature = creature_com1)

    creature_com2 = com_Creature('Skeletor')
    **ai_com = ai_Test()**
    ENEMY = obj_Actor(15, 15, 'Skeleton', constants.S_ENEMY, **ai = ai_com)**

    GAME_OBJECTS = [PLAYER, ENEMY]

    #execute game

def game_handle_keys():
    #gets player input
    events_list = pygame.event.get()
    #process player input
    for event in events_list:
        if event.type == pygame.QUIT:
            return 'QUIT'

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                PLAYER.move(0, -1)
                return 'player-moved'

            if event.key == pygame.K_DOWN:
                PLAYER.move(0, 1)
                return 'player-moved'

            if event.key == pygame.K_LEFT:
                PLAYER.move(-1, 0)
                return 'player-moved'

            if event.key == pygame.K_RIGHT:
                PLAYER.move (1, 0)
                return 'player-moved'
    return "no-action"

if __name__ == '__main__':
    game_initialize()
    game_main_loop()

Traceback (most recent call last):
  File "/Users/wills/Desktop/my-pygame/my_pygame.py", line 152, in <module>
    game_main_loop()
  File "/Users/wills/Desktop/my-pygame/my_pygame.py", line 93, in game_main_loop
    obj.ai.take_turn()
AttributeError: obj_Actor instance has no attribute 'ai'


Solution

  • I think your problem is here:

    I thought by not assigning it a specific ai, PLAYER which is initialized in the initialize function would ignore the "take turn" function.

    If you set something to None, it doesn't ignore everything you call on it, it raises an AttributeError any time you try to call anything.


    One option is to just check the value before you call it:

    for obj in GAME_OBJECTS:
        if obj.ai:
            obj.ai.take_turn()
    

    If this is definitely the only place you'll ever use that ai attribute, this is probably the best answer.


    But if you're going to use it in multiple places, it's way too easy to do the check in 7 places but forget the 8th and get mysterious errors that only happen in weird circumstances. In that case, you really do want it to act the way you were expecting it to act, and the way to do that is to assign some "dummy" object that automatically ignores the "take turn" function.

    The simplest version is:

    class DummyPlayer:
        def take_turn(self):
            pass
    

    And then, when you were assigning the player to None, assign it to a DummyPlayer instance. I think that's here:

    class obj_Actor:
        def __init__(self, x, y, name_object, sprite, creature = None, ai = None):
            # etc.
    
            if ai:
                ai.owner = self
            else:
                ai = DummyPlayer()
            self.ai = ai
    
            # etc.
    

    Now, self.ai will always be something with a take_turn method—either a real AI, or a DummyPlayer—so it's always safe to just call that method without checking.