X140Yu
12/4/2015 - 5:28 PM

battleship-ver-2.py

# play the battleship game

import sys
import random

def is_valid_int(integer):
    """
    checks to see if number represents a valid integer
    @number: a string that might represent an integer
    @returns: true if the string represents an integer
    """
    integer = integer.strip()
    if len(integer) == 0:
        return False
    else:
        return (integer.isdigit() or #only digits
                #or a negative sign followed by digits
                (integer.startswith('-') and integer[1:].isdigit()))

def create_board(width, height):
    """
    create the play board
    @width: width of the board which is int
    @height: height of the board, int
    @returns: list of list characters, represents the board
    """
    board = []
    for board_row in range(height):
        board.append(["*"] * width)
    return board

def get_seed_input():
    """
    get user's seed input
    @returns: seed number, should be an int
    """
    # get the seed
    seed = " "
    while not is_valid_int(seed):
        seed = input("Enter the seed: ")
    # change it to int
    seed = int(seed)
    return seed 

def get_width_and_height_input():
    """
    get user's width and height input
    @returns: widht and height, both int
    """
    # get the width
    width = " "
    while not width.isdigit():
        width = input("Enter the width of the board: ")
    # change it to int
    width = int(width)
    
    # get the height
    height = " "
    while not height.isdigit():
        height = input("Enter the height of the board: ")
    # change it to int
    height = int(height)    
    
    return (width, height)

def is_valid_ai_level(ai_level):
    """
    check the AI Level input
    @ai_level: a character represents the AI's level
    @returns:  true if the level is right
    """
    if ai_level == "1":
        return True
    elif ai_level == "2":
        return True
    elif ai_level == "3":
        return True
    else:
        return False

def get_ai_level():
    """
    get user's AI levle input
    @returns: AI's level, which is an int
    """
    ai_level = ""
    while not is_valid_ai_level(ai_level):
        ai_level = input("Choose your AI.\n1. Random\n2. Smart\n3. Cheater\n Your choice: ")
    ai_level = int(ai_level)
    return ai_level

def check_minus_location(line):
    """
    if user put the ship with minus point
    @line: a line of strings
    @returns: None
    """
    # if number less than 0, there is a problem
    for character in line:
        if character == '-':
            print("Error %s is placed outside of the board." % line[0])
            print("Terminating game.")
            sys.exit(0)    

def change_line_to_one_ship_list(line):
    """
    change a line to a ship list
    @line: a line of strings
    @returns: a list, represents a ship location info 
    like ["P", "0", "1", "2", "3"]
    """
    ship_location = list(line)
    # remove the space and "\n"
    ship_location = list(filter(lambda x: x != " " and x != "\n", ship_location))
    return ship_location  

def check_reserved_symbols(name_of_the_ship):
    """
    check if use the reserved symbols
    @name_of_the_ship: symbol, a character
    @returns: None
    """
    # if use the reserved symbols
    if name_of_the_ship in ["x", "X", "o", "O", "*"]:
        print("You should not use the reserved symbols.")
        print("Terminating game.")
        sys.exit(0)

def check_file_content(board_file_name, width, height):
    """
    check the user's input file is right or not
    @board_file_name: file's name, string
    @width: the board's width, int
    @height: the board's height, int
    @returns: the user ships's locations, should be a list
    """
    # open the file
    try:
        board_file = open(board_file_name)
    except:
        # open file failed
        print("File cannot open.")
        print("Terminating game.")
        sys.exit(0)

    # list for symbols eg.'['P', 'A']'
    # check duplicate
    ship_names = []
    # a board to check if user put the ship at the same place
    board = create_board(width, height)
    # the user ships's locations
    user_location_list = []
    # iterate the file with line
    for line in board_file:
        # if user put the ship with minus point
        check_minus_location(line)
        # change a line to a ship list
        ship_location = change_line_to_one_ship_list(line)
        # the name of the ship
        name_of_the_ship = ship_location[0]
        # the two points of the ship
        first_point_x = int(ship_location[1])
        first_point_y = int(ship_location[2])
        second_point_x = int(ship_location[3])
        second_point_y = int(ship_location[4])   
        # check if use the reserved symbols
        check_reserved_symbols(name_of_the_ship)  

        # check outside the board
        if first_point_x >= height or second_point_x >= height or first_point_y >= width or second_point_y >= width:
            print("Error %s is placed outside of the board." % line[0])
            print("Terminating game.")
            sys.exit(0)   

        # if has duplicate names
        if name_of_the_ship in ship_names:
            print("Error symbol %s is already in use." % name_of_the_ship)
            print("Terminating game")
            sys.exit(0)
        # put the name in list
        ship_names.append(name_of_the_ship)

        # if user place diagonal ship 
        if first_point_x != second_point_x and first_point_y != second_point_y:
            print("Ships cannot be placed diagonally.")
            print("Terminating game.")
            sys.exit(0)

        user_location_list.append(ship_location)
        # check if user put the ship on another ship
        # horizontal
        if first_point_x == second_point_x:
            # first_point_y should less than second_point_y
            if first_point_y > second_point_y:
                temp = first_point_y
                first_point_y = second_point_y 
                second_point_y = temp
            for y in range(first_point_y, second_point_y + 1):
                if board[first_point_x][y] != "*":
                    print("There is already a ship at location %d, %d." % (first_point_x, y))
                    print("Terminating game.")
                    sys.exit(0)
                board[first_point_x][y] = name_of_the_ship
        # vertical                
        else:
            if first_point_x > second_point_x:
                temp = first_point_x
                first_point_x = second_point_x 
                second_point_x = temp
            for x in range(first_point_x, second_point_x + 1):
                if board[x][first_point_y] != "*":
                    print("There is already a ship at location %d, %d." % (x, first_point_y))
                    print("Terminating game.")
                    sys.exit(0)
                board[x][first_point_y] = name_of_the_ship
    return user_location_list

def draw_user_board(user_location_list, user_board):
    """
    draw user's board
    @user_location_list: a list with user's location
    @user_board: the user's board
    @returns: None
    """
    for ship_location in user_location_list:
        # the name of the ship
        name_of_the_ship = ship_location[0]
        # the two points of the ship
        first_point_x = int(ship_location[1])
        first_point_y = int(ship_location[2])
        second_point_x = int(ship_location[3])
        second_point_y = int(ship_location[4])  
        
        # horizontal
        if first_point_x == second_point_x:
            # first_point_y should less than second_point_y
            if first_point_y > second_point_y:
                temp = first_point_y
                first_point_y = second_point_y
                second_point_y = temp
            for y in range(first_point_y, second_point_y + 1):
                user_board[first_point_x][y] = name_of_the_ship
        # vertical                
        else:
            if first_point_x > second_point_x:
                temp = first_point_x
                first_point_x = second_point_x
                second_point_x = temp
            for x in range(first_point_x, second_point_x + 1):
                user_board[x][first_point_y] = name_of_the_ship

def get_ship_length(ship_location):
    """
    get the ship length with ship_location
    @ship_location: a list 
    @returns: ship's length, int
    """
    # the name of the ship
    name_of_the_ship = ship_location[0]
    # the two points of the ship
    first_point_x = int(ship_location[1])
    first_point_y = int(ship_location[2])
    second_point_x = int(ship_location[3])
    second_point_y = int(ship_location[4]) 

    length = 0
    if first_point_x == second_point_x:
        length = abs(first_point_y - second_point_y)
    else:
        length = abs(first_point_x - second_point_x)

    length += 1
    return length

def can_draw_on_the_ai_board(ai_board, direction, row, col, ship_length):
    """
    check if point can draw on the ai_board
    @ai_board: board of the AI
    @direction: direction of the ship, 'horz' or 'vert'
    @row: int, row number
    @col: int, col number
    @ship_length: int, length of the ship
    """
    # ai_board is a list of list, copy it not changing it
    ai_temp_board = [x[:] for x in ai_board]
    # the first time 
    if row == " ":
        return False

    if direction == 'horz':
        for i in range(col, col + ship_length):
            if ai_temp_board[row][i] != '*':
                return False
            else:
                ai_temp_board[row][i] = "-"
    else:
        for i in range(row, row + ship_length):
            if ai_temp_board[i][col] != '*':
                return False
            else:
                ai_temp_board[i][col] = "-"

    return True

def draw_ai_real_board(user_location_list, ai_board):
    """
    draw AI's board
    @user_location_list: a list with user's location
    @ai_board: the AI's board
    @returns: None
    """
    width = len(ai_board[0])
    height = len(ai_board)

    # names should in order
    user_location_list = sorted(user_location_list, key = lambda x: x[0])
    messages = []

    for ship_location in user_location_list:
        direction = ''
        row = " "
        col = " "
        ship_length = get_ship_length(ship_location)
        ship_name = ship_location[0]
        while not can_draw_on_the_ai_board(ai_board, direction, row, col, ship_length):
            direction = random.choice(['vert', 'horz'])
            if direction == 'horz':
                row = random.randint(0, height - 1)
                col = random.randint(0, width - ship_length)
            else:
                row = random.randint(0, height - ship_length)
                col = random.randint(0, width - 1)


        if direction == 'horz':
            for i in range(col, col + ship_length):
                ai_board[row][i] = ship_name
            print("Placing ship from %d,%d to %d,%d." % (row, col, row, col + ship_length - 1))
        else:
            for i in range(row, row + ship_length):
                ai_board[i][col] = ship_name
            print("Placing ship from %d,%d to %d,%d." % (row, col, row + ship_length - 1, col))

def print_board(board):
    """
    print the board
    @board:  list of list characters
    @returns: None
    """
    width = len(board[0])
    height = len(board)
    # to fit the test
    print(" ", end="")
    row_index = 0
    for num in range(width):
        print ((' %d' % row_index), end="")
        row_index += 1
    print("")

    col_index = 0
    for row in board:
        print(('%d ' % col_index) + " ".join(row))
        col_index += 1

def print_both_boards(ai_board_blank, user_board):
    """
    print both the boards
    @ai_board_blank: the ai board showing to user
    @user_board: user's board
    both list of list
    """
    width = len(ai_board_blank[0])
    height = len(ai_board_blank)
    # print AI's board
    print("Scanning Board")
    print_board(ai_board_blank)
    # print user's board
    print("My Board")
    print_board(user_board)

def check_wins_with_print(ai_board, user_board):
    """
    check who wins with print info
    @ai_board: AI's board
    @user_board: user's board
    @returns: true if someone wins
    """    
    # user wins
    # change ai_board to a list
    ai_sums = sum(ai_board, [])
    if set(ai_sums) == set(['*', 'X']) or set(ai_sums) == set(['X']):
        print("You win!")
        return True
    # AI wins
    user_sums = sum(user_board, [])
    if set(user_sums) == set(['X']) or set(user_sums) == set(['*', 'X']) or set(user_sums) == set(['*', 'X', 'O']):
        print("The AI wins.")
        return True

    return False

def check_wins_without_print(ai_board, user_board):
    """
    check who wins without print info
    @ai_board: AI's board
    @user_board: user's board
    @returns: true if someone wins
    """
    # user wins
    # change ai_board to a list
    ai_sums = sum(ai_board, [])
    if set(ai_sums) == set(['*', 'X']) or set(ai_sums) == set(['X']):
        return True

    # AI wins
    user_sums = sum(user_board, [])
    if set(user_sums) == set(['X']) or set(user_sums) == set(['*', 'X']) or set(user_sums) == set(['*', 'X', 'O']):
        return True

    return False    


def is_valid_user_guess_point(user_guess_string, ai_board):
    """
    check if the user guess point is right
    @user_guess_string: user's guess string
    @ai_board: AI's board
    @returns: Ture if the string is right
    """
    width = len(ai_board[0])
    height = len(ai_board)

    guess = user_guess_string.split()
    if len(guess) != 2:
        return False

    # unwrap
    (row, col) = guess
    if not row.isdigit() or not col.isdigit():
        return False

    # change to int
    row = int(row)
    col = int(col)

    # out the board
    if col >= width or row >= height:
        return False

    if ai_board[row][col] == '*' or ai_board[row][col] != '.':
        return True

    return False

def get_user_guess(ai_board):
    """
    get user's input
    @returns: row and col both int
    """
    user_guess_point = ''
    while not is_valid_user_guess_point(user_guess_point, ai_board):
        user_guess_point = input("Enter row and column to fire on separated by a space: ")
    (row, col) = user_guess_point.split()
    row = int(row)
    col = int(col)
    return (row, col)

def draw_point_on_ai_board(guess_point, ai_board_blank, ai_board):
    """
    put the point on the ai_board_blank
    @guess_point: the guess point, (int, int)
    @ai_board_blank: the blank AI board
    @ai_board: AI's real board
    @returns: None
    """
    (row, col) = guess_point

    symbol = ai_board[row][col]
    # missed
    if symbol == '*':
        ai_board_blank[row][col] = 'O'
        print("Miss!")
    else: # hit!
        ai_board_blank[row][col] = 'X'
        sunk_name = ai_board[row][col]
        ai_board[row][col] = 'X'
        symbols = sum(ai_board, [])
        if not sunk_name in symbols:
            print("You sunk my %s" % sunk_name)
        else:
            print("Hit!")

def draw_point_on_user_board(point, user_board):
    """
    put the point on the user's board
    @point: the guess point
    @user_board: the user's board
    @returns: true if the AI hit a ship
    """
    row = point[0]
    col = point[1]
    symbol = user_board[row][col]
    if symbol == '*':
        # missed
        user_board[row][col] = 'O'
        print("Miss!")
        return False
    else:
        # hit
        sunk_symbol = user_board[row][col]
        user_board[row][col] = 'X'
        symbols = sum(user_board, [])
        if not sunk_symbol in symbols:
            print("You sunk my %s" % sunk_symbol)
            return True
        else:
            print("Hit!")
            return True

def create_ai_random_list(width, height):
    """
    create AI's random list
    @width:   width of the board
    @height:  height of the board
    @returns: the created list
    """
    random_list = []
    for row in range(height):
        for col in range(width):
            random_list.append([row, col])
    return random_list

def level_one_ai_turn(random_ai_list, user_board):
    """
    level one AI plays the game
    @random_ai_list: the rest of the random list
    @user_board: user's board
    @returns: None
    """
    ai_guess_point = random.choice(random_ai_list)
    random_ai_list.remove(ai_guess_point)
    print("The AI fires at location (%d, %d)" % (ai_guess_point[0], ai_guess_point[1]))
    draw_point_on_user_board(ai_guess_point, user_board)

def level_two_ai_turn(random_ai_list, destory_list, user_board):
    """
    level Two AI plays the game
    @random_ai_list: the rest of the random list
    @destory_list: the destory list
    @user_board: user's board
    @returns: None
    """    
 
    if len(destory_list) > 0:
        # destory mode
        ai_guess_point = destory_list[0]
    
    else:
        # hunt mode
        ai_guess_point = random.choice(random_ai_list)
    if ai_guess_point in destory_list:
        destory_list.remove(ai_guess_point)
    if ai_guess_point in random_ai_list:
        random_ai_list.remove(ai_guess_point)

    print("The AI fires at location (%d, %d)" % (ai_guess_point[0], ai_guess_point[1]))
    if draw_point_on_user_board(ai_guess_point, user_board) == True:
        #add point to destory_list
        width = len(user_board[0])
        height = len(user_board)
        row = ai_guess_point[0]
        col = ai_guess_point[1]

        # up
        new_row = row - 1
        new_col = col
        if new_row >= 0 and user_board[new_row][new_col] != 'O' and user_board[new_row][new_col] != 'X':
            if not [new_row, new_col] in destory_list:
                destory_list.append([new_row, new_col])

        # below
        new_row = row + 1
        new_col = col
        if new_row < height and user_board[new_row][new_col] != 'O' and user_board[new_row][new_col] != 'X':
            if not [new_row, new_col] in destory_list:
                destory_list.append([new_row, new_col])

        # left
        new_row = row
        new_col = col - 1
        if new_col >= 0 and user_board[new_row][new_col] != 'O' and user_board[new_row][new_col] != 'X':
            if not [new_row, new_col] in destory_list:
                destory_list.append([new_row, new_col])

        # right
        new_row = row
        new_col = col + 1
        if new_col < width and user_board[new_row][new_col] != 'O' and user_board[new_row][new_col] != 'X':
            if not [new_row, new_col] in destory_list:
                destory_list.append([new_row, new_col])

def level_three_ai_turn(user_board):
    """
    level three AI plays the game
    @user_board: user's board
    @returns: None
    """
    width = len(user_board[0])
    height = len(user_board)
    
    for x in range(height):
        for y in range(width):
            symbol = user_board[x][y]
            if symbol != '*' and symbol != 'X':
                print("The AI fires at location (%d, %d)" % (x, y))
                draw_point_on_user_board([x, y], user_board)
                return


def play_game():
    """
    play the battleship game
    """

    # -------- get user's input
    # get the seed
    seed = get_seed_input()
    # get the width and height
    (width, height) = get_width_and_height_input()
    # get the file name
    board_file_name = input("Enter the name of the file containing your ship placements: ")
    # get the AI level
    ai_level = get_ai_level()
    
    # -------- game's setup and check the file contnet
    # Seed the random number generator with the provided seed
    random.seed(seed)
    # create the blank AI board
    ai_board_blank = create_board(width, height)
    # Construct the user's board
    user_board = create_board(width, height)
    # Construct the AI's board
    ai_board = create_board(width, height)
    # check if the file content is right
    user_location_list = check_file_content(board_file_name, width, height)

    # --------- file's checked, draw the board of user
    # draw the user's board
    draw_user_board(user_location_list, user_board)
    # draw the AI's real board
    draw_ai_real_board(user_location_list, ai_board)

    # --------- random the seed to decide who is the first
    who_is_the_first = random.randint(0, 1)
    # if user's the first, should print the boards info first to fit the test
    if who_is_the_first == 0:
        print_both_boards(ai_board_blank, user_board)

    # Construct a random list for AI level 1 and 2
    random_ai_list = create_ai_random_list(width, height)
    # Construct the destory list for AI level 2 destory mode
    destory_list = []

    while not check_wins_with_print(ai_board, user_board):
        # ------- user's turn
        if who_is_the_first == 0:
            # get the user's guess point
            guess_point = get_user_guess(ai_board)
            # change AI's board
            draw_point_on_ai_board(guess_point, ai_board_blank, ai_board)
            # print if needed to fit the test
            if check_wins_without_print(ai_board, user_board) == True:
                print_both_boards(ai_board_blank, user_board)
            who_is_the_first = 1
        else:
        # ------- AI's turn
            if ai_level == 1:
                level_one_ai_turn(random_ai_list, user_board)
            elif ai_level == 2:
                level_two_ai_turn(random_ai_list, destory_list, user_board)
            else:
                level_three_ai_turn(user_board)
            print_both_boards(ai_board_blank, user_board)
            who_is_the_first = 0

if __name__ == '__main__':
    play_game()