Extra Credit: Let me see what you have!


#1
Welcome to the Extra Credit Mega Thread, post your code simple or complex let's see what you have!

It would be unfair of me to ask for your examples if I did not provide my own.

Owen's Battleships Updated Git
Codeacademy Lab The Codeacademy Labs is finicky best of luck, just hit run and play.

The above is a full game of Battleship I am still adding things as I learn more as it has kinda become a code repository for me in a way. It does play with 2 players with full boards and fleets.

There are no rules as to what you can post for the Extra Credit other than it must be an addition that adds something that you wanted.

Ask questions on why something was implemented or how is what I am after because it increase the speed at which we learns things if misunderstanding is dissipated.

EDIT:

Redid the whole Git thing, I am working on it a little while I work on my other projects, which happens to including finding primes over 10,000,000,000.


Extra credit 19/19 multiple ships
13. Battleship: Extra functionality for the game
14/19 test run: what about letters?
Maximum-recursion-depth-exceeded-while-calling-a-python-object
After 'Extra Credit' - Migrating to Python 3.5
#2

I put a new Battleship up with a new AI that does better, if you want an old version go to the master branch and grab it. Also there may be bugs because I rewrote the whole program, if you find one just send me a mail or leave a comment.


#3

I've completed the extra credit, except for adding multiple players. The game includes 5 different boats of varying sizes, placed randomly on the board. I'm still in the first stages of learning Python, and I'd greatly appreciate feedback on ways I could improve the code.

Here's the code: http://codeshare.io/Jgvni

Steve


#4

@mrawesomeman

Looks good, real good. One thing I would recommend though is that you have tons of duplicate code that you could convert into functions and just supply variables instead of repeating the code over and over again.

After looking through your code I have a few suggestions to add for you. I have example code for you.

Let's create an input function so we always get valid input from the user., and our random numbers from the computer.

def return_random_cord():
    return randint(1, 10), randint(1, 10)

def get_cord(valid=False):
    while not valid:
        try:
            cord = int(raw_input("Pick a number 1-10")), int(raw_input("Pick a number 1-10"))
            if 0 < (cord[0] and cord[1]) <= 10:
                valid = True
                return cord
        except ValueError:
            print("You failed to select a valid number!"

Also change how we store the board to make it easier to use the data. After all it is a cord system in Battleship so let's use that to our advantage. This is going to be a dictionary of cords with a value stored at the cord.

board_range = 10
board = {(x, y): "~Water" for x in range(1, board_range + 1) for y in range(1, board_range + 1)

Next we need to be able to print the board.

from collections import OrderedDict

def print_board(masked=False):
    if masked:
        for y in range(1, self.sides):
            print(' '.join(str(printable_board[x, y])[0] for x in range(1, board_range 
)))
    elif not masked:
        for y in range(1, self.sides):
            print(' '.join(str(printable_board[x, y])[1] for x in range(1, board_range 
)))

Now that we can print the board we should probably have some way to put boats on the board. Using the fact you are already placing sections I'm only going to do that part. So let's just place one part of the boat.

ship_to_be_placed((1,1), 'Battleship')

# You can add checking to see if it's occupied here then return False and have
# the function that calls this handle the False into restarting the ship placement.
def place_ship_section(cord, ship):
    board[cord] = board[cord][0] + ship

Ok now on to shooting at the ships, all we have to do is ensure that a space does not contain water or a miss and it is a hit, easy enough. Also I only am showing how to check the spot you will have to add code to check for a ship, a list w/ the ships in it and a for loop would handle most of that issue.

shoot_cords = (1, 1)
def fire_at_ship(cord):
    if 'water not in board[cord].lower(): # Might want to handle a miss differently
        print("You failed to hit")
    else:
        print("You hit %s at [%s, %s]" % (board[cord][1:], cord[0], cord[1])
        board[cord] = '^' + 'hit' + board[1:]

These are all decent suggestions, but it's up to your really. If you don't get why i did something ask.

You should document your code too while your at it, it will help you gain a better understanding of what it does and it makes it easier to pick it back up later.

As always best of luck.


#5

Thank you for your feedback! I'm looking the code over and trying to understand how it works. I'm still fairly new to Python (and programming in general), so I'm not sure what some of the syntax means, but I'm looking up anything I'm unclear about. Thanks again!


#6

I still have to update my lab with the new AI, but it might not work because of how slow the lab python is. Though unless you are allowing the cpu to beat its self up should present no issues. I should have that up later tonight.


#7

Yea, the Lab will not work with my AI and I do not feel like going through the whole script just to make it compatible with Python 2.7.2.

In other news I have updated my Script, while i still have a few more things I want to do I have made it save the game data of the winner for now. It will be saved in the file-path of where the script is run. It tells what the player is and hit/miss data.

I wonder if I will let me post it...

Battleship 1.0 for Python 3.

__author__ = 'Charles Engen'

"""
This is a full implementation of the Game Battleship!
There are still somethings I would like to work on but as of right now this is a full text game of Battleship.
TODO:
    Have preference file so you can change if the game saves accuracy or not.
"""

from collections import OrderedDict
from random import randint
from functools import wraps
import cProfile

Display = True
Save_Stats = True
turns = 0

board_size = 10

out_put_data = {
    0: "Player %s Are you 'Man' or 'Machine'?",
    1: "You failed to enter a valid entry.",
    2: "What is your name?",
    3: "Pick a number 1-10",
    4: "You have already fired here at [%s, %s]",
    5: "At [%s, %s] was nothing!",
    6: 'You have hit the something at [%s, %s]',
    7: "You tried and failed to attack, try again.",
    8: "You sunk %s!",
    9: "You are placing the %s",
    10: "You are placing %s at (%s, %s) %s",
    11: "Failed to place %s at (%s, %s)",
    12: "%s at %s",
    13: "You are a %s!!!",
    14: "Hit a key to continue.",
    15: "Place your shot Player 1",
    16: "Player %s\'s known Board, on turn %s",
    17: "Place your shot Player 2",
    18: "Your accuracy was %s, you fired %s times!",
    19: "Enter 1 to profile.",
    21: "\n******************************\n"
        "*********CONGRATS*************\n"
        "******************************\n",
    22: "'%s is %s tiles long.'",
    23: "Horizontal or Vertical?",
    24: "A random entry has been done.",
    25: "{'Player': '%s', 'Shots': %s, 'Hits': %s, 'Accuracy': %s, 'Win ON': %s}\n"
}


def handle_player_input(func):
    """
    This function wraps the two functions that deal with player input.
    If the player hit enter when asked for input it will give a random response.
    :param func: Pass the function you wish to test
    :return: Returns the function after working on it's data
    """
    @wraps(func)
    def function_wrap(*args, **kwargs):
        """
        This function takes the arguments of the passed function and works on them.
        :param args: The arguments of the function
        :param kwargs: The key arguments of the function
        :return: Returns the data if a response is valid
        """
        def change_bool_value(*args, **kwargs):
            """
            This function changes the bool value of player if no response was entered.
            :param args: The arguments of the function
            :param kwargs: The key arguments of the function
            :return: Returns converted data
            """
            arg_list = list(args)
            if True in args:
                arg_list = list(args)
                index = arg_list.index(True)
                arg_list[index] = False
            try:
                if True in kwargs['player']:
                    kwargs['player'] = True
            except KeyError:
                pass
            return tuple(arg_list), kwargs

        valid = False
        while not valid:
            try:
                valid = True
                return func(*args, **kwargs)
            except ValueError:
                valid = True
                print(out_put_data[1], "\n", out_put_data[24])
                return func(*change_bool_value()[0], **change_bool_value()[1])
            except IndexError:
                valid = True
                print(out_put_data[1], "\n", out_put_data[24])
                return func(*change_bool_value()[0], **change_bool_value()[1])
            except TypeError:
                valid = False
                print(out_put_data[1])
    return function_wrap


@handle_player_input
def ask_xy(player=False):
    """
    This function asks for player input regarding the position wanted.
    :param player: When True will ask for input, when False will return a random number
    :return: Returns position
    """
    if player:
        return int(input(out_put_data[3]))
    else:
        return randint(1, board_size)


@handle_player_input
def horizontal_or_vertical(player=False):
    """
    This function ask the orientation wanted.
    :param player: If True will ask for input if False will return random direction.
    :return: Returns direction
    """
    if player:
        h_v = str(input(out_put_data[23]))
        if h_v[0].lower() == 'v':
            return 0
        elif h_v[0].lower() == 'h':
            return 1
        else:
            raise ValueError
    else:
        return randint(0, 1)


def ask_type(player):
    """
    This function asks for player type, when no type is entered returns False
    :param player: Pass the player Class to work on
    :return: Returns True/False based on Player/Not
    """
    p_type = str(input(out_put_data[0] % player))
    if 'man' in p_type.lower():
        return True
    else:
        return False


def check_sink(opponent, ship):
    """
    This function checks to see if a ship has been sunk,
    also sets the ship's bool value to True if sunk.
    :param opponent: Pass the Opponents Class Object here
    :param ship: Pass the Ship you would like to check here
    :return: Returns True if the ship is sunk
    """
    ship_hp = 0
    for i in range(1, opponent.sides):
        for j in range(1, opponent.sides):
            if ship.get_name() in opponent.storedBoard[i, j] and 'Damaged' in opponent.storedBoard[i, j]:
                ship_hp += 1

    if ship.get_length() == ship_hp:
        if Display:
            print(out_put_data[8] % ship.get_name())
        for boat in opponent.fleet:
            if ship.get_name() == boat.__dict__['shipName']:
                for number, nship in enumerate(opponent.fleet):
                    if nship.__dict__['shipName'] == ship.get_name():
                        opponent.sink_ship(number)
                return True


def check_win(player):
    """
    This function checks to see if a player has sunk all ships by
    checking if they have hit 17 positions
    :param player: Pass the player to be checked for all sunk ships
    :raise: When invoked it will cause an exception that is caught by the _start_game Mainloop
    """
    if player.hits == 17:
        raise GameWin(player)


# These Two Classes are Custom Errors to catch specific events
class ShipError(BaseException):
    """
    This Class Exception is used for Flow control only.
    """
    pass


class GameWin(BaseException):
    """
    This Class Exception is used for flow control and to display data when called.
    """

    def __init__(self, player1):
        """
        The __init__ computes a players accuracy and displays it.
        :param player1:
        """
        self.player = player1.playerType
        self.hits = player1.hits
        self.misses = player1.misses
        self.save_data()
        self.print_accuracy()

    def print_accuracy(self):
        total = self.hits + self.misses
        print(out_put_data[21])
        print(out_put_data[18] % (((self.hits / total) * 100), total))

    def save_data(self):
        """
        This function is used to save game data after a win.
        """
        global turns
        if Save_Stats:
            with open("battleship_stats.txt", "a") as file:
                file.write(out_put_data[25] % (self.player, (self.hits+self.misses), self.hits,
                                               (self.hits / (self.hits+self.misses)), turns))
        turns = 0


class ShipBlueprint(object):
    """
    This function is the framework for all player ships.
    """

    def __init__(self, ship_length, ship_name, sunk):
        """
        The __init__ takes the length and name of the ship and assigns it a bool value
        to tell if it has been sunk or not.
        :param ship_length: Length of ship to be generated
        :param ship_name: Name of ship to be generated
        :param sunk: True/False if the ship is sunk
        """
        self.shipLength = ship_length
        self.shipName = ship_name
        self.sunk = sunk

    def get_name(self):
        """
        This function gets the name of the ship
        :return: Returns the name of the called ship
        """
        return self.__dict__['shipName']

    def get_length(self):
        """
        This function returns the length of the ship
        :return: Returns the length of the ship
        """
        return self.__dict__['shipLength']

    def __str__(self):
        """
        When a ship is printed it will print the str of out_put_data[22] which takes two
        string inputs of ship name and ship length
        :return: Returns the string to be displayed
        """
        return out_put_data[22] % (self.__dict__['shipName'], self.__dict__['shipLength'])

    def __call__(self):
        """
        When a ship blueprint object is called it will return if the ship is sunk or not
        :return: Returns True/False based on the Ship's state
        """
        return self.sunk


class Board(object):
    """
    This is the board class that contains the board and all the functions
    to work on the board.
    """

    def __init__(self, size=10):
        """
        The __init__ has a standard size of 10 but can be larger/smaller if the user wants to edit it.
        :param size: Changes the size of the board
        """
        self.sides = size
        self.startBoard = {(x, y): '~Water' for y in range(1, self.sides+1) for x in range(1, self.sides+1)}
        self.backedup_board = self.startBoard
        self.storedBoard = dict()

    def print_board(self):
        """
        This function prints out a man-readable board with no masking.
        """
        printable_board = OrderedDict(sorted(self.startBoard.items()))
        for y in range(1, self.sides):
            print(' '.join(str(printable_board[x, y])[0] for x in range(1, self.sides)))

    def print_masked_board(self, nonprint=False):
        """
        This function prinks out a masked human readable board.
        :param nonprint: If set to False will print out the dictionary of the board
        :return: Returns the masked_ships for printing.
        """
        masked_ships = OrderedDict(sorted(self.storedBoard.items()))
        for x in range(1, self.sides):
            for y in range(1, self.sides):
                if 'Damage' in masked_ships[x, y]:
                    masked_ships[x, y] = 'X'
                elif 'Miss' in masked_ships[x, y]:
                    masked_ships[x, y] = '^'
                else:
                    masked_ships[x, y] = '~'
        if not nonprint:
            for y in range(1, self.sides):
                print(' '.join(str(masked_ships[x, y])[0] for x in range(1, self.sides)))
        elif nonprint:
            return masked_ships

    def backup(self, revert=False):
        """
        This function saves the state of the bard and reverts it when told to do so.
        :param revert: When True will revert the board to a previous state
        :return: Returns the board
        """
        def revert_board():
            """
            This nested function only reverts the board to a previous state
            :return: Returns the Start board state
            """
            self.startBoard = dict(self.backedup_board)
            return self.startBoard

        if revert:
            revert_board()
        else:
            self.backedup_board = dict(self.startBoard)
            return self.backedup_board

    def final_board(self):
        """
        This function finalizes the board for play.
        :return: Returns the final board
        """
        self.storedBoard = self.startBoard
        return self.storedBoard


class Player(Board):
    """
    This class has all the player functions and methods
    """

    def __init__(self, type_player):
        """
        The __init__ sets the parameters for the player
        :param type_player: The passes argument tells the class what type the player is
        """
        Board.__init__(self)
        self.playerType = type_player
        self.misses = 0
        self.hits = 0
        # This list fills it's self with ships using the arguments inside
        self.fleet = [
            ShipBlueprint(5, 'Aircraft Carrier', False),
            ShipBlueprint(4, 'Battleship', False),
            ShipBlueprint(3, 'Submarine', False),
            ShipBlueprint(3, 'Destroyer', False),
            ShipBlueprint(2, 'Patrol Boat', False)
        ]

    def sink_ship(self, ship):
        """
        This function changes the state of a ship to True when sunk
        :param ship: Pass the ship to be sunk.
        """
        self.fleet[ship].__dict__['sunk'] = True

    def attack_player(self, opposition_player):
        """
        This function allows the player to attack the other player.

        It also uses a while loop in conjunction with a try to keep the player
        from missing a turn for a bad entry.
        :param opposition_player: Pass the player to be attack
        """

        def get_cords():
            """
            This function is used to call ask_xy twice. just because I can.
            :return: Returns the values of ask_xy
            """
            return ask_xy(player=True), ask_xy(player=True)

        while True:
            try:
                i_x, i_y = get_cords()
                # The following line figure out if a ship was hit or not
                # and does the necessary operations to hit/sink/miss the ship
                if ('Damaged' or 'Miss') in opposition_player.storedBoard[i_x, i_y]:
                    print(out_put_data[4] % (i_x, i_y))
                    raise ValueError

                elif 'Water' in opposition_player.storedBoard[i_x, i_y]:
                    print(out_put_data[5] % (i_x, i_y))
                    opposition_player.storedBoard[i_x, i_y] = 'Missed'
                    self.misses += 1
                    return False

                else:
                    for ship in range(len(opposition_player.fleet)):
                        if opposition_player.storedBoard[i_x, i_y] in opposition_player.fleet[ship].get_name():
                            print(out_put_data[6] % (i_x, i_y))
                            opposition_player.storedBoard[i_x, i_y] += 'Damaged'
                            self.hits += 1
                            check_sink(opposition_player, self.fleet[ship])
                            check_win(self)
                            return False
            except ValueError:
                print(out_put_data[7])
            except IndexError:
                pass
            except KeyError:
                pass
            except TypeError:
                pass

    def place_ship_section(self, ship, pos_x, pos_y):
        """
        This function places one section of the ship
        :param ship: Ship to be placed
        :param pos_x: Location of the placed ship in x
        :param pos_y: Location of the ship in y
        """
        if 'Water' not in self.startBoard[pos_x, pos_y]:
            raise StopIteration()
        else:
            self.startBoard[pos_x, pos_y] = ship.get_name()

    def ship_gen(self, ship, pos_x, pos_y, h_v):
        """
        This function is used to place ship sections based on the length
        and horizontal or vertical orientation of said ship. It uses the
        place_ship_section method to place each section of a ship
        :param ship: Ship to be placed
        :param pos_x: Top leftmost x value of Ship
        :param pos_y: Top leftmost y value of Ship
        :param h_v: Horizontal or Vertical Orientation of Ship
        """
        try:
            self.backup()
            for tile in range(ship.get_length()):
                if h_v:
                    self.place_ship_section(ship, pos_x, (pos_y + tile))
                elif not h_v:
                    self.place_ship_section(ship, (pos_x + tile), pos_y)
        except StopIteration:
            raise ShipError()
        except KeyError:
            raise ShipError()
        except IndexError:
            raise ShipError()

    def fleet_gen(self, on=True):
        """
        This function generates all ships in the player's fleet.
        Everything needed to place the ship is called and used in this function.
        Also has error checking to ensure that a ship is placed in bounds.
        :param on: Inter-function State
        """
        global Display
        display = Display
        while on:
            try:
                self.backup()
                for number, ship in enumerate(self.fleet):
                    if 'Man' in self.playerType:
                        while True:
                            try:
                                if display:
                                    print(out_put_data[9] % self.fleet[number].get_name())
                                pos_x, pos_y = ask_xy(player=True), ask_xy(player=True)
                                h_v = horizontal_or_vertical(player=True)
                                if display:
                                    print(out_put_data[10] % (self.fleet[number].get_name(), pos_x, pos_y, h_v))
                                self.ship_gen(self.fleet[number], pos_x, pos_y, h_v)
                            except ShipError:
                                self.backup(revert=True)
                                if display:
                                    print(out_put_data[11] % (self.fleet[number], pos_x, pos_y))
                                continue
                            break
                        else:
                            on = False
                            return on
                    elif 'Machine' in self.playerType:
                        while True:
                            try:
                                pos_x, pos_y, h_v = ask_xy(), ask_xy(), horizontal_or_vertical()
                                self.ship_gen(self.fleet[number], pos_x, pos_y, h_v)
                            except ShipError:
                                self.backup(revert=True)
                                continue
                            break
                else:
                    on = False
                    return on
            except TypeError as e:
                print(out_put_data[12] % (TypeError, e))
            finally:
                self.storedBoard = self.final_board()
                pass

    def __str__(self):
        """
        When a player object is called it prints out what that player's type is.
        """
        return out_put_data[13] % self.playerType


class AI(Player):
    """
    This Class inherits from the Player class and overwrites the attack method of the Player class.
    There are three difficultly Ais in this class.
    """

    def __init__(self, difficulty, type_player):
        """
        The __init__ sets up the AI parameters
        :param difficulty: Difficulty 0-2 *easy-hard*
        :param type_player: Player type to be passed to the Player.__init__
        """
        Player.__init__(self, type_player)
        self.difficulty = difficulty
        self.hit_state = False
        self.pos_hit = []
        self.even_moves = [(x, y) for x, y in self.startBoard if (x or y) % 2 == 0]
        self.odd_moves = [(x, y) for x, y in self.startBoard if (x or y) % 2 != 0]
        self.moves_left = [(x, y) for x, y in self.startBoard]
        self.point_map = {(x, y): 0 for x in range(1, self.sides+1) for y in range(1, self.sides+1)}
        self.hit_map = []
        self.last_hit = ()
        self.delta_move = [
            (1, 0),
            (-1, 0),
            (0, 1),
            (0, -1)
        ]

    def easy_difficulty(self):
        """
        Easy AI
        :return: returns x, y cords to attack
        """
        pos_x, pos_y = self.moves_left[randint(0, len(self.moves_left))]
        return pos_x, pos_y

    def med_difficulty(self):
        """
        Medium AI
        :return: Returns x, y cords to attack
        """
        # Target Mode
        if self.pos_hit:
            posx, posy = self.pos_hit[-1]
            pos_moves = [((posx + move[0]), (posy + move[1])) for move in self.delta_move
                         if ((posx + move[0]), (posy + move[1])) in self.moves_left]
            if not pos_moves:
                self.pos_hit.remove((posx, posy))
            else:
                pos = pos_moves[randint(0, len(pos_moves)-1)]
                x, y = pos
                return x, y
        # Hunt(w/ parity) Mode
        else:
            pos_e = [(pos[0], pos[1]) for pos in self.even_moves if pos in self.moves_left]
            if pos_e:
                pos_x, pos_y = pos_e[randint(0, len(pos_e)-1)]
            else:
                pos_x, pos_y = self.moves_left[randint(0, len(self.moves_left)-1)]
            return pos_x, pos_y

    # All Following Functions are part of Hard Difficulty
    def check_delta_moves(self, coord, magnitude):
        """
        This function checks all the adjacent tiles around the given cords,
        it then checks to see if they are valid moves. If there is any invalid move
        in a set of cords it does not put them into the list.
        :param coord: Pass cords to be checked
        :param magnitude: Pass how far out from cords you want to check
        :return: Returns a list of all adjacent cords
        """

        to_move = [possible_move for possible_move in
                   [move for move in
                    [
                        [(coord[0] + (c_x * mag), coord[1] + (c_y * mag))
                         for mag in range(1, magnitude)]
                        for c_x, c_y in self.delta_move] if coord in self.moves_left]
                   if magnitude == possible_move]
        return to_move

    def valid_attack_move(self, coord):
        """
        This function creates a list using the check_delta_moves Method to create a list
        of cords that all player ships that can start at a give cord.
        :param coord: Cord to be checked
        """
        test = [item for sublist in
                [self.check_delta_moves(coord, ship_length) for ship_length in
                 [self.fleet[ship].get_length() for ship in range(len(self.fleet)) if not self.fleet[ship]()]]
                for item in sublist]

        for item in test:
            self.point_map[item] += 1
        self.point_map[coord] += 1

    def check_all_moves(self):
        """
        This Method calls the valid_attack_move Method for all cords in the move_left list.
        :return:
        """
        for c_x, c_y in self.moves_left:
            self.valid_attack_move((c_x, c_y))

    def best_move(self):
        """
        This function determins all tiles with the most points and returns a random value from that list.
        :return: Returns the highest score tile
        """
        max_value = max(self.point_map.values())
        best_moves = [key for key in self.point_map.keys() if self.point_map[key] == max_value]
        return best_moves[randint(0, len(best_moves)) - 1]

    def remove_move(self, coord):
        """
        This function removes a coord from moves_left.
        :param coord: Cord to be removed
        """
        self.moves_left.remove(coord)

    def reset_point_map(self):
        """
        This Method resets all points on teh point map to 0
        """
        self.point_map = {(x, y): 0 for x in range(1, self.sides+1) for y in range(1, self.sides+1)}

    def check_adj_tiles(self, map_to_check):
        """
        This method returns all adjacent tiles that are still in moves left.
        :param map_to_check: The set of tiles to be checked.
        :return: Returns list of adjacent tiles that are valid
        """
        return [(x1+d_x, y1+d_y) for x1, y1 in map_to_check for d_x, d_y in self.delta_move
                if (x1+d_x, y1+d_y) in self.moves_left]

    def adjust_for_hits(self):
        """
        This function adds points to the point map for all spaces adjacent to hit tiles if valid.
        """
        all_moves = [sublist_5 for sublist_5 in self.check_adj_tiles(
            [sublist_4 for sublist_4 in self.check_adj_tiles(
                [sublist_3 for sublist_3 in self.check_adj_tiles(
                    [sublist_2 for sublist_2 in self.check_adj_tiles(
                        [sublist for sublist in self.check_adj_tiles(self.hit_map)
                         if sublist]) if sublist_2]) if sublist_3]) if sublist_4]) if sublist_5]
        for cord in all_moves:
            self.point_map[cord] += 1

    def adjust_last_hit(self):
        """
        This function adds points to the point map around the spaces of the last hit.
        """
        moves = [cord for cord in
                 [(c_x + self.last_hit[0], c_y + self.last_hit[1])
                  for c_x, c_y in self.delta_move] if cord in self.moves_left]
        for move in moves:
            self.point_map[move] += 5

    def hard_difficulty(self):
        """
        Hard Difficulty AI
        """
        self.reset_point_map()
        self.check_all_moves()
        self.adjust_for_hits()
        # Hunt Mode
        if not self.hit_state:
            return self.best_move()
        # Target mode
        elif self.hit_state:
            self.adjust_last_hit()
            return self.best_move()

    # This overrides the Player class attack_player
    # That way it does a computer attack instead
    def attack_player(self, opposition_player, a_x=0, a_y=0):
        """
        This Method is used to attack the other player and is an override of the
        Player attack_player method
        :param opposition_player: Pass the player to be attacked
        :param a_x: x cord to be attacked
        :param a_y: y cord to be attacked
        """
        if self.difficulty == 0:
            a_x, a_y = self.easy_difficulty()
        elif self.difficulty == 1:
            a_x, a_y = self.med_difficulty()
        elif self.difficulty == 2:
            a_x, a_y = self.hard_difficulty()

        self.moves_left.remove((a_x, a_y))

        if (a_x, a_y) in self.moves_left:
            print("FAIL at %s,%s" % (a_x, a_y))

        if 'Water' in opposition_player.storedBoard[a_x, a_y]:
            # print(out_put_data[5] % (a_x, a_y))
            opposition_player.storedBoard[a_x, a_y] = 'Missed'
            self.misses += 1
            self.hit_state = False

        else:
            for ship in range(len(opposition_player.fleet)):
                if opposition_player.storedBoard[a_x, a_y] in opposition_player.fleet[ship].get_name():
                    if Display:
                        print(out_put_data[6] % (a_x, a_y))
                    opposition_player.storedBoard[a_x, a_y] += 'Damaged'
                    self.hits += 1
                    check_sink(opposition_player, opposition_player.fleet[ship])
                    check_win(self)
                    self.hit_map.append((a_x, a_y))
                    self.hit_state = True
                    self.last_hit = (a_x, a_y)


def _start_game(wait_x=1):
    """
    This function is used to control the game.
    :param wait_x: Inter-function Variable
    """
    global p1
    global p2
    global turns
    global Display
    display__start = Display
    if not display__start:
        p1 = AI(difficulty=2, type_player='Machine_1')
        p1.fleet_gen()
        p2 = AI(difficulty=2, type_player='Machine_2')
        p2.fleet_gen()
    elif display__start:
        one = ask_type(1)
        if one:
            p1 = Player(type_player='Man')
        else:
            p1 = AI(difficulty=2, type_player='Machine_1')
            p1.playerType = 'Machine'
        p1.fleet_gen()
        two = ask_type(2)
        if two:
            p2 = Player(type_player='Man')
        else:
            p2 = AI(difficulty=2, type_player='Machine_2')
        p2.fleet_gen()
        input(out_put_data[14])
    while wait_x:
        while True:
            try:
                if turns % 2 == 0:
                    if display__start:
                        print(out_put_data[15])
                    p1.attack_player(p2)
                    turns += 1
                    if display__start:
                        p2.print_masked_board()
                        print(out_put_data[16] % ('Two', turns))
                elif turns % 2 != 0:
                    if display__start:
                        print(out_put_data[17])
                    p2.attack_player(p1)
                    turns += 1
                    if display__start:
                        p1.print_masked_board()
                        print(out_put_data[16] % ('One', turns))
            except GameWin:
                wait_x -= 1
                if turns % 2 == 0:
                    p2.print_masked_board()
                if turns % 2 != 0:
                    p1.print_masked_board()
                return False


if __name__ == "__main__":
    Profile = input(out_put_data[19])
    if Profile:
        Display = False

        def run_lot():
            for x in range(1000):
                _start_game()
                if x % 10 == 0:
                    print(x)
        cProfile.run('run_lot()')
    _start_game()

#8

How did you come up with the logic for the code? Did you write it from scratch or you had help from other resources?


#9

@methodslayer03402

I wrote the whole thing from scratch but I did use ton of resources. I had to look around for certain things because I didn't know how they worked yet so it was a scavenger hunt for the bit's and pieces needed.

The logic for the cpu targeting is an ideal from this paper I read about the best way to attack in a battleship game. I still have to implement another check but atm it wins on average after 67.7 turns after 2078 games.

The lowest being 37 turns and the highest being 99.

I think after it misses 2-3 times in a row I will have it run a different algorithm. I still have to think about what how it will score the moves but that would be the best so there is more consistency between games....


#10

Battleship Code

from random import randint
board = []
for x in range(5):
board.append(["O"] * 5)

def print_board(board):
for row in board:
print ((" ").join(row))

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

def random_row(board):
return randint(0, len(board) - 1)
def random_col(board):
return randint(0, len(board) - 1)

ship_row = random_row(board)
ship_col = random_col(board)

print ("")

The next two lines show where the ship is!

print (ship_row)

print (ship_col)

Everything from here on should go in your for loop!

Be sure to indent four spaces!

for turn in range(4):
print ("")
print (("Turn"), turn + 1)
guess_row = int(input("Guess Row: "))
guess_col = int(input("Guess Col: "))
if guess_row == ship_row and guess_col == ship_col:
print ("Congratulations! You sunk the Battleship!")
break
else:
if (guess_row < 0 or guess_row > 4)\
or (guess_col < 0 or guess_col > 4):
print ("Oops, that's not even in the ocean.")
elif(board[guess_row][guess_col] == "X"):
print ("You guessed that one already.")
else:
print ("You missed the Battleship!")
board[guess_row][guess_col] = "X"
if turn == (3):
print ("")
print_board(board)
print ("")
print ("Game Over!")
print ("")
print (("The Battleship was at...")\
+ ("Row ") + str(ship_row) + (" ")\
+ ("Col ") + str(ship_col))
board[ship_row][ship_col] = "B"
print ("")

print_board(board)

#11

Talk about a pain in the rear, if you ever want to flatten an arbitrary amount of lists and want to retain the inner most data types then i have something for you because I need it.

The Function

def flatten(a_list):
            hold = []
            for item in a_list:
                if list == type(item):
                    for it in item:
                        hold.append(it)
                else:
                    hold.append(item)
            return flatten(hold) if any(type(part) == list for part in hold) else hold

Here is some data to test it on,

items = [[[[[[[[[[[[[[[[[[[[(1, 1)], (2, 2)], (3, 3)], (4, 4)], (5, 5)], (6, 6)], 7], 8], 9], 10], (11, 11)], 12], {13: 13}], 14], (15, 15)], 16], 17], 18], 19], (20, 20)]

And the MAGIC

# OUTPUT: [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), 7, 8, 9, 10, (11, 11), 12, {13: 13}, 14, (15, 15), 16, 17, 18, 19, (20, 20)]

And as a bonus it maintains the ordering!

Man that was pain in the read to get working.


Anyone please help
#12

Alrighty, I revamped my AI for hard and fixed my point map display program. The next thing I am going to work on is adding GUI support for the game.

Also, if you want to run a bunch of concurrent games at once I did include a new script for that too.

There are also a few scripts in the other folder on Github that you can check out if you want.

Here is the link to the post

I hope some more people will post their code as well, I have seen a few good examples so far.


#13

this code is still a work in progress.
you can run two instances of the program on your own computer, or run a single instance on two separate computers that are connected on the same LAN.
start one as the server, and the other as the client.
'jsonsockets.py' is required and available for download from github at: https://github.com/mdebbar/jsonsocket

this code is still a work in progress. there is little error handling. if either player fails to enter a coordinate, they will break their instance of the program

Scalability:
line 5: board space
lines 40 & 41: ships key
lines 222 & 278: turn range

from random import randint
import os
from jsonsocket import *

board_space = 5

class Player(object):
	def __init__(self):
		self.board = []
		self.tug = {
			'name': 'tug',
			'coordinates': [[],[]],
			'damage': 0,
			'is_floating': True
		}
		self.destroyer = {
			'name': 'destroyer',
			'coordinates': [[],[],[]],
			'damage': 0,
			'is_floating': True
		}
		self.submarine = {
			'name': 'submarine',
			'coordinates': [[],[],[]],
			'damage': 0,
			'is_floating': True
		}
		self.battleship = {
			'name': 'battleship',
			'coordinates': [[],[],[],[]],
			'damage': 0,
			'is_floating': True
		}
		self.aircraft_carrier = {
			'name': 'carrier',
			'coordinates': [[],[],[],[],[]],
			'damage': 0,
			'is_floating': True
		}
		'''self.aircraft_carrier, self.battleship, self.submarine,'''
		self.ships_key = [self.destroyer, self.tug]
		self.generate_board()
		self.generate_ships(self.board)

	def generate_board(self):
		for x in range(board_space):
			self.board.append(['O'] * board_space)

	def print_ships(self):
		print(self.tug)
		print(self.destroyer)
		#print self.submarine
		#print self.battleship
		#print self.aircraft_carrier
		print

	def random_orientation(self):
		return randint(0, 1)

	def random_row(self, board):
		return randint(0, len(self.board) - 1)

	def random_col(self, board):
		return randint(0, len(self.board[0]) - 1)

	def is_out_of_range(self, row, col):
		if (row < 0 or row > (board_space - 1)) or \
		   (col < 0 or col > (board_space - 1)):
				return True

	def is_open_water(self, row, col):
		for ship in self.ships_key:
			if ship['is_floating'] == True:
				for pair in ship['coordinates']:
					if len(pair) != 0:
						if pair[0] == row and \
						   pair[1] == col:
							return False
		else:
			return True

	def generate_ships(self, board):
		for ship in self.ships_key:
			while True:
				orientation = self.random_orientation()
				gen_row = self.random_row(board)
				gen_col = self.random_col(board)
				if orientation == 0:
					col = gen_col
					for pair in ship['coordinates']:
						if self.is_out_of_range(gen_row, col): break
						elif self.is_open_water(gen_row, col): col += 1
						else: break
					else:
						col = gen_col
						for pair in ship['coordinates']:
							pair.append(gen_row)
							pair.append(col)
							col += 1
						else:
							break
				else:
					row = gen_row
					for pair in ship['coordinates']:
						if self.is_out_of_range(row, gen_col): break
						elif self.is_open_water(row, gen_col): row += 1
						else: break
					else:
						row = gen_row
						for pair in ship['coordinates']:
							pair.append(row)
							pair.append(gen_col)
							row += 1
						else: break

	def is_fleet_destroyed(self):
		destroyed_ships = 0
		for ship in self.ships_key:
			if ship['is_floating'] == False: destroyed_ships += 1
		if destroyed_ships == len(self.ships_key):
			return True

	def get_ship(self, row, col):
		for ship in self.ships_key:
			for pair in ship['coordinates']:
				if pair[0] == row and \
				   pair[1] == col:
						return ship

	def resolve_attack(self, guess_row, guess_col):
		if self.is_out_of_range(guess_row, guess_col): return 'limits'
		if self.board[guess_row][guess_col] == 'X' or \
			 self.board[guess_row][guess_col] == 'H' or \
			 self.board[guess_row][guess_col] == '*':
				return 'guessed'
		elif self.is_open_water(guess_row, guess_col) == False: # Hit
			ship = self.get_ship(guess_row, guess_col)
			ship['damage'] += 1
			if ship['damage'] == len(ship['coordinates']): # sink ship
				ship['is_floating'] = False
				for pair in ship['coordinates']:
					self.board[pair[0]][pair[1]] = '*'
				return ship['name'] # return sunk ship name
			else:
				self.board[guess_row][guess_col] = 'H'
				return 'hit'
		else: # or Miss
			self.board[guess_row][guess_col] = 'X'
			return 'miss'

def print_attack_result(result):
	os.system('clear')
	print
	if result == 'tug': print('Hit! - You sank my Tug Boat!')
	elif result == 'destroyer': print('Hit! - You sank my Destroyer!')
	elif result == 'submarine': print('Hit! - You sank my Submarine!')
	elif result == 'battleship': print('Hit! - You sank my Battleship!')
	elif result == 'carrier': print('Hit! - You sank my Aircraft Carrier!')
	elif result == 'hit': print('Hit!')
	elif result == 'miss': print('Miss!')
	elif result == 'guessed': print('You\'ve guessed that one already!')
	elif result == 'limits': print('That guess was out of range! You\'ve wasted a turn')
	print

def print_damage_report(result, target):
	os.system('clear')
	print
	if result == 'tug': print('Your Tug Boat has been sent to the depths!')
	elif result == 'destroyer': print('Destroyer down!')
	elif result == 'submarine': print('Submarine destroyed!')
	elif result == 'battleship': print('Battleship!')
	elif result == 'carrier': print('Aircraft Carrier!')
	elif result == 'hit': print('Hit! %s, %s' % (target[0], target[1]))
	elif result == 'miss': print('Miss! %s, %s' % (target[0], target[1]))
	elif result == 'guessed': print('Your opponent made a repeat attack!')
	elif result == 'limits': print('Your opponent fired beyond the game scope!')
	print

def print_board(board):
	print('  '),
	for col in range(board_space):
		print('  ' + str(col)),
	print
	for index, row in enumerate(board):
		print('   |' + '---|' * board_space)
		print(str(index) + '  | ' + ' | '.join(row) + ' |')
	print('   |' + '---|' * board_space + '\n')

def print_success():
	print
	print('You have anihilated your opponents entire fleet')
	print
	os.system('read -s -n 1 -p "Press enter to continue..."')
	menu()

def print_defeat():
	print
	print('Your fleet has been destoyed')
	print
	os.system('read -s -n 1 -p "Press enter to continue..."')
	menu()

def two_player_socket_server():
	os.system('clear')
	player = Player()
	host = socket.gethostbyname(socket.gethostname())
	port = 12345
	server = Server(host, port)
	print('Your Ip is: %s' % host)
	print('Waiting for client connection...')
	server.accept()
	print server.client
	os.system('clear')
	# exchange board data
	server.send({'board': player.board})
	data = server.recv()
	# print initial board state
	opponents_board = data['board']
	print_board(opponents_board)
	print_board(player.board)
	try:
		for turn in range(20):
			# Enter attack coordinates and send them to opponent
			print('Turn %s Player\n' % (turn + 1))
			guess_row = int(input('Guess Row: '))
			guess_col = int(input('Guess Col: '))
			server.send({'guess': [guess_row, guess_col]})
			# Receive attack results
			data = server.recv()
			result = data['attack_result']
			opponents_board = data['board']
			opponents_fleet_obliterated = data['fleet_obliterated']
			# print attack results
			print_attack_result(result)
			###player.print_ships()
			print_board(opponents_board)
			print_board(player.board)
			if opponents_fleet_obliterated == True: 
				server.close()
				print_success()
			else: print('Turn %s Opponent' % (turn + 1))
			# receive damage report
			data = server.recv()
			target = data['guess']
			result = player.resolve_attack(target[0], target[1])
			fleet_obliterated = player.is_fleet_destroyed()
			server.send({'attack_result': result,
						 'fleet_obliterated': fleet_obliterated,
						 'board': player.board
			})
			# print damage report
			print_damage_report(result, target)
			print_board(opponents_board)
			print_board(player.board)
			if fleet_obliterated == True: 
				server.close()
				print_defeat()
	finally:
		server.close()

def two_player_socket_client():
	os.system('clear')
	player = Player()
	###player.print_ships()
	server_host = raw_input('Enter the IP address of the server: ')
	port = 12345
	client = Client()
	client.connect(server_host, port)
	
	# exchange board data
	data = client.recv()
	client.send({'board': player.board})
	# print initial board state
	opponents_board = data['board']
	print_board(opponents_board)
	print_board(player.board)
	try:
		for turn in range(20):
			if turn == 0: print('Turn %s Opponent' % (turn + 1))
			# receive grid, assess damage, send 
			data = client.recv()
			target = data['guess']
			result = player.resolve_attack(target[0], target[1])
			fleet_obliterated = player.is_fleet_destroyed()
			client.send({'attack_result': result,
						 'fleet_obliterated': fleet_obliterated,
						 'board': player.board
			})
			# print damage report
			print_damage_report(result, target)
			print_board(opponents_board)
			print_board(player.board)
			if fleet_obliterated == True: 
				client.close()
				print_defeat()
			# Enter attack coordinates and send them to opponent
			print('Turn %s Player\n' % (turn + 1))
			guess_row = int(input('Guess Row: '))
			guess_col = int(input('Guess Col: '))
			client.send({'guess': [guess_row, guess_col]})
			# Receive attack results
			data = client.recv()
			result = data['attack_result']
			opponents_board = data['board']
			opponents_fleet_obliterated = data['fleet_obliterated']
			# print attack results
			print_attack_result(result)
			###player.print_ships()
			print_board(opponents_board)
			print_board(player.board)
			if opponents_fleet_obliterated == True: 
				client.close()
				print_success()
			else: print('Turn %s Opponent' % (turn + 1))
	finally:
		client.close()

def menu():
	os.system('clear')
	print
	print('Welcome to Battleship')
	print('-' * 40)
	print('3 - Two player - socket, server')
	print('4 - Two player - socket, client')
	print('-' * 40)
	print
	mode_choice = int(input('Input game mode number and press Enter: '))
	if mode_choice == 3: two_player_socket_server()
	elif mode_choice == 4: two_player_socket_client()

menu()

Bugs that weren't adressed in the BATTLESHIP tutorial
#14

@chris.rose.one

Very nice, that is nice use of the jsonsocket to create a server for you to pass information back and forth.


#15

thanks @zeziba

my server has now become a lobby that handles multiple clients and their games
i've uploaded my latest work to github: https://github.com/chris-rose-one/battleship


#16

Simple additions to the code in the course:
I added a function (check_if_close) that checks if the guess was within 1 space of the battleship, and lets the user know they were close and marks it on the board with a C
Marks the battleship on the board with a B after it is sunk.

from random import randint

board = []

for x in range(5):
  board.append(["O"] * 5)

def print_board(board):
  for row in board:
    print " ".join(row)

print_board(board)

def random_row(board):
  return randint(0, len(board) - 1)

def random_col(board):
  return randint(0, len(board[0]) - 1)

def check_if_close(row, col) :
  close = 0
  if (row == ship_row + 1 or row == ship_row - 1 or row == ship_row ) and (col == ship_col + 1 or col == ship_col - 1 or col == ship_col) :
    close = 1
  return close


ship_row = random_row(board)
ship_col = random_col(board)
#if you need the answer, uncomment these two lines
#print ship_row
#print ship_col

# Everything from here on should go in your for loop!
for turn in range(5) :
  print "Turn", turn + 1
# Be sure to indent four spaces!
  guess_row = int(raw_input("Guess Row: "))
  guess_col = int(raw_input("Guess Col: "))

  if guess_row == ship_row and guess_col == ship_col:
    print "Congratulations! You sunk my battleship!"
    board[guess_row][guess_col] = "B"
    # Print (turn + 1) here!
    print_board(board)
    break
  else:
    if (guess_row < 0 or guess_row > 4) or (guess_col < 0 or guess_col > 4):
      print "Oops, that's not even in the ocean."
    elif(board[guess_row][guess_col] == "X"):
      print "You guessed that one already."
    else:
      if check_if_close(guess_row, guess_col) == 1 :
        print "That was a close one!"
        board[guess_row][guess_col] = "C"
      else :
        print "You missed my battleship!"
        board[guess_row][guess_col] = "X"
    # Print (turn + 1) here!
    print_board(board)
    if turn == 4 :
      print "Game Over"

Example:

Turn 3
Guess Row: 2
Guess Col: 2
You missed my battleship!
X O O O O
O X O O O
O O X O O
O O O O O
O O O O O
Turn 4
Guess Row: 3
Guess Col: 3
That was a close one!
X O O O O
O X O O O
O O X O O
O O O C O
O O O O O