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 = [['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.', '.', '.']]
-
A 10x10 grid representing the game board. This will be implemented as a 10x10 list in Python.
-
Three ships for each player: a 2-unit Destroyer, a 3-unit Submarine and a 4-unit Battleship.
-
Randomized positioning of ships on the grids.
-
Game loop that alternates moves between the two players.
-
Input handling to receive grid coordinates for strikes from players.
-
Game rules to check for hits, misses, sinks andwins.
-
Printing grids to display updated game board after moves.
Required Knowledge
To follow along and implement the game logic in Python, you should have:
-
Basic Python programming experience and knowledge of:
-
variables and data types
-
lists
-
functions
-
loops (for, while)
-
conditionals (if, else)
-
randomness and random module
-
-
Familiarity with 2D lists to represent grids
-
Ability to breakdown larger problems into smaller functions
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:
- Getting coordinates input for strike.
- Checking for a hit or miss.
- 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:
- Checking for sink of all enemy ships
- Handling incorrect inputs
- 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:
-
Improving the print_grid() function to better visualize the game board.
-
Adding a GUI for graphical version using PyGame or Tkinter.
-
Allowing players to position their own ships before game starts.
-
Implementing AI for the enemy, using strategy to target ships.
-
Saving and loading game state to play over multiple sessions.
-
Supporting network multiplayer over local network or internet.
-
Tracking stats like number of hits, ships sunk, shot accuracy.
-
Options for different grid sizes, ships types/sizes etc.
The core game engine provides a solid foundation to build upon in many different ways.
Summary
In this guide you learned how to:
-
Initialize game constants, grids and ships
-
Randomly place ships on the game grid
-
Alternate moves between two players
-
Handle user input for strikes and validate coordinates
-
Detect hits, misses and sinks during moves
-
Check for end game conditions based on ships remaining
-
Print updated game boards after each turn
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!