Battleship Game In C# Console

Battleship is a strategy-type guessing game for two players. It is played on ruled grids (paper or board) on which each player’s fleet of ships (including battleships) are marked. (Source: Wikipedia Battleship (game) )

I created the game in C#, which looks something like this.

Battleship Game In C# Console

The blue color [~] represents water that is unexplored.

The red color [*] represents your/enemy ship being hit.

The green color [O] represents your ship.

The dark color is the position that is being shot, but miss.

The left-hand side is your fireboat, where the right-hand side is the fire scoreboard by your enemy (computer).

Indicators serve as a scoreboard to shows how many ships remaining of each player.

There are only two classes being created in this console, one is Position and another one is NavyAsset.

class Position {
    public int x {
        get;
        set;
    } = -1;
    public int y {
        get;
        set;
    } = -1;
}

Since the Battleship board is 10 x 10, so it is not hard to imagine I define each position with (x,y) coordinates. A Position class is created to store coordinate.

NavyAsset is created to store and perform all operations of the ship.

private const int CARRIER = 5;
private const int BATTLESHIP = 4;
private const int DESTROYER = 3;
private const int SUBMARINE = 3;
private const int PATROLBOAT = 2;

This is the declaration of the size of each ship.

public List<Position> Carrier { get; set; }//5
public List<Position> Battleship { get; set; }//4
public List<Position> Destroyer { get; set; }//3
public List<Position> Submarine { get; set; }//3
public List<Position> PatrolBoat { get; set; }//2
public List<Position> AllPosition { get; set; } = new List<Position>();
public List<Position> FirePositions { get; set; } = new List<Position>();

A collection of object class Position to store each position of each ship, hence a particular ship consider fully obliterated after all coordinates in its collections of the object has been hit.

List of AllPosition is to store all locations of all ships, this collection serves to prevent there is an overlap of ship’s position during random generation of ships’ position.

FirePositions is to store all fired coordinates, this serves as checking to prevent duplicated coordinates from being fired.

public bool IsCarrierSunk { get; set; } = false;
public bool IsBattleshipSunk { get; set; } = false;
public bool IsDestroyerSunk { get; set; } = false;
public bool IsSubmarineSunk { get; set; } = false;
public bool IsPatrolBoatSunk { get; set; } = false;
public bool IsAllObliterated { get; set; } = false;

This is just to check if a particular ship is a sink, and if IsAllObliterated is equal to true, the game will end.

public NavyAsset CheckShipStatus(List < Position > HitPositions) {
    IsCarrierSunk = Carrier.Where(C => !HitPositions.Any(H => C.x == H.x && C.y == H.y)).ToList().Count == 0;
    IsBattleshipSunk = Battleship.Where(B => !HitPositions.Any(H => B.x == H.x && B.y == H.y)).ToList().Count == 0;
    IsDestroyerSunk = Destroyer.Where(D => !HitPositions.Any(H => D.x == H.x && D.y == H.y)).ToList().Count == 0;
    IsSubmarineSunk = Submarine.Where(S => !HitPositions.Any(H => S.x == H.x && S.y == H.y)).ToList().Count == 0;
    IsPatrolBoatSunk = PatrolBoat.Where(P => !HitPositions.Any(H => P.x == H.x && P.y == H.y)).ToList().Count == 0;
    IsObliteratedAll = IsCarrierSunk && IsBattleshipSunk && IsDestroyerSunk && IsSubmarineSunk && IsPatrolBoatSunk;
    return this;
}

CheckShipStatus function will be called each round during the game to check the status of each ship.

public NavyAsset() {
    Carrier = GeneratePosistion(CARRIER, AllPosition);
    Battleship = GeneratePosistion(BATTLESHIP, AllPosition);
    Destroyer = GeneratePosistion(DESTROYER, AllPosition);
    Submarine = GeneratePosistion(SUBMARINE, AllPosition);
    PatrolBoat = GeneratePosistion(PATROLBOAT, AllPosition);
}

Constructor is to generate the position of each kind of ship randomly.

public List < Position > GeneratePosistion(int size, List < Position > AllPosition) {
        List < Position > positions = new List < Position > ();
        bool IsExist = false;
        do {
            positions = GeneratePositionRandomly(size);
            IsExist = positions.Where(AP => AllPosition.Exists(ShipPos => ShipPos.x == AP.x && ShipPos.y == AP.y)).Any();
        }
        while (IsExist);
        AllPosition.AddRange(positions);
        return

A checking using a collection of AllPosition to prevent there is no ship overlap the same gameboard even one coordinate.

public List < Position > GeneratePositionRandomly(int size) {
        List < Position > positions = new List < Position > ();
        int direction = random.Next(1, 3); //odd for horizontal and even for vertical
        int row = random.Next(1, 11);
        int col = random.Next(1, 11);
        if (direction % 2 != 0) {
            //left first, then right
            if (row - size > 0) {
                for (int i = 0; i < size; i++) {
                    Position pos = new Position();
                    pos.x = row - i;
                    pos.y = col;
                    positions.Add(pos);
                }
            } else // row
            {
                for (int i = 0; i < size; i++) {
                    Position pos = new Position();
                    pos.x = row + i;
                    pos.y = col;
                    positions.Add(pos);
                }
            }
        } else {
            //top first, then bottom
            if (col - size > 0) {
                ....

‘direction’ variable is to determine whether the ship is being put horizontally or vertically.

Row and col to put the first coordinate of the ship randomly.

Row – size > 0 (or col-size >0 if vertically) is to ensure the coordinates of the ship do not go beyond outside of the game board, otherwise, we put our coordinate on invert position.

public NavyAsset Fire() {
    Position EnemyShotPos = new Position();
    bool alreadyShot = false;
    do {
        EnemyShotPos.x = random.Next(1, 11);
        EnemyShotPos.y = random.Next(1, 11);
        alreadyShot = FirePositions.Any(EFP => EFP.x == EnemyShotPos.x && EFP.y == EnemyShotPos.y);
    }
    while (alreadyShot);
    FirePositions.Add(EnemyShotPos);
    return this;
}

This is the fire algorithm of the console right now, it just randomly attack. I will include an intelligent guess (e.g. attack adjacent coordinate once a hit was found) in part 2.

Main Program

My apology that the code of the main program might little bit messy since I try to print 2 boards on one screen, a lot of WriteLine(” “) or Write(” “) were used to ‘Draw’ the gameboard and ship position in the console.

The main function of printing the map is PrintMap(),

PrintMap(MyNavyAsset.FirePositions, MyNavyAsset, EnemyNavyAsset, isShowShips);

Basically, I will reprint every map of ships, hit, miss, and non-explored for each iteration.

I will check each coordinate from (1 , 1 ) until (10, 10) , with the priority,

hit > ship > miss > unexplored water

So, it means I start with coordinate (1, 1 ), if it is hit, print [*] in red color, if it is not hit, check if it is a ship, if yes, print [O] in green color, if it is not ship check if is a miss, if yes, print [X] in black color, if it is not missing. Then it is uncharted water print [~] in blue color. The above process repeats for coordinate (1, 2) until coordinate (10, 10).

List<Position> SortedLFirePositions = positions.OrderBy(o => o.x).ThenBy(n => n.y).ToList();
List<Position> SortedShipsPositions = EnemyMyNavyAsset.AllShipsPosition.OrderBy(o => o.x).ThenBy(n => n.y).ToList();

SortedLFirePositions has stored all the fired positions, SortedShipsPositions has stored all the positions of the ships, by comparing this two I can determine a particular coordinate is considered hit or ship or miss.

PrintMapOfEnemy(x, row, MyNavyAsset, EnemyMyNavyAsset, ref myShipCounter, ref enemyHitCounter);

PrintMapOfEnemy is similar to PrintMap(), the only difference is only print the game board of the computer console.

NavyAsset MyNavyAsset = new NavyAsset();            
NavyAsset EnemyNavyAsset = new NavyAsset();

Create two different objects to indicate between the player and the computer

Full source code can be download from my GitHub.

Coming soon in Part 2

I will include a better firing algorithm, which are,

  • Fire adjacent coordinate after find hit
  • Stop fire adjacent coordinate after sinking a ship
  • Write a Probability Density Function in C# to make an intelligent guess of the position of the battleship
  • Compare the results (average total steps required to win a game) of my algorithm if it scores better than the average best/least steps in winning a battleship game by a human.
  • Based on a google search, shows that 96 shots are required to complete approximately 50% of the games, and 99% of the games will take more than 78 shots. (Means at least 39 shots from one player)
  • There are tons of materials including published research paper on the internet about how to win a Battleship game, so to develop a better firing algorithm, I might take longer time to understand the theory before convert it (if it is possible based on my skillset) into C# program.