Text based naval game

here is the second game I have made. its not easy and I was hoping someone could test it and bring some improvements to the table. fun project for learning.

import random

class Ship:
def __init__(self, name, symbol, position):
self.name = name
self.symbol = symbol
self.health = 100
self.crew = 100
self.position = position
self.cannonballs = 100
self.morale = 100
self.morale_boost_wait_turns = 0
self.enemy = None

def move(self, distance):
new_position = self.position + distance
if 0 <= new_position <= 6 and new_position != self.enemy.position:
self.position = new_position
print(f"{self.name} moved to position {new_position}.")
else:
print("Invalid move!")

def crew_attack(self, enemy_ship):
if abs(self.position - enemy_ship.position) in range(1, 3):
damage = random.randint(5, 10)
enemy_ship.health -= damage
self.crew -= random.randint(5, 10)
print(f"{self.name} executed a crew attack! {damage} damage dealt to {enemy_ship.name}.")
else:
print("Crew attack is not possible at this distance!")

def cannon_attack(self, enemy_ship):
if abs(self.position - enemy_ship.position) in range(3, 5):
damage = random.randint(10, 20)
enemy_ship.health -= damage
self.cannonballs -= random.randint(10, 20)
print(f"{self.name} executed a cannon attack! {damage} damage dealt to {enemy_ship.name}.")
else:
print("Cannon attack is not possible at this distance!")

def ram_attack(self, enemy_ship):
if abs(self.position - enemy_ship.position) >= 5:
damage = random.randint(20, 30)
enemy_ship.health -= damage
if self.position < enemy_ship.position:
self.position = min(enemy_ship.position - 1, 6)
else:
self.position = max(enemy_ship.position + 1, 0)
print(f"{self.name} executed a ram attack! {damage} damage dealt to {enemy_ship.name}.")
print(f"{self.name} moved to position {self.position}.")
else:
print("Ram attack is not possible at this distance!")

def repair(self):
if self.crew >= 10:
restored_health = random.randint(10, 20)
self.health += restored_health
self.crew -= 10
print(f"{self.name} repaired their ship. Health restored by {restored_health}.")
else:
print("Insufficient crew members for repair.")

def steal_crew_members(self):
if abs(self.position - self.enemy.position) == 1:
stolen_crew = random.randint(5, 10)
self.crew += stolen_crew
self.enemy.crew -= stolen_crew
print(f"{self.name} stole {stolen_crew} crew members from {self.enemy.name}!")

def morale_booster(self):
if self.crew >= 20:
if self.morale_boost_wait_turns == 0:
morale_boost_amount = random.randint(15, 20) * (self.crew // 10)
self.morale += morale_boost_amount
self.crew -= 20
self.morale_boost_wait_turns = 3  # Crew members need to wait 3 turns before using morale booster again
print(f"{self.name} used a morale booster. Morale boosted by {morale_boost_amount}.")
else:
print("Crew members need to wait before using the morale booster again.")
else:
print("Insufficient crew members for morale booster.")

def update_morale_boost_wait_turns(self):
if self.morale_boost_wait_turns > 0:
self.morale_boost_wait_turns -= 1

def is_alive(self):
return self.health > 0 and self.crew > 0

def display_distance_bar(player1, player2):
bar = [" "] * 7  # Initialize the bar with 7 empty slots
bar[player1.position] = player1.symbol
bar[player2.position] = player2.symbol

# Adjust positions if they overlap or go beyond the valid range
if player1.position == player2.position:
bar[player1.position] = "X"  # Mark overlapping position with "X"
elif player1.position > player2.position:
bar[player1.position] = ">"  # Mark player1's position with ">"
bar[player2.position] = "<"  # Mark player2's position with "<"

distance_bar = "|".join(bar)
print(f"Distance Bar: {distance_bar}")

def display_stats(player):
print(f"{player.name}'s Stats:")
print(f"Health: {player.health}")
print(f"Crew: {player.crew}")
print(f"Position: {player.position}")
print(f"Cannonballs: {player.cannonballs}")
print(f"Morale: {player.morale}")
print(f"Morale Boost Turns Remaining: {player.morale_boost_wait_turns}")

def main():
print("==== Welcome to Jolley Roger ====")
player1_name = input("Player 1, enter your name: ")
player2_name = input("Player 2, enter your name: ")

player1 = Ship(player1_name, player1_name[0], 0)
player2 = Ship(player2_name, player2_name[0], 6)
player1.enemy = player2
player2.enemy = player1

while player1.is_alive() and player2.is_alive():
print("\nPlayer 1's Turn:")
display_stats(player1)
display_distance_bar(player1, player2)
steps = int(input("Enter the number of steps to move (0-3): "))
if steps != 0:
direction = input("Enter the direction to move (left or right): ")
if direction == "left":
steps *= -1
player1.move(steps)

print("\nPlayer 1's Turn - Action Menu:")
display_stats(player1)
display_distance_bar(player1, player2)
print("Choose an action:")
print("1. Crew Attack (1-2 distance)")
print("2. Cannon Attack (3-4 distance)")
print("3. Ram Attack (5+ distance)")
print("4. Repair")
print("5. Steal Crew Members")
print("6. Morale Booster")
choice = int(input())

if choice == 1:
player1.crew_attack(player2)
elif choice == 2:
player1.cannon_attack(player2)
elif choice == 3:
player1.ram_attack(player2)
elif choice == 4:
player1.repair()
elif choice == 5:
player1.steal_crew_members()
elif choice == 6:
player1.morale_booster()

player1.update_morale_boost_wait_turns()

print("\nPlayer 2's Turn:")
display_stats(player2)
display_distance_bar(player1, player2)
steps = int(input("Enter the number of steps to move (0-3): "))
if steps != 0:
direction = input("Enter the direction to move (left or right): ")
if direction == "left":
steps *= -1
player2.move(steps)

print("\nPlayer 2's Turn - Action Menu:")
display_stats(player2)
display_distance_bar(player1, player2)
print("Choose an action:")
print("1. Crew Attack (1-2 distance)")
print("2. Cannon Attack (3-4 distance)")
print("3. Ram Attack (5+ distance)")
print("4. Repair")
print("5. Steal Crew Members")
print("6. Morale Booster")
choice = int(input())

if choice == 1:
player2.crew_attack(player1)
elif choice == 2:
player2.cannon_attack(player1)
elif choice == 3:
player2.ram_attack(player1)
elif choice == 4:
player2.repair()
elif choice == 5:
player2.steal_crew_members()
elif choice == 6:
player2.morale_booster()

player2.update_morale_boost_wait_turns()

if player1.is_alive():
print(f"\n{player1.name} won the battle!")
elif player2.is_alive():
print(f"\n{player2.name} won the battle!")
else:
print("\nIt's a draw!")

print("==== Game Over ====")

if __name__ == "__main__":
main()

• Welcome to Code Review! The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How do I ask a good question?, as well as How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. May 23 at 22:38
• Additionally, it would benefit reviewers to have a bit more information about the code in the description. From the help center page How to ask: "You will get more insightful reviews if you not only provide your code, but also give an explanation of what it does. The more detail, the better." May 23 at 22:38
• The indentation seems off. Please fix.
– vnp
May 24 at 2:58

Your code is good, but there are a few improvements to be made.

First, the repeated code. You repeat the same code for player1 and player2, when you could just use a method:

# inside the Ship class
def turn(self):
print(f"\n{self.name}'s Turn:")
display_stats(self)
display_distance_bar(self, self.enemy)

steps = int(input("Enter the number of steps to move (0-3): "))

if steps != 0:
direction = input("Enter the direction to move (left or right): ")
if direction == "left":
steps *= -1
player1.move(steps)

print("\nPlayer 1's Turn - Action Menu:")
display_stats(self)
display_distance_bar(self, self.enemy)
print("Choose an action:")
print("1. Crew Attack (1-2 distance)")
print("2. Cannon Attack (3-4 distance)")
print("3. Ram Attack (5+ distance)")
print("4. Repair")
print("5. Steal Crew Members")
print("6. Morale Booster")
choice = int(input())

if choice == 1:
self.crew_attack(self.enemy)
elif choice == 2:
self.cannon_attack(self.enemy)
elif choice == 3:
self.ram_attack(self.enemy)
elif choice == 4:
self.repair()
elif choice == 5:
self.steal_crew_members()
elif choice == 6:
self.morale_booster()

self.update_morale_boost_wait_turns()


I would also advise you to use a match-case statement:

def turn(self):
...
choice = int(input())

match choice:
case 1:
self.crew_attack(self.enemy)
case 2:
self.cannon_attack(self.enemy)
...


You could also make some of your existing functions into methods, for example display_distance_bar():

# inside Ship class

def display_distance_bar(self):
bar = [" "] * 7  # Initialize the bar with 7 empty slots
bar[self.position] = self.symbol
bar[self.enemy.position] = self.enemy.symbol

# Adjust positions if they overlap or go beyond the valid range
if self.position == self.enemy.position:
bar[self.position] = "X"  # Mark overlapping position with "X"

elif self.position > self.enemy.position:
bar[self.position] = ">"  # Mark player1's position with ">"
bar[self.enemy.position] = "<"  # Mark player2's position with "<"

distance_bar = "|".join(bar)
print(f"Distance Bar: {distance_bar}")


This would also work for display_stats().

The game loop would now look something like:

def main():
print("==== Welcome to Jolley Roger ====")
player1_name = input("Player 1, enter your name: ")
player2_name = input("Player 2, enter your name: ")

player1 = Ship(player1_name, player1_name[0], 0)
player2 = Ship(player2_name, player2_name[0], 6)
player1.enemy = player2
player2.enemy = player1

while player1.is_alive() and player2.is_alive():
player1.turn()
player2.turn()

if player1.is_alive():
print(f"\n{player1.name} won the battle!")
elif player2.is_alive():
print(f"\n{player2.name} won the battle!")
else:
print("\nIt's a draw!")

print("==== Game Over ====")


N.B.
Your indentation is also wrong but I assume that's from incorrect copy and pasting.

Overall, a very engaging text-based game.