"""This module is a battleship game""" import os import re import string # Corrected import order (see point 14) import random import gspread from google.oauth2.service_account import Credentials SCOPE = [ "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive" ] CREDS = Credentials.from_service_account_file('creds.json') SCOPED_CREDS = CREDS.with_scopes(SCOPE) GSPREAD_CLIENT = gspread.authorize(SCOPED_CREDS) SHEET = GSPREAD_CLIENT.open('battleship_scores').sheet1 def center_text(text): """ Function to center text in the console """ console_width = os.get_terminal_size().columns text_length = len(re.sub(r'\x1b\[[0-9;]*m', '', text)) padding = (console_width - text_length) // 2 return " " * padding + text + " " * padding def pause(): """ Function to pause and clear the screen """ input("\n" + Bcolors.OKCYAN + center_text("Press Enter to continue...") + Bcolors.ENDC) os.system('cls' if os.name == 'nt' else 'clear') class Bcolors: # pylint: disable=too-few-public-methods """ Colors for console output """ HEADER = '\033[95m' OKBLUE = '\033[34m' OKCYAN = '\033[96m' OKGREEN = '\033[32m' WARNING = '\033[93m' RED = '\033[31m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def welcome_message(): """ Function to display the welcome message with colors """ print(Bcolors.HEADER + Bcolors.BOLD + center_text("Welcome to Battleships!") + Bcolors.ENDC) print("\n" + Bcolors.BOLD + center_text("Welcome to my simple battleships game!") + Bcolors.ENDC) print("\n" + Bcolors.BOLD + center_text("The aim of the game is to hit your opponents ships" + " in the least trys possible!") + Bcolors.ENDC) print("\n" + Bcolors.BOLD + center_text("Just select a gameboard size between a 5x5 grid and a" + " 9x9 grid!") + Bcolors.ENDC) print("\n" + Bcolors.BOLD + center_text("Then enter the co-ordinates of where you want to" + " shoot when prompted!") + Bcolors.ENDC) def display_high_scores(): """ Function to display top 5 high scores """ print("\n" + Bcolors.HEADER + center_text("Current High Scores") + Bcolors.ENDC) print("\n" + Bcolors.OKCYAN + center_text("Top 5 Lowest Misses:") + Bcolors.ENDC) scores = SHEET.get_all_values() sorted_scores = scores[1:] sorted_scores.sort(key=lambda x: int(x[1])) for i in range(1, 6): if i <= len(sorted_scores): print(center_text(f"{i}. {sorted_scores[i-1][0]}: { sorted_scores[i-1][1]}")) else: print(f"{i}. N/A") def get_board_size(): """ Function to get valid board size input """ while True: try: size = int(input("Choose board size (5-9): ")) if 5 <= size <= 9: return size print("Invalid size. Please enter a number between 5 and 9") except ValueError: print("Invalid input. Please enter a number.") def create_boards(size): """ Function to create and initialize boards """ player_board = [['O' for _ in range(size)] for _ in range(size)] computer_board = [['O' for _ in range(size)] for _ in range(size)] return player_board, computer_board def place_ships(board, num_ships): """ Function to place ships randomly """ ship_locations = [] for _ in range(num_ships): while True: row = random.randint(0, len(board) - 1) col = random.randint(0, len(board[0]) - 1) if board[row][col] == 'O': board[row][col] = 'S' ship_locations.append((row, col)) break return ship_locations def display_boards(player_board, computer_board): """ Function to display both boards side by side with colors and spacing """ board_size = len(player_board) header_spacing = board_size - 3 - max(0, board_size - 6) print("\n" + center_text(Bcolors.RED + "Player Board " + " "*header_spacing + Bcolors.ENDC + " | " + Bcolors.RED + " Computer Board" + Bcolors.ENDC)) print("\n" + center_text(Bcolors.OKCYAN + " " + " ".join([str(i) for i in range( 1, board_size + 1)]) + " " + Bcolors.ENDC + " |" + " " + Bcolors.OKCYAN + " ".join([str(i) for i in range( 1, board_size + 1)]) + " " + Bcolors.ENDC)) letters = list(string.ascii_uppercase[:board_size]) for i in range(board_size): player_row = [] computer_row = [] for j in range(board_size): if player_board[i][j] == 'X': player_row.append(Bcolors.RED + Bcolors.BOLD + 'X' + Bcolors.ENDC) elif player_board[i][j] == 'S': player_row.append(Bcolors.OKBLUE + Bcolors.BOLD + 'S' + Bcolors.ENDC) elif player_board[i][j] == 'M': player_row.append(Bcolors.OKGREEN + Bcolors.BOLD + 'M' + Bcolors.ENDC) else: player_row.append(player_board[i][j]) if computer_board[i][j] == 'X': computer_row.append(Bcolors.RED + Bcolors.BOLD + 'X' + Bcolors.ENDC) elif computer_board[i][j] == 'M': computer_row.append(Bcolors.OKGREEN + Bcolors.BOLD + 'M' + Bcolors.ENDC) else: computer_row.append('O') player_row_str = ' '.join(player_row) computer_row_str = ' '.join(computer_row) row_str = f"{Bcolors.OKCYAN}{letters[i]: <2} {Bcolors.ENDC}{ player_row_str: <{len(player_row_str) + 2}} | { Bcolors.OKCYAN}{ letters[i]: <2}{ Bcolors.ENDC} { computer_row_str: <{len(computer_row_str) + 2}}" print(center_text(row_str)) def get_target(board): """ Function to get valid target coordinates """ while True: try: row_letter = input(f"Enter row (A-{chr(ord('A') + len(board) - 1)}). ").upper() row = string.ascii_uppercase.index(row_letter.upper()) col = int(input(f"Enter column (1-{len(board)}). ")) - 1 if 0 <= row < len(board) and 0 <= col < len(board[0]): return row, col print("Invalid coordinates. Please enter numbers within the" + " board size.") except ValueError: print("Invalid input. Please enter letters for Rows and numbers" + " for columns.") def player_turn(player_board, computer_board, computer_ships, misses): """ Function to handle player's turn """ print(Bcolors.OKBLUE + "\nYour turn:" + Bcolors.ENDC) display_boards(player_board, computer_board) while True: row, col = get_target(computer_board) if computer_board[row][col] == 'M' or computer_board[row][col] == 'X': print(Bcolors.WARNING + "You already tried that target. Choose another one.\n" + Bcolors.ENDC) else: if (row, col) in computer_ships: computer_board[row][col] = 'X' computer_ships.remove((row, col)) print(Bcolors.OKGREEN + "Hit! Target: " + chr(ord('A') + row) + " " + str(col + 1) + Bcolors.ENDC) else: computer_board[row][col] = 'M' print(Bcolors.RED + "Miss! Target: " + chr(ord('A') + row) + " " + str(col + 1) + Bcolors.ENDC) misses += 1 print(Bcolors.OKCYAN + "\nMisses: " + Bcolors.ENDC + str(misses)) break return computer_board, computer_ships, misses def computer_turn(player_board, player_ships, misses): """ Function to handle computer's turn """ print(Bcolors.OKCYAN + "\nComputer's turn:" + Bcolors.ENDC) while True: row = random.randint(0, len(player_board) - 1) col = random.randint(0, len(player_board[0]) - 1) if player_board[row][col] == 'M' or player_board[row][col] == 'X': continue if (row, col) in player_ships: player_board[row][col] = 'X' player_ships.remove((row, col)) print(Bcolors.OKGREEN + "Computer hit! Target: " + chr(ord('A') + row) + " " + str(col + 1) + Bcolors.ENDC) break player_board[row][col] = 'M' print(Bcolors.RED + "Computer missed! Target: " + chr(ord('A') + row) + " " + str(col + 1) + Bcolors.ENDC) misses += 1 break pause() return player_board, player_ships, misses def play_game(): """ Function to handle the game loop """ while True: os.system('cl' if os.name == 'nt' else 'clear') welcome_message() display_high_scores() board_size = get_board_size() pause() player_board, computer_board = create_boards(board_size) player_ships = place_ships(player_board, 5) computer_ships = place_ships(computer_board, 5) player_misses = 0 computer_misses = 0 while player_ships and computer_ships: computer_board, computer_ships, player_misses = player_turn( player_board, computer_board, computer_ships, player_misses) if not computer_ships: print(Bcolors.OKGREEN + "\nCongratulations! You win!" + Bcolors.ENDC) save_to_high_scores(player_misses) break player_board, player_ships, computer_misses = computer_turn( player_board, player_ships, computer_misses) if not player_ships: print(Bcolors.RED + "\nYou lose! Better luck next time." + Bcolors.ENDC) break print("\nFinal Score:") print(f"Player: {player_misses}") print(f"Computer: {computer_misses}") while True: play_again = input("\nPlay again? (y/n): ").lower() if play_again in ('y', 'n'): os.system('cls' if os.name == 'nt' else 'clear') break print("Invalid input. Please enter 'y' or 'n'.") if play_again == 'n': print("Goodbye!") break def save_to_high_scores(misses): """ Function to handle saving scores to the spreadsheet """ while True: save_score = input("Do you want to save your score to the high " + "scores? (y/n): ") if save_score.lower() == 'y': name = input("Enter your name: ") SHEET.append_row([name, misses]) os.system('cls' if os.name == 'nt' else 'clear') print("Score saved successfully!") display_high_scores() break if save_score.lower() == 'n': os.system('cls' if os.name == 'nt' else 'clear') print("Score not saved.") display_high_scores() break print("Invalid input. Please enter 'y' or 'n'.") if __name__ == "__main__": play_game()