Generate random number outside of range in python

后端 未结 5 1265
盖世英雄少女心
盖世英雄少女心 2021-02-18 22:52

I\'m currently working on a pygame game and I need to place objects randomly on the screen, except they cannot be within a designated rectangle. Is there an easy way to do this

5条回答
  •  梦如初夏
    2021-02-18 23:17

    It requires a bit of thought to generate a uniformly random point with these constraints. The simplest brute force way I can think of is to generate a list of all valid points and use random.choice() to select from this list. This uses a few MB of memory for the list, but generating a point is very fast:

    import random
    
    screen_width = 1000
    screen_height = 800
    rect_x = 500
    rect_y = 250
    rect_width = 100
    rect_height = 75
    
    valid_points = []
    for x in range(screen_width):
        if rect_x <= x < (rect_x + rect_width):
            for y in range(rect_y):
                valid_points.append( (x, y) )
            for y in range(rect_y + rect_height, screen_height):
                valid_points.append( (x, y) )
        else:
            for y in range(screen_height):
                valid_points.append( (x, y) )
    
    for i in range(10):
        rand_point = random.choice(valid_points)
        print(rand_point)
    

    It is possible to generate a random number and map it to a valid point on the screen, which uses less memory, but it is a bit messy and takes more time to generate the point. There might be a cleaner way to do this, but one approach using the same screen size variables as above is here:

    rand_max = (screen_width * screen_height) - (rect_width * rect_height) 
    def rand_point():
        rand_raw = random.randint(0, rand_max-1)
        x = rand_raw % screen_width
        y = rand_raw // screen_width
        if rect_y <= y < rect_y+rect_height and rect_x <= x < rect_x+rect_width:
            rand_raw = rand_max + (y-rect_y) * rect_width + (x-rect_x)
            x = rand_raw % screen_width
            y = rand_raw // screen_width
        return (x, y)
    

    The logic here is similar to the inverse of the way that screen addresses are calculated from x and y coordinates on old 8 and 16 bit microprocessors. The variable rand_max is equal to the number of valid screen coordinates. The x and y co-ordinates of the pixel are calculated, and if it is within the rectangle the pixel is pushed above rand_max, into the region that couldn't be generated with the first call.

    If you don't care too much about the point being uniformly random, this solution is easy to implement and very quick. The x values are random, but the Y value is constrained if the chosen X is in the column with the rectangle, so the pixels above and below the rectangle will have a higher probability of being chosen than pizels to the left and right of the rectangle:

    def pseudo_rand_point():        
        x = random.randint(0, screen_width-1)
        if rect_x <= x < rect_x + rect_width: 
            y = random.randint(0, screen_height-rect_height-1)
            if y >= rect_y:
                y += rect_height
        else:
            y = random.randint(0, screen_height-1)
        return (x, y)
    

    Another answer was calculating the probability that the pixel is in certain regions of the screen, but their answer isn't quite correct yet. Here's a version using a similar idea, calculate the probability that the pixel is in a given region and then calculate where it is within that region:

    valid_screen_pixels = screen_width*screen_height - rect_width * rect_height
    prob_left = float(rect_x * screen_height) / valid_screen_pixels
    prob_right = float((screen_width - rect_x - rect_width) * screen_height) / valid_screen_pixels
    prob_above_rect = float(rect_y) / (screen_height-rect_height)
    def generate_rand():
        ymin, ymax = 0, screen_height-1
        xrand = random.random()
        if xrand < prob_left:
            xmin, xmax = 0, rect_x-1
        elif xrand > (1-prob_right):
            xmin, xmax = rect_x+rect_width, screen_width-1
        else:
            xmin, xmax = rect_x, rect_x+rect_width-1
            yrand = random.random()
            if yrand < prob_above_rect:
                ymax = rect_y-1
            else:
                ymin=rect_y+rect_height
        x = random.randrange(xmin, xmax)
        y = random.randrange(ymin, ymax)
        return (x, y)
    

提交回复
热议问题