3
import random

twoDimMap = [["H", "-", "-", "-", "-", "-"], ["-", "-", "-", "-", "-", "-"],
             ["-", "-", "-", "-", "-", "-"], ["-", "-", "-", "-", "-", "-"],
             ["-", "-", "-", "-", "-", "-"], ["-", "-", "-", "-", "-", "-"]]

items = 0

while items <= 4:
    test = random.randrange(0, 3)
    if test == 0:
        twoDimMap[random.randrange(0, 5)][random.randrange(0, 5)] = "S"
    if test == 1:
        twoDimMap[random.randrange(0, 5)][random.randrange(0, 5)] = "R"
    if test == 2:
        twoDimMap[random.randrange(0, 5)][random.randrange(0, 5)] = "*"
    #  I couldn't think of an easier way to do this
    if twoDimMap[0][0] != "H":
        twoDimMap[0][0] = "H"
        items -= 1
    items += 1

print(twoDimMap)

Title explains it all pretty much (even though I know it isn't too descriptive :/), I am trying to make a game where the hero starts on the map at position [0],[0]. I can't work out why the hell I'm sometimes generating fewer items than other times.

Edit: Thanks for all of your feedback, and sorry for wasting your time with my stupid mistake :/. I'm going to blame it on the lack of coffee.

Håken Lid
  • 22,318
  • 9
  • 52
  • 67
Ch0w
  • 33
  • 4
  • 2
    What do you mean by "generating fewer items than other times"? – Jon Clements Nov 12 '18 at 00:22
  • I need to place 5 items in the list twoDimMap, an example of an instance wherein fewer items were placed is this: [['H', '-', 'S', '-', '-', '-'], ['-', '-', '-', '-', '-', '-'], ['*', '-', '-', '-', '*', '-'], ['-', '-', '-', '-', '-', '-'], ['S', '-', '-', '-', '-', '-'], ['-', '-', '-', '-', '-', '-']] – Ch0w Nov 12 '18 at 00:23
  • Do not try to explain more in a comment. You can always [edit] your post, you know. – Jongware Nov 12 '18 at 00:24
  • 4
    This is probably caused by randomly getting the same x, y coordinates more than once. – Håken Lid Nov 12 '18 at 00:24
  • Ok, sorry about that. – Ch0w Nov 12 '18 at 00:24
  • 1
    I bet you *generate* 5 but one overwrites an earlier. So test if the contents of your new item are `-` first. – Jongware Nov 12 '18 at 00:24
  • Okay... so you've either generated the same place more than once or you've generated it where the H was and then overwritten it again... – Jon Clements Nov 12 '18 at 00:25
  • Just to note: doing `random.randrange(0, 5)` means you'll never have anything in the last list or anything in the last item of each list? – Jon Clements Nov 12 '18 at 00:44
  • @JonClements you are totally right, today is not my day :( – Ch0w Nov 12 '18 at 00:48

6 Answers6

4

You're only checking if the player is overwritten, not if the objects are, you should first get the random coordinates and then check if something's there.

while items <= 4:
    test = random.randrange(0,3)
    x = random.randrange(0, 5)
    y = random.randrange(0, 5)
    if twoDimMap[x][y] == '-':
        if test == 0:
            twoDimMap[x][y] = "S"
        if test ==1:
            twoDimMap[x][y] = "R"
        if test == 2:
            twoDimMap[x][y] = "*"
        items += 1

a more compact solution as suggested in the comments is

while items <= 4:
    x = random.randrange(0, 5)
    y = random.randrange(0, 5)
    if twoDimMap[x][y] == '-':
        twoDimMap[x][y] = random.choice('SR*')
        items += 1
vlizana
  • 2,962
  • 1
  • 16
  • 26
  • 4
    Or unless something else is going to happen with those `if`s... you can do `twoDimMap[x][y] = random.choice('SR*')`. Also, instead of the while, do `for items in range(5)`... then you can't forget to increment the counter. – Jon Clements Nov 12 '18 at 00:27
  • the only problem I saw with changing the `while` with a `for` loop is that there would be less objects in the end if you get the same `x` and `y`, so the `for` solution would also require a `while` inside to generate unrepeated coordinates. – vlizana Nov 12 '18 at 00:33
  • Ahh... good point... Can still simplify the `if`s though... maybe just make `test = random.choice('SR*')` and then assign `test` inside the single if – Jon Clements Nov 12 '18 at 00:33
3

Since the problem space is so small, it's probably not a terrible idea to just generate three of them that are guaranteed unique.

valid_locations = (tup for tup in itertools.product(range(5), repeat=2) if tup != (0, 0))
items = ["S", "R", "*"]
choices = [(random.choice(items), loc) for loc in random.sample(valid_locations, 3)]

for item, (x, y) in choices:
    twoDimMap[y][x] = item

random.sample(collection, n) guarantees n non-duplicated random results from collection. random.choice(collection) gives you a random element from collection.

Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • I was thinking similarly but you don't need `itertools.product` here... I think you should be able to jump straight to: `choices = ((random.choice(items), divmod(pos, 5)) for pos in random.sample(range(1, 25), 5))`... – Jon Clements Nov 12 '18 at 00:47
  • @JonClements true, and great work avoiding the `(0, 0)` with that, but it's obviously a bit more of a logical leap to see that. – Adam Smith Nov 12 '18 at 00:48
  • 1
    Yeah... and if the H isn't going to be the first or last - it's doesn't work... but... – Jon Clements Nov 12 '18 at 00:49
  • @JonClements but it's *super* neat :D – Adam Smith Nov 12 '18 at 00:52
1

Sometimes randrange(0, 5) returns the same thing several times, thus some point is reassigned.

This could be solved by generating the coordinates together with the type and only executing the rest of the loop if that point is currently unoccupied. This would also eliminate the need for a separate (0,0) test-case.

MegaBluejay
  • 426
  • 2
  • 8
1

To avoid overwriting previously placed items, you can use random.sample, which will pick random items from the input sample without replacement.

There are several other convenient functions for sequences in the random module.

I've rewritten your code and turned it into a function that can generate rectangular maps of any size and number and type of items.

import random

def make_map(width, height, number_of_items, items='SR*'):
    """Create a map with the Hero in top left corner
       and random items spread around"""
    # build an empty map using a nested list comprehension
    game_map = [['-' for x in range(width)] for y in range(height)]
    # place the hero at coordinates 0, 0
    game_map[0][0] = 'H'
    # possible item positions, excluding 0, where the hero is.
    positions = range(1, width * height)
    # loop over n random choices from the available positions
    for pos in random.sample(positions, number_of_items):
        # convert pos to x, y coordinates using modular arithmetic
        x, y = pos % width, pos // width  
        # select a random item to and place it at coordinates x, y
        game_map[y][x] = random.choice(items)

    return game_map

# generate a map. You can change the input arguments to vary size and items
game_map = make_map(6, 6, 5)
# convert the nested list to one string and print
print('\n'.join(''.join(line) for line in game_map))

We use modular arithmetic to convert the position value which is a number in the range 1 to 36 into x,y coordinates on a 6×6 grid. This is a very common and useful operation in computer graphics.

x, y = pos % width, pos // width  

This is such a common operation that python has a built-in function you can use for this exact thing.

y, x = divmod(pos, width)

I will not explain all of the code, but I encourage you to read through the answers and try to understand how every line works. You can learn a lot from reading other people's solutions to the same problem as you have solved yourself.

Håken Lid
  • 22,318
  • 9
  • 52
  • 67
0

Less items are generated because there are times where the same coordinates are generated again. One way to avoid this is to check if the location already has an item before assigning it. This way, you will solve the issue of replacing your hero too.

import random

twoDimMap = [["H","-","-","-","-","-"],["-","-","-","-","-","-"],["-","-","-","-","-","-"],["-","-","-","-","-","-"],["-","-","-","-","-","-"],["-","-","-","-","-","-"]]

items = 0
item_list = ['S', 'R', '*']

while items <= 4:
    x = random.randrange(0,5)
    y = random.randrange(0,5)

    if twoDimMap[x][y] == '-':
        twoDimMap[x][y] = item_list[random.randrange(0,3)]
        items += 1
boonwj
  • 356
  • 1
  • 10
0

If your map isn't too big, you could use random.sample to Choose at random from combinations.

twoDimMap = [list(line) for line in """\
H-----
------
------
------
------
------""".split("\n")]

width, height = len(twoDimMap[0]), len(twoDimMap)

allLocations = [(x, y) for x in range(width) for y in range(height) if (x, y) != (0, 0)]

for x, y in random.sample(allLocations, 5):
    case = random.randrange(3)
    twoDimMap[y][x] = "SR*"[case]
loxaxs
  • 2,149
  • 23
  • 27