Skip to content

How to Code a Simple Battleship Game in Python

Updated: at 03:12 AM

Battleship is a classic board game that pits players against each other in a naval battle. In the game, each player positions a fleet of ships in a grid while their opponent attempts to locate and destroy these ships by calling out grid coordinates. It is a fun game of strategy and logic that has enthralled players for generations.

In this comprehensive guide, you will learn how to code a simple, text-based Battleship game in Python. We will walk through the key steps and logic involved, using code snippets and explanations to illustrate the implementation. By the end, you will have a clear understanding of how to build your own basic Battleship game to play against friends or the computer.

Table of Contents

Open Table of Contents

Overview

Here is a quick rundown of the game elements we will code:

# 10x10 grid initialized with all '.'
grid = [['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
        ['.', '.', '.', '.', '.', '.', '.', '.', '.', '.']]

Required Knowledge

To follow along and implement the game logic in Python, you should have:

With these core concepts, you will be able to build out the Battleship game step-by-step. Let’s get started!

Game Setup

We start by initializing our game constants, grids and ships.

Game Constants

First, set up some constants that will be used throughout the game:

GRID_SIZE = 10
SHIPS = {'Destroyer': 2, 'Submarine': 3, 'Battleship': 4}

GRID_SIZE will represent the 10x10 dimension of our board.

SHIPS will hold the ship names mapped to their sizes.

Initialize Empty Grids

Next we need to create two 10x10 grids to represent the boards for each player. This can be achieved with list comprehension:

player_grid = [['.'] * GRID_SIZE for _ in range(GRID_SIZE)]
enemy_grid = [['.'] * GRID_SIZE for _ in range(GRID_SIZE)]

This populates the grids with dots to indicate empty squares.

Place Ships Randomly

Now we need to randomly place our ships onto the player’s grid. We will write a place_ship function to handle positioning a single ship, which we can call multiple times to populate the board:

import random

def random_row():
  return random.randint(0, GRID_SIZE - 1)

def random_col():
  return random.randint(0, GRID_SIZE - 1)

def place_ship(ship, size, grid):

  # Randomly generate row, col index for head of ship
  row = random_row()
  col = random_col()

  # Randomly choose vertical or horizontal orientation
  is_vertical = random.choice([True, False])

  if is_vertical:
    if row + size > GRID_SIZE:
      return False

    for i in range(size):
      grid[row+i][col] = ship[0]
  else:
    if col + size > GRID_SIZE:
      return False

    for i in range(size):
      grid[row][col+i] = ship[0]

  return True

We use the random_row() and random_col() helper functions to generate a random starting point for each ship.

The ship can be positioned either vertically or horizontally based on a random boolean choice.

We loop through and update the grid cells with the first letter of the ship name to indicate placement.

Finally we return True if the ship fit or False if it went out of bounds.

To populate the grid, we loop through the ships and repeatedly call place_ship() until each one fits:

for ship, size in ships.items():
  while True:
    placed = place_ship(ship, size, grid)
    if placed:
      break

This will handle randomly positioning our ships onto the player’s grid so we are ready to play!

Gameplay

Now that setup is complete, we need to build out the game loop that will alternate moves between the player and enemy.

Player Moves

First we will implement the logic for the player’s turns. This involves:

  1. Getting coordinates input for strike.
  2. Checking for a hit or miss.
  3. Updating enemy grid accordingly.

We can implement this in a player_move() function:

def player_move():

  print("Enter row and column to strike: ")

  row, col = input().split()
  row, col = int(row), int(col)

  mark = enemy_grid[row][col]

  if mark == 'X' or mark == '-':
    print("You already struck here!")
    return

  if mark == '.':
    print("You missed!")
    enemy_grid[row][col] = '-'
  else:
    print("Hit!")
    enemy_grid[row][col] = 'X'

This takes in the target coordinates, checks the enemy grid for a hit or miss, then prints output and updates the grid cell.

Enemy Moves

The enemy moves will follow the same logic, but instead of taking user input we will randomly generate coordinates:

def enemy_move():

  row = random_row()
  col = random_col()

  mark = player_grid[row][col]

  if mark == 'X' or mark == '-':
    return

  if mark == '.':
    print("Enemy missed!")
    player_grid[row][col] = '-'
  else:
    print("Enemy hit!")
    player_grid[row][col] = 'X'

This automates the enemy targeting our ships based on random coordinate selection.

Game Loop

With the move functions defined, we can now implement the main game loop to alternate turns:

while True:
  player_move()
  print_grid(enemy_grid)

  enemy_move()
  print_grid(player_grid)

This allows both sides to take shots, displays updated boards after moves, and repeats continuously until game over conditions are met.

Game End Conditions

Some additional logic needs to be implemented to handle game ending conditions:

  1. Checking for sink of all enemy ships
  2. Handling incorrect inputs
  3. Allowing player to quit game

Checking for Win

After each player move, we can check if all enemy ships have been sunk to end the game:

if all_ships_sunk(enemy_grid):
  print("You won!")
  break

The all_ships_sunk() function would iterate through the enemy grid to see if it contained any remaining ship tiles.

Input Validation

For invalid coordinate inputs, we can catch errors and prompt again:

try:
  row, col = input("Enter row and column: ").split()
  row, col = int(row), int(col)
except ValueError:
  print("Please enter valid numbers")
  continue

This ensures we get proper coord inputs.

Quitting Logic

To let the player exit the game, we can check for a ‘quit’ input:

action = input("Your move: ")
if action.lower() == 'quit':
  print("You quit the game")
  break

This gives the player control to end the game.

Putting It All Together

With the major game components built out, we can connect everything together into a complete playable Battleship game:

import random

GRID_SIZE = 10

# Ships and sizes
ships = {
    'Destroyer': 2,
    'Submarine': 3,
    'Battleship': 4
}

def random_row():
    return random.randint(0, GRID_SIZE - 1)

def random_col():
    return random.randint(0, GRID_SIZE - 1)

def place_ship(ship, size, grid):
    while True:
        row = random_row()
        col = random_col()

        is_vertical = random.choice([True, False])

        if is_vertical:
            if row + size > GRID_SIZE:
                continue

            if '.' not in [grid[row + i][col] for i in range(size)]:
                continue

            for i in range(size):
                grid[row + i][col] = ship[0]
        else:
            if col + size > GRID_SIZE:
                continue

            if '.' not in [grid[row][col + i] for i in range(size)]:
                continue

            for i in range(size):
                grid[row][col + i] = ship[0]

        break

    return True

def place_ships(grid):
    for ship, size in ships.items():
        while True:
            placed = place_ship(ship, size, grid)
            if placed:
                break
    return grid

def fire(row, col, grid):
    mark = grid[row][col]
    if mark == 'X' or mark == '-':
        print("\nYou've already fired at this location. Try somewhere new!")
        return
    elif mark != '.':
        print(f"\nHit! {mark} ship segment destroyed.")
        grid[row][col] = 'X'
        return mark
    else:
        print("\nYou missed!")
        grid[row][col] = '-'
        return False

def print_grid(grid, fog_of_war=True):
    print('  ' + ' '.join(map(str, range(GRID_SIZE))))
    for i, row in enumerate(grid):
        if fog_of_war:
            row = ['.' if cell != 'X' else 'X' for cell in row]
        print(f'{i} ' + ' '.join(row))

def print_help():
    print("Welcome to Battleship!")
    print("To play the game, enter the row and column number of the square you want to fire at.")
    print("The row and column numbers are numbered from 0 to 9, starting from the top left corner of the grid.")
    print("You will have 10 turns to sink all of the enemy ships.")
    print("If you sink all of the enemy ships before you run out of turns, you win!")
    print("Good luck!")

def main():
    print_help()

    # Place player ships
    player_grid = [['.'] * GRID_SIZE for _ in range(GRID_SIZE)]
    player_grid = place_ships(player_grid)

    # Place enemy ships
    enemy_grid = [['.'] * GRID_SIZE for _ in range(GRID_SIZE)]
    enemy_grid = place_ships(enemy_grid)

    ships_remaining = len(ships)

    while ships_remaining > 0:
        print("(Type 'quit' to exit the game)")

        # Get input
        action = input("Enter row (or 'quit' to exit), followed by column to fire at: ")
        if action.lower().strip() == 'quit':
            break

        coordinates = action.split()
        if len(coordinates) != 2:
            print("\nInvalid coordinates, please re-enter.")
            continue

        try:
            row, col = map(int, coordinates)
            if row < 0 or row >= GRID_SIZE or col < 0 or col >= GRID_SIZE:
                print("\nInvalid coordinates, retry.")
                continue
        except ValueError:
            print("\nInvalid input, please enter row and column as integers.")
            continue

        hit_ship = fire(row, col, enemy_grid)

        if hit_ship:
            # If all ship segments are hit, mark the ship as destroyed
            ship_name = [name for name, size in ships.items() if name[0] == hit_ship][0]
            if hit_ship not in ''.join([''.join(row) for row in enemy_grid]):
                print(f"\nYou have destroyed the enemy's {ship_name}!")
                ships_remaining -= 1

        print("\nYour Grid:")
        print_grid(player_grid, fog_of_war=False)
        print("\nEnemy Grid:")
        print_grid(enemy_grid)

    if action.lower().strip() == 'quit':
        print("\nYou chose to quit the game.")
    elif ships_remaining != 0 and action.lower().strip() != "quit":
        print("\nAll your turns are over! You did not shoot down all enemy ships!")
    else:
        print("\nAll enemy ships destroyed! You win!")

if __name__ == "__main__":
    main()

The main game loop continues until either all player or enemy ships are sunk, determining the winner.

And with that, we have a fully playable Battleship game in Python!

Enhancements

While this covers a basic implementation, there are many ways you could expand on this simple game, like:

The core game engine provides a solid foundation to build upon in many different ways.

Summary

In this guide you learned how to:

These key steps implement the essential gameplay logic behind Battleship. The full code creates a terminal-based version that two players can enjoy.

Battleship provides a great Python project to practice core programming skills like lists, functions, loops, conditionals and more. The text-based approach keeps the scope manageable while allowing many avenues for extension and enhancement.

Now grab a friend and start sinking some ships!