Search code examples
python2d-games

Randomizing list of lists without overlap


I'm making a function that randomizes a gameboard (represented by a list of ten lists of ten) for the game battleship. what my function does currently is randomly place numbers on the board representing ships. My function also makes sure that the ships don't loop around the edge of the board and appear on the other side, as well as randomly generating the orientation of the ships. what my function fails to achieve however is making sure that the "ships" don't overlap onto each other. I've been having trouble coming up with a solution, though I'm sure its a pretty simple one. is there a way to achieve my goal?

import random
l = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

for a in range(1, 5):
    p = random.randrange(0, 10, 1)
    o = random.randrange(0, 10, 1)
    #the p and o variables determine the coordinates of the starting point
    r = random.randrange(1, 3)
    #the r variable randomizes orientation of the ship
    if r == 1:
        for n in range(1, 7 - a):
            #the function uses the length of the ship to determine whether or not 
            #the ship will go off the end of the board
            if o < 6 - a:
                l[p][(6 - a) - n] = 6 - a
            else:
                l[p][o-n] = 6 - a
    else:
        for e in range(1, 7 - a):

            if p < 6-a:
                l[(6-a) - e][o] = 6-a
            else:
                l[p - e][o] = 6-a
for v in range(0, len(l)):
    print(l[v])

Output example:

[0, 3, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 3, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 3, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 0, 0, 5, 5, 5, 5, 5, 0]
[0, 4, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 4, 0, 0, 0, 0, 0, 0, 2, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 2, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Output with overlap (the five ship is covered by the three ship):

[0, 0, 0, 0, 5, 5, 5, 5, 3, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 3, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 3, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[2, 2, 0, 0, 0, 0, 0, 0, 0, 0]
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Solution

  • At the risk of over-complicating things, I suggest an object oriented approach. It is possible to modify your method, but I find it gets messy fast.

    In the place function, we assemble a list of locations to place in order to make the ship. We then check if there is any ship already located there grid[y][x] != 0. If so, we need to re-generate random values for the position and rotation, then we can try to place again.

    import random
    
    GRID_WIDTH, GRID_HEIGHT = 10, 10  # constants representing width and height of the board
    grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]  # generate a 10x10 grid of 0's
    
    
    class Ship:
        def __init__(self, length):
            self.length = length
            self.x, self.y, self.horizontal = None, None, None
            self.generate()
    
        def generate(self):  # randomize position and rotation
            self.x = random.randint(0, GRID_WIDTH-self.length)
            self.y = random.randint(0, GRID_HEIGHT-self.length)
            self.horizontal = random.choice([True, False])
            self.place()
    
        def place(self):  # place ship on the grid
            locations = []
            if self.horizontal:
                for x in range(self.x, self.x+self.length):
                    locations.append((x, self.y))
            else:  # if vertical
                for y in range(self.y, self.y+self.length):
                    locations.append((self.x, y))
            for x, y in locations:
                if grid[y][x] != 0:  # if occupied, regenerate whole ship
                    self.generate()
                    return
            for x, y in locations:  # actually place ship now
                grid[y][x] = self.length
    
    
    ships = []
    for ship_length in range(2, 6):
        ships.append(Ship(ship_length))
    
    for row in grid:  # print the board
        print(row)
    
    # for row in grid:  # print the board without 0's
    #     print(str(row).replace('0', ' '))
    
    

    Let me know if you have any questions about the code.