A Simple Battleship Game
Magic Numbers

Your biggest issue is too many magic numbers and hard-coded information.

If you added at the top of your script:

ROWS = 5
COLUMNS = 5


then you could have:

def create_random_ship():
    return random.randrange(ROWS), random.randrange(COLUMNS)


Game board can be created with:

game_board = [["O"] * COLUMNS for _ in range(ROWS)]


And you can ask for input from the user with:

row = int(input(f"Enter a row number between 1-{ROWS} &gt;: "))


Along the same vein, you could have:

print(f"""\nIntroductions:
\nYou have {INIT_AMMO} ammo and there are {INIT_ENEMY} hidden ships on map.
...""")


so your welcome instructions can keep up-to-date with any changes to your game initial conditions.

Instructions

You have a if __name__ == "__main__": guard to prevent the code from running if imported into another script, but your instruction are unconditionally printed.  You should move the instruction printing into an instructions() function, and call that only if appropriate.

Infinite Recursion

play_game() calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), which calls play_again(), which can call play_game(), ...

Traceback (most recent call last):
  [...omitted...]
RecursionError: maximum recursion depth exceeded


Don't do that.  Much simpler is to have play_again() return a True/False result, and loop based on the return value:

def play_many_games():

    instructions()

    play_game()

    while play_again():
        play_game()

    print("Goodbye!")

if __name__ == "__main__":
    play_many_games()


No more recursion.

In the comments, @Graipher suggests an alternate method to avoid the recursion:

while True:
    play_game()
    if not play_again():
        break


Yet another alternative introduces a loop condition variable, which is pre-set before the loop:

 playing = True
 while playing:
     play_game()
     playing = play_again()


All three approaches avoid the infinite recursion.

I Saw Three Ships...

ship1 = create_random_ship()
ship2 = create_random_ship()
ship3 = create_random_ship()

# ...

    elif (row, column) == ship1 or (row, column) == ship2 or (row, column) == ship3:


What are you going to do if you change this to four ships, or even 5 ships?

With computers, there are 3 important numbers: zero, one and many.  If you have more than one ship, you have "many" ships, and should put them in a container, such as a list():

ship_coordinates = []
for _ in range(ships_left):
    ship_coordinates.append(create_random_ship())


And when you want to see if a particular row/column matches any one of the ship coordinates in the container, you use the in function:

    elif (row, column) in ship_coordinates:


There are other container types you can use, such as a set() which has faster in performance.  However, it cannot add the same value more than once, and using it would result in slightly different behaviour to what you currently have.  You've mentioned that you have a bug where ships can have the same coordinates, and I am trying very hard not to accidentally fix that.  (Code Review is for reviewing working code only, not help debugging, so I am tiptoeing around the bug, so that you can fix it yourself.)