-1

I was creating some buttons for a menu being written using Pygame, when I noticed something that didn't seem to add up. Here's what the code currently looks like:

    #Load up the modules
    import pygame, time, random, linecache
    pygame.init()

    #Let's get some basic colours stored.
    black = (0,0,0)
    gray = (123,123,123)
    white = (255,255,255)
    red = (200,0,0)
    bright_red = (255,0,0)
    green = (0,200,0)
    bright_green = (0,255,0)
    blue = (0,0,200)
    bright_blue = (0,0,255)
    yellow = (225,225,0)
    bright_yellow = (255,255,0)

    #Loading up a time obect function for later.
    clock = pygame.time.Clock()

    #This function is necessary all round.
    #n is the line to be read.
    def readline(file, n):
            current_line = linecache.getline(file , n)
            current_line = current_line[:-1]
            return current_line

    #Let's set the screen dimensions based on the options file.
    screen_width = int(readline('Options.txt', 2))
    screen_height = int(readline('Options.txt', 3))
    game_display = pygame.display.set_mode((screen_width,screen_height))
    pygame.display.set_caption('Le jeux')

    #Functions for displaying text.
    def text_objects(text, font, colour):
        textSurface = font.render(text, True, colour)
        return textSurface, textSurface.get_rect()

    def message_display(text, x_ordinate, y_ordinate, font_size, colour):
        #Consider letting the font be a paramter.
        largeText = pygame.font.Font("freesansbold.ttf",font_size)
        TextSurf, TextRect = text_objects(text, largeText, colour)
        TextRect.center = (x_ordinate,y_ordinate)
        game_display.blit(TextSurf, TextRect)


    def button(x, y, width, height, active_colour, inactive_colour, message, font_size, function):
        if(x <= mouse[0]/screen_width <= x + width and y <= mouse[1]/screen_height <= y + height):
            pygame.draw.rect(game_display, active_colour, (x * screen_width, y * screen_height, width * screen_width, height * screen_height))
            message_display(message, round(screen_width * (x + width/2)), round(screen_height * (y + height/2)), round(font_size * screen_height), black)
            function
        else:
            pygame.draw.rect(game_display, inactive_colour, (x * screen_width, y * screen_height, width * screen_width, height * screen_height))
            message_display(message, round(screen_width * (x + width/2)), round(screen_height * (y + height/2)), round(font_size * screen_height), black)

    def my_test(x, y, width, height):
        if(x <= mouse[0]/screen_width <= x + width and y <= mouse[1]/screen_height <= y + height):
        #The line above seems completely redundant yet for some reason is essential
            print("hi")

    #Creating a few variables for the loops to come.
    viewing_menu = True
    playing_game = False

    #Showing a very basic menu.
    while viewing_menu == True:

        for event in pygame.event.get():
            if(event.type == pygame.QUIT) :
                viewing_menu = False

        #Determining the mouse's current position
        mouse = pygame.mouse.get_pos()

        game_display.fill(white)


        button(0.25, 0.1, 0.5, 0.2, bright_green, green, 'Play', 0.15, my_test(0.25, 0.1, 0.5, 0.2))
        #button(0.25, 0.4, 0.5, 0.2, bright_yellow, yellow, 'Options', 0.15, my_test())
        #button(0.25, 0.7, 0.5, 0.2, bright_red, red, 'Quit', 0.15, my_test())

        pygame.display.update()
        clock.tick(60)

    pygame.quit()
    quit()

Like this, python continually prints "hi" while my mouse hovers over the play button which is exactly what I intend it to at this point. The thing is, line 65 if(x <= mouse[0]/screen_width <= x + width and y <= mouse[1]/screen_height <= y + height): seems like a waste of space and time since the function my_test should only be called up while line 58 (which is exactly the same) is true. That is to say that I'm evaluating the same condition twice.

For some reason though, if I comment that line out, "hi" is printed out continually regardless of the position of the mouse. This isn't a huge issue at present (considering how early it is) but I imagine having to evaluate the same conditions multiple times could reduce performance later down the line when is starts to matter.

What I need to know is why the my_test function seems to be unconditionally called.

Thanks in advance for any help.

The Orca
  • 220
  • 2
  • 10

1 Answers1

0

You have a call to my_test(0.25, 0.1, 0.5, 0.2) as one of the arguments when you call button() function, so mytest() and button() are both called at every iteration.

button(0.25, 0.1, 0.5, 0.2, bright_green, green, 'Play', 0.15, my_test(0.25, 0.1, 0.5, 0.2))
                                                                ^^ right there

You should just pass in the function itself:

button(0.25, 0.1, 0.5, 0.2, bright_green, green, 'Play', 0.15, my_test)

And inside button() you could call it:

def button(x, y, width, height, active_colour, inactive_colour, message, font_size, function):
    if(x <= mouse[0]/screen_width <= x + width and y <= mouse[1]/screen_height <= y + height):
        #...
        function(x,y,width,height)

This lets you vary the function that button() calls. If it's always mytest() you could callmytest()directly from button() and omit the last argument tobutton()` altogether.

nos
  • 223,662
  • 58
  • 417
  • 506
  • I'd like to allow each individual button to use an entirely different set of arguments. For example, the currently commented out quit button (7 lines from the bottom) will later be changed to exit the game loop and close the window. The way you're suggesting would force all buttons to accept the same set of arguments. It does however solve the issue of having to evaluate things twice, but I'm not sure why the two cases are different. – The Orca Sep 08 '15 at 13:24
  • @TheOrca. Well, your mytest() functions requires 4 arguments, x, y, width, height. If those are not available inside the function where you want to call mytest(), then create a class that holds the state you need, and pass in an instance if that class, instead of a function. – nos Sep 08 '15 at 13:27
  • After going about this with classes, I've now gotten the exact opposite problem; the function is only called once at the very beginning and never again. https://drive.google.com/file/d/0B_B1ID1IgZayWkZPTi1CVjdPcEE/view?usp=sharing – The Orca Sep 08 '15 at 13:52
  • Your new code suffers the same problem, in the call to the buttons() constructor, you are calling the function: `mytest()`. You should not call it there, you want to pass in the function so you can call it later on. Just pass the function itself by its name: `mytest`. Inside your button() function in the buttons class, you are not calling the function. `self.function` does not do anything. You want to call that function by doing: `self.function()` (See e.g. http://stackoverflow.com/questions/1349332/python-passing-a-function-into-another-function) – nos Sep 08 '15 at 13:58