Adding Intelligence To Your Bot

Editing the Code

Once you are ready to start improving your bot you should draw your attention to the calculateMove() function, this is where all the action happens. This function is called every time the server wants you to make a move. It receives a dictionary of information containing the state of the game, namely the gamestate. For greater detail on the contents of the gamestate check out the Understanding The Gamestate section of our Battleships Quick Reference Guide.

From this information it is your task to determine two things:

  1. Which round you are in.
  2. What your move for this round is going to be.

You can specify your move by returning a valid dictionary of the form specified in the Making A Valid Move section of our Quick Reference Guide from the calculateMove() function.

For example:

def calculateMove(gamestate):
    if gamestate["Round"] == 0:  # If we are in the ship placement round
        # The Placement list adds ships in the order that the ships are 
        # listed in the game style e.g. 5,4,3,3,2 places the ship of length 
        # 5 first, the ship of length 4 second, the ship of length 3 third.
        # 
        # This function does not check for any land and, so, should be used
        # with a gamestyle that does not include land.
        move = {
                    "Placement": [
                        {
                            "Row": "A",
                            "Column": 1,
                            "Orientation": "H"
                        },
                        {
                            "Row": "B",
                            "Column": 6,
                            "Orientation": "V"
                        },
                        {
                            "Row": "C",
                            "Column": 1,
                            "Orientation": "H"
                        },
                        {
                            "Row": "D",
                            "Column": 1,
                            "Orientation": "H"
                        },
                        {
                            "Row": "E",
                            "Column": 1,
                            "Orientation": "V"
                        }
                    ]
                }
    else:  # If we are in the ship hunting round
        move = {
                     "Row": "B",
                     "Column": 4
               }
    return move

Here is an example of a function that takes into account land to ensure that the move is valid:

def shipPlacement(gameState):
    move = []  # Initialise our move list
    # Start in the top left corner
    start = (0,0)
    row = 0
    column = 0
    for i in gameState["Ships"]:  # For every ship we need to place
        space = 0
        start = (row,column)  # Start trying to find room at the current row and column
        while space < i:  # Until we find enough room to fit the ship keep on looking
            if column >= len(gameState["MyBoard"][0]):  # If we have reached the end of a row without finding enough room
                row += 1  # Move to the next row down
                column = 0  # Start at the beginning of the row
                space = 0  # Reset the amount of space we have found on the row since the last obstacle
                start = (row, column)  # Initialise the potential start location for our ship
            if gameState["MyBoard"][row][column] == "":  # If the current cell is empty
                space += 1  # Increment the amount of space we have found on the row since the last obstacle
                column += 1  # Move to the next column along
            else:  # If there is something blocking a ship being placed
                space = 0  # Reset the amount of space we have found on the row since the last obstacle
                column += 1  # Move to the next column along
                start = (row, column)  # Reset the potential start location for our ship
        # If we have found enough room to place our ship
        shipPlace = BATHelperFunctions.translateMove(start[0], start[1])  # Call the translateMove hepler function with our coordinates to convert it to a valid move format
        shipPlace["Orientation"] = "H"  # Set the orientation we wish to place our ship as horizontal
        move.append(shipPlace)  # Add this ship placement to our move list
    # Once we have added all of our ships
    return {"Placement": move}  # Return the valid placement move

Case Study

Let’s say we would like to implement the following strategy:

  1. In the first round we will just randomly place our ships.
  2. In the second round if we haven't hit any ships we will randomly guess a location in the sea.
  3. If we have hit a ship we will randomly choose one of the adjacent tiles.
  4. If we are playing the particular bot “Barney01”, we will guess around the edges of the board.

We will make use of the built in helper functions from the battleships_helper_function module.
There is a helper function called deployRandomly() that we can use to randomly place our ships.
There is also a helper function called chooseRandomValidTarget() that we can use to randomly guess a location in the sea.
You can read more about the helper functions in the Helper Functions section of our Quick Reference Guide

The code looks like the following:

import BATHelperFunctions  # Allows us to use the helper functions
from random import choice  # So we can randomly choose an element from a list
from random import randint  # So we can randomly pick a number
 
def calculateMove(gamestate):
    if gamestate["Round"] == 0:  # If we are in the ship placement round
        move = BATHelperFunctions.deployRandomly(gamestate)  # Randomly place your ships
    else:  # If we are in the ship hunting round
        for i in range(len(gamestate["OppBoard"])):
            for j in range(len(gamestate["OppBoard"][0])):  # Look through every cell on the board
                    if gamestate["OppBoard"][i][j] == "H":  # If it has been hit
                        if i > 0 and gamestate["OppBoard"][i-1][j] == "":  # Try and guess above
                            return {"Row": chr(i + 64), "Column": j + 1}
                        elif i < len(gamestate["OppBoard"])-1 and gamestate["OppBoard"][i+1][j] == "":  # Otherwise try and guess below
                            return {"Row": chr(i + 66), "Column": j + 1}
                        elif j > 0 and gamestate["OppBoard"][i][j-1] == "":  # Otherwise try and guess left
                            return {"Row": chr(i + 65), "Column": j}
                        elif j < len(gamestate["OppBoard"][0])-1 and gamestate["OppBoard"][i][j+1] == "":  # Otherwise try and guess right
                            return {"Row": chr(i + 65), "Column": j + 2}
        # If there are no hit ships
        if gamestate["OpponentId"] == "Barney01":  # If we are playing against Barney01
            move = guess_edges(gamestate)  # Call our guess_edges function to choose a target on the edge of the board if available
        else:
            move = BATHelperFunctions.chooseRandomValidTarget(gamestate)  # Randomly fire at valid sea targets
    return move
 
def guess_edges(gamestate):
    count = 0
    while count < 50:  # While we haven't given up trying to find a valid edge
        (row, column) = choice(  # Randomly choose one of the following:
                                [
                                    (0, randint(0, len(gamestate["OppBoard"][0])-1)),  # A random cell along the top edge
                                    (len(gamestate["OppBoard"])-1, randint(0, len(gamestate["OppBoard"][0])-1)),  # A random cell along the bottom edge
                                    (randint(0, len(gamestate["OppBoard"])-1), 0),  # A random cell along the left edge
                                    (randint(0, len(gamestate["OppBoard"])-1), len(gamestate["OppBoard"][0])-1)  # A random cell along the right edge
                                ]
                               )
        if gamestate["OppBoard"][row][column] == "":  # If the move is valid
            return {"Row": chr(row + 65), "Column": column + 1}  # Set values to a valid move
        else:  # Otherwise increase number of attempts by one
            count += 1
    # If we have given up trying to find a valid edge
    return BATHelperFunctions.chooseRandomValidTarget(gamestate)  # Randomly fire at valid sea targets

This code is inefficient, see if you can improve its performance and its strategy!

Advanced

Also you may like to edit the method play_game() from the battleships_layout.py module. The method is called during both player's turns. To maximise your efficiency, you may like to do some processing here while your opponent calculates their move. In particular you may want to add code to the following section:

        while True:
            if self.game_cancelled:
                break
 
            if game_state['IsMover']:
                move = battleships_move.calculateMove(game_state)
                move_results = self.make_move(move)
 
                if move_results['Result'] != 'SUCCESS':
                    break
 
                game_state = move_results['GameState']
 
            else:
 
                # ---- Code here will be called on your opponent's turn ----
 
                # ----------------------------------------------------------
 
                poll_results = self.poll_for_game_state()
 
                if poll_results['Result'] != 'SUCCESS':
                    break
                game_state = poll_results['GameState']
 
            if game_state['GameStatus'] != 'RUNNING':
                break

Suggested reading…

Quick Start Guide

Quick Reference Guide

iconBattleshipsBlack.png
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License