4
\$\begingroup\$

I have implemented Battleship in Python by using lists. I have used threading to prevent KeyboardInterrupt, so the player can't cheat by using control-c and stuff.

How can I simplify this code?

import random
import sys
from threading import Thread
import keyring

master_break = keyring.get_password('ship', 'break')
enable = keyring.get_password('ship', 'enable')
activate = keyring.get_password('ship', 'activate')

def no_interupt():
    
    print('This is Battleship.')
    print('Hits are marked with Hs and misses are marked with Ms.')
    print('If you miss too many times, you lose.')
    
    nuke_enabled = False
    
    while True:
        try:
            dif = input('What difficulty would you like (1-3): ')
            if dif == enable:
                nuke_enabled = True
                print('Nukes have been enabled.')
            if dif != master_break:
                dif = int(dif)
            elif dif == master_break:
                sys.exit()
        except ValueError:
            if nuke_enabled != True:
                print('Invalid format.')
            else:
                pass
        else:
            break
    lines = 8

    board = []
    nuke_board = []
    
    for x in range(8):
        board.append(['O'] * 8)
        nuke_board.append(['N'] * 8)
        
    def split(word):
        return [char for char in word]
    
    def print_board(board):
        x = 1
        print('    A   B   C   D   E   F   G   H')
        print()
        for row in board:
            print(x, ' ', ' | '.join(row))
            print('  ', ' -'*15)
            x += 1
            
    def print_nuke(nuke_board):
        x = 1
        print('    A   B   C   D   E   F   G   H')
        print()
        for row in nuke_board:
            print(x, ' ', ' | '.join(row))
            print('  ', ' -'*15)
            x += 1
            
    print('Let\'s play Battleship!')
    print()
    print_board(board)

    def random_col(board):
        return random.randint(0, 7)

    carrier_direct = random.randint(1, 2)
    battleship_direct = random.randint(1, 2)
    destroyer_direct = random.randint(1, 2)
    
    carrier = 5
    battleship = 4
    destroyer = 3
    ship_len = 12
    
    carrier_sunk = False
    battleship_sunk = False
    destroyer_sunk = False

    turns = 12 - dif

    if carrier_direct == 1:
        carrier_row = random_col(board)
        carrier_col_start = random.randint(0, 2)
        carrier_col_end = carrier_col_start + 5
        if battleship_direct == 1:
            while True:
                battleship_row = random_col(board)
                battleship_col_start = random.randint(0, 3)
                battleship_col_end = battleship_col_start + 4
                if battleship_row != carrier_row:
                    break
            if destroyer_direct == 1:
                while True:
                    destroyer_row = random_col(board)
                    destroyer_col_start = random.randint(0, 4)
                    destroyer_col_end = destroyer_col_start + 3
                    if destroyer_row != battleship_row and destroyer_row != carrier_row:
                        break
            else:
                while True:
                    destroyer_col = random_col(board)
                    destroyer_row_start = random.randint(0, 4)
                    destroyer_row_end = destroyer_row_start + 3
                    if carrier_col_start <= destroyer_col and destroyer_col <= carrier_col_end or battleship_col_start <= destroyer_col and destroyer_col <= battleship_col_end:
                        pass
                    else:
                        break
        else:
            while True:
                battleship_col = random_col(board)
                battleship_row_start = random.randint(0, 3)
                battleship_row_end = battleship_row_start + 4
                if carrier_col_start <= battleship_col and battleship_col <= carrier_col_end:
                    pass
                else:
                    break
            if destroyer_direct == 1:
                while True:
                    destroyer_row = random_col(board)
                    destroyer_col_start = random.randint(0, 4)
                    destroyer_col_end = destroyer_col_start + 3
                    if destroyer_row != carrier_row:
                        if battleship_row_start <=  destroyer_row and destroyer_row <= battleship_row_end:
                            pass
                        else:
                            break
            else:
                while True:
                    destroyer_col = random_col(board)
                    destroyer_row_start = random.randint(0, 4)
                    destroyer_row_end = destroyer_row_start + 3
                    if destroyer_col != battleship_col:
                        if destroyer_col <= carrier_col_end and destroyer_col >= carrier_col_start:
                            pass
                        else:
                            break
    else:
        carrier_col = random_col(board)
        carrier_row_start = random.randint(0, 2)
        carrier_row_end = carrier_row_start + 5
        if battleship_direct == 1:
            while True:
                battleship_row = random_col(board)
                battleship_col_start = random.randint(0, 3)
                battleship_col_end = battleship_col_start + 4
                if battleship_row <= carrier_row_end and battleship_row >= carrier_row_start:
                    pass
                else:
                    break
            if destroyer_direct == 1:
                while True:
                    destroyer_row = random_col(board)
                    destroyer_col_start = random.randint(0, 4)
                    destroyer_col_end = destroyer_col_start + 3
                    if destroyer_row != battleship_row:
                        if destroyer_row <= carrier_row_end and destroyer_row >= carrier_row_start:
                            pass
                        else:
                            break
            else:
                while True:
                    destroyer_col = random_col(board)
                    destroyer_row_start = random.randint(0, 4)
                    destroyer_row_end = destroyer_row_start + 3
                    if destroyer_col != carrier_col:
                        if destroyer_col <= battleship_col_end and destroyer_col >= battleship_col_start:
                            pass
                        else:
                            break
        else:
            while True:
                battleship_col = random_col(board)
                battleship_row_start = random.randint(0, 3)
                battleship_row_end = battleship_row_start + 4
                if battleship_col != carrier_col:
                    break
            if destroyer_direct == 1:
                while True:
                    destroyer_row = random_col(board)
                    destroyer_col_start = random.randint(0, 4)
                    destroyer_col_end = destroyer_col_start + 3
                    if destroyer_row <= carrier_row_end and destroyer_row >= carrier_row_start or destroyer_row <= battleship_row_end and destroyer_row >= battleship_row_start:
                        pass
                    else:
                        break
            else:
                while True:
                    destroyer_col = random_col(board)
                    destroyer_row_start = random.randint(0, 4)
                    destroyer_row_end = destroyer_row_start + 3
                    if destroyer_col != battleship_col and destroyer_col != carrier_col:
                        break

    while turns > 0:
        if ship_len == 0:
            print('You win!')
            sys.exit()
        if carrier == 0 and carrier_sunk != True:
            print('You sunk the carrier!')
            carrier_sunk = True
        elif battleship == 0 and battleship_sunk != True:
            print('You sunk the battleship!')
            battleship_sunk = True
        elif destroyer == 0 and destroyer_sunk != True:
            print('You sunk the destroyer!')
            destroyer_sunk = True
        test_var = False
        not_enabled = False
        guess = input('Guess coordinate: ').lower()
        if guess == master_break:
            sys.exit()
        if guess == activate:
            if nuke_enabled == True:
                print('NUKE EM\' ALL!')
                print()
                print_nuke(nuke_board)
                print('You sunk the carrier!')
                print('You sunk the battleship!')
                print('You sunk the destroyer!')
                print('You win! (by using cheats, but WHO CARES!)')
                sys.exit()
            else:
                print('Nukes are not enabled.')
                not_enabled = True
        try:
            guess_col_let = split(guess)[0]
            guess_row = int(split(guess)[1])
            guess_col = int(ord(guess_col_let) - 96)
        except ValueError:
            if not_enabled != True:
                print('Invalid format.')
            else:
                pass
        except IndexError:
            if not_enabled != True:
                print('Invalid format.')
            else:
                pass
        else:
            test_var = True
        if not_enabled != True:
            if test_var == True:
                try:
                    if guess_row == carrier_row and carrier_col_start <= guess_col <= carrier_col_end:
                        print('Hit on the carrier!')
                        board[guess_row - 1][guess_col - 1] = 'H'
                        print_board(board)
                        carrier -= 1
                        ship_len -= 1
                    elif guess_row == battleship_row and battleship_col_start <= guess_col <= battleship_col_end:
                        print('Hit on the battleship!')
                        board[guess_row - 1][guess_col - 1] = 'H'
                        print_board(board)
                        destroyer -= 1
                        ship_len -= 1
                    elif guess_row == destroyer_row and destroyer_col_start <= guess_col <= destroyer_col_end:
                        print('Hit on the destroyer!')
                        board[guess_row - 1][guess_col - 1] = 'H'
                        print_board(board)
                        destroyer -= 1
                        ship_len -= 1
                    else:
                        if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                            print('Oops, that\'s not even in the ocean.')
                        elif board[guess_row - 1][guess_col - 1] != 'O':
                            print('You guessed that one already.')
                        else:
                            print('You missed!')
                            board[guess_row - 1][guess_col - 1] = 'M'
                            turns -= 1
                        if turns > 1 or turns == 0:
                            print('You have {} guesses remaining.'.format(turns))
                        else:
                            print('You have 1 guess remaining.')
                        print()
                        print_board(board)
                except NameError:
                    try:
                        if guess_row == carrier_row and carrier_col_start <= guess_col <= carrier_col_end:
                            print('Hit on the carrier!')
                            board[guess_row - 1][guess_col - 1] = 'H'
                            print_board(board)
                            carrier -= 1
                            ship_len -= 1
                        elif guess_col == battleship_col and battleship_row_start <= guess_row <= battleship_row_end:
                            print('Hit on the battleship!')
                            board[guess_row - 1][guess_col - 1] = 'H'
                            print_board(board)
                            destroyer -= 1
                            ship_len -= 1
                        elif guess_row == destroyer_row and destroyer_col_start <= guess_col <= destroyer_col_end:
                            print('Hit on the destroyer!')
                            board[guess_row - 1][guess_col - 1] = 'H'
                            print_board(board)
                            destroyer -= 1
                            ship_len -= 1
                        else:
                            if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                print('Oops, that\'s not even in the ocean.')
                            elif board[guess_row - 1][guess_col - 1] != 'O':
                                print('You guessed that one already.')
                            else:
                                print('You missed!')
                                board[guess_row - 1][guess_col - 1] = 'M'
                                turns -= 1
                            if turns > 1 or turns == 0:
                                print('You have {} guesses remaining.'.format(turns))
                            else:
                                print('You have 1 guess remaining.')
                            print()
                            print_board(board)
                    except NameError:
                        try:
                            if guess_row == carrier_row and carrier_col_start <= guess_col <= carrier_col_end:
                                print('Hit on the carrier!')
                                board[guess_row - 1][guess_col - 1] = 'H'
                                print_board(board)
                                carrier -= 1
                                ship_len -= 1
                            elif guess_col == battleship_col and battleship_row_start <= guess_row <= battleship_row_end:
                                print('Hit on the battleship!')
                                board[guess_row - 1][guess_col - 1] = 'H'
                                print_board(board)
                                destroyer -= 1
                                ship_len -= 1
                            elif guess_row == destroyer_row and destroyer_col_start <= guess_col <= destroyer_col_end:
                                print('Hit on the destroyer!')
                                board[guess_row - 1][guess_col - 1] = 'H'
                                print_board(board)
                                destroyer -= 1
                                ship_len -= 1
                            else:
                                if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                    print('Oops, that\'s not even in the ocean.')
                                elif board[guess_row - 1][guess_col - 1] != 'O':
                                    print('You guessed that one already.')
                                else:
                                    print('You missed!')
                                    board[guess_row - 1][guess_col - 1] = 'M'
                                    turns -= 1
                                if turns > 1 or turns == 0:
                                    print('You have {} guesses remaining.'.format(turns))
                                else:
                                    print('You have 1 guess remaining.')
                                print()
                                print_board(board)
                        except NameError:
                            try:
                                if guess_row == carrier_row and carrier_col_start <= guess_col <= carrier_col_end:
                                    print('Hit on the carrier!')
                                    board[guess_row - 1][guess_col - 1] = 'H'
                                    print_board(board)
                                    carrier -= 1
                                    ship_len -= 1
                                elif guess_row == battleship_row and battleship_col_start <= guess_col <= battleship_col_end:
                                    print('Hit on the battleship!')
                                    board[guess_row - 1][guess_col - 1] = 'H'
                                    print_board(board)
                                    destroyer -= 1
                                    ship_len -= 1
                                elif guess_col == destroyer_col and destroyer_row_start <= guess_row <= destroyer_row_end:
                                    print('Hit on the destroyer!')
                                    board[guess_row - 1][guess_col - 1] = 'H'
                                    print_board(board)
                                    destroyer -= 1
                                    ship_len -= 1
                                else:
                                    if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                        print('Oops, that\'s not even in the ocean.')
                                    elif board[guess_row - 1][guess_col - 1] != 'O':
                                        print('You guessed that one already.')
                                    else:
                                        print('You missed!')
                                        board[guess_row - 1][guess_col - 1] = 'M'
                                        turns -= 1
                                    if turns > 1 or turns == 0:
                                        print('You have {} guesses remaining.'.format(turns))
                                    else:
                                        print('You have 1 guess remaining.')
                                    print()
                                    print_board(board)
                            except NameError:
                                try:
                                    if guess_col == carrier_col and carrier_row_start <= guess_row <= carrier_row_end:
                                        print('Hit on the carrier!')
                                        board[guess_row - 1][guess_col - 1] = 'H'
                                        print_board(board)
                                        carrier -= 1
                                        ship_len -= 1
                                    elif guess_col == battleship_col and battleship_row_start <= guess_row <= battleship_row_end:
                                        print('Hit on the battleship!')
                                        board[guess_row - 1][guess_col - 1] = 'H'
                                        print_board(board)
                                        destroyer -= 1
                                        ship_len -= 1
                                    elif guess_col == destroyer_col and destroyer_row_start <= guess_row <= destroyer_row_end:
                                        print('Hit on the destroyer!')
                                        board[guess_row - 1][guess_col - 1] = 'H'
                                        print_board(board)
                                        destroyer -= 1
                                        ship_len -= 1
                                    else:
                                        if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                            print('Oops, that\'s not even in the ocean.')
                                        elif board[guess_row - 1][guess_col - 1] != 'O':
                                            print('You guessed that one already.')
                                        else:
                                            print('You missed!')
                                            board[guess_row - 1][guess_col - 1] = 'M'
                                            turns -= 1
                                        if turns > 1 or turns == 0:
                                            print('You have {} guesses remaining.'.format(turns))
                                        else:
                                            print('You have 1 guess remaining.')
                                        print()
                                        print_board(board)
                                except NameError:
                                    try:
                                        if guess_col == carrier_col and carrier_row_start <= guess_row <= carrier_row_end:
                                            print('Hit on the carrier!')
                                            board[guess_row - 1][guess_col - 1] = 'H'
                                            print_board(board)
                                            carrier -= 1
                                            ship_len -= 1
                                        elif guess_col == battleship_col and battleship_row_start <= guess_row <= battleship_row_end:
                                            print('Hit on the battleship!')
                                            board[guess_row - 1][guess_col - 1] = 'H'
                                            print_board(board)
                                            destroyer -= 1
                                            ship_len -= 1
                                        elif guess_row == destroyer_row and destroyer_col_start <= guess_col <= destroyer_col_end:
                                            print('Hit on the destroyer!')
                                            board[guess_row - 1][guess_col - 1] = 'H'
                                            print_board(board)
                                            destroyer -= 1
                                            ship_len -= 1
                                        else:
                                            if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                                print('Oops, that\'s not even in the ocean.')
                                            elif board[guess_row - 1][guess_col - 1] != 'O':
                                                print('You guessed that one already.')
                                            else:
                                                print('You missed!')
                                                board[guess_row - 1][guess_col - 1] = 'M'
                                                turns -= 1
                                            if turns > 1 or turns == 0:
                                                print('You have {} guesses remaining.'.format(turns))
                                            else:
                                                print('You have 1 guess remaining.')
                                            print()
                                            print_board(board)
                                    except NameError:
                                        try:
                                            if guess_col == carrier_col and carrier_row_start <= guess_row <= carrier_row_end:
                                                print('Hit on the carrier!')
                                                board[guess_row - 1][guess_col - 1] = 'H'
                                                print_board(board)
                                                carrier -= 1
                                                ship_len -= 1
                                            elif guess_row == battleship_row and battleship_col_start <= guess_col <= battleship_col_end:
                                                print('Hit on the battleship!')
                                                board[guess_row - 1][guess_col - 1] = 'H'
                                                print_board(board)
                                                destroyer -= 1
                                                ship_len -= 1
                                            elif guess_col == destroyer_col and destroyer_row_start <= guess_row <= destroyer_row_end:
                                                print('Hit on the destroyer!')
                                                board[guess_row - 1][guess_col - 1] = 'H'
                                                print_board(board)
                                                destroyer -= 1
                                                ship_len -= 1
                                            else:
                                                if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                                    print('Oops, that\'s not even in the ocean.')
                                                elif board[guess_row - 1][guess_col - 1] != 'O':
                                                    print('You guessed that one already.')
                                                else:
                                                    print('You missed!')
                                                    board[guess_row - 1][guess_col - 1] = 'M'
                                                    turns -= 1
                                                if turns > 1 or turns == 0:
                                                    print('You have {} guesses remaining.'.format(turns))
                                                else:
                                                    print('You have 1 guess remaining.')
                                                print()
                                                print_board(board)
                                        except NameError:
                                            try:
                                                if guess_col == carrier_col and carrier_row_start <= guess_row <= carrier_row_end:
                                                    print('Hit on the carrier!')
                                                    board[guess_row - 1][guess_col - 1] = 'H'
                                                    print_board(board)
                                                    carrier -= 1
                                                    ship_len -= 1
                                                elif guess_row == battleship_row and battleship_col_start <= guess_col <= battleship_col_end:
                                                    print('Hit on the battleship!')
                                                    board[guess_row - 1][guess_col - 1] = 'H'
                                                    print_board(board)
                                                    destroyer -= 1
                                                    ship_len -= 1
                                                elif guess_row == destroyer_row and destroyer_col_start <= guess_col <= destroyer_col_end:
                                                    print('Hit on the destroyer!')
                                                    board[guess_row - 1][guess_col - 1] = 'H'
                                                    print_board(board)
                                                    destroyer -= 1
                                                    ship_len -= 1
                                                else:
                                                    if guess_row < 1 or guess_row > lines + 1 or guess_col < 1 or guess_col > lines + 1:
                                                        print('Oops, that\'s not even in the ocean.')
                                                    elif board[guess_row - 1][guess_col - 1] != 'O':
                                                        print('You guessed that one already.')
                                                    else:
                                                        print('You missed!')
                                                        board[guess_row - 1][guess_col - 1] = 'M'
                                                        turns -= 1
                                                    if turns > 1 or turns == 0:
                                                        print('You have {} guesses remaining.'.format(turns))
                                                    else:
                                                        print('You have 1 guess remaining.')
                                                    print()
                                                    print_board(board)
                                            except NameError:
                                                pass
        else:
            pass
    print('You lose!')
    sys.exit()
a = Thread(target=no_interupt)
a.Daemon = True
a.start()
a.join()
\$\endgroup\$
2
\$\begingroup\$

Preface

At first glance, your code is unnecessarily convoluted. I see that you tagged the question as beginner, so I'll cut you some slack. But as a general warning, if you're developing a simple application/game, and you have a nesting of if/else 15 indentations deep(!!!), you should take another look at your logic.

That being said, I had fun playing your game and rewriting it! I got the line count from 537 to ~180. This is going to be an extensive, but not exhaustive, answer about things I changed in your program, why I made those changes, among other things.

I'm going to post the entire new code, and go through each function narrating the changes I've made and why.

Updated Code

import random
import sys
from threading import Thread
import keyring
from typing import List

master_break = keyring.get_password('ship', 'break')
enable = keyring.get_password('ship', 'enable')
activate = keyring.get_password('ship', 'activate')

DIMENSION = 8 # Dimension for battleship board
picked_columns = []

def hit(row: int, col: int, board: List[List[str]]) -> str:
    if board[row][col] == 'C': return 'C'
    if board[row][col] == 'B': return 'B'
    if board[row][col] == 'D': return 'D'
    return 'M'

def ships_down(board: List[List[str]]) -> (bool, bool, bool, bool):
    c, b, d, count = 0, 0, 0, 0
    for row in range(DIMENSION):
        for col in range(DIMENSION):
            value = board[row][col]
            if value in 'CBD': count += 1
            if value == 'C': c += 1
            if value == 'B': b += 1
            if value == 'D': d += 1
    
    return c == 0, b == 0, d == 0, count == 0

def random_col():
    while True:
        column = random.randint(0, 7)
        if column not in picked_columns:
            picked_columns.append(column)
            return column


def generate_board() -> List[List[str]]:
    board = [['O'] * DIMENSION for _ in range(DIMENSION)]
    
    # Carrier
    col = random_col()
    row = random.randint(0, 3)
    for idx in range(5): # 5 = carrier length
        board[row + idx][col] = 'C'

    # Battleship
    col = random_col()
    row = random.randint(0, 4)
    for idx in range(4): # 4 = battleship length
        board[row + idx][col] = 'B'

    # Destroyer
    col = random_col()
    row = random.randint(0, 5)
    for idx in range(3): # 4 = destroyer length
        board[row + idx][col] = 'D'

    return board


def print_board(board: List[List[str]]) -> None:
    x = 1
    print('    A   B   C   D   E   F   G   H')
    print()
    for row in board:
        print(x, ' ', ' | '.join(row))
        print('  ', ' -' * 15)
        x += 1


def battleship():

    print('This is Battleship.')
    print('Hits are marked with (H) and misses are marked with (M).')
    print("If you miss too many times, you lose.")

    nuke_enabled = False

    while True:
        try:
            diff = input('What difficulty would you like (1-3): ')
            if diff == enable:
                nuke_enabled = True
                print('Nukes have been enabled.')
            if diff == master_break:
                sys.exit()
            diff = int(diff)
        except ValueError:
            if not nuke_enabled:
                print('Invalid format.')
            else:
                pass
        else:
            break

    board = [['O'] * DIMENSION for _ in range(DIMENSION)]
    nuke_board = [['N'] * DIMENSION for _ in range(DIMENSION)]

    print("Let's play Battleship!")
    print()
    print_board(board)

    carrier_sunk = False
    battleship_sunk = False
    destroyer_sunk = False
    
    true_board = generate_board()

    turns = 12 - diff
    while turns > 0:

        carrier_down, battleship_down, destroyer_down, win = ships_down(true_board)

        if win:
            print('You win!')
            sys.exit()
        if carrier_down and not carrier_sunk:
            print('You sunk the carrier!')
            carrier_sunk = True
        if battleship_down and not battleship_sunk:
            print('You sunk the battleship!')
            battleship_sunk = True
        if destroyer_down and not destroyer_sunk:
            print('You sunk the destroyer!')
            destroyer_sunk = True

        guess = input('Guess coordinate: ').lower()
        if len(guess) != 2:
            print('Invalid input: Please only enter one letter and one number!')
        no_exceptions = False
        if guess == master_break:
            sys.exit()
        if guess == activate:
            if nuke_enabled:
                print("NUKE EM' ALL!")
                print()
                print_board(nuke_board)
                print('You sunk the carrier!')
                print('You sunk the battleship!')
                print('You sunk the destroyer!')
                print('You win! (by using cheats, but WHO CARES!)')
                sys.exit()
            else:
                print('Nukes are not enabled.')

        try:
            guess_row = int(guess[1]) - 1
            guess_col = int(ord(guess[0]) - 96) - 1
            if guess[0] not in "abcdefgh":
                print('Invalid letter: Please only enter ABCDEFGH.')
                continue
            if int(guess[1]) < 0 or int(guess[1]) > DIMENSION:
                print('Invalid number: Please only enter 12345678.')
                continue
        except ValueError:
            if not nuke_enabled:
                print('Invalid format.')
            else:
                pass
        except IndexError:
            if not nuke_enabled:
                print('Invalid format.')
            else:
                pass
        else:
            no_exceptions = True
        
        if not nuke_enabled and no_exceptions:
            result = hit(guess_row, guess_col, true_board)
            if result in 'CBD':
                if result == 'C': print('Hit on the carrier!')
                if result == 'B': print('Hit on the battleship!')
                if result == 'D': print('Hit on the destroyer!')
                board[guess_row][guess_col] = 'H'
                true_board[guess_row][guess_col] = 'H'
                print_board(board)
            else:
                turns -= 1
                print(f'You missed! You have {turns} guesses left.')
                board[guess_row][guess_col] = 'M'
                print_board(board)


if __name__ == '__main__':
    a = Thread(target=battleship, daemon=True)
    a.start()
    a.join()

General Construction

The first thing I noticed is that the entire program is contained within 1 function. Personally, this isn't the path I would recommend. I would write utility functions (print_board, for example) outside of the main game function. Then inside the game loop, I would use these functions. Having all those functions defined in the main game function decreases readability and adds an unnecessary level of indentation.

The new code is significantly different from your code. We use two boards, the board the player sees board, and the actual board where the ships are stored true_board. With this, we can check the users guess against the actual board, and display results on the board the player sees. The actual board has identifiers for the carrier, battleship and destroyer ('C', 'B', 'D' respectfully). This allows us a much easier way to check for hits against certain ships.

hit

def hit(row: int, col: int, board: List[List[str]]) -> str:
    if board[row][col] == 'C': return 'C'
    if board[row][col] == 'B': return 'B'
    if board[row][col] == 'D': return 'D'
    return 'M'

This function determines if the user has hit a ship on the board. It accepts a row, a column, and the actual board where the ships are stored (true_board). It grabs the value of the board at that row and column, and determines if the user has hit a boat or not. If so, it returns the identifier of the boat the user has hit.

You may notice the type annotations in the function parameters. These show us the type of parameters a function accepts, and what a function returns (in this case, a 'str'). So board is a list of list of strings, or a 2D array of strings.

ships_down

def ships_down(board: List[List[str]]) -> (bool, bool, bool, bool):
    c, b, d, count = 0, 0, 0, 0
    for row in range(DIMENSION):
        for col in range(DIMENSION):
            value = board[row][col]
            if value in 'CBD': count += 1
            if value == 'C': c += 1
            if value == 'B': b += 1
            if value == 'D': d += 1
    
    return c == 0, b == 0, d == 0, count == 0

This function determines the current status of each ship on the board. It returns four booleans, each one representing the status of a ship. It iterates through the board, and it counts each identifier of a ship found. It then returns the amount of these identifiers against the value 0, which results to a boolean for each ship. For a winning board position, this function will return (True, True, True, True). That may have been hard to understand, so take another look at the updated code if you need to.

random_col

def random_col():
    while True:
        column = random.randint(0, 7)
        if column not in picked_columns:
            picked_columns.append(column)
            return column

I've edited this function a bit so it only returns columns that haven't been chosen already. Leaving the responsibility of the function to return correct columns instead of the main game loop having that responsibility helps reduce the complexity of the program. Now, you don't need to check if this function is returning a correct value, because the function does that itself.

generate_board

def generate_board() -> List[List[str]]:
    board = [['O'] * DIMENSION for _ in range(DIMENSION)]
    
    # Carrier
    col = random_col()
    row = random.randint(0, 3)
    for idx in range(5): # 5 = carrier length
        board[row + idx][col] = 'C'

    # Battleship
    col = random_col()
    row = random.randint(0, 4)
    for idx in range(4): # 4 = battleship length
        board[row + idx][col] = 'B'

    # Destroyer
    col = random_col()
    row = random.randint(0, 5)
    for idx in range(3): # 4 = destroyer length
        board[row + idx][col] = 'D'

    return board

This function is a ~20 line response to your ~100 line solution to "generating" a game board. For each ship, it generates a random unused column, a random row (this is determined by the length of the ship. A carrier only gets a range of [0, 3) because of its length of 5, while the destroyer gets a range of [0, 5) because it's only 3 units long), and sets identifiers in the board for the ship. We do row + idx because we need to go down the column for each unit of the ship.

print_board

This function was not changed.

battleship

I'm not going to include code for this as all of it is at the top of the answer. The biggest difference here is that this function has the sole purpose of managing and calling the other functions. All it really does it get and validate input, checks for values returned by the ships_down function, and checks for values returned by the hit function.

This separation of responsibilities is a huge part of programming. Knowing which parts of a program should have their own function is key to writing good, efficient, and nice looking code.

Instead of checking the bounds of each ship, we call the hit function and analyze the identifier returned. Since the code for setting the board and true_board is the exact same for each ship hit, we only check the specific ship hit for the print statements.

General Feedback

Bugs

If I enter A9, I get an IndexError, which crashes the game. As a user, my first reaction is that the game is broken and I stop playing. The same happens if I enter an invalid letter like T4. You need to assume the user will never enter valid input. Always write checks to ensure that user input doesn't break your code.

For the error checking that you do have, your messages are not descriptive. "Invalid Format.". Okay, what does that tell me as a user? What about my input was invalid? Having descriptive message helps the user understand what they did wrong, and can even help you when debugging your program.

Threading

For threading, you can specify a daemon thread in the Thread constructor.

a = Thread(target=battleship, daemon=True)

Boolean Checking

When you're checking for true/false values, you don't need to do if value == True:. You can evaluate the variable itself with if value: for true, and if not value: for false. Here's a good article about this.

Comments

Your code has zero comments. When writing a program, you need to think about the people who will read it too. Not everyone will read your code and understand it the same way you did while writing it. Use comments to explain algorithms, design decisions and other complicated logic to help readers better understand how your code works.

Weird Variables

test_var and not_enabled stuck out to me while rewriting your code. I spent a good 10 minutes wondering how this fit into your code. It turns out test_var was only used to determine if any exceptions were called, and not_enabled was an unnecessary replication of the nuke_enabled variable. When designing a program, think about how you can reuse variables later on, instead of creating new ones for the exact same purpose.

Unnecessary Functions

split, in itself, is unnecessary. When you get coordinates from the user, you only expect a string of length two. You can just index the string itself instead of calling the split function and indexing the list that it returns.

Before rewriting, random_col was unnecessary. It was one line, which just generated a random number. You could have replaced every occurrence of random_col with the one line it contained, and saved people the trouble of going back to this function while reading your code.

Main Guard

You should wrap your starting code in a main guard, in the event you want to import this code into another project. This prevents this code from running when you import.

if __name__ == '__main__':
    a = Thread(target=battleship, daemon=True)
    a.start()
    a.join()

Here's a Stack Overflow question that beautifully explains this.

\$\endgroup\$
1
  • \$\begingroup\$ +1 for the extensive improvements. You could make hit more readable/maintainable by replacing the multiple if statements by a membership test: if board[row][col] in {'B', 'C', 'D'}: return board[row][col] or if board[row][col] in 'BCD': return board[row][col] \$\endgroup\$ – riskypenguin May 5 at 18:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.