19. Track hits across different ships without using classes?

For exercise 19 - Extra Credit, in lesson 13 - Battleship!, one of the enhancements is to add multiple differently-sized battleships. I’ve found a way to place multiple, non-overlapping randomly-sized ships (up to a max size). However, I’m having trouble with hit tracking.

Link to exercise

Currently, I store all coordinates of all ship sections in a set (ships). I attempt to place an individual ship (temp_ship), and add it to ships if it doesn’t overlap with an existing ship. Then I check for hits using (guess_row, guess_col) in ships.

What I can’t figure out is how to separately track hits for each ship, so that I can tell when all sections of a ship are hit and the ship is sunk. I’ve seen several solutions utilizing classes or hardcoding hit detection for this purpose, but I was wondering if this was possible without doing so.

def place_ships(ships, board_dim, num_ships, max_size = 1):
    ship_num = 0
    while ship_num < num_ships:
        ship_length = randint(1, max_size)
        temp_ship = set()
        if randint(0,1) == 0:  # horizontal placement, left to right
            bow_col = random_one_coord(board_dim + 1 - max_size)
            bow_row = random_one_coord(board_dim)
            for col in range(bow_col, bow_col + ship_length):
                temp_ship.add((col, bow_row))
        else:
            bow_col = random_one_coord(board_dim)
            bow_row = random_one_coord(board_dim + 1 - max_size)
            for row in range(bow_row, bow_row + ship_length):
                temp_ship.add((bow_col, row))
        if len(ships.intersection(temp_ship)):  # don't place ship if overlap
            continue
        else:
            for coord in temp_ship:
                ships.add(coord)
            ship_num += 1

Thanks for reading!

Using a brute-force approach and a checksum I was able to get a proof-of-concept to eventually produce a valid board layout for the computer.

>>> computer = set_comp()
75
>>> computer = set_comp()
61
>>> computer = set_comp()
68
>>> computer = set_comp()
75
>>> computer = set_comp()
77
>>> computer = set_comp()
63
>>> computer = set_comp()
67
>>> computer = set_comp()
70
>>> computer = set_comp()
80
>>> computer = set_comp()
78
>>> computer = set_comp()
81
>>> computer = set_comp()
74
>>> computer = set_comp()
83
>>> print_comp(computer)
0 0 0 0 0 0 0 0 0 0
0 0 0 0 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
5 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 3 3 3
5 0 0 0 0 0 2 2 0 0
5 0 3 3 3 0 0 0 0 0
5 0 0 0 4 4 4 4 0 2
0 0 0 0 0 0 0 0 0 2
>>> 

I worked at it from scratch without studying your code so the slate would be fresh. It’s still very much draft form. The numbers will permit the program to describe what ship was hit.

from random import randint

ship_length = [5, 4, 4, 3, 3, 2, 2]

def get_comp():
    r = range(10)
    return [[0 * x for x in r][:] for x in r]

def check_sum(comp):
    checksum = 0
    for i in range(10):
        checksum += sum(comp[i])
    print (checksum)
    return checksum == 83
def set_comp():
    global ship_length
    comp = get_comp()
    for i in range(len(ship_length)):
        n = ship_length[i]
        z = randint(0,1)
        a = randint(0, 10 - n)
        b = randint(0,9)
        r, c = (b, a) if z else (a, b)
        if z:
            for x in range(c, c+n):
                comp[r][x] = n
        else:
            for y in range(r, r+n):
                comp[y][c] = n
    check_sum(comp)
    return comp

def print_comp(c):
    for i in range(10):
        print (" ".join([str(c[i][j]) for j in range(10)]))

As we can see this is still manual, one-off operation. All that’s needed is a control loop to return the generated board with a checksum of 83 (and some tidying up). Once that is in place we have a mask.

The more complex idea I’m toying with is building seven separate lists with references to all the grid points in the computer map. As each one changes on the map, it would correspondingly change in these reference objects. When their sum is zero, they are destroyed. After each hit, an audit of these lists would bring up the occasional, “You destroyed my …”, and an eventual win, perhaps.

>>> computer = set_comp()
70
83
>>> print_comp(computer)
0 0 3 3 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 4 4 4 4 0
0 5 5 5 5 5 0 2 0 0
0 0 0 0 0 0 0 2 0 0
0 0 0 0 0 4 4 4 4 0
0 0 3 3 3 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
>>> 

Produced with,

def set_comp():
    global ship_length
    while (True):
        comp = get_comp()
        for i in range(len(ship_length)):
            n = ship_length[i]
            z = randint(0,1)
            a = randint(0, 10 - n)
            b = randint(0,9)
            r, c = (b, a) if z else (a, b)
            if z:
                for x in range(c, c+n):
                    comp[r][x] = n
            else:
                for y in range(r, r+n):
                    comp[y][c] = n
        if check_sum(comp):
            return comp

The separate lists is intriguing. I’m trying to avoid storing complete grids for each ship, so I used a set. Unfortunately I don’t know how to generate an arbitrary number of lists programmatically, nor how to refer to them. Indirection seems to be frowned upon.

I gave up on my original list idea since it wasn’t referencing the way I’d hoped it would. Instead we’ll be going with a coordinate set for each object on the board as you suggested.

>>> set_comp()
83
>>> ships
{(4, 8), (3, 0), (2, 8), (1, 6), (9, 4), (2, 5), (4, 0), (5, 5), (1, 5), (5, 0), (2, 6), (4, 5), (9, 3), (6, 0), (9, 6), (3, 5), (2, 7), (8, 3), (7, 0), (3, 8), (1, 8), (9, 5), (8, 4)}
>>> 

is the output from this revised code:

def set_comp():
    global ships, ship_length, comp
    while (True):
        ships = set()
        comp = get_comp()
        for i in range(len(ship_length)):
            n = ship_length[i]
            z = randint(0,1)
            a = randint(0, 10 - n)
            b = randint(0,9)
            r, c = (b, a) if z else (a, b)
            if z:
                for x in range(c, c + n):
                    comp[r][x] = n
                    ships.add((r, x))
            else:
                for y in range(r, r + n):
                    comp[y][c] = n
                    ships.add((y, c))
        if check_sum(comp):
            break

Using the set as a permanent record of the game setup, we can now alter the computer list values by setting them to zero. That makes a win easy to detect. The checksum will be zero.

def check_sum(comp):
    checksum = 0
    for i in range(10):
        checksum += sum(comp[i])
    print (checksum)
    return checksum == 83 or checksum == 0

The board can be effectively reconstructed from the ships list, if so desired, but it won’t be done easily since the set is unordered. It will take some thought.

Checking for a hit is as simple as,

>>> (4,8) in ships
True
>>> comp[4][8]
4
>>> ship_inventory = {5:"Battleship",4:"Destroyer",3:"Frigate",2:"Cruiser"}
>>> print ("You hit my %s." % ship_inventory[comp[4][8]])
You hit my Destroyer.
>>> comp[4][8] = 0
>>> check_sum(comp)
79
False
>>> sample = get_comp()
>>> check_sum(sample)
0
True
>>> 

On a different tack,

>>> ship_class = {5:"Battleship",4:"Destroyer",3:"Frigate",2:"Cruiser"}
>>> ship_inventory = {5:[],4:[],3:[],2:[]}
>>> set_comp()
63
72
71
80
73
70
74
73
71
78
83
>>> ships
{(3, 0), (6, 2), (1, 6), (5, 1), (7, 2), (4, 0), (4, 9), (2, 9), (7, 6), (5, 0), (8, 6), (4, 1), (8, 2), (6, 0), (3, 9), (1, 9), (6, 1), (3, 1), (2, 0), (1, 8), (0, 6), (1, 7), (5, 2)}
>>> guess_y = 6
>>> guess_x = 2
>>> (guess_y, guess_x) in ships
True
>>> ship_inventory[comp[guess_y][guess_x]] += [(guess_y, guess_x)]
>>> ship_inventory
{2: [], 3: [], 4: [(6, 2)], 5: []}
>>> print ("You hit my %s." % ship_class[comp[guess_y][guess_x]])
You hit my Destroyer.
>>> comp[guess_y][guess_x] = 0
>>> check_sum(comp)
79
False
>>> print_comp(comp)
0 0 0 0 0 0 2 0 0 0
0 0 0 0 0 0 2 3 3 3
5 0 0 0 0 0 0 0 0 3
5 4 0 0 0 0 0 0 0 3
5 4 0 0 0 0 0 0 0 3
5 4 4 0 0 0 0 0 0 0
5 4 0 0 0 0 0 0 0 0
0 0 4 0 0 0 2 0 0 0
0 0 4 0 0 0 2 0 0 0
0 0 0 0 0 0 0 0 0 0
>>> 

The ship inventory records only hits. This is how we can recreate the board at any time, and restore it to current state.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

Bringing this one out to the front, again. It demands extra study and implementation. Let the good folk here go to town on it…

Probably won’t hurt to take this into account:

Battleship, take home version