Search code examples
pythonrandomstatisticstwisteddistribution

Pick a random item from a list based on the distance from a point?


I am writing a game server in Python with the help of Twisted. I have a Map class which generates the map for the game. In my game I have a central point, with rings of items every certain distance outward. I want to randomize the items but also weight items so that they are more common toward the outside of the map. Currently this is my code for building the map, with some variable and function names changed:

def generate():
    centralItem = Item(ITEM_TYPE_GOLD, 0, 0, ITEM_SIZE_GIANT, None)
    self.items.append(centralItem)
    distance = 10000
    numberOfRings = 100
    for ring in range(numberOfRings):
        itemsInRing = (ring + 1) * 100
        distanceToRing = (ring + 1) * distance
        for item in range(itemsInRing):
            angle = math.radians(float(360) / itemsInRing) * item
            xPos = math.cos(angle) * distanceToRing
            yPos = math.sin(angle) * distanceToRing
            # set variable itemSize using method described above (details below)
            # set variable itemType using method described above (details below)

I also have this class with an initializer for creating items.

class Item:

def __init__(self, type, posX, posY, size, owner):
    self.type = type
    self.posX = float(posX)
    self.posY = float(posY)
    self.size = size
    self.owner = owner

These are my definitions.

ITEM_TYPE_FIRE = 0
ITEM_TYPE_ICE = 1
ITEM_TYPE_WIND = 2
ITEM_TYPE_EARTH = 3
ITEM_TYPE_GOLD = 4

ITEM_SIZE_SMALL = 200
ITEM_SIZE_MEDIUM = 350
ITEM_SIZE_LARGE = 500
ITEM_SIZE_GIANT = 1000

Based on distanceToRing in the generate method, I want to make, for example, ITEM_SIZE_SMALL be most common when the distanceToRing is 1,000,000 and least common when distanceToRing is 10,000. I also want ITEM_SIZE_LARGE to be the opposite of that and ITEM_SIZE_MEDIUM to be more common in the middle. As for the item types, I need ITEM_TYPE_FIRE and ITEM_TYPE_ICE to be most common toward 1,000,000. ITEM_TYPE_WIND and ITEM_TYPE_EARTH should be more common toward 10,000.

I know that is a lot to ask, but if you can present a way of handling distribution like this and explain it I think that I can figure it out. Also, I apologize in advance for my lack of skill with Python. I haven't been using it for long enough to understand best practices and even some basics.

Thanks for your time, Brian


Solution

  • Obviously, you'll need to decide exactly how the probability of each of your item types/sizes relates to distance. The simplest answer is to make a triangle with its peak where you prefer and a consistent range (I'm going to say 10000 to 1000000), something like this:

    item_sizes = [ITEM_SIZE_SMALL, ITEM_SIZE_MEDIUM, ITEM_SIZE_LARGE]
    
    def get_item_weights(distance):
        return [1010000 - distance, 1000000 - abs((505000-distance)*2), distance]
    

    This will give you mostly small items at 10000, mostly large items at 1000000, and about a 25/50/25 split in the middle. Maybe you don't like the way that will work, but I have no way to know that. When building random processes, it can take some trial and error to find something you like. (Technically, since 505000 isn't a valid distance, the middle weight will peak at 990000, but I don't think it matters enough to be worth my time fixing, especially since you might not like this distribution.)

    To choose an item given the weights, I'd do something like this:

    import random
    
    random.seed()
    
    def get_random_index(weights):
        n = random.randint(1, sum(weights))
        cumsum = 0
        for index, weight in enumerate(weights):
            cumsum += weight
            if n <= cumsum:
                return index
    
    def get_random_size(distance):
        return item_sizes[get_random_index(get_item_weights(distance))]
    

    itemSize = get_random_size(distanceToRing)